diff options
25 files changed, 458 insertions, 373 deletions
diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java index 06e86af0208f..78248d9775f4 100644 --- a/core/java/android/service/contentcapture/ContentCaptureService.java +++ b/core/java/android/service/contentcapture/ContentCaptureService.java @@ -138,7 +138,8 @@ public abstract class ContentCaptureService extends Service { new LocalDataShareAdapterResourceManager(); private Handler mHandler; - private IContentCaptureServiceCallback mCallback; + @Nullable private IContentCaptureServiceCallback mContentCaptureServiceCallback; + @Nullable private IContentProtectionAllowlistCallback mContentProtectionAllowlistCallback; private long mCallerMismatchTimeout = 1000; private long mLastCallerMismatchLog; @@ -215,6 +216,16 @@ public abstract class ContentCaptureService extends Service { Binder.getCallingUid(), events)); } + + @Override + public void onUpdateAllowlistRequest(IBinder callback) { + mHandler.sendMessage( + obtainMessage( + ContentCaptureService::handleOnUpdateAllowlistRequest, + ContentCaptureService.this, + Binder.getCallingUid(), + callback)); + } }; /** Binder that receives calls from the app in the content capture flow. */ @@ -278,14 +289,31 @@ public abstract class ContentCaptureService extends Service { */ public final void setContentCaptureWhitelist(@Nullable Set<String> packages, @Nullable Set<ComponentName> activities) { - final IContentCaptureServiceCallback callback = mCallback; - if (callback == null) { - Log.w(TAG, "setContentCaptureWhitelist(): no server callback"); + + IContentCaptureServiceCallback contentCaptureCallback = mContentCaptureServiceCallback; + IContentProtectionAllowlistCallback contentProtectionAllowlistCallback = + mContentProtectionAllowlistCallback; + + if (contentCaptureCallback == null && contentProtectionAllowlistCallback == null) { + Log.w(TAG, "setContentCaptureWhitelist(): missing both server callbacks"); + return; + } + + if (contentCaptureCallback != null) { + if (contentProtectionAllowlistCallback != null) { + throw new IllegalStateException("Have both server callbacks"); + } + try { + contentCaptureCallback.setContentCaptureWhitelist( + toList(packages), toList(activities)); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } return; } try { - callback.setContentCaptureWhitelist(toList(packages), toList(activities)); + contentProtectionAllowlistCallback.setAllowlist(toList(packages)); } catch (RemoteException e) { e.rethrowFromSystemServer(); } @@ -314,7 +342,7 @@ public abstract class ContentCaptureService extends Service { */ public final void setContentCaptureConditions(@NonNull String packageName, @Nullable Set<ContentCaptureCondition> conditions) { - final IContentCaptureServiceCallback callback = mCallback; + final IContentCaptureServiceCallback callback = mContentCaptureServiceCallback; if (callback == null) { Log.w(TAG, "setContentCaptureConditions(): no server callback"); return; @@ -425,7 +453,7 @@ public abstract class ContentCaptureService extends Service { public final void disableSelf() { if (sDebug) Log.d(TAG, "disableSelf()"); - final IContentCaptureServiceCallback callback = mCallback; + final IContentCaptureServiceCallback callback = mContentCaptureServiceCallback; if (callback == null) { Log.w(TAG, "disableSelf(): no server callback"); return; @@ -464,13 +492,14 @@ public abstract class ContentCaptureService extends Service { } private void handleOnConnected(@NonNull IBinder callback) { - mCallback = IContentCaptureServiceCallback.Stub.asInterface(callback); + mContentCaptureServiceCallback = IContentCaptureServiceCallback.Stub.asInterface(callback); onConnected(); } private void handleOnDisconnected() { onDisconnected(); - mCallback = null; + mContentCaptureServiceCallback = null; + mContentProtectionAllowlistCallback = null; } //TODO(b/111276913): consider caching the InteractionSessionId for the lifetime of the session, @@ -583,6 +612,16 @@ public abstract class ContentCaptureService extends Service { onContentCaptureEvent(sessionId, endEvent); } + private void handleOnUpdateAllowlistRequest(int uid, @NonNull IBinder callback) { + if (uid != Process.SYSTEM_UID) { + Log.e(TAG, "handleOnUpdateAllowlistRequest() not allowed for uid: " + uid); + return; + } + mContentProtectionAllowlistCallback = + IContentProtectionAllowlistCallback.Stub.asInterface(callback); + onConnected(); + } + private void handleOnActivitySnapshot(int sessionId, @NonNull SnapshotData snapshotData) { onActivitySnapshot(new ContentCaptureSessionId(sessionId), snapshotData); } @@ -701,13 +740,14 @@ public abstract class ContentCaptureService extends Service { private void writeFlushMetrics(int sessionId, @Nullable ComponentName app, @NonNull FlushMetrics flushMetrics, @Nullable ContentCaptureOptions options, int flushReason) { - if (mCallback == null) { + if (mContentCaptureServiceCallback == null) { Log.w(TAG, "writeSessionFlush(): no server callback"); return; } try { - mCallback.writeSessionFlush(sessionId, app, flushMetrics, options, flushReason); + mContentCaptureServiceCallback.writeSessionFlush( + sessionId, app, flushMetrics, options, flushReason); } catch (RemoteException e) { Log.e(TAG, "failed to write flush metrics: " + e); } diff --git a/core/java/android/service/contentcapture/IContentProtectionAllowlistCallback.aidl b/core/java/android/service/contentcapture/IContentProtectionAllowlistCallback.aidl new file mode 100644 index 000000000000..8d5a15c1fdd2 --- /dev/null +++ b/core/java/android/service/contentcapture/IContentProtectionAllowlistCallback.aidl @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 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.contentcapture; + +import android.content.ComponentName; +import android.view.contentcapture.ContentCaptureCondition; +import android.service.contentcapture.FlushMetrics; +import android.content.ContentCaptureOptions; + +import java.util.List; + +/** + * Interface for the callback used by the content protection service to call the system server and + * update the allowlist. + * + * @hide + */ +oneway interface IContentProtectionAllowlistCallback { + void setAllowlist(in List<String> packages); +} diff --git a/core/java/android/service/contentcapture/IContentProtectionService.aidl b/core/java/android/service/contentcapture/IContentProtectionService.aidl index 4a13c3f63a33..25b248fcc8d4 100644 --- a/core/java/android/service/contentcapture/IContentProtectionService.aidl +++ b/core/java/android/service/contentcapture/IContentProtectionService.aidl @@ -17,6 +17,7 @@ package android.service.contentcapture; import android.content.pm.ParceledListSlice; +import android.os.IBinder; import android.view.contentcapture.ContentCaptureEvent; /** @@ -27,4 +28,6 @@ import android.view.contentcapture.ContentCaptureEvent; oneway interface IContentProtectionService { void onLoginDetected(in ParceledListSlice events); + + void onUpdateAllowlistRequest(in IBinder callback); } diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index c10fc9f9cb09..6c3b8ab19792 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -525,11 +525,11 @@ interface IWindowManager out InputChannel inputChannel); /** - * Destroy an input consumer by name and display id. + * Destroy an input consumer by token and display id. * This method will also dispose the input channels associated with that InputConsumer. */ @UnsupportedAppUsage - boolean destroyInputConsumer(String name, int displayId); + boolean destroyInputConsumer(IBinder token, int displayId); /** * Return the touch region for the current IME window, or an empty region if there is none. diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java index 5f612d6b0009..970baf2ce264 100644 --- a/core/java/android/view/contentcapture/ContentCaptureManager.java +++ b/core/java/android/view/contentcapture/ContentCaptureManager.java @@ -411,6 +411,15 @@ public final class ContentCaptureManager { public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_TIMEOUT_MS = "content_protection_allowlist_timeout_ms"; + /** + * Sets the auto disconnect timeout for the content protection service in milliseconds. + * + * @hide + */ + // Unit can't be in the name in order to pass the checkstyle hook, line would be too long. + public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_AUTO_DISCONNECT_TIMEOUT = + "content_protection_auto_disconnect_timeout_ms"; + /** @hide */ @TestApi public static final int LOGGING_LEVEL_OFF = 0; @@ -465,6 +474,8 @@ public final class ContentCaptureManager { public static final long DEFAULT_CONTENT_PROTECTION_ALLOWLIST_DELAY_MS = 30000; /** @hide */ public static final long DEFAULT_CONTENT_PROTECTION_ALLOWLIST_TIMEOUT_MS = 250; + /** @hide */ + public static final long DEFAULT_CONTENT_PROTECTION_AUTO_DISCONNECT_TIMEOUT_MS = 3000; private final Object mLock = new Object(); diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index f19a2f961391..30788014ff92 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -104,7 +104,6 @@ import android.widget.CompoundButton.OnCheckedChangeListener; import com.android.internal.R; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; -import com.android.internal.util.ContrastColorUtil; import com.android.internal.util.Preconditions; import com.android.internal.widget.IRemoteViewsFactory; @@ -130,7 +129,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Stack; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; @@ -225,10 +223,8 @@ public class RemoteViews implements Parcelable, Filter { private static final int BITMAP_REFLECTION_ACTION_TAG = 12; private static final int TEXT_VIEW_SIZE_ACTION_TAG = 13; private static final int VIEW_PADDING_ACTION_TAG = 14; - private static final int SET_REMOTE_VIEW_ADAPTER_LIST_TAG = 15; private static final int SET_REMOTE_INPUTS_ACTION_TAG = 18; private static final int LAYOUT_PARAM_ACTION_TAG = 19; - private static final int OVERRIDE_TEXT_COLORS_TAG = 20; private static final int SET_RIPPLE_DRAWABLE_COLOR_TAG = 21; private static final int SET_INT_TAG_TAG = 22; private static final int REMOVE_FROM_PARENT_ACTION_TAG = 23; @@ -494,17 +490,6 @@ public class RemoteViews implements Parcelable, Filter { } /** - * Override all text colors in this layout and replace them by the given text color. - * - * @param textColor The color to use. - * - * @hide - */ - public void overrideTextColors(int textColor) { - addAction(new OverrideTextColorsAction(textColor)); - } - - /** * Sets an integer tag to the view. * * @hide @@ -640,7 +625,7 @@ public class RemoteViews implements Parcelable, Filter { * * SUBCLASSES MUST BE IMMUTABLE SO CLONE WORKS!!!!! */ - private abstract static class Action implements Parcelable { + private abstract static class Action { @IdRes @UnsupportedAppUsage int mViewId; @@ -652,10 +637,6 @@ public class RemoteViews implements Parcelable, Filter { public static final int MERGE_APPEND = 1; public static final int MERGE_IGNORE = 2; - public int describeContents() { - return 0; - } - public void setHierarchyRootData(HierarchyRootData root) { // Do nothing } @@ -689,6 +670,8 @@ public class RemoteViews implements Parcelable, Filter { public void visitUris(@NonNull Consumer<Uri> visitor) { // Nothing to visit by default. } + + public abstract void writeToParcel(Parcel dest, int flags); } /** @@ -1020,86 +1003,6 @@ public class RemoteViews implements Parcelable, Filter { } } - private static class SetRemoteViewsAdapterList extends Action { - int mViewTypeCount; - ArrayList<RemoteViews> mList; - - public SetRemoteViewsAdapterList(@IdRes int id, ArrayList<RemoteViews> list, - int viewTypeCount) { - this.mViewId = id; - this.mList = list; - this.mViewTypeCount = viewTypeCount; - } - - public SetRemoteViewsAdapterList(Parcel parcel) { - mViewId = parcel.readInt(); - mViewTypeCount = parcel.readInt(); - mList = parcel.createTypedArrayList(RemoteViews.CREATOR); - } - - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mViewId); - dest.writeInt(mViewTypeCount); - dest.writeTypedList(mList, flags); - } - - @Override - public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { - final View target = root.findViewById(mViewId); - if (target == null) return; - - // Ensure that we are applying to an AppWidget root - if (!(rootParent instanceof AppWidgetHostView)) { - Log.e(LOG_TAG, "SetRemoteViewsAdapterIntent action can only be used for " + - "AppWidgets (root id: " + mViewId + ")"); - return; - } - // Ensure that we are calling setRemoteAdapter on an AdapterView that supports it - if (!(target instanceof AbsListView) && !(target instanceof AdapterViewAnimator)) { - Log.e(LOG_TAG, "Cannot setRemoteViewsAdapter on a view which is not " + - "an AbsListView or AdapterViewAnimator (id: " + mViewId + ")"); - return; - } - - if (target instanceof AbsListView) { - AbsListView v = (AbsListView) target; - Adapter a = v.getAdapter(); - if (a instanceof RemoteViewsListAdapter && mViewTypeCount <= a.getViewTypeCount()) { - ((RemoteViewsListAdapter) a).setViewsList(mList); - } else { - v.setAdapter(new RemoteViewsListAdapter(v.getContext(), mList, mViewTypeCount, - params.colorResources)); - } - } else if (target instanceof AdapterViewAnimator) { - AdapterViewAnimator v = (AdapterViewAnimator) target; - Adapter a = v.getAdapter(); - if (a instanceof RemoteViewsListAdapter && mViewTypeCount <= a.getViewTypeCount()) { - ((RemoteViewsListAdapter) a).setViewsList(mList); - } else { - v.setAdapter(new RemoteViewsListAdapter(v.getContext(), mList, mViewTypeCount, - params.colorResources)); - } - } - } - - @Override - public int getActionTag() { - return SET_REMOTE_VIEW_ADAPTER_LIST_TAG; - } - - @Override - public String getUniqueKey() { - return (SET_REMOTE_ADAPTER_TAG + "_" + mViewId); - } - - @Override - public void visitUris(@NonNull Consumer<Uri> visitor) { - for (RemoteViews remoteViews : mList) { - remoteViews.visitUris(visitor); - } - } - } - /** * Cache of {@link ApplicationInfo}s that can be used to ensure that the same * {@link ApplicationInfo} instance is used throughout the RemoteViews. @@ -3491,51 +3394,6 @@ public class RemoteViews implements Parcelable, Filter { } } - /** - * Helper action to override all textViewColors - */ - private static class OverrideTextColorsAction extends Action { - private final int mTextColor; - - public OverrideTextColorsAction(int textColor) { - this.mTextColor = textColor; - } - - public OverrideTextColorsAction(Parcel parcel) { - mTextColor = parcel.readInt(); - } - - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mTextColor); - } - - @Override - public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { - // Let's traverse the viewtree and override all textColors! - Stack<View> viewsToProcess = new Stack<>(); - viewsToProcess.add(root); - while (!viewsToProcess.isEmpty()) { - View v = viewsToProcess.pop(); - if (v instanceof TextView) { - TextView textView = (TextView) v; - textView.setText(ContrastColorUtil.clearColorSpans(textView.getText())); - textView.setTextColor(mTextColor); - } - if (v instanceof ViewGroup) { - ViewGroup viewGroup = (ViewGroup) v; - for (int i = 0; i < viewGroup.getChildCount(); i++) { - viewsToProcess.push(viewGroup.getChildAt(i)); - } - } - } - } - - @Override - public int getActionTag() { - return OVERRIDE_TEXT_COLORS_TAG; - } - } - private static class SetIntTagAction extends Action { @IdRes private final int mViewId; @IdRes private final int mKey; @@ -4156,14 +4014,10 @@ public class RemoteViews implements Parcelable, Filter { return new ViewPaddingAction(parcel); case BITMAP_REFLECTION_ACTION_TAG: return new BitmapReflectionAction(parcel); - case SET_REMOTE_VIEW_ADAPTER_LIST_TAG: - return new SetRemoteViewsAdapterList(parcel); case SET_REMOTE_INPUTS_ACTION_TAG: return new SetRemoteInputsAction(parcel); case LAYOUT_PARAM_ACTION_TAG: return new LayoutParamAction(parcel); - case OVERRIDE_TEXT_COLORS_TAG: - return new OverrideTextColorsAction(parcel); case SET_RIPPLE_DRAWABLE_COLOR_TAG: return new SetRippleDrawableColor(parcel); case SET_INT_TAG_TAG: @@ -4910,7 +4764,11 @@ public class RemoteViews implements Parcelable, Filter { @Deprecated public void setRemoteAdapter(@IdRes int viewId, ArrayList<RemoteViews> list, int viewTypeCount) { - addAction(new SetRemoteViewsAdapterList(viewId, list, viewTypeCount)); + RemoteCollectionItems.Builder b = new RemoteCollectionItems.Builder(); + for (int i = 0; i < list.size(); i++) { + b.addItem(i, list.get(i)); + } + setRemoteAdapter(viewId, b.setViewTypeCount(viewTypeCount).build()); } /** diff --git a/core/java/android/widget/RemoteViewsListAdapter.java b/core/java/android/widget/RemoteViewsListAdapter.java deleted file mode 100644 index 46f80f3b5129..000000000000 --- a/core/java/android/widget/RemoteViewsListAdapter.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2012 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.widget; - -import android.content.Context; -import android.view.View; -import android.view.ViewGroup; - -import java.util.ArrayList; - -/** - * @hide - */ -public class RemoteViewsListAdapter extends BaseAdapter { - - private Context mContext; - private ArrayList<RemoteViews> mRemoteViewsList; - private ArrayList<Integer> mViewTypes = new ArrayList<Integer>(); - private int mViewTypeCount; - private RemoteViews.ColorResources mColorResources; - - public RemoteViewsListAdapter(Context context, ArrayList<RemoteViews> remoteViews, - int viewTypeCount, RemoteViews.ColorResources colorResources) { - mContext = context; - mRemoteViewsList = remoteViews; - mViewTypeCount = viewTypeCount; - mColorResources = colorResources; - init(); - } - - public void setViewsList(ArrayList<RemoteViews> remoteViews) { - mRemoteViewsList = remoteViews; - init(); - notifyDataSetChanged(); - } - - private void init() { - if (mRemoteViewsList == null) return; - - mViewTypes.clear(); - for (RemoteViews rv: mRemoteViewsList) { - if (!mViewTypes.contains(rv.getLayoutId())) { - mViewTypes.add(rv.getLayoutId()); - } - } - - if (mViewTypes.size() > mViewTypeCount || mViewTypeCount < 1) { - throw new RuntimeException("Invalid view type count -- view type count must be >= 1" + - "and must be as large as the total number of distinct view types"); - } - } - - @Override - public int getCount() { - if (mRemoteViewsList != null) { - return mRemoteViewsList.size(); - } else { - return 0; - } - } - - @Override - public Object getItem(int position) { - return null; - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - if (position < getCount()) { - RemoteViews rv = mRemoteViewsList.get(position); - rv.addFlags(RemoteViews.FLAG_WIDGET_IS_COLLECTION_CHILD); - View v; - if (convertView != null && convertView.getId() == rv.getLayoutId()) { - v = convertView; - rv.reapply(mContext, v, null /* handler */, null /* size */, mColorResources); - } else { - v = rv.apply(mContext, parent, null /* handler */, null /* size */, - mColorResources); - } - return v; - } else { - return null; - } - } - - @Override - public int getItemViewType(int position) { - if (position < getCount()) { - int layoutId = mRemoteViewsList.get(position).getLayoutId(); - return mViewTypes.indexOf(layoutId); - } else { - return 0; - } - } - - public int getViewTypeCount() { - return mViewTypeCount; - } - - @Override - public boolean hasStableIds() { - return false; - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index cb5d1c42c7d2..03c546dd2cf3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -386,7 +386,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont // two gestures are waiting to be processed at the moment, skip any further user touches if (mCurrentTracker.isFinished() && mQueuedTracker.isFinished()) { - Log.d(TAG, "Ignoring MotionEvent because two gestures are already being queued."); + ProtoLog.d(WM_SHELL_BACK_PREVIEW, + "Ignoring MotionEvent because two gestures are already being queued."); return; } @@ -420,7 +421,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } else if (mQueuedTracker.isInInitialState()) { touchTracker = mQueuedTracker; } else { - Log.w(TAG, "Cannot start tracking new gesture with neither tracker in initial state."); + ProtoLog.w(WM_SHELL_BACK_PREVIEW, + "Cannot start tracking new gesture with neither tracker in initial state."); return; } touchTracker.setGestureStartLocation(touchX, touchY, swipeEdge); @@ -449,7 +451,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont @NonNull TouchTracker touchTracker) { ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Received backNavigationInfo:%s", backNavigationInfo); if (backNavigationInfo == null) { - Log.e(TAG, "Received BackNavigationInfo is null."); + ProtoLog.e(WM_SHELL_BACK_PREVIEW, "Received BackNavigationInfo is null."); return; } final int backType = backNavigationInfo.getType(); @@ -475,7 +477,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } private void injectBackKey() { - Log.d(TAG, "injectBackKey"); + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "injectBackKey"); sendBackEvent(KeyEvent.ACTION_DOWN); sendBackEvent(KeyEvent.ACTION_UP); } @@ -491,7 +493,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont ev.setDisplayId(mContext.getDisplay().getDisplayId()); if (!mContext.getSystemService(InputManager.class) .injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC)) { - Log.e(TAG, "Inject input event fail"); + ProtoLog.e(WM_SHELL_BACK_PREVIEW, "Inject input event fail"); } } @@ -669,7 +671,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont TouchTracker activeTouchTracker = getActiveTracker(); if (!mBackGestureStarted || activeTouchTracker == null) { // This can happen when an unfinished gesture has been reset in resetTouchTracker - Log.d(TAG, "onGestureFinished called while no gesture is started"); + ProtoLog.d(WM_SHELL_BACK_PREVIEW, + "onGestureFinished called while no gesture is started"); return; } boolean triggerBack = activeTouchTracker.getTriggerBack(); @@ -687,8 +690,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont // No focus window found or core are running recents animation, inject back key as // legacy behavior, or new back gesture was started while previous has not finished yet if (!mQueuedTracker.isInInitialState()) { - Log.e(TAG, "mBackNavigationInfo is null AND there is another back animation in " - + "progress"); + ProtoLog.e(WM_SHELL_BACK_PREVIEW, "mBackNavigationInfo is null AND there is " + + "another back animation in progress"); } mCurrentTracker.reset(); if (triggerBack) { @@ -702,7 +705,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont // Simply trigger and finish back navigation when no animator defined. if (!shouldDispatchToAnimator() || mShellBackAnimationRegistry.isAnimationCancelledOrNull(backType)) { - Log.d(TAG, "Trigger back without dispatching to animator."); + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Trigger back without dispatching to animator."); invokeOrCancelBack(mCurrentTracker); mCurrentTracker.reset(); return; @@ -748,13 +751,14 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable); mPostCommitAnimationInProgress = false; - Log.d(TAG, "BackAnimationController: onBackAnimationFinished()"); + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: onBackAnimationFinished()"); if (mCurrentTracker.isActive() || mCurrentTracker.isFinished()) { // Trigger the real back. invokeOrCancelBack(mCurrentTracker); } else { - Log.d(TAG, "mCurrentBackGestureInfo was null when back animation finished"); + ProtoLog.d(WM_SHELL_BACK_PREVIEW, + "mCurrentBackGestureInfo was null when back animation finished"); } resetTouchTracker(); } @@ -773,23 +777,25 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mBackGestureStarted = false; dispatchOnBackCancelled(mActiveCallback); finishBackNavigation(false); - Log.d(TAG, "resetTouchTracker -> reset an unfinished gesture"); + ProtoLog.d(WM_SHELL_BACK_PREVIEW, + "resetTouchTracker -> reset an unfinished gesture"); } else { - Log.d(TAG, "resetTouchTracker -> no queued gesture"); + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "resetTouchTracker -> no queued gesture"); } return; } if (mCurrentTracker.isFinished() && mCurrentTracker.getTriggerBack()) { - Log.d(TAG, "resetTouchTracker -> start queued back navigation AND post commit " - + "animation"); + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "resetTouchTracker -> start queued back navigation " + + "AND post commit animation"); injectBackKey(); finishBackNavigation(true); mCurrentTracker.reset(); } else if (!mCurrentTracker.isFinished()) { - Log.d(TAG, "resetTouchTracker -> queued gesture not finished; do nothing"); + ProtoLog.d(WM_SHELL_BACK_PREVIEW, + "resetTouchTracker -> queued gesture not finished; do nothing"); } else { - Log.d(TAG, "resetTouchTracker -> reset queued gesture"); + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "resetTouchTracker -> reset queued gesture"); mCurrentTracker.reset(); } } @@ -821,7 +827,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mShellExecutor.execute( () -> { if (mBackNavigationInfo == null) { - Log.e(TAG, "Lack of navigation info to start animation."); + ProtoLog.e(WM_SHELL_BACK_PREVIEW, + "Lack of navigation info to start animation."); return; } final BackAnimationRunner runner = diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java index 8e3376f163c1..f6cab485fa2a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java @@ -139,7 +139,7 @@ public class PipInputConsumer { final InputChannel inputChannel = new InputChannel(); try { // TODO(b/113087003): Support Picture-in-picture in multi-display. - mWindowManager.destroyInputConsumer(mName, DEFAULT_DISPLAY); + mWindowManager.destroyInputConsumer(mToken, DEFAULT_DISPLAY); mWindowManager.createInputConsumer(mToken, mName, DEFAULT_DISPLAY, inputChannel); } catch (RemoteException e) { ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, @@ -163,7 +163,7 @@ public class PipInputConsumer { } try { // TODO(b/113087003): Support Picture-in-picture in multi-display. - mWindowManager.destroyInputConsumer(mName, DEFAULT_DISPLAY); + mWindowManager.destroyInputConsumer(mToken, DEFAULT_DISPLAY); } catch (RemoteException e) { ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: Failed to destroy input consumer, %s", TAG, e); diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index ce0772ff84a1..f0741061a051 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -607,6 +607,8 @@ public class Utils { userType = UserIconInfo.TYPE_CLONED; } else if (ui.isManagedProfile()) { userType = UserIconInfo.TYPE_WORK; + } else if (ui.isPrivateProfile()) { + userType = UserIconInfo.TYPE_PRIVATE; } } } catch (Exception e) { diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 572f6ff2745e..1a37e2ddcc4b 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -977,4 +977,13 @@ Width in pixels of the Side FPS sensor. --> <integer name="config_sfpsSensorWidth">200</integer> + + <!-- + They are service names that, if enabled, will cause the magnification settings button + to never hide after timeout. + --> + <string-array name="services_always_show_magnification_settings" translatable="false"> + <item>com.android.switchaccess.SwitchAccessService</item> + <item>com.google.android.apps.accessibility.voiceaccess.JustSpeakService</item> + </string-array> </resources> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputConsumerController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputConsumerController.java index ba0a6d149707..b406e72ffb98 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputConsumerController.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputConsumerController.java @@ -139,7 +139,7 @@ public class InputConsumerController { if (mInputEventReceiver == null) { final InputChannel inputChannel = new InputChannel(); try { - mWindowManager.destroyInputConsumer(mName, DEFAULT_DISPLAY); + mWindowManager.destroyInputConsumer(mToken, DEFAULT_DISPLAY); mWindowManager.createInputConsumer(mToken, mName, DEFAULT_DISPLAY, inputChannel); } catch (RemoteException e) { Log.e(TAG, "Failed to create input consumer", e); @@ -158,7 +158,7 @@ public class InputConsumerController { public void unregisterInputConsumer() { if (mInputEventReceiver != null) { try { - mWindowManager.destroyInputConsumer(mName, DEFAULT_DISPLAY); + mWindowManager.destroyInputConsumer(mToken, DEFAULT_DISPLAY); } catch (RemoteException e) { Log.e(TAG, "Failed to destroy input consumer", e); } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java index 84f1395051eb..d9d9e3781242 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java @@ -20,12 +20,14 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; +import android.accessibilityservice.AccessibilityServiceInfo; import android.annotation.NonNull; import android.annotation.UiContext; import android.content.ComponentCallbacks; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.Rect; @@ -49,6 +51,8 @@ import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.systemui.res.R; import java.util.Collections; +import java.util.Optional; +import java.util.Set; /** * Shows/hides a {@link android.widget.ImageView} on the screen and changes the values of @@ -315,10 +319,46 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL DEFAULT_FADE_OUT_ANIMATION_DELAY_MS, AccessibilityManager.FLAG_CONTENT_ICONS | AccessibilityManager.FLAG_CONTENT_CONTROLS); + if (shouldAlwaysShowSettings()) { + mUiTimeout = -1; + } } // Refresh the time slot of the fade-out task whenever this method is called. stopFadeOutAnimation(); - mImageView.postOnAnimationDelayed(mFadeOutAnimationTask, mUiTimeout); + if (mUiTimeout >= 0) { + mImageView.postOnAnimationDelayed(mFadeOutAnimationTask, mUiTimeout); + } + } + + private boolean shouldAlwaysShowSettings() { + try { + var serviceNamesArray = mContext.getResources().getStringArray( + R.array.services_always_show_magnification_settings); + if (serviceNamesArray.length == 0) { + return false; + } + Set serviceNamesSet = Set.of(serviceNamesArray); + + var serviceInfoList = mAccessibilityManager + .getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK); + for (var serviceInfo : serviceInfoList) { + var serviceName = Optional.ofNullable(serviceInfo) + .map(AccessibilityServiceInfo::getResolveInfo) + .map(resolveInfo -> resolveInfo.serviceInfo) + .map(resolvedServiceInfo -> resolvedServiceInfo.name) + .orElse(null); + if (serviceName == null) { + continue; + } + + if (serviceNamesSet.contains(serviceName)) { + return true; + } + } + } catch (Resources.NotFoundException nfe) { + // No-op. Do not crash for not finding resources. + } + return false; } private void stopFadeOutAnimation() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt index 62c5988ff42c..755549b5478b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt @@ -60,8 +60,7 @@ constructor( private val deviceEntryIconViewId = R.id.device_entry_icon_view override fun addViews(constraintLayout: ConstraintLayout) { - if ( - !featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON) && + if (!featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON) && !featureFlags.isEnabled(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS) ) { return @@ -76,7 +75,7 @@ constructor( DeviceEntryIconView(context, null).apply { id = deviceEntryIconViewId } } else { // Flags.MIGRATE_LOCK_ICON - LockIconView(context, null).apply { id = deviceEntryIconViewId } + LockIconView(context, null).apply { id = R.id.lock_icon_view } } constraintLayout.addView(view) } @@ -91,7 +90,7 @@ constructor( ) } } else { - constraintLayout.findViewById<LockIconView?>(deviceEntryIconViewId)?.let { + constraintLayout.findViewById<LockIconView?>(R.id.lock_icon_view)?.let { lockIconViewController.get().setLockIconView(it) } } @@ -133,7 +132,11 @@ constructor( } override fun removeViews(constraintLayout: ConstraintLayout) { - constraintLayout.removeView(deviceEntryIconViewId) + if (featureFlags.isEnabled(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS)) { + constraintLayout.removeView(deviceEntryIconViewId) + } else { + constraintLayout.removeView(R.id.lock_icon_view) + } } @VisibleForTesting @@ -148,18 +151,25 @@ constructor( ) } + val iconId = + if (featureFlags.isEnabled(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS)) { + deviceEntryIconViewId + } else { + R.id.lock_icon_view + } + constraintSet.apply { - constrainWidth(deviceEntryIconViewId, sensorRect.right - sensorRect.left) - constrainHeight(deviceEntryIconViewId, sensorRect.bottom - sensorRect.top) + constrainWidth(iconId, sensorRect.right - sensorRect.left) + constrainHeight(iconId, sensorRect.bottom - sensorRect.top) connect( - deviceEntryIconViewId, + iconId, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP, sensorRect.top ) connect( - deviceEntryIconViewId, + iconId, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START, diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java index 8fd2bd6ad762..1a885453eccb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java @@ -49,8 +49,11 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.accessibilityservice.AccessibilityServiceInfo; import android.content.Context; import android.content.pm.ActivityInfo; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; import android.graphics.Insets; import android.graphics.Rect; import android.os.Handler; @@ -71,8 +74,8 @@ import android.widget.ImageView; import androidx.test.filters.SmallTest; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; -import com.android.systemui.res.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.res.R; import org.junit.After; import org.junit.Before; @@ -186,6 +189,87 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { } @Test + public void showMagnificationButton_noA11yServicesRunning_postDelayedAnimationsWithTimeout() { + final int a11yTimeout = 12345; + when(mAccessibilityManager.getRecommendedTimeoutMillis(anyInt(), anyInt())).thenReturn( + a11yTimeout); + when(mAccessibilityManager.getEnabledAccessibilityServiceList(anyInt())) + .thenReturn(List.of()); + + mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); + + verify(mAccessibilityManager).getRecommendedTimeoutMillis( + DEFAULT_FADE_OUT_ANIMATION_DELAY_MS, AccessibilityManager.FLAG_CONTENT_ICONS + | AccessibilityManager.FLAG_CONTENT_CONTROLS); + verify(mSpyImageView).postOnAnimationDelayed(any(Runnable.class), eq((long) a11yTimeout)); + } + + @Test + public void showMagnificationButton_voiceAccessRunning_noTimeout() { + var serviceInfo = createServiceInfoWithName( + "com.google.android.apps.accessibility.voiceaccess.JustSpeakService"); + when(mAccessibilityManager.getEnabledAccessibilityServiceList(anyInt())) + .thenReturn(List.of(serviceInfo)); + + mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); + + verify(mSpyImageView, never()).postOnAnimationDelayed(any(Runnable.class), anyLong()); + } + + @Test + public void showMagnificationButton_switchAccessRunning_noTimeout() { + var serviceInfo = createServiceInfoWithName( + "com.android.switchaccess.SwitchAccessService"); + when(mAccessibilityManager.getEnabledAccessibilityServiceList(anyInt())) + .thenReturn(List.of(serviceInfo)); + + mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); + + verify(mSpyImageView, never()).postOnAnimationDelayed(any(Runnable.class), anyLong()); + } + + @Test + public void showMagnificationButton_switchAccessAndVoiceAccessBothRunning_noTimeout() { + var switchAccessServiceInfo = createServiceInfoWithName( + "com.android.switchaccess.SwitchAccessService"); + var voiceAccessServiceInfo = createServiceInfoWithName( + "com.google.android.apps.accessibility.voiceaccess.JustSpeakService"); + when(mAccessibilityManager.getEnabledAccessibilityServiceList(anyInt())) + .thenReturn(List.of(switchAccessServiceInfo, voiceAccessServiceInfo)); + + mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); + + verify(mSpyImageView, never()).postOnAnimationDelayed(any(Runnable.class), anyLong()); + } + + @Test + public void showMagnificationButton_someOtherServiceRunning_postDelayedAnimationsWithTimeout() { + final int a11yTimeout = 12345; + when(mAccessibilityManager.getRecommendedTimeoutMillis(anyInt(), anyInt())).thenReturn( + a11yTimeout); + var serviceInfo1 = createServiceInfoWithName("com.test.someService1"); + var serviceInfo2 = createServiceInfoWithName("com.test.someService2"); + when(mAccessibilityManager.getEnabledAccessibilityServiceList(anyInt())) + .thenReturn(List.of(serviceInfo1, serviceInfo2)); + + mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); + + verify(mAccessibilityManager).getRecommendedTimeoutMillis( + DEFAULT_FADE_OUT_ANIMATION_DELAY_MS, AccessibilityManager.FLAG_CONTENT_ICONS + | AccessibilityManager.FLAG_CONTENT_CONTROLS); + verify(mSpyImageView).postOnAnimationDelayed(any(Runnable.class), eq((long) a11yTimeout)); + } + + private AccessibilityServiceInfo createServiceInfoWithName(String name) { + var resolveInfo = new ResolveInfo(); + resolveInfo.serviceInfo = new ServiceInfo(); + resolveInfo.serviceInfo.name = name; + var serviceInfo = new AccessibilityServiceInfo(); + serviceInfo.setResolveInfo(resolveInfo); + return serviceInfo; + } + + @Test public void showMagnificationButton_windowModeAndFadingOut_verifyAnimationEndAction() { mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); executeFadeOutAnimation(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt index 5f22c7da3920..c7f7c3c3cecf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt @@ -106,7 +106,20 @@ class DefaultDeviceEntryIconSectionTest : SysuiTestCase() { } @Test - fun applyConstraints() { + fun applyConstraints_udfps_refactor_off() { + featureFlags.set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, false) + val cs = ConstraintSet() + underTest.applyConstraints(cs) + + val constraint = cs.getConstraint(R.id.lock_icon_view) + + assertThat(constraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID) + assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID) + } + + @Test + fun applyConstraints_udfps_refactor_on() { + featureFlags.set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true) val cs = ConstraintSet() underTest.applyConstraints(cs) @@ -117,7 +130,24 @@ class DefaultDeviceEntryIconSectionTest : SysuiTestCase() { } @Test - fun testCenterIcon() { + fun testCenterIcon_udfps_refactor_off() { + featureFlags.set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, false) + val cs = ConstraintSet() + underTest.centerIcon(Point(5, 6), 1F, cs) + + val constraint = cs.getConstraint(R.id.lock_icon_view) + + assertThat(constraint.layout.mWidth).isEqualTo(2) + assertThat(constraint.layout.mHeight).isEqualTo(2) + assertThat(constraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID) + assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID) + assertThat(constraint.layout.topMargin).isEqualTo(5) + assertThat(constraint.layout.startMargin).isEqualTo(4) + } + + @Test + fun testCenterIcon_udfps_refactor_on() { + featureFlags.set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true) val cs = ConstraintSet() underTest.centerIcon(Point(5, 6), 1F, cs) diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index 9f4528b113e7..b2ff3c313837 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java @@ -22,9 +22,12 @@ import static android.service.contentcapture.ContentCaptureService.setClientStat import static android.view.contentcapture.ContentCaptureHelper.toList; import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_DELAY_MS; import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_TIMEOUT_MS; +import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_AUTO_DISCONNECT_TIMEOUT; +import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE; import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG; import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD; import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG; +import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER; import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_FALSE; import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_OK; import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_SECURITY_EXCEPTION; @@ -227,6 +230,9 @@ public class ContentCaptureManagerService extends @GuardedBy("mLock") long mDevCfgContentProtectionAllowlistTimeoutMs; + @GuardedBy("mLock") + long mDevCfgContentProtectionAutoDisconnectTimeoutMs; + private final Executor mDataShareExecutor = Executors.newCachedThreadPool(); private final Handler mHandler = new Handler(Looper.getMainLooper()); @@ -435,14 +441,15 @@ public class ContentCaptureManagerService extends case ContentCaptureManager.DEVICE_CONFIG_PROPERTY_IDLE_UNBIND_TIMEOUT: case ContentCaptureManager .DEVICE_CONFIG_PROPERTY_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING: - case ContentCaptureManager - .DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER: - case ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE: + // Content protection below + case DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER: + case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE: case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG: case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG: case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD: case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_DELAY_MS: case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_TIMEOUT_MS: + case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_AUTO_DISCONNECT_TIMEOUT: setFineTuneParamsFromDeviceConfig(); return; default: @@ -502,8 +509,7 @@ public class ContentCaptureManagerService extends mDevCfgContentProtectionBufferSize = DeviceConfig.getInt( DeviceConfig.NAMESPACE_CONTENT_CAPTURE, - ContentCaptureManager - .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE, + DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE, ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE); contentProtectionRequiredGroupsConfig = DeviceConfig.getString( @@ -533,7 +539,12 @@ public class ContentCaptureManagerService extends DeviceConfig.NAMESPACE_CONTENT_CAPTURE, DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_ALLOWLIST_TIMEOUT_MS, ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_ALLOWLIST_TIMEOUT_MS); - + mDevCfgContentProtectionAutoDisconnectTimeoutMs = + DeviceConfig.getLong( + DeviceConfig.NAMESPACE_CONTENT_CAPTURE, + DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_AUTO_DISCONNECT_TIMEOUT, + ContentCaptureManager + .DEFAULT_CONTENT_PROTECTION_AUTO_DISCONNECT_TIMEOUT_MS); contentProtectionAllowlistManagerOld = mContentProtectionAllowlistManager; if (verbose) { @@ -565,7 +576,9 @@ public class ContentCaptureManagerService extends + ", contentProtectionAllowlistDelayMs=" + contentProtectionAllowlistDelayMs + ", contentProtectionAllowlistTimeoutMs=" - + contentProtectionAllowlistTimeoutMs); + + contentProtectionAllowlistTimeoutMs + + ", contentProtectionAutoDisconnectTimeoutMs=" + + mDevCfgContentProtectionAutoDisconnectTimeoutMs); } } @@ -893,6 +906,9 @@ public class ContentCaptureManagerService extends pw.print(prefix2); pw.print("contentProtectionAllowlistTimeoutMs: "); pw.println(mDevCfgContentProtectionAllowlistTimeoutMs); + pw.print(prefix2); + pw.print("contentProtectionAutoDisconnectTimeoutMs: "); + pw.println(mDevCfgContentProtectionAutoDisconnectTimeoutMs); pw.print(prefix); pw.println("Global Options:"); mGlobalContentCaptureOptions.dump(prefix2, pw); @@ -962,12 +978,14 @@ public class ContentCaptureManagerService extends @Nullable public RemoteContentProtectionService createRemoteContentProtectionService() { ComponentName componentName; + long autoDisconnectTimeoutMs; synchronized (mLock) { if (!mDevCfgEnableContentProtectionReceiver || mContentProtectionServiceComponentName == null) { return null; } componentName = mContentProtectionServiceComponentName; + autoDisconnectTimeoutMs = mDevCfgContentProtectionAutoDisconnectTimeoutMs; } // Check permissions by trying to construct {@link ContentCaptureServiceInfo} @@ -978,19 +996,20 @@ public class ContentCaptureManagerService extends return null; } - return createRemoteContentProtectionService(componentName); + return createRemoteContentProtectionService(componentName, autoDisconnectTimeoutMs); } /** @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @NonNull protected RemoteContentProtectionService createRemoteContentProtectionService( - @NonNull ComponentName componentName) { + @NonNull ComponentName componentName, long autoDisconnectTimeoutMs) { return new RemoteContentProtectionService( getContext(), componentName, UserHandle.getCallingUserId(), - isBindInstantServiceAllowed()); + isBindInstantServiceAllowed(), + autoDisconnectTimeoutMs); } /** @hide */ diff --git a/services/contentcapture/java/com/android/server/contentprotection/RemoteContentProtectionService.java b/services/contentcapture/java/com/android/server/contentprotection/RemoteContentProtectionService.java index dd5545dcccc7..bc11fc3cdaf0 100644 --- a/services/contentcapture/java/com/android/server/contentprotection/RemoteContentProtectionService.java +++ b/services/contentcapture/java/com/android/server/contentprotection/RemoteContentProtectionService.java @@ -22,14 +22,13 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ParceledListSlice; import android.service.contentcapture.ContentCaptureService; +import android.service.contentcapture.IContentProtectionAllowlistCallback; import android.service.contentcapture.IContentProtectionService; import android.util.Slog; import android.view.contentcapture.ContentCaptureEvent; import com.android.internal.infra.ServiceConnector; -import java.time.Duration; - /** * Connector for the remote content protection service. * @@ -40,15 +39,16 @@ public class RemoteContentProtectionService private static final String TAG = RemoteContentProtectionService.class.getSimpleName(); - private static final Duration AUTO_DISCONNECT_TIMEOUT = Duration.ofSeconds(3); - @NonNull private final ComponentName mComponentName; + private final long mAutoDisconnectTimeoutMs; + public RemoteContentProtectionService( @NonNull Context context, @NonNull ComponentName componentName, int userId, - boolean bindAllowInstant) { + boolean bindAllowInstant, + long autoDisconnectTimeoutMs) { super( context, new Intent(ContentCaptureService.PROTECTION_SERVICE_INTERFACE) @@ -57,11 +57,12 @@ public class RemoteContentProtectionService userId, IContentProtectionService.Stub::asInterface); mComponentName = componentName; + mAutoDisconnectTimeoutMs = autoDisconnectTimeoutMs; } @Override // from ServiceConnector.Impl protected long getAutoDisconnectTimeoutMs() { - return AUTO_DISCONNECT_TIMEOUT.toMillis(); + return mAutoDisconnectTimeoutMs; } @Override // from ServiceConnector.Impl @@ -75,7 +76,13 @@ public class RemoteContentProtectionService + (isConnected ? "connected" : "disconnected")); } + /** Calls the remote service when login is detected. */ public void onLoginDetected(@NonNull ParceledListSlice<ContentCaptureEvent> events) { run(service -> service.onLoginDetected(events)); } + + /** Calls the remote service with a request to update allowlist. */ + public void onUpdateAllowlistRequest(@NonNull IContentProtectionAllowlistCallback callback) { + run(service -> service.onUpdateAllowlistRequest(callback.asBinder())); + } } diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index cb4cf9d3162c..4579cc1412b3 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -422,8 +422,9 @@ public class BackgroundActivityStartController { } BalVerdict resultForCaller = checkBackgroundActivityStartAllowedByCaller(state); - BalVerdict resultForRealCaller = callingUid == realCallingUid + BalVerdict resultForRealCaller = callingUid == realCallingUid && resultForCaller.allows() ? resultForCaller // no need to calculate again + // otherwise we might need to recalculate because the logic is not the same : checkBackgroundActivityStartAllowedBySender(state, checkedOptions); if (resultForCaller.allows() diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java index 1fa7d2a2aa13..34d765117a57 100644 --- a/services/core/java/com/android/server/wm/InputConsumerImpl.java +++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java @@ -160,7 +160,7 @@ class InputConsumerImpl implements IBinder.DeathRecipient { if (dc == null) { return; } - dc.getInputMonitor().destroyInputConsumer(mName); + dc.getInputMonitor().destroyInputConsumer(mToken); unlinkFromDeathRecipient(); } } diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 5c0bc28779a8..61fea4d9212d 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -73,6 +73,7 @@ import com.android.server.inputmethod.InputMethodManagerInternal; import java.io.PrintWriter; import java.lang.ref.WeakReference; +import java.util.ArrayList; import java.util.Set; import java.util.function.Consumer; @@ -104,7 +105,7 @@ final class InputMonitor { * The set of input consumer added to the window manager by name, which consumes input events * for the windows below it. */ - private final ArrayMap<String, InputConsumerImpl> mInputConsumers = new ArrayMap(); + private final ArrayList<InputConsumerImpl> mInputConsumers = new ArrayList<>(); /** * Set when recents (overview) is active as part of a shell transition. While set, any focus @@ -164,31 +165,35 @@ final class InputMonitor { mDisplayRemoved = true; } - private void addInputConsumer(String name, InputConsumerImpl consumer) { - mInputConsumers.put(name, consumer); + private void addInputConsumer(InputConsumerImpl consumer) { + mInputConsumers.add(consumer); consumer.linkToDeathRecipient(); consumer.layout(mInputTransaction, mDisplayWidth, mDisplayHeight); updateInputWindowsLw(true /* force */); } - boolean destroyInputConsumer(String name) { - if (disposeInputConsumer(mInputConsumers.remove(name))) { - updateInputWindowsLw(true /* force */); - return true; - } - return false; - } - - private boolean disposeInputConsumer(InputConsumerImpl consumer) { - if (consumer != null) { - consumer.disposeChannelsLw(mInputTransaction); - return true; + boolean destroyInputConsumer(IBinder token) { + for (int i = 0; i < mInputConsumers.size(); i++) { + final InputConsumerImpl consumer = mInputConsumers.get(i); + if (consumer != null && consumer.mToken == token) { + consumer.disposeChannelsLw(mInputTransaction); + mInputConsumers.remove(consumer); + updateInputWindowsLw(true /* force */); + return true; + } } return false; } InputConsumerImpl getInputConsumer(String name) { - return mInputConsumers.get(name); + // Search in reverse order as the latest input consumer with the name takes precedence + for (int i = mInputConsumers.size() - 1; i >= 0; i--) { + final InputConsumerImpl consumer = mInputConsumers.get(i); + if (consumer.mName.equals(name)) { + return consumer; + } + } + return null; } void layoutInputConsumers(int dw, int dh) { @@ -200,7 +205,7 @@ final class InputMonitor { try { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "layoutInputConsumer"); for (int i = mInputConsumers.size() - 1; i >= 0; i--) { - mInputConsumers.valueAt(i).layout(mInputTransaction, dw, dh); + mInputConsumers.get(i).layout(mInputTransaction, dw, dh); } } finally { Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); @@ -212,15 +217,16 @@ final class InputMonitor { // (set so by this function) and must meet some condition for visibility on each update. void resetInputConsumers(SurfaceControl.Transaction t) { for (int i = mInputConsumers.size() - 1; i >= 0; i--) { - mInputConsumers.valueAt(i).hide(t); + mInputConsumers.get(i).hide(t); } } void createInputConsumer(IBinder token, String name, InputChannel inputChannel, int clientPid, UserHandle clientUser) { - if (mInputConsumers.containsKey(name)) { + final InputConsumerImpl existingConsumer = getInputConsumer(name); + if (existingConsumer != null && existingConsumer.mClientUser.equals(clientUser)) { throw new IllegalStateException("Existing input consumer found with name: " + name - + ", display: " + mDisplayId); + + ", display: " + mDisplayId + ", user: " + clientUser); } final InputConsumerImpl consumer = new InputConsumerImpl(mService, token, name, @@ -239,7 +245,7 @@ final class InputMonitor { throw new IllegalArgumentException("Illegal input consumer : " + name + ", display: " + mDisplayId); } - addInputConsumer(name, consumer); + addInputConsumer(consumer); } @VisibleForTesting @@ -541,11 +547,11 @@ final class InputMonitor { } void dump(PrintWriter pw, String prefix) { - final Set<String> inputConsumerKeys = mInputConsumers.keySet(); - if (!inputConsumerKeys.isEmpty()) { + if (!mInputConsumers.isEmpty()) { pw.println(prefix + "InputConsumers:"); - for (String key : inputConsumerKeys) { - mInputConsumers.get(key).dump(pw, key, prefix); + for (int i = 0; i < mInputConsumers.size(); i++) { + final InputConsumerImpl consumer = mInputConsumers.get(i); + consumer.dump(pw, consumer.mName, prefix); } } } diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index 82d4b90d06be..ef2572665281 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -1021,7 +1021,11 @@ public class RecentsAnimationController implements DeathRecipient { synchronized (mService.getWindowManagerLock()) { // Clear associated input consumers on runner death final InputMonitor inputMonitor = mDisplayContent.getInputMonitor(); - inputMonitor.destroyInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION); + final InputConsumerImpl consumer = inputMonitor.getInputConsumer( + INPUT_CONSUMER_RECENTS_ANIMATION); + if (consumer != null) { + inputMonitor.destroyInputConsumer(consumer.mToken); + } } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index a90e08e4f372..9fb7e8ddbd20 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -6553,7 +6553,7 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public boolean destroyInputConsumer(String name, int displayId) { + public boolean destroyInputConsumer(IBinder token, int displayId) { if (!mAtmService.isCallerRecents(Binder.getCallingUid()) && mContext.checkCallingOrSelfPermission(INPUT_CONSUMER) != PERMISSION_GRANTED) { throw new SecurityException("destroyInputConsumer requires INPUT_CONSUMER permission"); @@ -6562,7 +6562,7 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mGlobalLock) { DisplayContent display = mRoot.getDisplayContent(displayId); if (display != null) { - return display.getInputMonitor().destroyInputConsumer(name); + return display.getInputMonitor().destroyInputConsumer(token); } return false; } diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java index 9a5241ea242a..6d13d876b06b 100644 --- a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java @@ -662,7 +662,7 @@ public class ContentCaptureManagerServiceTest { @Override protected RemoteContentProtectionService createRemoteContentProtectionService( - @NonNull ComponentName componentName) { + @NonNull ComponentName componentName, long autoDisconnectTimeoutMs) { mRemoteContentProtectionServicesCreated++; return mMockRemoteContentProtectionService; } diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java index 9135ef3a1286..6a7e2865fb32 100644 --- a/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/contentprotection/RemoteContentProtectionServiceTest.java @@ -21,12 +21,16 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; import android.annotation.NonNull; import android.content.ComponentName; import android.content.Context; import android.content.pm.ParceledListSlice; +import android.os.Binder; +import android.os.IBinder; import android.os.UserHandle; +import android.service.contentcapture.IContentProtectionAllowlistCallback; import android.service.contentcapture.IContentProtectionService; import android.view.contentcapture.ContentCaptureEvent; @@ -57,21 +61,27 @@ import org.mockito.junit.MockitoRule; @SmallTest public class RemoteContentProtectionServiceTest { - private final Context mContext = ApplicationProvider.getApplicationContext(); + private static final long AUTO_DISCONNECT_TIMEOUT_MS = 12345L; + + private static final IBinder BINDER = new Binder(); + + private static final Context CONTEXT = ApplicationProvider.getApplicationContext(); @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @Mock private IContentProtectionService mMockContentProtectionService; + @Mock private IContentProtectionAllowlistCallback mMockContentProtectionAllowlistCallback; + private RemoteContentProtectionService mRemoteContentProtectionService; private int mConnectCallCount = 0; @Before public void setup() { - ComponentName componentName = new ComponentName(mContext.getPackageName(), "TestClass"); + ComponentName componentName = new ComponentName(CONTEXT.getPackageName(), "TestClass"); mRemoteContentProtectionService = - new TestRemoteContentProtectionService(mContext, componentName); + new TestRemoteContentProtectionService(CONTEXT, componentName); } @Test @@ -84,7 +94,7 @@ public class RemoteContentProtectionServiceTest { public void getAutoDisconnectTimeoutMs() { long actual = mRemoteContentProtectionService.getAutoDisconnectTimeoutMs(); - assertThat(actual).isEqualTo(3000L); + assertThat(actual).isEqualTo(AUTO_DISCONNECT_TIMEOUT_MS); } @Test @@ -99,10 +109,43 @@ public class RemoteContentProtectionServiceTest { verify(mMockContentProtectionService).onLoginDetected(events); } + @Test + public void onUpdateAllowlistRequest() throws Exception { + when(mMockContentProtectionAllowlistCallback.asBinder()).thenReturn(BINDER); + + mRemoteContentProtectionService.onUpdateAllowlistRequest( + mMockContentProtectionAllowlistCallback); + + verify(mMockContentProtectionService).onUpdateAllowlistRequest(BINDER); + } + + @Test + public void onServiceConnectionStatusChanged_connected_noSideEffects() { + mRemoteContentProtectionService.onServiceConnectionStatusChanged( + mMockContentProtectionService, /* isConnected= */ true); + + verifyZeroInteractions(mMockContentProtectionService); + assertThat(mConnectCallCount).isEqualTo(0); + } + + @Test + public void onServiceConnectionStatusChanged_disconnected_noSideEffects() { + mRemoteContentProtectionService.onServiceConnectionStatusChanged( + mMockContentProtectionService, /* isConnected= */ false); + + verifyZeroInteractions(mMockContentProtectionService); + assertThat(mConnectCallCount).isEqualTo(0); + } + private final class TestRemoteContentProtectionService extends RemoteContentProtectionService { TestRemoteContentProtectionService(Context context, ComponentName componentName) { - super(context, componentName, UserHandle.myUserId(), /* bindAllowInstant= */ false); + super( + context, + componentName, + UserHandle.myUserId(), + /* bindAllowInstant= */ false, + AUTO_DISCONNECT_TIMEOUT_MS); } @Override // from ServiceConnector |