Merge "Use PM instead of PMI in DefGrantPolicy for simple cases"
diff --git a/Android.bp b/Android.bp
index 65f232d..8163ff8 100644
--- a/Android.bp
+++ b/Android.bp
@@ -287,6 +287,8 @@
"core/java/android/service/euicc/ISwitchToSubscriptionCallback.aidl",
"core/java/android/service/euicc/IUpdateSubscriptionNicknameCallback.aidl",
"core/java/android/service/gatekeeper/IGateKeeperService.aidl",
+ "core/java/android/service/intelligence/IIntelligenceService.aidl",
+
"core/java/android/service/notification/INotificationListener.aidl",
"core/java/android/service/notification/IStatusBarNotificationHolder.aidl",
"core/java/android/service/notification/IConditionListener.aidl",
@@ -343,6 +345,7 @@
"core/java/android/view/autofill/IAutoFillManager.aidl",
"core/java/android/view/autofill/IAutoFillManagerClient.aidl",
"core/java/android/view/autofill/IAutofillWindowPresenter.aidl",
+ "core/java/android/view/intelligence/IIntelligenceManager.aidl",
"core/java/android/view/IApplicationToken.aidl",
"core/java/android/view/IAppTransitionAnimationSpecsFuture.aidl",
"core/java/android/view/IDockedStackListener.aidl",
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index a666819..8d54e91 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -120,6 +120,7 @@
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 @@
/** 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 @@
}
/**
- * (Create and) return the autofill manager
+ * (Creates, sets and) returns the autofill manager
*
* @return The autofill manager
*/
@@ -1006,6 +1011,18 @@
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 @@
}
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 @@
}
getApplication().dispatchActivityDestroyed(this);
+
+ if (getIntelligenceManager() != null) {
+ mIntelligenceManager.onActivityDestroyed();
+ }
}
/**
@@ -6403,9 +6430,16 @@
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 @@
mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix);
dumpAutofillManager(prefix, writer);
+ dumpIntelligenceManager(prefix, writer);
ResourcesManager.getInstance().dump(prefix, writer);
}
@@ -6450,6 +6485,15 @@
}
}
+ 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.
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index f267169..e95f9ab 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -66,8 +66,8 @@
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.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 @@
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 {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 6a7829b..ccf8417 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3865,6 +3865,14 @@
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}.
*
diff --git a/core/java/android/service/intelligence/IIntelligenceService.aidl b/core/java/android/service/intelligence/IIntelligenceService.aidl
new file mode 100644
index 0000000..ee93326
--- /dev/null
+++ b/core/java/android/service/intelligence/IIntelligenceService.aidl
@@ -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);
+}
diff --git a/core/java/android/service/intelligence/IntelligenceService.java b/core/java/android/service/intelligence/IntelligenceService.java
index 4b8825d..ce0a88a 100644
--- a/core/java/android/service/intelligence/IntelligenceService.java
+++ b/core/java/android/service/intelligence/IntelligenceService.java
@@ -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 @@
@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 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 @@
@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
*/
diff --git a/core/java/android/service/intelligence/InteractionContext.aidl b/core/java/android/service/intelligence/InteractionContext.aidl
new file mode 100644
index 0000000..4ce6aa4
--- /dev/null
+++ b/core/java/android/service/intelligence/InteractionContext.aidl
@@ -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;
diff --git a/core/java/android/service/intelligence/InteractionContext.java b/core/java/android/service/intelligence/InteractionContext.java
index 4d83820..c1803ad 100644
--- a/core/java/android/service/intelligence/InteractionContext.java
+++ b/core/java/android/service/intelligence/InteractionContext.java
@@ -23,6 +23,9 @@
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 @@
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 @@
@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 @@
* {G android.hardware.display.DisplayManager#getDisplay(int) DisplayManager.getDisplay()}.
*/
public int getDisplayId() {
- //TODO(b/111276913): implement
- return 42;
+ return mDisplayId;
}
/**
@@ -90,8 +102,26 @@
* {@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 @@
@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 @@
@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
diff --git a/core/java/android/service/intelligence/InteractionSessionId.aidl b/core/java/android/service/intelligence/InteractionSessionId.aidl
new file mode 100644
index 0000000..a5392b6
--- /dev/null
+++ b/core/java/android/service/intelligence/InteractionSessionId.aidl
@@ -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;
diff --git a/core/java/android/service/intelligence/InteractionSessionId.java b/core/java/android/service/intelligence/InteractionSessionId.java
index 4c9d706..ca68f8e 100644
--- a/core/java/android/service/intelligence/InteractionSessionId.java
+++ b/core/java/android/service/intelligence/InteractionSessionId.java
@@ -20,13 +20,39 @@
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 @@
@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 @@
@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
diff --git a/core/java/android/view/intelligence/ContentCaptureEvent.java b/core/java/android/view/intelligence/ContentCaptureEvent.java
index b8330e5..d6aec34 100644
--- a/core/java/android/view/intelligence/ContentCaptureEvent.java
+++ b/core/java/android/view/intelligence/ContentCaptureEvent.java
@@ -149,7 +149,6 @@
return null;
}
-
@Override
public int describeContents() {
return 0;
@@ -157,6 +156,7 @@
@Override
public void writeToParcel(Parcel parcel, int flags) {
+ // TODO(b/111276913): implement
}
public static final Parcelable.Creator<ContentCaptureEvent> CREATOR =
diff --git a/core/java/android/view/intelligence/IIntelligenceManager.aidl b/core/java/android/view/intelligence/IIntelligenceManager.aidl
new file mode 100644
index 0000000..f4901c3
--- /dev/null
+++ b/core/java/android/view/intelligence/IIntelligenceManager.aidl
@@ -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);
+}
diff --git a/core/java/android/view/intelligence/IntelligenceManager.java b/core/java/android/view/intelligence/IntelligenceManager.java
index 5513ce2f..b1d06f7 100644
--- a/core/java/android/view/intelligence/IntelligenceManager.java
+++ b/core/java/android/view/intelligence/IntelligenceManager.java
@@ -18,29 +18,174 @@
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 @@
}
/**
- * 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 @@
* it on {@link android.app.Activity#onCreate(android.os.Bundle, android.os.PersistableBundle)}.
*/
public void disableContentCapture() {
+ //TODO(b/111276913): implement
}
/**
@@ -140,4 +288,41 @@
//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;
+ }
+ }
}
diff --git a/services/autofill/java/com/android/server/intelligence/ContentCaptureSession.java b/services/autofill/java/com/android/server/intelligence/ContentCaptureSession.java
new file mode 100644
index 0000000..a437a39
--- /dev/null
+++ b/services/autofill/java/com/android/server/intelligence/ContentCaptureSession.java
@@ -0,0 +1,121 @@
+/*
+ * 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 com.android.server.intelligence;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.IBinder;
+import android.service.intelligence.IntelligenceService;
+import android.service.intelligence.InteractionContext;
+import android.service.intelligence.InteractionSessionId;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.AbstractRemoteService;
+import com.android.server.intelligence.RemoteIntelligenceService.RemoteIntelligenceServiceCallbacks;
+
+import java.io.PrintWriter;
+
+final class ContentCaptureSession implements RemoteIntelligenceServiceCallbacks {
+
+ private static final String TAG = "ContentCaptureSession";
+
+ private final Object mLock;
+ private final IBinder mActivityToken;
+
+ private final IntelligencePerUserService mService;
+ private final RemoteIntelligenceService mRemoteService;
+ private final InteractionContext mInterationContext;
+ private final InteractionSessionId mId;
+
+ ContentCaptureSession(@NonNull Context context, int userId, @NonNull Object lock,
+ @NonNull IBinder activityToken, @NonNull IntelligencePerUserService service,
+ @NonNull ComponentName serviceComponentName, @NonNull ComponentName appComponentName,
+ int taskId, int displayId, int localSessionId, int globalSessionId, int flags,
+ boolean bindInstantServiceAllowed, boolean verbose) {
+ mLock = lock;
+ mActivityToken = activityToken;
+ mService = service;
+ mRemoteService = new RemoteIntelligenceService(context,
+ IntelligenceService.SERVICE_INTERFACE, serviceComponentName, userId, this,
+ bindInstantServiceAllowed, verbose);
+ mId = new InteractionSessionId(globalSessionId, localSessionId);
+ mInterationContext = new InteractionContext(appComponentName, taskId, displayId, flags);
+ }
+
+ /**
+ * Notifies the {@link IntelligenceService} that the service started.
+ */
+ @GuardedBy("mLock")
+ public void notifySessionStartedLocked() {
+ mRemoteService.onSessionLifecycleRequest(mInterationContext, mId);
+ }
+
+ /**
+ * Cleans up the session and remove itself from the service.
+ *
+ * @param notifyRemoteService whether it should trigger a {@link
+ * IntelligenceService#onDestroyInteractionSession(InteractionSessionId)}
+ * request.
+ */
+ @GuardedBy("mLock")
+ public void removeSelfLocked(boolean notifyRemoteService) {
+ try {
+ if (notifyRemoteService) {
+ mRemoteService.onSessionLifecycleRequest(/* context= */ null, mId);
+ }
+ } finally {
+ mService.removeSessionLocked(mInterationContext.getActivityComponent());
+ }
+ }
+
+ @Override // from RemoteScreenObservationServiceCallbacks
+ public void onServiceDied(AbstractRemoteService service) {
+ // TODO(b/111276913): implement (remove session from PerUserSession?)
+ if (mService.isDebug()) {
+ Slog.d(TAG, "onServiceDied() for " + mId);
+ }
+ synchronized (mLock) {
+ removeSelfLocked(/* notifyRemoteService= */ false);
+ }
+ }
+
+ @Override // from RemoteScreenObservationServiceCallbacks
+ public void onSessionLifecycleRequestFailureOrTimeout(boolean timedOut) {
+ // TODO(b/111276913): log metrics on whether timed out or not
+ if (mService.isDebug()) {
+ Slog.d(TAG, "onSessionLifecycleRequestFailure(" + mId + "): timed out=" + timedOut);
+ }
+ synchronized (mLock) {
+ removeSelfLocked(/* notifyRemoteService= */ false);
+ }
+ }
+
+ /**
+ * Gets global id, unique per {@link IntelligencePerUserService}.
+ */
+ public int getGlobalSessionId() {
+ return mId.getGlobalId();
+ }
+
+ @GuardedBy("mLock")
+ public void dumpLocked(@NonNull String prefix, @NonNull PrintWriter pw) {
+ pw.print(prefix); pw.print("id: "); mId.dump(pw); pw.println();
+ pw.print(prefix); pw.print("context: "); mInterationContext.dump(pw); pw.println();
+ pw.print(prefix); pw.print("activity token: "); pw.println(mActivityToken);
+ }
+}
diff --git a/services/autofill/java/com/android/server/intelligence/IntelligenceManagerService.java b/services/autofill/java/com/android/server/intelligence/IntelligenceManagerService.java
new file mode 100644
index 0000000..4ea9036
--- /dev/null
+++ b/services/autofill/java/com/android/server/intelligence/IntelligenceManagerService.java
@@ -0,0 +1,127 @@
+/*
+ * 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 com.android.server.intelligence;
+
+import static android.content.Context.INTELLIGENCE_MANAGER_SERVICE;
+
+import android.annotation.NonNull;
+import android.app.ActivityManagerInternal;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.UserManager;
+import android.view.intelligence.IIntelligenceManager;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.IResultReceiver;
+import com.android.internal.util.DumpUtils;
+import com.android.internal.util.Preconditions;
+import com.android.server.AbstractMasterSystemService;
+import com.android.server.LocalServices;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * A service used to observe the contents 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.
+ */
+public final class IntelligenceManagerService
+ extends AbstractMasterSystemService<IntelligencePerUserService> {
+
+ private static final String TAG = "IntelligenceManagerService";
+
+ @GuardedBy("mLock")
+ private ActivityManagerInternal mAm;
+
+ public IntelligenceManagerService(Context context) {
+ super(context, UserManager.DISALLOW_INTELLIGENCE_CAPTURE);
+ }
+
+ @Override // from MasterSystemService
+ protected String getServiceSettingsProperty() {
+ // TODO(b/111276913): STOPSHIP temporary settings, until it's set by resourcs + cmd
+ return "intel_service";
+ }
+
+ @Override // from MasterSystemService
+ protected IntelligencePerUserService newServiceLocked(int resolvedUserId,
+ boolean disabled) {
+ return new IntelligencePerUserService(this, mLock, resolvedUserId);
+ }
+
+ @Override // from SystemService
+ public void onStart() {
+ publishBinderService(INTELLIGENCE_MANAGER_SERVICE,
+ new IntelligenceManagerServiceStub());
+ }
+
+ private ActivityManagerInternal getAmInternal() {
+ synchronized (mLock) {
+ if (mAm == null) {
+ mAm = LocalServices.getService(ActivityManagerInternal.class);
+ }
+ }
+ return mAm;
+ }
+
+ final class IntelligenceManagerServiceStub extends IIntelligenceManager.Stub {
+
+ @Override
+ public void startSession(int userId, @NonNull IBinder activityToken,
+ @NonNull ComponentName componentName, int localSessionId, int flags,
+ @NonNull IResultReceiver result) {
+ Preconditions.checkNotNull(activityToken);
+
+ // TODO(b/111276913): refactor getTaskIdForActivity() to also return ComponentName,
+ // so we don't pass it on startSession (same for Autofill)
+ final int taskId = getAmInternal().getTaskIdForActivity(activityToken, false);
+
+ // TODO(b/111276913): get from AM as well
+ final int displayId = 0;
+
+ synchronized (mLock) {
+ final IntelligencePerUserService service = getServiceForUserLocked(userId);
+ service.startSessionLocked(activityToken, componentName, taskId, displayId,
+ localSessionId, flags, result);
+ }
+ }
+
+ @Override
+ public void finishSession(int userId, @NonNull IBinder activityToken,
+ @NonNull ComponentName componentName, int localSessionId, int globalSessionId) {
+ Preconditions.checkNotNull(activityToken);
+
+ synchronized (mLock) {
+ final IntelligencePerUserService service = getServiceForUserLocked(userId);
+ service.finishSessionLocked(activityToken, componentName, localSessionId,
+ globalSessionId);
+ }
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
+
+ synchronized (mLock) {
+ dumpLocked("", pw);
+ }
+ }
+ }
+}
diff --git a/services/autofill/java/com/android/server/intelligence/IntelligencePerUserService.java b/services/autofill/java/com/android/server/intelligence/IntelligencePerUserService.java
new file mode 100644
index 0000000..b62b239
--- /dev/null
+++ b/services/autofill/java/com/android/server/intelligence/IntelligencePerUserService.java
@@ -0,0 +1,174 @@
+/*
+ * 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 com.android.server.intelligence;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.IResultReceiver;
+import com.android.server.AbstractPerUserSystemService;
+
+import java.io.PrintWriter;
+
+/**
+ * Per-user instance of {@link IntelligenceManagerService}.
+ */
+final class IntelligencePerUserService
+ extends AbstractPerUserSystemService<IntelligencePerUserService> {
+
+ private static final String TAG = "IntelligencePerUserService";
+
+ private static int sNextSessionId;
+
+ // TODO(b/111276913): should key by componentName + taskId or ActivityToken
+ @GuardedBy("mLock")
+ private final ArrayMap<ComponentName, ContentCaptureSession> mSessions = new ArrayMap<>();
+
+ // TODO(b/111276913): add mechanism to prune stale sessions, similar to Autofill's
+
+ protected IntelligencePerUserService(
+ IntelligenceManagerService master, Object lock, int userId) {
+ super(master, lock, userId);
+ }
+
+ @Override // from PerUserSystemService
+ protected ServiceInfo newServiceInfo(@NonNull ComponentName serviceComponent)
+ throws NameNotFoundException {
+
+ ServiceInfo si;
+ try {
+ // TODO(b/111276913): must check that either the service is from a system component,
+ // or it matches a service set by shell cmd (so it can be used on CTS tests and when
+ // OEMs are implementing the real service
+ si = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
+ PackageManager.GET_META_DATA, mUserId);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Could not get service for " + serviceComponent + ": " + e);
+ return null;
+ }
+ if (!Manifest.permission.BIND_INTELLIGENCE_SERVICE.equals(si.permission)) {
+ Slog.w(TAG, "IntelligenceService from '" + si.packageName
+ + "' does not require permission "
+ + Manifest.permission.BIND_INTELLIGENCE_SERVICE);
+ throw new SecurityException("Service does not require permission "
+ + Manifest.permission.BIND_INTELLIGENCE_SERVICE);
+ }
+ return si;
+ }
+
+ // TODO(b/111276913): log metrics
+ @GuardedBy("mLock")
+ public void startSessionLocked(@NonNull IBinder activityToken,
+ @NonNull ComponentName componentName, int taskId, int displayId, int localSessionId,
+ int flags, @NonNull IResultReceiver resultReceiver) {
+ final ComponentName serviceComponentName = getServiceComponentName();
+ if (serviceComponentName == null) {
+ // TODO(b/111276913): this happens when the system service is starting, we should
+ // probably handle it in a more elegant way (like waiting for boot_complete or
+ // something like that
+ Slog.w(TAG, "startSession(" + activityToken + "): hold your horses");
+ return;
+ }
+
+ ContentCaptureSession session = mSessions.get(componentName);
+ if (session != null) {
+ if (mMaster.debug) {
+ Slog.d(TAG, "startSession(): reusing session " + session.getGlobalSessionId()
+ + " for " + componentName);
+ }
+ // TODO(b/111276913): check if local ids match and decide what to do if they don't
+ // TODO(b/111276913): should we call session.notifySessionStartedLocked() again??
+ // if not, move notifySessionStartedLocked() into session constructor
+ sendToClient(resultReceiver, session.getGlobalSessionId());
+ return;
+ }
+
+ // TODO(b/117779333): get from mMaster once it's moved to superclass
+ final boolean bindInstantServiceAllowed = false;
+
+ session = new ContentCaptureSession(getContext(), mUserId, mLock, activityToken,
+ this, serviceComponentName, componentName, taskId, displayId, localSessionId,
+ ++sNextSessionId, flags, bindInstantServiceAllowed, mMaster.verbose);
+ if (mMaster.verbose) {
+ Slog.v(TAG, "startSession(): new session for " + componentName + "; globalId ="
+ + session.getGlobalSessionId());
+ }
+ mSessions.put(componentName, session);
+ session.notifySessionStartedLocked();
+ sendToClient(resultReceiver, session.getGlobalSessionId());
+ }
+
+ // TODO(b/111276913): log metrics
+ @GuardedBy("mLock")
+ public void finishSessionLocked(@NonNull IBinder activityToken,
+ @NonNull ComponentName componentName, int localSessionId, int globalSessionId) {
+ final ContentCaptureSession session = mSessions.get(componentName);
+ if (session == null) {
+ Slog.w(TAG, "finishSession(): no session for " + componentName);
+ return;
+ }
+ if (mMaster.verbose) {
+ Slog.v(TAG, "finishSession(): comp=" + componentName + "; globalId ="
+ + session.getGlobalSessionId());
+ }
+ // TODO(b/111276913): check if all arguments match existing session and throw exception if
+ // not. Or just use componentName if we change AIDL to pass just ApplicationToken and
+ // retrieve componentName from AMInternal
+ session.removeSelfLocked(true);
+ }
+
+ @GuardedBy("mLock")
+ public void removeSessionLocked(@NonNull ComponentName key) {
+ mSessions.remove(key);
+ }
+
+ @Override
+ protected void dumpLocked(String prefix, PrintWriter pw) {
+ super.dumpLocked(prefix, pw);
+ pw.print(prefix); pw.print("next id: "); pw.println(sNextSessionId);
+ if (mSessions.isEmpty()) {
+ pw.print(prefix); pw.println("no sessions");
+ } else {
+ final int size = mSessions.size();
+ pw.print(prefix); pw.print("number sessions: "); pw.println(size);
+ final String prefix2 = prefix + " ";
+ for (int i = 0; i < size; i++) {
+ pw.print(prefix); pw.print("session@"); pw.println(i);
+ final ContentCaptureSession session = mSessions.valueAt(i);
+ session.dumpLocked(prefix2, pw);
+ }
+ }
+ }
+
+ private static void sendToClient(@NonNull IResultReceiver resultReceiver, int value) {
+ try {
+ resultReceiver.send(value, null);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error async reporting result to client: " + e);
+ }
+ }
+}
diff --git a/services/autofill/java/com/android/server/intelligence/RemoteIntelligenceService.java b/services/autofill/java/com/android/server/intelligence/RemoteIntelligenceService.java
new file mode 100644
index 0000000..ee66b4e
--- /dev/null
+++ b/services/autofill/java/com/android/server/intelligence/RemoteIntelligenceService.java
@@ -0,0 +1,122 @@
+/*
+ * 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 com.android.server.intelligence;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.RemoteException;
+import android.service.intelligence.IIntelligenceService;
+import android.service.intelligence.InteractionContext;
+import android.service.intelligence.InteractionSessionId;
+import android.text.format.DateUtils;
+import android.util.Slog;
+
+import com.android.server.AbstractRemoteService;
+
+final class RemoteIntelligenceService extends AbstractRemoteService {
+
+ private static final String TAG = "RemoteIntelligenceService";
+
+ private static final long TIMEOUT_IDLE_BIND_MILLIS = 2 * DateUtils.MINUTE_IN_MILLIS;
+ private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 2 * DateUtils.MINUTE_IN_MILLIS;
+
+ private final RemoteIntelligenceServiceCallbacks mCallbacks;
+ private IIntelligenceService mService;
+
+ RemoteIntelligenceService(Context context, String serviceInterface,
+ ComponentName componentName, int userId,
+ RemoteIntelligenceServiceCallbacks callbacks, boolean bindInstantServiceAllowed,
+ boolean verbose) {
+ super(context, serviceInterface, componentName, userId, callbacks,
+ bindInstantServiceAllowed, verbose);
+ mCallbacks = callbacks;
+ }
+
+ @Override // from RemoteService
+ protected IInterface getServiceInterface(@NonNull IBinder service) {
+ mService = IIntelligenceService.Stub.asInterface(service);
+ return mService;
+ }
+
+ // TODO(b/111276913): modify super class to allow permanent binding when value is 0 or negative
+ @Override // from RemoteService
+ protected long getTimeoutIdleBindMillis() {
+ // TODO(b/111276913): read from Settings so it can be changed in the field
+ return TIMEOUT_IDLE_BIND_MILLIS;
+ }
+
+ @Override // from RemoteService
+ protected long getRemoteRequestMillis() {
+ // TODO(b/111276913): read from Settings so it can be changed in the field
+ return TIMEOUT_REMOTE_REQUEST_MILLIS;
+ }
+
+ /**
+ * Called by {@link ContentCaptureSession} to generate a call to the
+ * {@link RemoteIntelligenceService} to indicate the session was created (when {@code context}
+ * is not {@code null} or destroyed (when {@code context} is {@code null}).
+ */
+ public void onSessionLifecycleRequest(@Nullable InteractionContext context,
+ @NonNull InteractionSessionId sessionId) {
+ cancelScheduledUnbind();
+ scheduleRequest(new PendingSessionLifecycleRequest(this, context, sessionId));
+ }
+
+ private static final class PendingSessionLifecycleRequest
+ extends PendingRequest<RemoteIntelligenceService> {
+
+ private final InteractionContext mContext;
+ private final InteractionSessionId mSessionId;
+
+ protected PendingSessionLifecycleRequest(@NonNull RemoteIntelligenceService service,
+ @Nullable InteractionContext context, @NonNull InteractionSessionId sessionId) {
+ super(service);
+ mContext = context;
+ mSessionId = sessionId;
+ }
+
+ @Override // from PendingRequest
+ public void run() {
+ final RemoteIntelligenceService remoteService = getService();
+ if (remoteService != null) {
+ try {
+ remoteService.mService.onSessionLifecycle(mContext, mSessionId);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "exception handling PendingSessionLifecycleRequest for "
+ + mSessionId + ": " + e);
+ remoteService.mCallbacks
+ .onSessionLifecycleRequestFailureOrTimeout(/* timedOut= */ false);
+ }
+ }
+ }
+
+ @Override // from PendingRequest
+ protected void onTimeout(RemoteIntelligenceService remoteService) {
+ Slog.w(TAG, "timed out handling PendingSessionLifecycleRequest for "
+ + mSessionId);
+ remoteService.mCallbacks
+ .onSessionLifecycleRequestFailureOrTimeout(/* timedOut= */ true);
+ }
+ }
+
+ public interface RemoteIntelligenceServiceCallbacks extends VultureCallback {
+ void onSessionLifecycleRequestFailureOrTimeout(boolean timedOut);
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index f432c8d..54a140d 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -16,6 +16,12 @@
package com.android.server;
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH;
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
+import static android.os.IServiceManager.DUMP_FLAG_PROTO;
+import static android.view.Display.DEFAULT_DISPLAY;
+
import android.app.ActivityThread;
import android.app.INotificationManager;
import android.app.usage.UsageStatsManagerInternal;
@@ -64,10 +70,11 @@
import com.android.internal.util.EmergencyAffordanceManager;
import com.android.internal.widget.ILockSettings;
import com.android.server.am.ActivityManagerService;
-import com.android.server.wm.ActivityTaskManagerService;
import com.android.server.appbinding.AppBindingService;
import com.android.server.audio.AudioService;
import com.android.server.biometrics.BiometricService;
+import com.android.server.biometrics.face.FaceService;
+import com.android.server.biometrics.fingerprint.FingerprintService;
import com.android.server.biometrics.iris.IrisService;
import com.android.server.broadcastradio.BroadcastRadioService;
import com.android.server.camera.CameraServiceProxy;
@@ -79,8 +86,6 @@
import com.android.server.display.DisplayManagerService;
import com.android.server.dreams.DreamManagerService;
import com.android.server.emergency.EmergencyAffordanceService;
-import com.android.server.biometrics.face.FaceService;
-import com.android.server.biometrics.fingerprint.FingerprintService;
import com.android.server.hdmi.HdmiControlService;
import com.android.server.input.InputManagerService;
import com.android.server.inputmethod.InputMethodManagerService;
@@ -88,8 +93,8 @@
import com.android.server.lights.LightsService;
import com.android.server.media.MediaResourceMonitorService;
import com.android.server.media.MediaRouterService;
-import com.android.server.media.MediaUpdateService;
import com.android.server.media.MediaSessionService;
+import com.android.server.media.MediaUpdateService;
import com.android.server.media.projection.MediaProjectionManagerService;
import com.android.server.net.NetworkPolicyManagerService;
import com.android.server.net.NetworkStatsService;
@@ -128,6 +133,7 @@
import com.android.server.usage.UsageStatsService;
import com.android.server.vr.VrManagerService;
import com.android.server.webkit.WebViewUpdateService;
+import com.android.server.wm.ActivityTaskManagerService;
import com.android.server.wm.WindowManagerService;
import dalvik.system.VMRuntime;
@@ -139,12 +145,6 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
-import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
-import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH;
-import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
-import static android.os.IServiceManager.DUMP_FLAG_PROTO;
-import static android.view.Display.DEFAULT_DISPLAY;
-
public final class SystemServer {
private static final String TAG = "SystemServer";
@@ -230,6 +230,8 @@
"com.android.server.wallpaper.WallpaperManagerService$Lifecycle";
private static final String AUTO_FILL_MANAGER_SERVICE_CLASS =
"com.android.server.autofill.AutofillManagerService";
+ private static final String INTELLIGENCE_MANAGER_SERVICE_CLASS =
+ "com.android.server.intelligence.IntelligenceManagerService";
private static final String TIME_ZONE_RULES_MANAGER_SERVICE_CLASS =
"com.android.server.timezone.RulesManagerService$Lifecycle";
private static final String IOT_SERVICE_CLASS =
@@ -795,6 +797,8 @@
boolean disableSystemTextClassifier = SystemProperties.getBoolean(
"config.disable_systemtextclassifier", false);
+ boolean disableIntelligence = SystemProperties.getBoolean(
+ "config.disable_intelligence", false);
boolean disableNetworkTime = SystemProperties.getBoolean("config.disable_networktime",
false);
boolean disableCameraService = SystemProperties.getBoolean("config.disable_cameraservice",
@@ -1737,6 +1741,12 @@
traceEnd();
}
+ if (!disableIntelligence) {
+ traceBeginAndSlog("StartIntelligenceService");
+ mSystemServiceManager.startService(INTELLIGENCE_MANAGER_SERVICE_CLASS);
+ traceEnd();
+ }
+
traceBeginAndSlog("AppServiceManager");
mSystemServiceManager.startService(AppBindingService.Lifecycle.class);
traceEnd();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
index 6c125d1..ae0e35b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
@@ -31,11 +31,9 @@
import android.app.Notification;
import android.app.NotificationChannel;
-import android.app.NotificationManager;
import android.app.NotificationManager.Policy;
import android.media.AudioAttributes;
import android.service.notification.StatusBarNotification;
-import android.service.notification.ZenModeConfig;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -45,6 +43,7 @@
import com.android.server.UiServiceTestCase;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -75,6 +74,7 @@
return new NotificationRecord(mContext, sbn, c);
}
+ @Ignore
@Test
public void testIsMessage() {
NotificationRecord r = getNotificationRecord();
@@ -86,6 +86,7 @@
assertFalse(mZenModeFiltering.isMessage(r));
}
+ @Ignore
@Test
public void testIsAlarm() {
NotificationChannel c = mock(NotificationChannel.class);
@@ -100,6 +101,7 @@
assertTrue(mZenModeFiltering.isAlarm(r));
}
+ @Ignore
@Test
public void testIsAlarm_wrongCategory() {
NotificationRecord r = getNotificationRecord();
@@ -107,6 +109,7 @@
assertFalse(mZenModeFiltering.isAlarm(r));
}
+ @Ignore
@Test
public void testIsAlarm_wrongUsage() {
NotificationChannel c = mock(NotificationChannel.class);
@@ -117,6 +120,7 @@
assertFalse(mZenModeFiltering.isAlarm(r));
}
+ @Ignore
@Test
public void testSuppressDNDInfo_yes_VisEffectsAllowed() {
NotificationRecord r = getNotificationRecord();
@@ -128,6 +132,7 @@
assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
}
+ @Ignore
@Test
public void testSuppressDNDInfo_yes_WrongId() {
NotificationRecord r = getNotificationRecord();
@@ -138,6 +143,7 @@
assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
}
+ @Ignore
@Test
public void testSuppressDNDInfo_yes_WrongPackage() {
NotificationRecord r = getNotificationRecord();
@@ -148,6 +154,7 @@
assertTrue(mZenModeFiltering.shouldIntercept(ZEN_MODE_IMPORTANT_INTERRUPTIONS, policy, r));
}
+ @Ignore
@Test
public void testSuppressDNDInfo_no() {
NotificationRecord r = getNotificationRecord();
@@ -160,6 +167,7 @@
assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_NO_INTERRUPTIONS, policy, r));
}
+ @Ignore
@Test
public void testSuppressAnything_yes_ZenModeOff() {
NotificationRecord r = getNotificationRecord();
@@ -169,6 +177,7 @@
assertFalse(mZenModeFiltering.shouldIntercept(ZEN_MODE_OFF, policy, r));
}
+ @Ignore
@Test
public void testSuppressAnything_bypass_ZenModeOn() {
NotificationRecord r = getNotificationRecord();