Initial implementation of the IntelligenceService pipeline.
It's still full of TODOs, but at leats it now provides an end-to-end
workflow from the activity creation / destruction to the service implementation.
Test: mmm -j packages/experimental/FillService && \
adb install -r ${OUT}/data/app/FillService/FillService.apk && \
adb shell settings put secure intel_service foo.bar.fill/.AiaiService
Bug: 111276913
Change-Id: Id5daf7b8b51e97c74d9b6ec00f953ddb02b48e46
This commit is contained in:
@@ -120,6 +120,7 @@ import android.view.autofill.AutofillManager;
|
||||
import android.view.autofill.AutofillManager.AutofillClient;
|
||||
import android.view.autofill.AutofillPopupWindow;
|
||||
import android.view.autofill.IAutofillWindowPresenter;
|
||||
import android.view.intelligence.IntelligenceManager;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.Toolbar;
|
||||
@@ -821,6 +822,10 @@ public class Activity extends ContextThemeWrapper
|
||||
/** The autofill manager. Always access via {@link #getAutofillManager()}. */
|
||||
@Nullable private AutofillManager mAutofillManager;
|
||||
|
||||
/** The screen observation manager. Always access via {@link #getIntelligenceManager()}. */
|
||||
@Nullable private IntelligenceManager mIntelligenceManager;
|
||||
|
||||
|
||||
static final class NonConfigurationInstances {
|
||||
Object activity;
|
||||
HashMap<String, Object> children;
|
||||
@@ -994,7 +999,7 @@ public class Activity extends ContextThemeWrapper
|
||||
}
|
||||
|
||||
/**
|
||||
* (Create and) return the autofill manager
|
||||
* (Creates, sets and) returns the autofill manager
|
||||
*
|
||||
* @return The autofill manager
|
||||
*/
|
||||
@@ -1006,6 +1011,18 @@ public class Activity extends ContextThemeWrapper
|
||||
return mAutofillManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* (Creates, sets, and ) returns the intelligence manager
|
||||
*
|
||||
* @return The intelligence manager
|
||||
*/
|
||||
@NonNull private IntelligenceManager getIntelligenceManager() {
|
||||
if (mIntelligenceManager == null) {
|
||||
mIntelligenceManager = getSystemService(IntelligenceManager.class);
|
||||
}
|
||||
return mIntelligenceManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context newBase) {
|
||||
super.attachBaseContext(newBase);
|
||||
@@ -1081,6 +1098,12 @@ public class Activity extends ContextThemeWrapper
|
||||
}
|
||||
mRestoredFromBundle = savedInstanceState != null;
|
||||
mCalled = true;
|
||||
|
||||
if (getIntelligenceManager() != null) {
|
||||
//TODO(b/111276913): decide whether the screen_obs session id should be saved / restored
|
||||
// in the activity bundle.
|
||||
mIntelligenceManager.onActivityCreated(mToken, getComponentName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2047,6 +2070,10 @@ public class Activity extends ContextThemeWrapper
|
||||
}
|
||||
|
||||
getApplication().dispatchActivityDestroyed(this);
|
||||
|
||||
if (getIntelligenceManager() != null) {
|
||||
mIntelligenceManager.onActivityDestroyed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -6403,9 +6430,16 @@ public class Activity extends ContextThemeWrapper
|
||||
|
||||
void dumpInner(@NonNull String prefix, @Nullable FileDescriptor fd,
|
||||
@NonNull PrintWriter writer, @Nullable String[] args) {
|
||||
if (args != null && args.length > 0 && args[0].equals("--autofill")) {
|
||||
dumpAutofillManager(prefix, writer);
|
||||
return;
|
||||
if (args != null && args.length > 0) {
|
||||
// Handle special cases
|
||||
switch (args[0]) {
|
||||
case "--autofill":
|
||||
dumpAutofillManager(prefix, writer);
|
||||
return;
|
||||
case "--intelligence":
|
||||
dumpIntelligenceManager(prefix, writer);
|
||||
return;
|
||||
}
|
||||
}
|
||||
writer.print(prefix); writer.print("Local Activity ");
|
||||
writer.print(Integer.toHexString(System.identityHashCode(this)));
|
||||
@@ -6435,6 +6469,7 @@ public class Activity extends ContextThemeWrapper
|
||||
mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix);
|
||||
|
||||
dumpAutofillManager(prefix, writer);
|
||||
dumpIntelligenceManager(prefix, writer);
|
||||
|
||||
ResourcesManager.getInstance().dump(prefix, writer);
|
||||
}
|
||||
@@ -6450,6 +6485,15 @@ public class Activity extends ContextThemeWrapper
|
||||
}
|
||||
}
|
||||
|
||||
void dumpIntelligenceManager(String prefix, PrintWriter writer) {
|
||||
final IntelligenceManager im = getIntelligenceManager();
|
||||
if (im != null) {
|
||||
im.dump(prefix, writer);
|
||||
} else {
|
||||
writer.print(prefix); writer.println("No IntelligenceManager");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bit indicating that this activity is "immersive" and should not be
|
||||
* interrupted by notifications if possible.
|
||||
|
||||
@@ -66,8 +66,8 @@ import android.hardware.fingerprint.IFingerprintService;
|
||||
import android.hardware.hdmi.HdmiControlManager;
|
||||
import android.hardware.hdmi.IHdmiControlService;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.hardware.iris.IrisManager;
|
||||
import android.hardware.iris.IIrisService;
|
||||
import android.hardware.iris.IrisManager;
|
||||
import android.hardware.location.ContextHubManager;
|
||||
import android.hardware.radio.RadioManager;
|
||||
import android.hardware.usb.IUsbManager;
|
||||
@@ -163,6 +163,8 @@ import android.view.accessibility.CaptioningManager;
|
||||
import android.view.autofill.AutofillManager;
|
||||
import android.view.autofill.IAutoFillManager;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.view.intelligence.IIntelligenceManager;
|
||||
import android.view.intelligence.IntelligenceManager;
|
||||
import android.view.textclassifier.TextClassificationManager;
|
||||
import android.view.textservice.TextServicesManager;
|
||||
|
||||
@@ -1032,6 +1034,17 @@ final class SystemServiceRegistry {
|
||||
return new AutofillManager(ctx.getOuterContext(), service);
|
||||
}});
|
||||
|
||||
registerService(Context.INTELLIGENCE_MANAGER_SERVICE, IntelligenceManager.class,
|
||||
new CachedServiceFetcher<IntelligenceManager>() {
|
||||
@Override
|
||||
public IntelligenceManager createService(ContextImpl ctx)
|
||||
throws ServiceNotFoundException {
|
||||
// Get the services without throwing as this is an optional feature
|
||||
IBinder b = ServiceManager.getService(Context.INTELLIGENCE_MANAGER_SERVICE);
|
||||
IIntelligenceManager service = IIntelligenceManager.Stub.asInterface(b);
|
||||
return new IntelligenceManager(ctx.getOuterContext(), service);
|
||||
}});
|
||||
|
||||
registerService(Context.VR_SERVICE, VrManager.class, new CachedServiceFetcher<VrManager>() {
|
||||
@Override
|
||||
public VrManager createService(ContextImpl ctx) throws ServiceNotFoundException {
|
||||
|
||||
@@ -3864,6 +3864,14 @@ public abstract class Context {
|
||||
*/
|
||||
public static final String AUTOFILL_MANAGER_SERVICE = "autofill";
|
||||
|
||||
/**
|
||||
* Official published name of the intelligence service.
|
||||
*
|
||||
* @hide
|
||||
* @see #getSystemService(String)
|
||||
*/
|
||||
public static final String INTELLIGENCE_MANAGER_SERVICE = "intelligence";
|
||||
|
||||
/**
|
||||
* Use with {@link #getSystemService(String)} to access the
|
||||
* {@link com.android.server.voiceinteraction.SoundTriggerService}.
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.service.intelligence;
|
||||
|
||||
import android.service.intelligence.InteractionSessionId;
|
||||
import android.service.intelligence.InteractionContext;
|
||||
|
||||
/**
|
||||
* Interface from the system to an intelligence service.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
oneway interface IIntelligenceService {
|
||||
|
||||
// Called when session is created (context not null) or destroyed (context null)
|
||||
void onSessionLifecycle(in InteractionContext context, in InteractionSessionId sessionId);
|
||||
}
|
||||
@@ -15,16 +15,24 @@
|
||||
*/
|
||||
package android.service.intelligence;
|
||||
|
||||
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
|
||||
|
||||
import android.annotation.CallSuper;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.SystemApi;
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.view.intelligence.ContentCaptureEvent;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A service used to captures the content of the screen.
|
||||
* A service used to capture the content of the screen.
|
||||
*
|
||||
* <p>The data collected by this service can be analyzed and combined with other sources to provide
|
||||
* contextual data in other areas of the system such as Autofill.
|
||||
@@ -34,6 +42,8 @@ import java.util.List;
|
||||
@SystemApi
|
||||
public abstract class IntelligenceService extends Service {
|
||||
|
||||
private static final String TAG = "IntelligenceService";
|
||||
|
||||
/**
|
||||
* The {@link Intent} that must be declared as handled by the service.
|
||||
* To be supported, the service must also require the
|
||||
@@ -43,6 +53,42 @@ public abstract class IntelligenceService extends Service {
|
||||
public static final String SERVICE_INTERFACE =
|
||||
"android.service.intelligence.IntelligenceService";
|
||||
|
||||
private Handler mHandler;
|
||||
|
||||
private final IIntelligenceService mInterface = new IIntelligenceService.Stub() {
|
||||
|
||||
@Override
|
||||
public void onSessionLifecycle(InteractionContext context, InteractionSessionId sessionId)
|
||||
throws RemoteException {
|
||||
if (context != null) {
|
||||
mHandler.sendMessage(
|
||||
obtainMessage(IntelligenceService::onCreateInteractionSession,
|
||||
IntelligenceService.this, context, sessionId));
|
||||
} else {
|
||||
mHandler.sendMessage(
|
||||
obtainMessage(IntelligenceService::onDestroyInteractionSession,
|
||||
IntelligenceService.this, sessionId));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@CallSuper
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
mHandler = new Handler(Looper.getMainLooper(), null, true);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
public final IBinder onBind(Intent intent) {
|
||||
if (SERVICE_INTERFACE.equals(intent.getAction())) {
|
||||
return mInterface.asBinder();
|
||||
}
|
||||
Log.w(TAG, "Tried to bind to wrong intent: " + intent);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new interaction session.
|
||||
*
|
||||
@@ -63,7 +109,7 @@ public abstract class IntelligenceService extends Service {
|
||||
@NonNull List<ContentCaptureEvent> events);
|
||||
|
||||
/**
|
||||
* Destroys the content capture session identified by the specified {@code sessionId}.
|
||||
* Destroys the interaction session.
|
||||
*
|
||||
* @param sessionId the id of the session to destroy
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2018, The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.service.intelligence;
|
||||
|
||||
parcelable InteractionContext;
|
||||
@@ -23,6 +23,9 @@ import android.content.ComponentName;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@@ -32,7 +35,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||
public final class InteractionContext implements Parcelable {
|
||||
|
||||
/**
|
||||
* Flag used to indicate that the app explicitly disabled contents capture for the activity
|
||||
* Flag used to indicate that the app explicitly disabled content capture for the activity
|
||||
* (using
|
||||
* {@link android.view.intelligence.IntelligenceManager#disableContentCapture()}),
|
||||
* in which case the service will just receive activity-level events.
|
||||
@@ -54,24 +57,34 @@ public final class InteractionContext implements Parcelable {
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@interface ContextCreationFlags{}
|
||||
|
||||
// TODO(b/111276913): create new object for taskId + componentName / reuse on other places
|
||||
private final @NonNull ComponentName mComponentName;
|
||||
private final int mTaskId;
|
||||
private final int mDisplayId;
|
||||
private final int mFlags;
|
||||
|
||||
|
||||
/** @hide */
|
||||
InteractionContext() {
|
||||
public InteractionContext(@NonNull ComponentName componentName, int taskId, int displayId,
|
||||
int flags) {
|
||||
mComponentName = Preconditions.checkNotNull(componentName);
|
||||
mTaskId = taskId;
|
||||
mDisplayId = displayId;
|
||||
mFlags = flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the id of the {@link TaskInfo task} associated with this context.
|
||||
*/
|
||||
public int getTaskId() {
|
||||
//TODO(b/111276913): implement
|
||||
return 108;
|
||||
return mTaskId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the activity associated with this context.
|
||||
*/
|
||||
public @NonNull ComponentName getActivityComponent() {
|
||||
//TODO(b/111276913): implement
|
||||
return null;
|
||||
return mComponentName;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,8 +92,7 @@ public final class InteractionContext implements Parcelable {
|
||||
* {G android.hardware.display.DisplayManager#getDisplay(int) DisplayManager.getDisplay()}.
|
||||
*/
|
||||
public int getDisplayId() {
|
||||
//TODO(b/111276913): implement
|
||||
return 42;
|
||||
return mDisplayId;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,8 +102,26 @@ public final class InteractionContext implements Parcelable {
|
||||
* {@link #FLAG_DISABLED_BY_APP}.
|
||||
*/
|
||||
public @ContextCreationFlags int getFlags() {
|
||||
//TODO(b/111276913): implement
|
||||
return 42;
|
||||
return mFlags;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
// TODO(b/111276913): dump to proto as well
|
||||
public void dump(PrintWriter pw) {
|
||||
pw.print("comp="); pw.print(mComponentName.flattenToShortString());
|
||||
pw.print(", taskId="); pw.print(mTaskId);
|
||||
pw.print(", displayId="); pw.print(mDisplayId);
|
||||
if (mFlags > 0) {
|
||||
pw.print(", flags="); pw.print(mFlags);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Context[act=" + mComponentName.flattenToShortString() + ", taskId=" + mTaskId
|
||||
+ ", displayId=" + mDisplayId + ", flags=" + mFlags + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -101,6 +131,10 @@ public final class InteractionContext implements Parcelable {
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel parcel, int flags) {
|
||||
parcel.writeParcelable(mComponentName, flags);
|
||||
parcel.writeInt(mTaskId);
|
||||
parcel.writeInt(mDisplayId);
|
||||
parcel.writeInt(mFlags);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<InteractionContext> CREATOR =
|
||||
@@ -108,8 +142,11 @@ public final class InteractionContext implements Parcelable {
|
||||
|
||||
@Override
|
||||
public InteractionContext createFromParcel(Parcel parcel) {
|
||||
// TODO(b/111276913): implement
|
||||
return null;
|
||||
final ComponentName componentName = parcel.readParcelable(null);
|
||||
final int taskId = parcel.readInt();
|
||||
final int displayId = parcel.readInt();
|
||||
final int flags = parcel.readInt();
|
||||
return new InteractionContext(componentName, taskId, displayId, flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2018, The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.service.intelligence;
|
||||
|
||||
parcelable InteractionSessionId;
|
||||
@@ -20,13 +20,39 @@ import android.annotation.SystemApi;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
// TODO(b/111276913): add javadocs / implement equals/hashcode/string
|
||||
/** @hide */
|
||||
@SystemApi
|
||||
public final class InteractionSessionId implements Parcelable {
|
||||
|
||||
private final int mGlobalId;
|
||||
|
||||
// TODO(b/111276913): remove if not needed
|
||||
private final int mLocalId;
|
||||
|
||||
/** @hide */
|
||||
public InteractionSessionId() {
|
||||
public InteractionSessionId(int globalId, int localId) {
|
||||
mGlobalId = globalId;
|
||||
mLocalId = localId;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public int getGlobalId() {
|
||||
return mGlobalId;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
// TODO(b/111276913): dump to proto as well
|
||||
public void dump(PrintWriter pw) {
|
||||
pw.print("globalId="); pw.print(mGlobalId);
|
||||
pw.print("localId="); pw.print(mLocalId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SessionId[globalId=" + mGlobalId + ", localId=" + mLocalId + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -36,6 +62,8 @@ public final class InteractionSessionId implements Parcelable {
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel parcel, int flags) {
|
||||
parcel.writeInt(mGlobalId);
|
||||
parcel.writeInt(mLocalId);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<InteractionSessionId> CREATOR =
|
||||
@@ -43,8 +71,9 @@ public final class InteractionSessionId implements Parcelable {
|
||||
|
||||
@Override
|
||||
public InteractionSessionId createFromParcel(Parcel parcel) {
|
||||
// TODO(b/111276913): implement
|
||||
return null;
|
||||
final int globalId = parcel.readInt();
|
||||
final int localId = parcel.readInt();
|
||||
return new InteractionSessionId(globalId, localId);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -149,7 +149,6 @@ public final class ContentCaptureEvent implements Parcelable {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
@@ -157,6 +156,7 @@ public final class ContentCaptureEvent implements Parcelable {
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel parcel, int flags) {
|
||||
// TODO(b/111276913): implement
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<ContentCaptureEvent> CREATOR =
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.view.intelligence;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.os.IBinder;
|
||||
import com.android.internal.os.IResultReceiver;
|
||||
|
||||
/**
|
||||
* {@hide}
|
||||
*/
|
||||
oneway interface IIntelligenceManager {
|
||||
/**
|
||||
* Starts a session, sending the "remote" sessionId to the receiver.
|
||||
*/
|
||||
void startSession(int userId, IBinder activityToken, in ComponentName componentName,
|
||||
int localSessionId, int flags, in IResultReceiver result);
|
||||
|
||||
/**
|
||||
* Finishes a session.
|
||||
*/
|
||||
void finishSession(int userId, IBinder activityToken, in ComponentName componentName,
|
||||
int localSessionId, int globalSessionId);
|
||||
}
|
||||
@@ -18,29 +18,174 @@ package android.view.intelligence;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.annotation.SystemService;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.os.IResultReceiver;
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* TODO(b/111276913): add javadocs / implement / add SystemService / PackageFeature
|
||||
* TODO(b/111276913): add javadocs / implement
|
||||
*/
|
||||
@SystemService(Context.INTELLIGENCE_MANAGER_SERVICE)
|
||||
public final class IntelligenceManager {
|
||||
|
||||
private static final String TAG = "IntelligenceManager";
|
||||
|
||||
// TODO(b/111276913): define a way to dynamically set it (for example, using settings?)
|
||||
private static final boolean VERBOSE = false;
|
||||
|
||||
/**
|
||||
* Used to indicate that a text change was caused by user input (for example, through IME).
|
||||
*/
|
||||
//TODO(b/111276913): link to notifyTextChanged() method once available
|
||||
public static final int FLAG_USER_INPUT = 0x1;
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
/** @hide */
|
||||
public IntelligenceManager(@NonNull Context context) {
|
||||
public static final int NO_SESSION = 0;
|
||||
|
||||
/**
|
||||
* Initial state, when there is no session.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int STATE_UNKNOWN = 0;
|
||||
|
||||
/**
|
||||
* Service's startSession() was called, but remote session id was not returned yet.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int STATE_WAITING_FOR_SESSION_ID = 1;
|
||||
|
||||
/**
|
||||
* Session is active.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int STATE_ACTIVE = 2;
|
||||
|
||||
private static int sNextSessionId;
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
@Nullable
|
||||
private final IIntelligenceManager mService;
|
||||
|
||||
private final Object mLock = new Object();
|
||||
|
||||
// TODO(b/111276913): localSessionId might be an overkill, perhaps just the global id is enough.
|
||||
// Let's keep both for now, and revisit once we decide whether the session id will be persisted
|
||||
// when the activity's process is killed
|
||||
@GuardedBy("mLock")
|
||||
private int mLocalSessionId = NO_SESSION;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private int mRemoteSessionId = NO_SESSION;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private int mState = STATE_UNKNOWN;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private IBinder mApplicationToken;
|
||||
|
||||
// TODO(b/111276913): replace by an interface name implemented by Activity, similar to
|
||||
// AutofillClient
|
||||
@GuardedBy("mLock")
|
||||
private ComponentName mComponentName;
|
||||
|
||||
/** @hide */
|
||||
public IntelligenceManager(@NonNull Context context, @Nullable IIntelligenceManager service) {
|
||||
mContext = Preconditions.checkNotNull(context, "context cannot be null");
|
||||
mService = service;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public void onActivityCreated(@NonNull IBinder token, @NonNull ComponentName componentName) {
|
||||
if (!isContentCaptureEnabled()) return;
|
||||
|
||||
synchronized (mLock) {
|
||||
if (mState != STATE_UNKNOWN) {
|
||||
Log.w(TAG, "ignoring onActivityStarted(" + token + ") while on state "
|
||||
+ getStateAsStringLocked());
|
||||
return;
|
||||
}
|
||||
mState = STATE_WAITING_FOR_SESSION_ID;
|
||||
mLocalSessionId = ++sNextSessionId;
|
||||
mRemoteSessionId = NO_SESSION;
|
||||
mApplicationToken = token;
|
||||
mComponentName = componentName;
|
||||
|
||||
if (VERBOSE) {
|
||||
Log.v(TAG, "onActivityStarted(): token=" + token + ", act=" + componentName
|
||||
+ ", localSessionId=" + mLocalSessionId);
|
||||
}
|
||||
final int flags = 0; // TODO(b/111276913): get proper flags
|
||||
|
||||
try {
|
||||
mService.startSession(mContext.getUserId(), mApplicationToken, componentName,
|
||||
mLocalSessionId, flags, new IResultReceiver.Stub() {
|
||||
@Override
|
||||
public void send(int resultCode, Bundle resultData)
|
||||
throws RemoteException {
|
||||
synchronized (mLock) {
|
||||
if (resultCode > 0) {
|
||||
mRemoteSessionId = resultCode;
|
||||
mState = STATE_ACTIVE;
|
||||
} else {
|
||||
// TODO(b/111276913): handle other cases like disabled by
|
||||
// service
|
||||
mState = STATE_UNKNOWN;
|
||||
}
|
||||
if (VERBOSE) {
|
||||
Log.v(TAG, "onActivityStarted() result: code=" + resultCode
|
||||
+ ", remoteSession=" + mRemoteSessionId
|
||||
+ ", state=" + getStateAsStringLocked());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public void onActivityDestroyed() {
|
||||
if (!isContentCaptureEnabled()) return;
|
||||
|
||||
synchronized (mLock) {
|
||||
//TODO(b/111276913): check state (for example, how to handle if it's waiting for remote
|
||||
// id) and send it to the cache of batched commands
|
||||
|
||||
if (VERBOSE) {
|
||||
Log.v(TAG, "onActivityDestroyed(): state=" + getStateAsStringLocked()
|
||||
+ ", localSessionId=" + mLocalSessionId
|
||||
+ ", mRemoteSessionId=" + mRemoteSessionId);
|
||||
}
|
||||
|
||||
try {
|
||||
mService.finishSession(mContext.getUserId(), mApplicationToken, mComponentName,
|
||||
mLocalSessionId, mRemoteSessionId);
|
||||
mState = STATE_UNKNOWN;
|
||||
mLocalSessionId = mRemoteSessionId = NO_SESSION;
|
||||
mApplicationToken = null;
|
||||
mComponentName = null;
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,11 +199,13 @@ public final class IntelligenceManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether contents capture is enabled for this activity.
|
||||
* Checks whether content capture is enabled for this activity.
|
||||
*/
|
||||
public boolean isContentCaptureEnabled() {
|
||||
//TODO(b/111276913): implement
|
||||
return false;
|
||||
//TODO(b/111276913): properly implement by checking if it was explicitly disabled by
|
||||
// service, or if service is not set
|
||||
// (and probably renamign to isEnabledLocked()
|
||||
return mService != null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,6 +215,7 @@ public final class IntelligenceManager {
|
||||
* it on {@link android.app.Activity#onCreate(android.os.Bundle, android.os.PersistableBundle)}.
|
||||
*/
|
||||
public void disableContentCapture() {
|
||||
//TODO(b/111276913): implement
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,4 +288,41 @@ public final class IntelligenceManager {
|
||||
//TODO(b/111276913): implement
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public void dump(String prefix, PrintWriter pw) {
|
||||
pw.print(prefix); pw.println("IntelligenceManager");
|
||||
final String prefix2 = prefix + " ";
|
||||
synchronized (mLock) {
|
||||
pw.print(prefix2); pw.print("mContext: "); pw.println(mContext);
|
||||
pw.print(prefix2); pw.print("mService: "); pw.println(mService);
|
||||
pw.print(prefix2); pw.print("user: "); pw.println(mContext.getUserId());
|
||||
pw.print(prefix2); pw.print("enabled: "); pw.println(isContentCaptureEnabled());
|
||||
pw.print(prefix2); pw.print("mLocalSessionId: "); pw.println(mLocalSessionId);
|
||||
pw.print(prefix2); pw.print("mRemoteSessionId: "); pw.println(mRemoteSessionId);
|
||||
pw.print(prefix2); pw.print("mState: "); pw.print(mState); pw.print(" (");
|
||||
pw.print(getStateAsStringLocked()); pw.println(")");
|
||||
pw.print(prefix2); pw.print("mAppToken: "); pw.println(mApplicationToken);
|
||||
pw.print(prefix2); pw.print("mComponentName: "); pw.println(mComponentName);
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private String getStateAsStringLocked() {
|
||||
return getStateAsString(mState);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static String getStateAsString(int state) {
|
||||
switch (state) {
|
||||
case STATE_UNKNOWN:
|
||||
return "UNKNOWN";
|
||||
case STATE_WAITING_FOR_SESSION_ID:
|
||||
return "WAITING_FOR_SESSION_ID";
|
||||
case STATE_ACTIVE:
|
||||
return "ACTIVE";
|
||||
default:
|
||||
return "INVALID:" + state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user