diff options
159 files changed, 4346 insertions, 1029 deletions
diff --git a/core/java/Android.bp b/core/java/Android.bp index 77589a213e17..a7d4342be642 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -15,14 +15,6 @@ filegroup { "**/*.java", "**/*.aidl", ], - exclude_srcs: [ - // Remove election toolbar code from build time - "android/service/selectiontoolbar/*.aidl", - "android/service/selectiontoolbar/*.java", - "android/view/selectiontoolbar/*.aidl", - "android/view/selectiontoolbar/*.java", - "com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java", - ], visibility: ["//frameworks/base"], } diff --git a/core/java/android/app/ActivityTransitionState.java b/core/java/android/app/ActivityTransitionState.java index 57dacd024ba1..877e7d3b3bf7 100644 --- a/core/java/android/app/ActivityTransitionState.java +++ b/core/java/android/app/ActivityTransitionState.java @@ -263,11 +263,6 @@ class ActivityTransitionState { // After orientation change, the onResume can come in before the top Activity has // left, so if the Activity is not top, wait a second for the top Activity to exit. if (mEnterTransitionCoordinator == null || activity.isTopOfTask()) { - if (mEnterTransitionCoordinator != null) { - mEnterTransitionCoordinator.runAfterTransitionsComplete(() -> { - mEnterTransitionCoordinator = null; - }); - } restoreExitedViews(); restoreReenteringViews(); } else { @@ -276,11 +271,6 @@ class ActivityTransitionState { public void run() { if (mEnterTransitionCoordinator == null || mEnterTransitionCoordinator.isWaitingForRemoteExit()) { - if (mEnterTransitionCoordinator != null) { - mEnterTransitionCoordinator.runAfterTransitionsComplete(() -> { - mEnterTransitionCoordinator = null; - }); - } restoreExitedViews(); restoreReenteringViews(); } else if (mEnterTransitionCoordinator.isReturning()) { diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 40192836e0a7..6615374f71ec 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -230,6 +230,8 @@ import android.view.contentcapture.ContentCaptureManager; import android.view.contentcapture.IContentCaptureManager; import android.view.displayhash.DisplayHashManager; import android.view.inputmethod.InputMethodManager; +import android.view.selectiontoolbar.ISelectionToolbarManager; +import android.view.selectiontoolbar.SelectionToolbarManager; import android.view.textclassifier.TextClassificationManager; import android.view.textservice.TextServicesManager; import android.view.translation.ITranslationManager; @@ -363,6 +365,15 @@ public final class SystemServiceRegistry { return new TextClassificationManager(ctx); }}); + registerService(Context.SELECTION_TOOLBAR_SERVICE, SelectionToolbarManager.class, + new CachedServiceFetcher<SelectionToolbarManager>() { + @Override + public SelectionToolbarManager createService(ContextImpl ctx) { + IBinder b = ServiceManager.getService(Context.SELECTION_TOOLBAR_SERVICE); + return new SelectionToolbarManager(ctx.getOuterContext(), + ISelectionToolbarManager.Stub.asInterface(b)); + }}); + registerService(Context.FONT_SERVICE, FontManager.class, new CachedServiceFetcher<FontManager>() { @Override diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java index 8be2b4873c67..e3bca9c9aadb 100644 --- a/core/java/android/appwidget/AppWidgetHostView.java +++ b/core/java/android/appwidget/AppWidgetHostView.java @@ -103,7 +103,6 @@ public class AppWidgetHostView extends FrameLayout { private boolean mOnLightBackground; private SizeF mCurrentSize = null; private RemoteViews.ColorResources mColorResources = null; - private SparseIntArray mColorMapping = null; // Stores the last remote views last inflated. private RemoteViews mLastInflatedRemoteViews = null; private long mLastInflatedRemoteViewsId = -1; @@ -900,11 +899,19 @@ public class AppWidgetHostView extends FrameLayout { * {@link android.R.color#system_neutral1_500}. */ public void setColorResources(@NonNull SparseIntArray colorMapping) { - if (mColorMapping != null && isSameColorMapping(mColorMapping, colorMapping)) { + if (mColorResources != null + && isSameColorMapping(mColorResources.getColorMapping(), colorMapping)) { return; } - mColorMapping = colorMapping.clone(); - mColorResources = RemoteViews.ColorResources.create(mContext, mColorMapping); + setColorResources(RemoteViews.ColorResources.create(mContext, colorMapping)); + } + + /** @hide **/ + public void setColorResources(RemoteViews.ColorResources colorResources) { + if (colorResources == mColorResources) { + return; + } + mColorResources = colorResources; mColorMappingChanged = true; mViewMode = VIEW_MODE_NOINIT; reapplyLastRemoteViews(); @@ -934,7 +941,6 @@ public class AppWidgetHostView extends FrameLayout { public void resetColorResources() { if (mColorResources != null) { mColorResources = null; - mColorMapping = null; mColorMappingChanged = true; mViewMode = VIEW_MODE_NOINIT; reapplyLastRemoteViews(); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 7690af693148..fa37ca177736 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -276,7 +276,7 @@ public final class ViewRootImpl implements ViewParent, * @hide */ public static final boolean CAPTION_ON_SHELL = - SystemProperties.getBoolean("persist.debug.caption_on_shell", false); + SystemProperties.getBoolean("persist.wm.debug.caption_on_shell", false); /** * Whether the client should compute the window frame on its own. diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 2879cd888d2d..a33906267736 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -594,8 +594,8 @@ public class RemoteViews implements Parcelable, Filter { * SUBCLASSES MUST BE IMMUTABLE SO CLONE WORKS!!!!! */ private abstract static class Action implements Parcelable { - public abstract void apply(View root, ViewGroup rootParent, InteractionHandler handler, - ColorResources colorResources) throws ActionException; + public abstract void apply(View root, ViewGroup rootParent, ActionApplyParams params) + throws ActionException; public static final int MERGE_REPLACE = 0; public static final int MERGE_APPEND = 1; @@ -626,7 +626,7 @@ public class RemoteViews implements Parcelable, Filter { * Override this if some of the tasks can be performed async. */ public Action initActionAsync(ViewTree root, ViewGroup rootParent, - InteractionHandler handler, ColorResources colorResources) { + ActionApplyParams params) { return this; } @@ -661,9 +661,7 @@ public class RemoteViews implements Parcelable, Filter { // Constant used during async execution. It is not parcelable. private static final Action ACTION_NOOP = new RuntimeAction() { @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler, - ColorResources colorResources) { - } + public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { } }; /** @@ -798,8 +796,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler, - ColorResources colorResources) { + public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { final View view = root.findViewById(viewId); if (!(view instanceof AdapterView<?>)) return; @@ -834,8 +831,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, final InteractionHandler handler, - ColorResources colorResources) { + public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { final View target = root.findViewById(viewId); if (target == null) return; @@ -846,7 +842,7 @@ public class RemoteViews implements Parcelable, Filter { OnItemClickListener listener = (parent, view, position, id) -> { RemoteResponse response = findRemoteResponseTag(view); if (response != null) { - response.handleViewInteraction(view, handler); + response.handleViewInteraction(view, params.handler); } }; av.setOnItemClickListener(listener); @@ -910,8 +906,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler, - ColorResources colorResources) { + public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { final View target = root.findViewById(viewId); if (target == null) return; @@ -935,7 +930,7 @@ public class RemoteViews implements Parcelable, Filter { ((RemoteViewsListAdapter) a).setViewsList(list); } else { v.setAdapter(new RemoteViewsListAdapter(v.getContext(), list, viewTypeCount, - colorResources)); + params.colorResources)); } } else if (target instanceof AdapterViewAnimator) { AdapterViewAnimator v = (AdapterViewAnimator) target; @@ -944,7 +939,7 @@ public class RemoteViews implements Parcelable, Filter { ((RemoteViewsListAdapter) a).setViewsList(list); } else { v.setAdapter(new RemoteViewsListAdapter(v.getContext(), list, viewTypeCount, - colorResources)); + params.colorResources)); } } } @@ -1025,8 +1020,8 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler, - ColorResources colorResources) throws ActionException { + public void apply(View root, ViewGroup rootParent, ActionApplyParams params) + throws ActionException { View target = root.findViewById(viewId); if (target == null) return; @@ -1053,7 +1048,7 @@ public class RemoteViews implements Parcelable, Filter { && adapter.getViewTypeCount() >= mItems.getViewTypeCount()) { try { ((RemoteCollectionItemsAdapter) adapter).setData( - mItems, handler, colorResources); + mItems, params.handler, params.colorResources); } catch (Throwable throwable) { // setData should never failed with the validation in the items builder, but if // it does, catch and rethrow. @@ -1063,8 +1058,8 @@ public class RemoteViews implements Parcelable, Filter { } try { - adapterView.setAdapter( - new RemoteCollectionItemsAdapter(mItems, handler, colorResources)); + adapterView.setAdapter(new RemoteCollectionItemsAdapter(mItems, + params.handler, params.colorResources)); } catch (Throwable throwable) { // This could throw if the AdapterView somehow doesn't accept BaseAdapter due to // a type error. @@ -1095,8 +1090,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler, - ColorResources colorResources) { + public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { final View target = root.findViewById(viewId); if (target == null) return; @@ -1124,17 +1118,17 @@ public class RemoteViews implements Parcelable, Filter { if (target instanceof AbsListView) { AbsListView v = (AbsListView) target; v.setRemoteViewsAdapter(intent, isAsync); - v.setRemoteViewsInteractionHandler(handler); + v.setRemoteViewsInteractionHandler(params.handler); } else if (target instanceof AdapterViewAnimator) { AdapterViewAnimator v = (AdapterViewAnimator) target; v.setRemoteViewsAdapter(intent, isAsync); - v.setRemoteViewsOnClickHandler(handler); + v.setRemoteViewsOnClickHandler(params.handler); } } @Override public Action initActionAsync(ViewTree root, ViewGroup rootParent, - InteractionHandler handler, ColorResources colorResources) { + ActionApplyParams params) { SetRemoteViewsAdapterIntent copy = new SetRemoteViewsAdapterIntent(viewId, intent); copy.isAsync = true; return copy; @@ -1173,8 +1167,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, final InteractionHandler handler, - ColorResources colorResources) { + public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { final View target = root.findViewById(viewId); if (target == null) return; @@ -1215,7 +1208,7 @@ public class RemoteViews implements Parcelable, Filter { target.setTagInternal(com.android.internal.R.id.fillInIntent, null); return; } - target.setOnClickListener(v -> mResponse.handleViewInteraction(v, handler)); + target.setOnClickListener(v -> mResponse.handleViewInteraction(v, params.handler)); } @Override @@ -1253,8 +1246,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, final InteractionHandler handler, - ColorResources colorResources) { + public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { final View target = root.findViewById(viewId); if (target == null) return; if (!(target instanceof CompoundButton)) { @@ -1287,7 +1279,7 @@ public class RemoteViews implements Parcelable, Filter { } OnCheckedChangeListener onCheckedChangeListener = - (v, isChecked) -> mResponse.handleViewInteraction(v, handler); + (v, isChecked) -> mResponse.handleViewInteraction(v, params.handler); button.setTagInternal(R.id.remote_checked_change_listener_tag, onCheckedChangeListener); button.setOnCheckedChangeListener(onCheckedChangeListener); } @@ -1459,8 +1451,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler, - ColorResources colorResources) { + public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { final View target = root.findViewById(viewId); if (target == null) return; @@ -1517,8 +1508,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler, - ColorResources colorResources) { + public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { final View target = root.findViewById(viewId); if (target == null) return; @@ -1561,8 +1551,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler, - ColorResources colorResources) { + public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { final View view = root.findViewById(viewId); if (view == null) return; @@ -1598,7 +1587,13 @@ public class RemoteViews implements Parcelable, Filter { public BitmapCache(Parcel source) { mBitmaps = source.createTypedArrayList(Bitmap.CREATOR); - mBitmapHashes = source.readSparseIntArray(); + mBitmapHashes = new SparseIntArray(); + for (int i = 0; i < mBitmaps.size(); i++) { + Bitmap b = mBitmaps.get(i); + if (b != null) { + mBitmapHashes.put(b.hashCode(), i); + } + } } public int getBitmapId(Bitmap b) { @@ -1614,7 +1609,7 @@ public class RemoteViews implements Parcelable, Filter { b = b.asShared(); } mBitmaps.add(b); - mBitmapHashes.put(mBitmaps.size() - 1, hash); + mBitmapHashes.put(hash, mBitmaps.size() - 1); mBitmapMemory = -1; return (mBitmaps.size() - 1); } @@ -1631,7 +1626,6 @@ public class RemoteViews implements Parcelable, Filter { public void writeBitmapsToParcel(Parcel dest, int flags) { dest.writeTypedList(mBitmaps, flags); - dest.writeSparseIntArray(mBitmapHashes); } public int getBitmapMemory() { @@ -1675,12 +1669,12 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler, - ColorResources colorResources) throws ActionException { + public void apply(View root, ViewGroup rootParent, ActionApplyParams params) + throws ActionException { ReflectionAction ra = new ReflectionAction(viewId, methodName, BaseReflectionAction.BITMAP, bitmap); - ra.apply(root, rootParent, handler, colorResources); + ra.apply(root, rootParent, params); } @Override @@ -1756,8 +1750,7 @@ public class RemoteViews implements Parcelable, Filter { protected abstract Object getParameterValue(@Nullable View view) throws ActionException; @Override - public final void apply(View root, ViewGroup rootParent, InteractionHandler handler, - ColorResources colorResources) { + public final void apply(View root, ViewGroup rootParent, ActionApplyParams params) { final View view = root.findViewById(viewId); if (view == null) return; @@ -1775,7 +1768,7 @@ public class RemoteViews implements Parcelable, Filter { @Override public final Action initActionAsync(ViewTree root, ViewGroup rootParent, - InteractionHandler handler, ColorResources colorResources) { + ActionApplyParams params) { final View view = root.findViewById(viewId); if (view == null) return ACTION_NOOP; @@ -2307,8 +2300,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler, - ColorResources colorResources) { + public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { mRunnable.run(); } } @@ -2421,8 +2413,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler, - ColorResources colorResources) { + public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { final Context context = root.getContext(); final ViewGroup target = root.findViewById(viewId); @@ -2451,8 +2442,7 @@ public class RemoteViews implements Parcelable, Filter { target.removeViews(nextChild, recycledViewIndex - nextChild); } setNextRecyclableChild(target, nextChild + 1, target.getChildCount()); - rvToApply.reapplyNestedViews(context, child, rootParent, handler, - null /* size */, colorResources); + rvToApply.reapplyNestedViews(context, child, rootParent, params); return; } // If we cannot recycle the views, we still remove all views in between to @@ -2463,8 +2453,7 @@ public class RemoteViews implements Parcelable, Filter { // If we cannot recycle, insert the new view before the next recyclable child. // Inflate nested views and add as children - View nestedView = rvToApply.applyNestedViews(context, target, rootParent, handler, - null /* size */, colorResources); + View nestedView = rvToApply.apply(context, target, rootParent, null /* size */, params); if (mStableId != NO_ID) { setStableId(nestedView, mStableId); } @@ -2477,7 +2466,7 @@ public class RemoteViews implements Parcelable, Filter { @Override public Action initActionAsync(ViewTree root, ViewGroup rootParent, - InteractionHandler handler, ColorResources colorResources) { + ActionApplyParams params) { // In the async implementation, update the view tree so that subsequent calls to // findViewById return the current view. root.createTree(); @@ -2511,8 +2500,7 @@ public class RemoteViews implements Parcelable, Filter { setNextRecyclableChild(targetVg, nextChild + 1, target.mChildren.size()); final AsyncApplyTask reapplyTask = rvToApply.getInternalAsyncApplyTask( context, - targetVg, null /* listener */, handler, null /* size */, - colorResources, + targetVg, null /* listener */, params, null /* size */, recycled.mRoot); final ViewTree tree = reapplyTask.doInBackground(); if (tree == null) { @@ -2521,8 +2509,7 @@ public class RemoteViews implements Parcelable, Filter { return new RuntimeAction() { @Override public void apply(View root, ViewGroup rootParent, - InteractionHandler handler, ColorResources colorResources) - throws ActionException { + ActionApplyParams params) throws ActionException { reapplyTask.onPostExecute(tree); if (recycledViewIndex > nextChild) { targetVg.removeViews(nextChild, recycledViewIndex - nextChild); @@ -2533,23 +2520,22 @@ public class RemoteViews implements Parcelable, Filter { // If the layout id is different, still remove the children as if we recycled // the view, to insert at the same place. target.removeChildren(nextChild, recycledViewIndex - nextChild + 1); - return insertNewView(context, target, handler, colorResources, + return insertNewView(context, target, params, () -> targetVg.removeViews(nextChild, recycledViewIndex - nextChild + 1)); } } // If we cannot recycle, simply add the view at the same available slot. - return insertNewView(context, target, handler, colorResources, () -> {}); + return insertNewView(context, target, params, () -> {}); } - private Action insertNewView(Context context, ViewTree target, InteractionHandler handler, - ColorResources colorResources, Runnable finalizeAction) { + private Action insertNewView(Context context, ViewTree target, + ActionApplyParams params, Runnable finalizeAction) { ViewGroup targetVg = (ViewGroup) target.mRoot; int nextChild = getNextRecyclableChild(targetVg); final AsyncApplyTask task = mNestedViews.getInternalAsyncApplyTask(context, targetVg, - null /* listener */, handler, null /* size */, colorResources, - null /* result */); + null /* listener */, params, null /* size */, null /* result */); final ViewTree tree = task.doInBackground(); if (tree == null) { @@ -2569,8 +2555,7 @@ public class RemoteViews implements Parcelable, Filter { return new RuntimeAction() { @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler, - ColorResources colorResources) throws ActionException { + public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { task.onPostExecute(tree); finalizeAction.run(); targetVg.addView(task.mResult, insertIndex); @@ -2627,8 +2612,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler, - ColorResources colorResources) { + public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { final ViewGroup target = root.findViewById(viewId); if (target == null) { @@ -2652,7 +2636,7 @@ public class RemoteViews implements Parcelable, Filter { @Override public Action initActionAsync(ViewTree root, ViewGroup rootParent, - InteractionHandler handler, ColorResources colorResources) { + ActionApplyParams params) { // In the async implementation, update the view tree so that subsequent calls to // findViewById return the current view. root.createTree(); @@ -2676,8 +2660,7 @@ public class RemoteViews implements Parcelable, Filter { } return new RuntimeAction() { @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler, - ColorResources colorResources) throws ActionException { + public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { if (mViewIdToKeep == REMOVE_ALL_VIEWS_ID) { for (int i = targetVg.getChildCount() - 1; i >= 0; i--) { if (!hasStableId(targetVg.getChildAt(i))) { @@ -2736,8 +2719,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler, - ColorResources colorResources) { + public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { final View target = root.findViewById(viewId); if (target == null || target == root) { @@ -2752,7 +2734,7 @@ public class RemoteViews implements Parcelable, Filter { @Override public Action initActionAsync(ViewTree root, ViewGroup rootParent, - InteractionHandler handler, ColorResources colorResources) { + ActionApplyParams params) { // In the async implementation, update the view tree so that subsequent calls to // findViewById return the correct view. root.createTree(); @@ -2771,8 +2753,7 @@ public class RemoteViews implements Parcelable, Filter { parent.mChildren.remove(target); return new RuntimeAction() { @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler, - ColorResources colorResources) throws ActionException { + public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { parentVg.removeView(target.mRoot); } }; @@ -2851,8 +2832,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler, - ColorResources colorResources) { + public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { final TextView target = root.findViewById(viewId); if (target == null) return; if (drawablesLoaded) { @@ -2883,7 +2863,7 @@ public class RemoteViews implements Parcelable, Filter { @Override public Action initActionAsync(ViewTree root, ViewGroup rootParent, - InteractionHandler handler, ColorResources colorResources) { + ActionApplyParams params) { final TextView target = root.findViewById(viewId); if (target == null) return ACTION_NOOP; @@ -2961,8 +2941,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler, - ColorResources colorResources) { + public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { final TextView target = root.findViewById(viewId); if (target == null) return; target.setTextSize(units, size); @@ -3007,8 +2986,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler, - ColorResources colorResources) { + public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { final View target = root.findViewById(viewId); if (target == null) return; target.setPadding(left, top, right, bottom); @@ -3084,8 +3062,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler, - ColorResources colorResources) { + public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { final View target = root.findViewById(viewId); if (target == null) { return; @@ -3230,8 +3207,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler, - ColorResources colorResources) { + public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { final View target = root.findViewById(viewId); if (target == null) return; @@ -3266,8 +3242,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler, - ColorResources colorResources) { + 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); @@ -3317,8 +3292,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler, - ColorResources colorResources) { + public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { final View target = root.findViewById(mViewId); if (target == null) return; @@ -3352,8 +3326,7 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler, - ColorResources colorResources) + public void apply(View root, ViewGroup rootParent, ActionApplyParams params) throws ActionException { final View target = root.findViewById(viewId); if (target == null) return; @@ -3404,8 +3377,8 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler, - ColorResources colorResources) throws ActionException { + public void apply(View root, ViewGroup rootParent, ActionApplyParams params) + throws ActionException { final View target = root.findViewById(viewId); if (target == null) return; @@ -3483,8 +3456,8 @@ public class RemoteViews implements Parcelable, Filter { } @Override - public void apply(View root, ViewGroup rootParent, InteractionHandler handler, - ColorResources colorResources) throws ActionException { + public void apply(View root, ViewGroup rootParent, ActionApplyParams params) + throws ActionException { final View target = root.findViewById(viewId); if (target == null) return; @@ -5578,54 +5551,41 @@ public class RemoteViews implements Parcelable, Filter { /** @hide */ public View apply(@NonNull Context context, @NonNull ViewGroup parent, @Nullable InteractionHandler handler, @Nullable SizeF size) { - RemoteViews rvToApply = getRemoteViewsToApply(context, size); - - View result = inflateView(context, rvToApply, parent); - rvToApply.performApply(result, parent, handler, null); - return result; + return apply(context, parent, size, new ActionApplyParams() + .withInteractionHandler(handler)); } /** @hide */ public View applyWithTheme(@NonNull Context context, @NonNull ViewGroup parent, @Nullable InteractionHandler handler, @StyleRes int applyThemeResId) { - return applyWithTheme(context, parent, handler, applyThemeResId, null); - } - - /** @hide */ - public View applyWithTheme(@NonNull Context context, @NonNull ViewGroup parent, - @Nullable InteractionHandler handler, @StyleRes int applyThemeResId, - @Nullable SizeF size) { - RemoteViews rvToApply = getRemoteViewsToApply(context, size); - - View result = inflateView(context, rvToApply, parent, applyThemeResId, null); - rvToApply.performApply(result, parent, handler, null); - return result; + return apply(context, parent, null, new ActionApplyParams() + .withInteractionHandler(handler) + .withThemeResId(applyThemeResId)); } /** @hide */ public View apply(Context context, ViewGroup parent, InteractionHandler handler, @Nullable SizeF size, @Nullable ColorResources colorResources) { - RemoteViews rvToApply = getRemoteViewsToApply(context, size); + return apply(context, parent, size, new ActionApplyParams() + .withInteractionHandler(handler) + .withColorResources(colorResources)); + } - View result = inflateView(context, rvToApply, parent, 0, colorResources); - rvToApply.performApply(result, parent, handler, colorResources); - return result; + /** @hide **/ + public View apply(Context context, ViewGroup parent, @Nullable SizeF size, + ActionApplyParams params) { + return apply(context, parent, parent, size, params); } - private View applyNestedViews(Context context, ViewGroup directParent, - ViewGroup rootParent, InteractionHandler handler, SizeF size, - ColorResources colorResources) { + private View apply(Context context, ViewGroup directParent, ViewGroup rootParent, + @Nullable SizeF size, ActionApplyParams params) { RemoteViews rvToApply = getRemoteViewsToApply(context, size); - - View result = inflateView(context, rvToApply, directParent, 0, colorResources); - rvToApply.performApply(result, rootParent, handler, colorResources); + View result = inflateView(context, rvToApply, directParent, + params.applyThemeResId, params.colorResources); + rvToApply.performApply(result, rootParent, params); return result; } - private View inflateView(Context context, RemoteViews rv, ViewGroup parent) { - return inflateView(context, rv, parent, 0, null); - } - private View inflateView(Context context, RemoteViews rv, @Nullable ViewGroup parent, @StyleRes int applyThemeResId, @Nullable ColorResources colorResources) { // RemoteViews may be built by an application installed in another @@ -5704,7 +5664,6 @@ public class RemoteViews implements Parcelable, Filter { return applyAsync(context, parent, executor, listener, null /* handler */); } - /** @hide */ public CancellationSignal applyAsync(Context context, ViewGroup parent, Executor executor, OnViewAppliedListener listener, InteractionHandler handler) { @@ -5723,16 +5682,19 @@ public class RemoteViews implements Parcelable, Filter { public CancellationSignal applyAsync(Context context, ViewGroup parent, Executor executor, OnViewAppliedListener listener, InteractionHandler handler, SizeF size, ColorResources colorResources) { + + ActionApplyParams params = new ActionApplyParams() + .withInteractionHandler(handler) + .withColorResources(colorResources) + .withExecutor(executor); return new AsyncApplyTask(getRemoteViewsToApply(context, size), parent, context, listener, - handler, colorResources, null /* result */, - true /* topLevel */).startTaskOnExecutor(executor); + params, null /* result */, true /* topLevel */).startTaskOnExecutor(executor); } private AsyncApplyTask getInternalAsyncApplyTask(Context context, ViewGroup parent, - OnViewAppliedListener listener, InteractionHandler handler, SizeF size, - ColorResources colorResources, View result) { + OnViewAppliedListener listener, ActionApplyParams params, SizeF size, View result) { return new AsyncApplyTask(getRemoteViewsToApply(context, size), parent, context, listener, - handler, colorResources, result, false /* topLevel */); + params, result, false /* topLevel */); } private class AsyncApplyTask extends AsyncTask<Void, Void, ViewTree> @@ -5742,8 +5704,8 @@ public class RemoteViews implements Parcelable, Filter { final ViewGroup mParent; final Context mContext; final OnViewAppliedListener mListener; - final InteractionHandler mHandler; - final ColorResources mColorResources; + final ActionApplyParams mApplyParams; + /** * Whether the remote view is the top-level one (i.e. not within an action). * @@ -5758,16 +5720,13 @@ public class RemoteViews implements Parcelable, Filter { private AsyncApplyTask( RemoteViews rv, ViewGroup parent, Context context, OnViewAppliedListener listener, - InteractionHandler handler, ColorResources colorResources, - View result, boolean topLevel) { + ActionApplyParams applyParams, View result, boolean topLevel) { mRV = rv; mParent = parent; mContext = context; mListener = listener; - mColorResources = colorResources; - mHandler = handler; mTopLevel = topLevel; - + mApplyParams = applyParams; mResult = result; } @@ -5776,17 +5735,18 @@ public class RemoteViews implements Parcelable, Filter { protected ViewTree doInBackground(Void... params) { try { if (mResult == null) { - mResult = inflateView(mContext, mRV, mParent, 0, mColorResources); + mResult = inflateView(mContext, mRV, mParent, 0, mApplyParams.colorResources); } mTree = new ViewTree(mResult); + if (mRV.mActions != null) { int count = mRV.mActions.size(); mActions = new Action[count]; for (int i = 0; i < count && !isCancelled(); i++) { // TODO: check if isCancelled in nested views. - mActions[i] = mRV.mActions.get(i).initActionAsync(mTree, mParent, mHandler, - mColorResources); + mActions[i] = mRV.mActions.get(i) + .initActionAsync(mTree, mParent, mApplyParams); } } else { mActions = null; @@ -5808,10 +5768,13 @@ public class RemoteViews implements Parcelable, Filter { try { if (mActions != null) { - InteractionHandler handler = mHandler == null - ? DEFAULT_INTERACTION_HANDLER : mHandler; + + ActionApplyParams applyParams = mApplyParams.clone(); + if (applyParams.handler == null) { + applyParams.handler = DEFAULT_INTERACTION_HANDLER; + } for (Action a : mActions) { - a.apply(viewTree.mRoot, mParent, handler, mColorResources); + a.apply(viewTree.mRoot, mParent, applyParams); } } // If the parent of the view is has is a root, resolve the recycling. @@ -5859,18 +5822,43 @@ public class RemoteViews implements Parcelable, Filter { * the {@link #apply(Context,ViewGroup)} call. */ public void reapply(Context context, View v) { - reapply(context, v, null /* handler */); + reapply(context, v, null /* size */, new ActionApplyParams()); } /** @hide */ public void reapply(Context context, View v, InteractionHandler handler) { - reapply(context, v, handler, null /* size */, null /* colorResources */); + reapply(context, v, null /* size */, + new ActionApplyParams().withInteractionHandler(handler)); } /** @hide */ public void reapply(Context context, View v, InteractionHandler handler, SizeF size, ColorResources colorResources) { - reapply(context, v, handler, size, colorResources, true); + reapply(context, v, size, new ActionApplyParams() + .withInteractionHandler(handler).withColorResources(colorResources)); + } + + /** @hide */ + public void reapply(Context context, View v, @Nullable SizeF size, ActionApplyParams params) { + reapply(context, v, (ViewGroup) v.getParent(), size, params, true); + } + + private void reapplyNestedViews(Context context, View v, ViewGroup rootParent, + ActionApplyParams params) { + reapply(context, v, rootParent, null, params, false); + } + + // Note: topLevel should be true only for calls on the topLevel RemoteViews, internal calls + // should set it to false. + private void reapply(Context context, View v, ViewGroup rootParent, + @Nullable SizeF size, ActionApplyParams params, boolean topLevel) { + RemoteViews rvToApply = getRemoteViewsToReapply(context, v, size); + rvToApply.performApply(v, rootParent, params); + + // If the parent of the view is has is a root, resolve the recycling. + if (topLevel && v instanceof ViewGroup) { + finalizeViewRecycling((ViewGroup) v); + } } /** @hide */ @@ -5922,27 +5910,6 @@ public class RemoteViews implements Parcelable, Filter { return rvToApply; } - // Note: topLevel should be true only for calls on the topLevel RemoteViews, internal calls - // should set it to false. - private void reapply(Context context, View v, InteractionHandler handler, SizeF size, - ColorResources colorResources, boolean topLevel) { - - RemoteViews rvToApply = getRemoteViewsToReapply(context, v, size); - - rvToApply.performApply(v, (ViewGroup) v.getParent(), handler, colorResources); - - // If the parent of the view is has is a root, resolve the recycling. - if (topLevel && v instanceof ViewGroup) { - finalizeViewRecycling((ViewGroup) v); - } - } - - private void reapplyNestedViews(Context context, View v, ViewGroup rootParent, - InteractionHandler handler, SizeF size, ColorResources colorResources) { - RemoteViews rvToApply = getRemoteViewsToReapply(context, v, size); - rvToApply.performApply(v, rootParent, handler, colorResources); - } - /** * Applies all the actions to the provided view, moving as much of the task on the background * thread as possible. @@ -5973,19 +5940,25 @@ public class RemoteViews implements Parcelable, Filter { ColorResources colorResources) { RemoteViews rvToApply = getRemoteViewsToReapply(context, v, size); + ActionApplyParams params = new ActionApplyParams() + .withColorResources(colorResources) + .withInteractionHandler(handler) + .withExecutor(executor); + return new AsyncApplyTask(rvToApply, (ViewGroup) v.getParent(), - context, listener, handler, colorResources, v, true /* topLevel */) + context, listener, params, v, true /* topLevel */) .startTaskOnExecutor(executor); } - private void performApply(View v, ViewGroup parent, InteractionHandler handler, - ColorResources colorResources) { + private void performApply(View v, ViewGroup parent, ActionApplyParams params) { + params = params.clone(); + if (params.handler == null) { + params.handler = DEFAULT_INTERACTION_HANDLER; + } if (mActions != null) { - handler = handler == null ? DEFAULT_INTERACTION_HANDLER : handler; final int count = mActions.size(); for (int i = 0; i < count; i++) { - Action a = mActions.get(i); - a.apply(v, parent, handler, colorResources); + mActions.get(i).apply(v, parent, params); } } } @@ -6043,6 +6016,47 @@ public class RemoteViews implements Parcelable, Filter { } /** + * Utility class to hold all the options when applying the remote views + * @hide + */ + public class ActionApplyParams { + + public InteractionHandler handler; + public ColorResources colorResources; + public Executor executor; + @StyleRes public int applyThemeResId; + + @Override + public ActionApplyParams clone() { + return new ActionApplyParams() + .withInteractionHandler(handler) + .withColorResources(colorResources) + .withExecutor(executor) + .withThemeResId(applyThemeResId); + } + + public ActionApplyParams withInteractionHandler(InteractionHandler handler) { + this.handler = handler; + return this; + } + + public ActionApplyParams withColorResources(ColorResources colorResources) { + this.colorResources = colorResources; + return this; + } + + public ActionApplyParams withThemeResId(@StyleRes int themeResId) { + this.applyThemeResId = themeResId; + return this; + } + + public ActionApplyParams withExecutor(Executor executor) { + this.executor = executor; + return this; + } + } + + /** * Object allowing the modification of a context to overload the system's dynamic colors. * * Only colors from {@link android.R.color#system_accent1_0} to @@ -6056,10 +6070,12 @@ public class RemoteViews implements Parcelable, Filter { // Size, in bytes, of an entry in the array of colors in an ARSC file. private static final int ARSC_ENTRY_SIZE = 16; - private ResourcesLoader mLoader; + private final ResourcesLoader mLoader; + private final SparseIntArray mColorMapping; - private ColorResources(ResourcesLoader loader) { + private ColorResources(ResourcesLoader loader, SparseIntArray colorMapping) { mLoader = loader; + mColorMapping = colorMapping; } /** @@ -6071,6 +6087,10 @@ public class RemoteViews implements Parcelable, Filter { context.getResources().addLoaders(mLoader); } + public SparseIntArray getColorMapping() { + return mColorMapping; + } + private static ByteArrayOutputStream readFileContent(InputStream input) throws IOException { ByteArrayOutputStream content = new ByteArrayOutputStream(2048); byte[] buffer = new byte[4096]; @@ -6145,7 +6165,7 @@ public class RemoteViews implements Parcelable, Filter { ResourcesLoader colorsLoader = new ResourcesLoader(); colorsLoader.addProvider(ResourcesProvider .loadFromTable(pfd, null /* assetsProvider */)); - return new ColorResources(colorsLoader); + return new ColorResources(colorsLoader, colorMapping.clone()); } } } finally { diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java index 1d396be6b478..0730f3ddf8ac 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -325,7 +325,8 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { public boolean checkApplicationCallbackRegistration(int priority, OnBackInvokedCallback callback) { if (!mApplicationCallBackEnabled - && !(callback instanceof CompatOnBackInvokedCallback)) { + && !(callback instanceof CompatOnBackInvokedCallback) + && !ALWAYS_ENFORCE_PREDICTIVE_BACK) { Log.w("OnBackInvokedCallback", "OnBackInvokedCallback is not enabled for the application." + "\nSet 'android:enableOnBackInvokedCallback=\"true\"' in the" diff --git a/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java index c484525dbe20..f7af67b3b2a8 100644 --- a/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java +++ b/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java @@ -21,6 +21,7 @@ import android.content.Context; import android.graphics.Rect; import android.view.MenuItem; import android.view.View; +import android.view.selectiontoolbar.SelectionToolbarManager; import android.widget.PopupWindow; import java.util.List; @@ -92,7 +93,10 @@ public interface FloatingToolbarPopup { * enabled, otherwise returns {@link LocalFloatingToolbarPopup} implementation. */ static FloatingToolbarPopup createInstance(Context context, View parent) { - return new LocalFloatingToolbarPopup(context, parent); + boolean enabled = SelectionToolbarManager.isRemoteSelectionToolbarEnabled(context); + return enabled + ? new RemoteFloatingToolbarPopup(context, parent) + : new LocalFloatingToolbarPopup(context, parent); } } diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar Binary files differindex f54ab08d8a8a..918e514f4c89 100644 --- a/libs/WindowManager/Jetpack/window-extensions-release.aar +++ b/libs/WindowManager/Jetpack/window-extensions-release.aar diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index 5cba3b4ec130..6ae0f9ba0485 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -55,6 +55,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.compatui.CompatUIController; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.startingsurface.StartingWindowController; +import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.unfold.UnfoldAnimationController; @@ -72,6 +73,7 @@ import java.util.function.Consumer; */ public class ShellTaskOrganizer extends TaskOrganizer implements CompatUIController.CompatUICallback { + private static final String TAG = "ShellTaskOrganizer"; // Intentionally using negative numbers here so the positive numbers can be used // for task id specific listeners that will be added later. @@ -90,8 +92,6 @@ public class ShellTaskOrganizer extends TaskOrganizer implements }) public @interface TaskListenerType {} - private static final String TAG = "ShellTaskOrganizer"; - /** * Callbacks for when the tasks change in the system. */ @@ -177,6 +177,9 @@ public class ShellTaskOrganizer extends TaskOrganizer implements @Nullable private final CompatUIController mCompatUI; + @NonNull + private final ShellCommandHandler mShellCommandHandler; + @Nullable private final Optional<RecentTasksController> mRecentTasks; @@ -187,29 +190,33 @@ public class ShellTaskOrganizer extends TaskOrganizer implements private RunningTaskInfo mLastFocusedTaskInfo; public ShellTaskOrganizer(ShellExecutor mainExecutor) { - this(null /* shellInit */, null /* taskOrganizerController */, null /* compatUI */, + this(null /* shellInit */, null /* shellCommandHandler */, + null /* taskOrganizerController */, null /* compatUI */, Optional.empty() /* unfoldAnimationController */, Optional.empty() /* recentTasksController */, mainExecutor); } public ShellTaskOrganizer(ShellInit shellInit, + ShellCommandHandler shellCommandHandler, @Nullable CompatUIController compatUI, Optional<UnfoldAnimationController> unfoldAnimationController, Optional<RecentTasksController> recentTasks, ShellExecutor mainExecutor) { - this(shellInit, null /* taskOrganizerController */, compatUI, + this(shellInit, shellCommandHandler, null /* taskOrganizerController */, compatUI, unfoldAnimationController, recentTasks, mainExecutor); } @VisibleForTesting protected ShellTaskOrganizer(ShellInit shellInit, + ShellCommandHandler shellCommandHandler, ITaskOrganizerController taskOrganizerController, @Nullable CompatUIController compatUI, Optional<UnfoldAnimationController> unfoldAnimationController, Optional<RecentTasksController> recentTasks, ShellExecutor mainExecutor) { super(taskOrganizerController, mainExecutor); + mShellCommandHandler = shellCommandHandler; mCompatUI = compatUI; mRecentTasks = recentTasks; mUnfoldAnimationController = unfoldAnimationController.orElse(null); @@ -219,6 +226,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements } private void onInit() { + mShellCommandHandler.addDumpCallback(this::dump, this); if (mCompatUI != null) { mCompatUI.setCompatUICallback(this); } 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 05fafc54c273..131afafc32c7 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 @@ -57,6 +57,7 @@ import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.annotations.ShellBackgroundThread; import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.sysui.ShellInit; import java.util.concurrent.atomic.AtomicBoolean; @@ -90,7 +91,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont * Raw delta between {@link #mInitTouchLocation} and the last touch location. */ private final Point mTouchEventDelta = new Point(); - private final ShellExecutor mShellExecutor; /** True when a back gesture is ongoing */ private boolean mBackGestureStarted = false; @@ -105,6 +105,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private final SurfaceControl.Transaction mTransaction; private final IActivityTaskManager mActivityTaskManager; private final Context mContext; + private final ContentResolver mContentResolver; + private final ShellExecutor mShellExecutor; + private final Handler mBgHandler; @Nullable private IOnBackInvokedCallback mBackToLauncherCallback; private float mTriggerThreshold; @@ -133,16 +136,19 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont }; public BackAnimationController( + @NonNull ShellInit shellInit, @NonNull @ShellMainThread ShellExecutor shellExecutor, @NonNull @ShellBackgroundThread Handler backgroundHandler, Context context) { - this(shellExecutor, backgroundHandler, new SurfaceControl.Transaction(), + this(shellInit, shellExecutor, backgroundHandler, new SurfaceControl.Transaction(), ActivityTaskManager.getService(), context, context.getContentResolver()); } @VisibleForTesting - BackAnimationController(@NonNull @ShellMainThread ShellExecutor shellExecutor, - @NonNull @ShellBackgroundThread Handler handler, + BackAnimationController( + @NonNull ShellInit shellInit, + @NonNull @ShellMainThread ShellExecutor shellExecutor, + @NonNull @ShellBackgroundThread Handler bgHandler, @NonNull SurfaceControl.Transaction transaction, @NonNull IActivityTaskManager activityTaskManager, Context context, ContentResolver contentResolver) { @@ -150,7 +156,13 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mTransaction = transaction; mActivityTaskManager = activityTaskManager; mContext = context; - setupAnimationDeveloperSettingsObserver(contentResolver, handler); + mContentResolver = contentResolver; + mBgHandler = bgHandler; + shellInit.addInitCallback(this::onInit, this); + } + + private void onInit() { + setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler); } private void setupAnimationDeveloperSettingsObserver( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index 31fc6a5be589..2c02006c8ca5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -452,6 +452,7 @@ public class Bubble implements BubbleViewProvider { */ void setEntry(@NonNull final BubbleEntry entry) { Objects.requireNonNull(entry); + boolean showingDotPreviously = showDot(); mLastUpdated = entry.getStatusBarNotification().getPostTime(); mIsBubble = entry.getStatusBarNotification().getNotification().isBubbleNotification(); mPackageName = entry.getStatusBarNotification().getPackageName(); @@ -498,6 +499,10 @@ public class Bubble implements BubbleViewProvider { mShouldSuppressNotificationDot = entry.shouldSuppressNotificationDot(); mShouldSuppressNotificationList = entry.shouldSuppressNotificationList(); mShouldSuppressPeek = entry.shouldSuppressPeek(); + if (showingDotPreviously != showDot()) { + // This will update the UI if needed + setShowDot(showDot()); + } } @Nullable diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 398261600dc7..8771ceb71d98 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -1055,18 +1055,28 @@ public class BubbleController implements ConfigurationChangeListener { public void updateBubble(BubbleEntry notif, boolean suppressFlyout, boolean showInShade) { // If this is an interruptive notif, mark that it's interrupted mSysuiProxy.setNotificationInterruption(notif.getKey()); - if (!notif.getRanking().isTextChanged() + boolean isNonInterruptiveNotExpanding = !notif.getRanking().isTextChanged() && (notif.getBubbleMetadata() != null - && !notif.getBubbleMetadata().getAutoExpandBubble()) + && !notif.getBubbleMetadata().getAutoExpandBubble()); + if (isNonInterruptiveNotExpanding && mBubbleData.hasOverflowBubbleWithKey(notif.getKey())) { // Update the bubble but don't promote it out of overflow Bubble b = mBubbleData.getOverflowBubbleWithKey(notif.getKey()); - b.setEntry(notif); + if (notif.isBubble()) { + notif.setFlagBubble(false); + } + updateNotNotifyingEntry(b, notif, showInShade); + } else if (mBubbleData.hasAnyBubbleWithKey(notif.getKey()) + && isNonInterruptiveNotExpanding) { + Bubble b = mBubbleData.getAnyBubbleWithkey(notif.getKey()); + if (b != null) { + updateNotNotifyingEntry(b, notif, showInShade); + } } else if (mBubbleData.isSuppressedWithLocusId(notif.getLocusId())) { // Update the bubble but don't promote it out of overflow Bubble b = mBubbleData.getSuppressedBubbleWithKey(notif.getKey()); if (b != null) { - b.setEntry(notif); + updateNotNotifyingEntry(b, notif, showInShade); } } else { Bubble bubble = mBubbleData.getOrCreateBubble(notif, null /* persistedBubble */); @@ -1076,13 +1086,25 @@ public class BubbleController implements ConfigurationChangeListener { if (bubble.shouldAutoExpand()) { bubble.setShouldAutoExpand(false); } + mImpl.mCachedState.updateBubbleSuppressedState(bubble); } else { inflateAndAdd(bubble, suppressFlyout, showInShade); } } } - void inflateAndAdd(Bubble bubble, boolean suppressFlyout, boolean showInShade) { + void updateNotNotifyingEntry(Bubble b, BubbleEntry entry, boolean showInShade) { + boolean isBubbleSelected = Objects.equals(b, mBubbleData.getSelectedBubble()); + boolean isBubbleExpandedAndSelected = isStackExpanded() && isBubbleSelected; + b.setEntry(entry); + boolean suppress = isBubbleExpandedAndSelected || !showInShade || !b.showInShade(); + b.setSuppressNotification(suppress); + b.setShowDot(!isBubbleExpandedAndSelected); + mImpl.mCachedState.updateBubbleSuppressedState(b); + } + + @VisibleForTesting + public void inflateAndAdd(Bubble bubble, boolean suppressFlyout, boolean showInShade) { // Lazy init stack view when a bubble is created ensureStackViewCreated(); bubble.setInflateSynchronously(mInflateSynchronously); @@ -1111,7 +1133,10 @@ public class BubbleController implements ConfigurationChangeListener { } @VisibleForTesting - public void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp) { + public void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp, boolean fromSystem) { + if (!fromSystem) { + return; + } // shouldBubbleUp checks canBubble & for bubble metadata boolean shouldBubble = shouldBubbleUp && canLaunchInTaskView(mContext, entry); if (!shouldBubble && mBubbleData.hasAnyBubbleWithKey(entry.getKey())) { @@ -1172,9 +1197,9 @@ public class BubbleController implements ConfigurationChangeListener { // notification, so that the bubble will be re-created if shouldBubbleUp returns // true. mBubbleData.dismissBubbleWithKey(key, DISMISS_NO_BUBBLE_UP); - } else if (entry != null && mTmpRanking.isBubble() && !isActive) { + } else if (entry != null && mTmpRanking.isBubble() && !isActiveOrInOverflow) { entry.setFlagBubble(true); - onEntryUpdated(entry, shouldBubbleUp); + onEntryUpdated(entry, shouldBubbleUp, /* fromSystem= */ true); } } } @@ -1773,9 +1798,9 @@ public class BubbleController implements ConfigurationChangeListener { } @Override - public void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp) { + public void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp, boolean fromSystem) { mMainExecutor.execute(() -> { - BubbleController.this.onEntryUpdated(entry, shouldBubbleUp); + BubbleController.this.onEntryUpdated(entry, shouldBubbleUp, fromSystem); }); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java index cf792cda91b5..37b96ffe5cd1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java @@ -171,8 +171,9 @@ public interface Bubbles { * * @param entry the {@link BubbleEntry} by the notification. * @param shouldBubbleUp {@code true} if this notification should bubble up. + * @param fromSystem {@code true} if this update is from NotificationManagerService. */ - void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp); + void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp, boolean fromSystem); /** * Called when new notification entry removed. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java index db8d9d423aca..235fd9c469ea 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java @@ -43,6 +43,7 @@ import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState; import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager; import com.android.wm.shell.sysui.KeyguardChangeListener; import com.android.wm.shell.sysui.ShellController; +import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; import java.lang.ref.WeakReference; @@ -119,6 +120,7 @@ public class CompatUIController implements OnDisplaysChangedListener, private boolean mKeyguardShowing; public CompatUIController(Context context, + ShellInit shellInit, ShellController shellController, DisplayController displayController, DisplayInsetsController displayInsetsController, @@ -134,10 +136,14 @@ public class CompatUIController implements OnDisplaysChangedListener, mSyncQueue = syncQueue; mMainExecutor = mainExecutor; mTransitionsLazy = transitionsLazy; + mCompatUIHintsState = new CompatUIHintsState(); + shellInit.addInitCallback(this::onInit, this); + } + + private void onInit() { + mShellController.addKeyguardChangeListener(this); mDisplayController.addDisplayWindowListener(this); mImeController.addPositionProcessor(this); - mCompatUIHintsState = new CompatUIHintsState(); - shellController.addKeyguardChangeListener(this); } /** Sets the callback for UI interactions. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 3add4179ef82..bbaf51f7c54c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -174,13 +174,14 @@ public abstract class WMShellBaseModule { @Provides static ShellTaskOrganizer provideShellTaskOrganizer( ShellInit shellInit, + ShellCommandHandler shellCommandHandler, CompatUIController compatUI, Optional<UnfoldAnimationController> unfoldAnimationController, Optional<RecentTasksController> recentTasksOptional, @ShellMainThread ShellExecutor mainExecutor ) { - return new ShellTaskOrganizer(shellInit, compatUI, unfoldAnimationController, - recentTasksOptional, mainExecutor); + return new ShellTaskOrganizer(shellInit, shellCommandHandler, compatUI, + unfoldAnimationController, recentTasksOptional, mainExecutor); } @WMSingleton @@ -188,6 +189,7 @@ public abstract class WMShellBaseModule { static KidsModeTaskOrganizer provideKidsModeTaskOrganizer( Context context, ShellInit shellInit, + ShellCommandHandler shellCommandHandler, SyncTransactionQueue syncTransactionQueue, DisplayController displayController, DisplayInsetsController displayInsetsController, @@ -196,19 +198,20 @@ public abstract class WMShellBaseModule { @ShellMainThread ShellExecutor mainExecutor, @ShellMainThread Handler mainHandler ) { - return new KidsModeTaskOrganizer(context, shellInit, syncTransactionQueue, - displayController, displayInsetsController, unfoldAnimationController, - recentTasksOptional, mainExecutor, mainHandler); + return new KidsModeTaskOrganizer(context, shellInit, shellCommandHandler, + syncTransactionQueue, displayController, displayInsetsController, + unfoldAnimationController, recentTasksOptional, mainExecutor, mainHandler); } @WMSingleton @Provides static CompatUIController provideCompatUIController(Context context, + ShellInit shellInit, ShellController shellController, DisplayController displayController, DisplayInsetsController displayInsetsController, DisplayImeController imeController, SyncTransactionQueue syncQueue, @ShellMainThread ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy) { - return new CompatUIController(context, shellController, displayController, + return new CompatUIController(context, shellInit, shellController, displayController, displayInsetsController, imeController, syncQueue, mainExecutor, transitionsLazy); } @@ -268,12 +271,14 @@ public abstract class WMShellBaseModule { @Provides static Optional<BackAnimationController> provideBackAnimationController( Context context, + ShellInit shellInit, @ShellMainThread ShellExecutor shellExecutor, @ShellBackgroundThread Handler backgroundHandler ) { if (BackAnimationController.IS_ENABLED) { return Optional.of( - new BackAnimationController(shellExecutor, backgroundHandler, context)); + new BackAnimationController(shellInit, shellExecutor, backgroundHandler, + context)); } return Optional.empty(); } @@ -384,11 +389,14 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides static Optional<HideDisplayCutoutController> provideHideDisplayCutoutController(Context context, - ShellController shellController, DisplayController displayController, + ShellInit shellInit, + ShellCommandHandler shellCommandHandler, + ShellController shellController, + DisplayController displayController, @ShellMainThread ShellExecutor mainExecutor) { return Optional.ofNullable( - HideDisplayCutoutController.create(context, shellController, displayController, - mainExecutor)); + HideDisplayCutoutController.create(context, shellInit, shellCommandHandler, + shellController, displayController, mainExecutor)); } // @@ -466,12 +474,13 @@ public abstract class WMShellBaseModule { static Optional<RecentTasksController> provideRecentTasksController( Context context, ShellInit shellInit, + ShellCommandHandler shellCommandHandler, TaskStackListenerImpl taskStackListener, @ShellMainThread ShellExecutor mainExecutor ) { return Optional.ofNullable( - RecentTasksController.create(context, shellInit, taskStackListener, - mainExecutor)); + RecentTasksController.create(context, shellInit, shellCommandHandler, + taskStackListener, mainExecutor)); } // @@ -649,8 +658,9 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides static ShellController provideShellController(ShellInit shellInit, + ShellCommandHandler shellCommandHandler, @ShellMainThread ShellExecutor mainExecutor) { - return new ShellController(shellInit, mainExecutor); + return new ShellController(shellInit, shellCommandHandler, mainExecutor); } // @@ -676,12 +686,15 @@ public abstract class WMShellBaseModule { KidsModeTaskOrganizer kidsModeTaskOrganizer, Optional<BubbleController> bubblesOptional, Optional<SplitScreenController> splitScreenOptional, + Optional<Pip> pipOptional, Optional<PipTouchHandler> pipTouchHandlerOptional, FullscreenTaskListener fullscreenTaskListener, Optional<UnfoldAnimationController> unfoldAnimationController, Optional<UnfoldTransitionHandler> unfoldTransitionHandler, Optional<FreeformComponents> freeformComponents, Optional<RecentTasksController> recentTasksOptional, + Optional<OneHandedController> oneHandedControllerOptional, + Optional<HideDisplayCutoutController> hideDisplayCutoutControllerOptional, ActivityEmbeddingController activityEmbeddingOptional, Transitions transitions, StartingWindowController startingWindow, @@ -697,18 +710,7 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides - static ShellCommandHandler provideShellCommandHandlerImpl( - ShellController shellController, - ShellTaskOrganizer shellTaskOrganizer, - KidsModeTaskOrganizer kidsModeTaskOrganizer, - Optional<SplitScreenController> splitScreenOptional, - Optional<Pip> pipOptional, - Optional<OneHandedController> oneHandedOptional, - Optional<HideDisplayCutoutController> hideDisplayCutout, - Optional<RecentTasksController> recentTasksOptional, - @ShellMainThread ShellExecutor mainExecutor) { - return new ShellCommandHandler(shellController, shellTaskOrganizer, - kidsModeTaskOrganizer, splitScreenOptional, pipOptional, oneHandedOptional, - hideDisplayCutout, recentTasksOptional, mainExecutor); + static ShellCommandHandler provideShellCommandHandler() { + return new ShellCommandHandler(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index d53451aa6034..2ca9c3be8a69 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -74,6 +74,7 @@ import com.android.wm.shell.pip.phone.PipMotionHelper; import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.splitscreen.SplitScreenController; +import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.SplitscreenPipMixedHandler; @@ -248,14 +249,20 @@ public abstract class WMShellModule { @Provides @DynamicOverride static OneHandedController provideOneHandedController(Context context, + ShellInit shellInit, + ShellCommandHandler shellCommandHandler, ShellController shellController, - WindowManager windowManager, DisplayController displayController, - DisplayLayout displayLayout, TaskStackListenerImpl taskStackListener, - UiEventLogger uiEventLogger, InteractionJankMonitor jankMonitor, - @ShellMainThread ShellExecutor mainExecutor, @ShellMainThread Handler mainHandler) { - return OneHandedController.create(context, shellController, windowManager, - displayController, displayLayout, taskStackListener, jankMonitor, uiEventLogger, - mainExecutor, mainHandler); + WindowManager windowManager, + DisplayController displayController, + DisplayLayout displayLayout, + TaskStackListenerImpl taskStackListener, + UiEventLogger uiEventLogger, + InteractionJankMonitor jankMonitor, + @ShellMainThread ShellExecutor mainExecutor, + @ShellMainThread Handler mainHandler) { + return OneHandedController.create(context, shellInit, shellCommandHandler, shellController, + windowManager, displayController, displayLayout, taskStackListener, jankMonitor, + uiEventLogger, mainExecutor, mainHandler); } // @@ -268,6 +275,7 @@ public abstract class WMShellModule { static SplitScreenController provideSplitScreenController( Context context, ShellInit shellInit, + ShellCommandHandler shellCommandHandler, ShellController shellController, ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, @@ -281,10 +289,10 @@ public abstract class WMShellModule { IconProvider iconProvider, Optional<RecentTasksController> recentTasks, @ShellMainThread ShellExecutor mainExecutor) { - return new SplitScreenController(context, shellInit, shellController, shellTaskOrganizer, - syncQueue, rootTaskDisplayAreaOrganizer, displayController, displayImeController, - displayInsetsController, dragAndDropController, transitions, transactionPool, - iconProvider, recentTasks, mainExecutor); + return new SplitScreenController(context, shellInit, shellCommandHandler, shellController, + shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, displayController, + displayImeController, displayInsetsController, dragAndDropController, transitions, + transactionPool, iconProvider, recentTasks, mainExecutor); } // @@ -294,24 +302,33 @@ public abstract class WMShellModule { @WMSingleton @Provides static Optional<Pip> providePip(Context context, - ShellController shellController, DisplayController displayController, - PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm, - PipKeepClearAlgorithm pipKeepClearAlgorithm, PipBoundsState pipBoundsState, - PipMotionHelper pipMotionHelper, PipMediaController pipMediaController, - PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer, + ShellInit shellInit, + ShellCommandHandler shellCommandHandler, + ShellController shellController, + DisplayController displayController, + PipAppOpsListener pipAppOpsListener, + PipBoundsAlgorithm pipBoundsAlgorithm, + PipKeepClearAlgorithm pipKeepClearAlgorithm, + PipBoundsState pipBoundsState, + PipMotionHelper pipMotionHelper, + PipMediaController pipMediaController, + PhonePipMenuController phonePipMenuController, + PipTaskOrganizer pipTaskOrganizer, PipTransitionState pipTransitionState, - PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController, + PipTouchHandler pipTouchHandler, + PipTransitionController pipTransitionController, WindowManagerShellWrapper windowManagerShellWrapper, TaskStackListenerImpl taskStackListener, PipParamsChangedForwarder pipParamsChangedForwarder, Optional<OneHandedController> oneHandedController, @ShellMainThread ShellExecutor mainExecutor) { - return Optional.ofNullable(PipController.create(context, shellController, displayController, - pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, - pipMotionHelper, - pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState, - pipTouchHandler, pipTransitionController, windowManagerShellWrapper, - taskStackListener, pipParamsChangedForwarder, oneHandedController, mainExecutor)); + return Optional.ofNullable(PipController.create( + context, shellInit, shellCommandHandler, shellController, + displayController, pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm, + pipBoundsState, pipMotionHelper, pipMediaController, phonePipMenuController, + pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController, + windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder, + oneHandedController, mainExecutor)); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md index 52f0c4222b64..99922fbc2d95 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md @@ -59,9 +59,9 @@ WMShell SysUI service: adb shell dumpsys activity service SystemUIService WMShell ``` -If information should be added to the dump, make updates to: -- `WMShell` if you are dumping SysUI state -- `ShellCommandHandler` if you are dumping Shell state +If information should be added to the dump, either: +- Update `WMShell` if you are dumping SysUI state +- Inject `ShellCommandHandler` into your Shell class, and add a dump callback ## Debugging in Android Studio diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md index 0dd50b1bee68..d6302e640ba7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md @@ -32,7 +32,7 @@ interfaces provided by the Shell and the rest of SystemUI. More detail can be found in [go/wm-sysui-dagger](http://go/wm-sysui-dagger). -## Interfaces to Shell components +## Interfaces from SysUI to Shell components Within the same process, the WM Shell components can be running on a different thread than the main SysUI thread (disabled on certain products). This introduces challenges where we have to be @@ -54,12 +54,30 @@ For example, you might have: Adding an interface to a Shell component may seem like a lot of boiler plate, but is currently necessary to maintain proper threading and logic isolation. -## Configuration changes & other SysUI events +## Listening for Configuration changes & other SysUI events -Aside from direct calls into Shell controllers for exposed features, the Shell also receives +Aside from direct calls into Shell controllers for exposed features, the Shell also receives common event callbacks from SysUI via the `ShellController`. This includes things like: - Configuration changes -- TODO: Shell init -- TODO: Shell command -- TODO: Keyguard events
\ No newline at end of file +- Keyguard events +- Shell init +- Shell dumps & commands + +For other events which are specific to the Shell feature, then you can add callback methods on +the Shell feature interface. Any such calls should <u>**never**</u> be synchronous calls as +they will need to post to the Shell main thread to run. + +## Shell commands & Dumps + +Since the Shell library is a part of the SysUI process, it relies on SysUI to trigger commands +on individual Shell components, or to dump individual shell components. + +```shell +# Dump everything +adb shell dumpsys activity service SystemUIService WMShell + +# Run a specific command +adb shell dumpsys activity service SystemUIService WMShell help +adb shell dumpsys activity service SystemUIService WMShell <cmd> <args> ... +```
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java index 665b035bc41c..32125fa44148 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java @@ -27,7 +27,9 @@ import androidx.annotation.VisibleForTesting; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.sysui.ConfigurationChangeListener; +import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; +import com.android.wm.shell.sysui.ShellInit; import java.io.PrintWriter; @@ -38,6 +40,7 @@ public class HideDisplayCutoutController implements ConfigurationChangeListener private static final String TAG = "HideDisplayCutoutController"; private final Context mContext; + private final ShellCommandHandler mShellCommandHandler; private final ShellController mShellController; private final HideDisplayCutoutOrganizer mOrganizer; @VisibleForTesting @@ -49,7 +52,10 @@ public class HideDisplayCutoutController implements ConfigurationChangeListener */ @Nullable public static HideDisplayCutoutController create(Context context, - ShellController shellController, DisplayController displayController, + ShellInit shellInit, + ShellCommandHandler shellCommandHandler, + ShellController shellController, + DisplayController displayController, ShellExecutor mainExecutor) { // The SystemProperty is set for devices that support this feature and is used to control // whether to create the HideDisplayCutout instance. @@ -60,14 +66,24 @@ public class HideDisplayCutoutController implements ConfigurationChangeListener HideDisplayCutoutOrganizer organizer = new HideDisplayCutoutOrganizer(context, displayController, mainExecutor); - return new HideDisplayCutoutController(context, shellController, organizer); + return new HideDisplayCutoutController(context, shellInit, shellCommandHandler, + shellController, organizer); } - HideDisplayCutoutController(Context context, ShellController shellController, + HideDisplayCutoutController(Context context, + ShellInit shellInit, + ShellCommandHandler shellCommandHandler, + ShellController shellController, HideDisplayCutoutOrganizer organizer) { mContext = context; + mShellCommandHandler = shellCommandHandler; mShellController = shellController; mOrganizer = organizer; + shellInit.addInitCallback(this::onInit, this); + } + + private void onInit() { + mShellCommandHandler.addDumpCallback(this::dump, this); updateStatus(); mShellController.addConfigurationChangeListener(this); } @@ -96,11 +112,11 @@ public class HideDisplayCutoutController implements ConfigurationChangeListener updateStatus(); } - public void dump(@NonNull PrintWriter pw) { - final String prefix = " "; + public void dump(@NonNull PrintWriter pw, String prefix) { + final String innerPrefix = " "; pw.print(TAG); pw.println(" states: "); - pw.print(prefix); + pw.print(innerPrefix); pw.print("mEnabled="); pw.println(mEnabled); mOrganizer.dump(pw); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java index 73b9b89e6993..2fdd12185551 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java @@ -50,6 +50,7 @@ import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.recents.RecentTasksController; +import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.unfold.UnfoldAnimationController; @@ -73,6 +74,7 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer { private final Handler mMainHandler; private final Context mContext; + private final ShellCommandHandler mShellCommandHandler; private final SyncTransactionQueue mSyncQueue; private final DisplayController mDisplayController; private final DisplayInsetsController mDisplayInsetsController; @@ -141,6 +143,8 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer { @VisibleForTesting KidsModeTaskOrganizer( Context context, + ShellInit shellInit, + ShellCommandHandler shellCommandHandler, ITaskOrganizerController taskOrganizerController, SyncTransactionQueue syncTransactionQueue, DisplayController displayController, @@ -150,19 +154,23 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer { KidsModeSettingsObserver kidsModeSettingsObserver, ShellExecutor mainExecutor, Handler mainHandler) { - super(/* shellInit= */ null, taskOrganizerController, /* compatUI= */ null, - unfoldAnimationController, recentTasks, mainExecutor); + // Note: we don't call super with the shell init because we will be initializing manually + super(/* shellInit= */ null, /* shellCommandHandler= */ null, taskOrganizerController, + /* compatUI= */ null, unfoldAnimationController, recentTasks, mainExecutor); mContext = context; + mShellCommandHandler = shellCommandHandler; mMainHandler = mainHandler; mSyncQueue = syncTransactionQueue; mDisplayController = displayController; mDisplayInsetsController = displayInsetsController; mKidsModeSettingsObserver = kidsModeSettingsObserver; + shellInit.addInitCallback(this::onInit, this); } public KidsModeTaskOrganizer( Context context, ShellInit shellInit, + ShellCommandHandler shellCommandHandler, SyncTransactionQueue syncTransactionQueue, DisplayController displayController, DisplayInsetsController displayInsetsController, @@ -171,9 +179,10 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer { ShellExecutor mainExecutor, Handler mainHandler) { // Note: we don't call super with the shell init because we will be initializing manually - super(/* shellInit= */ null, /* compatUI= */ null, unfoldAnimationController, recentTasks, - mainExecutor); + super(/* shellInit= */ null, /* taskOrganizerController= */ null, /* compatUI= */ null, + unfoldAnimationController, recentTasks, mainExecutor); mContext = context; + mShellCommandHandler = shellCommandHandler; mMainHandler = mainHandler; mSyncQueue = syncTransactionQueue; mDisplayController = displayController; @@ -185,6 +194,9 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer { * Initializes kids mode status. */ public void onInit() { + if (mShellCommandHandler != null) { + mShellCommandHandler.addDumpCallback(this::dump, this); + } if (mKidsModeSettingsObserver == null) { mKidsModeSettingsObserver = new KidsModeSettingsObserver(mMainHandler, mContext); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java index 24f02ac1a6cf..9149204b94ce 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java @@ -56,7 +56,9 @@ import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.annotations.ExternalThread; import com.android.wm.shell.sysui.ConfigurationChangeListener; import com.android.wm.shell.sysui.KeyguardChangeListener; +import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; +import com.android.wm.shell.sysui.ShellInit; import java.io.PrintWriter; @@ -85,6 +87,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, private Context mContext; + private final ShellCommandHandler mShellCommandHandler; private final ShellController mShellController; private final AccessibilityManager mAccessibilityManager; private final DisplayController mDisplayController; @@ -192,8 +195,9 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, /** * Creates {@link OneHandedController}, returns {@code null} if the feature is not supported. */ - public static OneHandedController create( - Context context, ShellController shellController, WindowManager windowManager, + public static OneHandedController create(Context context, + ShellInit shellInit, ShellCommandHandler shellCommandHandler, + ShellController shellController, WindowManager windowManager, DisplayController displayController, DisplayLayout displayLayout, TaskStackListenerImpl taskStackListener, InteractionJankMonitor jankMonitor, UiEventLogger uiEventLogger, @@ -213,14 +217,16 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, context, displayLayout, settingsUtil, animationController, tutorialHandler, jankMonitor, mainExecutor); OneHandedUiEventLogger oneHandedUiEventsLogger = new OneHandedUiEventLogger(uiEventLogger); - return new OneHandedController(context, shellController, displayController, organizer, - touchHandler, tutorialHandler, settingsUtil, accessibilityUtil, timeoutHandler, - oneHandedState, oneHandedUiEventsLogger, taskStackListener, - mainExecutor, mainHandler); + return new OneHandedController(context, shellInit, shellCommandHandler, shellController, + displayController, organizer, touchHandler, tutorialHandler, settingsUtil, + accessibilityUtil, timeoutHandler, oneHandedState, oneHandedUiEventsLogger, + taskStackListener, mainExecutor, mainHandler); } @VisibleForTesting OneHandedController(Context context, + ShellInit shellInit, + ShellCommandHandler shellCommandHandler, ShellController shellController, DisplayController displayController, OneHandedDisplayAreaOrganizer displayAreaOrganizer, @@ -235,6 +241,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, ShellExecutor mainExecutor, Handler mainHandler) { mContext = context; + mShellCommandHandler = shellCommandHandler; mShellController = shellController; mOneHandedSettingsUtil = settingsUtil; mOneHandedAccessibilityUtil = oneHandedAccessibilityUtil; @@ -247,8 +254,8 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, mMainHandler = mainHandler; mOneHandedUiEventLogger = uiEventsLogger; mTaskStackListener = taskStackListener; + mAccessibilityManager = AccessibilityManager.getInstance(mContext); - mDisplayController.addDisplayWindowListener(mDisplaysChangedListener); final float offsetPercentageConfig = context.getResources().getFraction( R.fraction.config_one_handed_offset, 1, 1); final int sysPropPercentageConfig = SystemProperties.getInt( @@ -268,6 +275,12 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, getObserver(this::onSwipeToNotificationEnabledChanged); mShortcutEnabledObserver = getObserver(this::onShortcutEnabledChanged); + shellInit.addInitCallback(this::onInit, this); + } + + private void onInit() { + mShellCommandHandler.addDumpCallback(this::dump, this); + mDisplayController.addDisplayWindowListener(mDisplaysChangedListener); mDisplayController.addDisplayChangingController(this); setupCallback(); registerSettingObservers(mUserId); @@ -275,7 +288,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, updateSettings(); updateDisplayLayout(mContext.getDisplayId()); - mAccessibilityManager = AccessibilityManager.getInstance(context); mAccessibilityManager.addAccessibilityStateChangeListener( mAccessibilityStateChangeListener); @@ -623,7 +635,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, updateOneHandedEnabled(); } - public void dump(@NonNull PrintWriter pw) { + public void dump(@NonNull PrintWriter pw, String prefix) { final String innerPrefix = " "; pw.println(); pw.println(TAG); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java index 38631ce26cd1..93172f82edd1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java @@ -20,7 +20,6 @@ import android.graphics.Rect; import com.android.wm.shell.common.annotations.ExternalThread; -import java.io.PrintWriter; import java.util.function.Consumer; /** @@ -99,12 +98,4 @@ public interface Pip { * view hierarchy or destroyed. */ default void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) { } - - /** - * Dump the current state and information if need. - * - * @param pw The stream to dump information to. - */ - default void dump(PrintWriter pw) { - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 586e3a014506..fc97f310ad4e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -89,7 +89,9 @@ import com.android.wm.shell.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.sysui.ConfigurationChangeListener; import com.android.wm.shell.sysui.KeyguardChangeListener; +import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; +import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; import java.io.PrintWriter; @@ -122,6 +124,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb private TaskStackListenerImpl mTaskStackListener; private PipParamsChangedForwarder mPipParamsChangedForwarder; private Optional<OneHandedController> mOneHandedController; + private final ShellCommandHandler mShellCommandHandler; private final ShellController mShellController; protected final PipImpl mImpl; @@ -295,6 +298,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb */ @Nullable public static Pip create(Context context, + ShellInit shellInit, + ShellCommandHandler shellCommandHandler, ShellController shellController, DisplayController displayController, PipAppOpsListener pipAppOpsListener, @@ -319,16 +324,18 @@ public class PipController implements PipTransitionController.PipTransitionCallb return null; } - return new PipController(context, shellController, displayController, pipAppOpsListener, - pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper, - pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState, - pipTouchHandler, pipTransitionController, + return new PipController(context, shellInit, shellCommandHandler, shellController, + displayController, pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm, + pipBoundsState, pipMotionHelper, pipMediaController, phonePipMenuController, + pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController, windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder, oneHandedController, mainExecutor) .mImpl; } protected PipController(Context context, + ShellInit shellInit, + ShellCommandHandler shellCommandHandler, ShellController shellController, DisplayController displayController, PipAppOpsListener pipAppOpsListener, @@ -355,6 +362,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb } mContext = context; + mShellCommandHandler = shellCommandHandler; mShellController = shellController; mImpl = new PipImpl(); mWindowManagerShellWrapper = windowManagerShellWrapper; @@ -378,11 +386,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb .getInteger(R.integer.config_pipEnterAnimationDuration); mPipParamsChangedForwarder = pipParamsChangedForwarder; - //TODO: move this to ShellInit when PipController can be injected - mMainExecutor.execute(this::init); + shellInit.addInitCallback(this::onInit, this); } - public void init() { + private void onInit() { + mShellCommandHandler.addDumpCallback(this::dump, this); mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(), INPUT_CONSUMER_PIP, mMainExecutor); mPipTransitionController.registerPipTransitionCallback(this); @@ -503,6 +511,12 @@ public class PipController implements PipTransitionController.PipTransitionCallb updateMovementBounds(null /* toBounds */, false /* fromRotation */, false /* fromImeAdjustment */, false /* fromShelfAdjustment */, null /* windowContainerTransaction */); + } else { + // when we enter pip for the first time, the destination bounds and pip + // bounds will already match, since they are calculated prior to + // starting the animation, so we only need to update the min/max size + // that is used for e.g. double tap to maximized state + mTouchHandler.updateMinMaxSize(ratio); } } @@ -622,7 +636,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb mPipTaskOrganizer.scheduleAnimateResizePip( postChangeBounds, duration, null /* updateBoundsCallback */); } else { - mTouchHandler.getMotionHelper().movePip(postChangeBounds); + // Directly move PiP to its final destination bounds without animation. + mPipTaskOrganizer.scheduleFinishResizePip(postChangeBounds); } } else { updateDisplayLayout.run(); @@ -912,7 +927,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb return true; } - private void dump(PrintWriter pw) { + private void dump(PrintWriter pw, String prefix) { final String innerPrefix = " "; pw.println(TAG); mMenuController.dump(pw, innerPrefix); @@ -1000,18 +1015,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb PipController.this.showPictureInPictureMenu(); }); } - - @Override - public void dump(PrintWriter pw) { - try { - mMainExecutor.executeBlocking(() -> { - PipController.this.dump(pw); - }); - } catch (InterruptedException e) { - ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: Failed to dump PipController in 2s", TAG); - } - } } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index c86c1368b88e..a2fa058e97b7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -412,13 +412,7 @@ public class PipTouchHandler { mPipBoundsState.getExpandedBounds(), insetBounds, expandedMovementBounds, bottomOffset); - if (mPipResizeGestureHandler.isUsingPinchToZoom()) { - updatePinchResizeSizeConstraints(insetBounds, normalBounds, aspectRatio); - } else { - mPipResizeGestureHandler.updateMinSize(normalBounds.width(), normalBounds.height()); - mPipResizeGestureHandler.updateMaxSize(mPipBoundsState.getExpandedBounds().width(), - mPipBoundsState.getExpandedBounds().height()); - } + updatePipSizeConstraints(insetBounds, normalBounds, aspectRatio); // The extra offset does not really affect the movement bounds, but are applied based on the // current state (ime showing, or shelf offset) when we need to actually shift @@ -487,6 +481,27 @@ public class PipTouchHandler { } } + /** + * Update the values for min/max allowed size of picture in picture window based on the aspect + * ratio. + * @param aspectRatio aspect ratio to use for the calculation of min/max size + */ + public void updateMinMaxSize(float aspectRatio) { + updatePipSizeConstraints(mInsetBounds, mPipBoundsState.getNormalBounds(), + aspectRatio); + } + + private void updatePipSizeConstraints(Rect insetBounds, Rect normalBounds, + float aspectRatio) { + if (mPipResizeGestureHandler.isUsingPinchToZoom()) { + updatePinchResizeSizeConstraints(insetBounds, normalBounds, aspectRatio); + } else { + mPipResizeGestureHandler.updateMinSize(normalBounds.width(), normalBounds.height()); + mPipResizeGestureHandler.updateMaxSize(mPipBoundsState.getExpandedBounds().width(), + mPipBoundsState.getExpandedBounds().height()); + } + } + private void updatePinchResizeSizeConstraints(Rect insetBounds, Rect normalBounds, float aspectRatio) { final int shorterLength = Math.min(mPipBoundsState.getDisplayBounds().width(), diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index 3d1a7e98e20d..7b42350b1365 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -44,6 +44,7 @@ import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.annotations.ExternalThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.protolog.ShellProtoLogGroup; +import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.util.GroupedRecentTaskInfo; import com.android.wm.shell.util.SplitBounds; @@ -62,6 +63,7 @@ public class RecentTasksController implements TaskStackListenerCallback, private static final String TAG = RecentTasksController.class.getSimpleName(); private final Context mContext; + private final ShellCommandHandler mShellCommandHandler; private final ShellExecutor mMainExecutor; private final TaskStackListenerImpl mTaskStackListener; private final RecentTasks mImpl = new RecentTasksImpl(); @@ -87,20 +89,24 @@ public class RecentTasksController implements TaskStackListenerCallback, public static RecentTasksController create( Context context, ShellInit shellInit, + ShellCommandHandler shellCommandHandler, TaskStackListenerImpl taskStackListener, @ShellMainThread ShellExecutor mainExecutor ) { if (!context.getResources().getBoolean(com.android.internal.R.bool.config_hasRecents)) { return null; } - return new RecentTasksController(context, shellInit, taskStackListener, mainExecutor); + return new RecentTasksController(context, shellInit, shellCommandHandler, taskStackListener, + mainExecutor); } RecentTasksController(Context context, ShellInit shellInit, + ShellCommandHandler shellCommandHandler, TaskStackListenerImpl taskStackListener, ShellExecutor mainExecutor) { mContext = context; + mShellCommandHandler = shellCommandHandler; mIsDesktopMode = mContext.getPackageManager().hasSystemFeature(FEATURE_PC); mTaskStackListener = taskStackListener; mMainExecutor = mainExecutor; @@ -112,6 +118,7 @@ public class RecentTasksController implements TaskStackListenerCallback, } private void onInit() { + mShellCommandHandler.addDumpCallback(this::dump, this); mTaskStackListener.addListener(this); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index d6120c409506..2117b69ebc2e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -84,6 +84,7 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.splitscreen.SplitScreen.StageType; import com.android.wm.shell.sysui.KeyguardChangeListener; +import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; @@ -131,6 +132,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, @Retention(RetentionPolicy.SOURCE) @interface ExitReason{} + private final ShellCommandHandler mShellCommandHandler; private final ShellController mShellController; private final ShellTaskOrganizer mTaskOrganizer; private final SyncTransactionQueue mSyncQueue; @@ -147,6 +149,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, private final SplitscreenEventLogger mLogger; private final IconProvider mIconProvider; private final Optional<RecentTasksController> mRecentTasksOptional; + private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler; private StageCoordinator mStageCoordinator; // Only used for the legacy recents animation from splitscreen to allow the tasks to be animated @@ -155,6 +158,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, public SplitScreenController(Context context, ShellInit shellInit, + ShellCommandHandler shellCommandHandler, ShellController shellController, ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, @@ -168,6 +172,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, IconProvider iconProvider, Optional<RecentTasksController> recentTasks, ShellExecutor mainExecutor) { + mShellCommandHandler = shellCommandHandler; mShellController = shellController; mTaskOrganizer = shellTaskOrganizer; mSyncQueue = syncQueue; @@ -183,6 +188,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, mLogger = new SplitscreenEventLogger(); mIconProvider = iconProvider; mRecentTasksOptional = recentTasks; + mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this); // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic // override for this controller from the base module if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) { @@ -200,6 +206,9 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, */ @VisibleForTesting void onInit() { + mShellCommandHandler.addDumpCallback(this::dump, this); + mShellCommandHandler.addCommandCallback("splitscreen", mSplitScreenShellCommandHandler, + this); mShellController.addKeyguardChangeListener(this); if (mStageCoordinator == null) { // TODO: Multi-display @@ -337,17 +346,39 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) { + final int[] result = new int[1]; + IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() { + @Override + public void onAnimationStart(@WindowManager.TransitionOldType int transit, + RemoteAnimationTarget[] apps, + RemoteAnimationTarget[] wallpapers, + RemoteAnimationTarget[] nonApps, + final IRemoteAnimationFinishedCallback finishedCallback) { + try { + finishedCallback.onAnimationFinished(); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to invoke onAnimationFinished", e); + } + if (result[0] == START_SUCCESS || result[0] == START_TASK_TO_FRONT) { + final WindowContainerTransaction evictWct = new WindowContainerTransaction(); + mStageCoordinator.prepareEvictNonOpeningChildTasks(position, apps, evictWct); + mSyncQueue.queue(evictWct); + } + } + @Override + public void onAnimationCancelled(boolean isKeyguardOccluded) { + } + }; options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */); + RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(wrapper, + 0 /* duration */, 0 /* statusBarTransitionDelay */); + ActivityOptions activityOptions = ActivityOptions.fromBundle(options); + activityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter)); try { - final WindowContainerTransaction evictWct = new WindowContainerTransaction(); - mStageCoordinator.prepareEvictChildTasks(position, evictWct); - final int result = - ActivityTaskManager.getService().startActivityFromRecents(taskId, options); - if (result == START_SUCCESS || result == START_TASK_TO_FRONT) { - mSyncQueue.queue(evictWct); - } + result[0] = ActivityTaskManager.getService().startActivityFromRecents(taskId, + activityOptions.toBundle()); } catch (RemoteException e) { Slog.e(TAG, "Failed to launch task", e); } @@ -403,7 +434,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of the // split. - if (isLaunchingAdjacently(intent.getIntent(), position)) { + if (shouldAddMultipleTaskFlag(intent.getIntent(), position)) { fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); } @@ -418,8 +449,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, /** Returns {@code true} if it's launching the same component on both sides of the split. */ @VisibleForTesting - boolean isLaunchingAdjacently(@Nullable Intent startIntent, - @SplitPosition int position) { + boolean shouldAddMultipleTaskFlag(@Nullable Intent startIntent, @SplitPosition int position) { if (startIntent == null) { return false; } @@ -430,6 +460,16 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } if (isSplitScreenVisible()) { + // To prevent users from constantly dropping the same app to the same side resulting in + // a large number of instances in the background. + final ActivityManager.RunningTaskInfo targetTaskInfo = getTaskInfo(position); + final ComponentName targetActivity = targetTaskInfo != null + ? targetTaskInfo.baseIntent.getComponent() : null; + if (Objects.equals(launchingActivity, targetActivity)) { + return false; + } + + // Allow users to start a new instance the same to adjacent side. final ActivityManager.RunningTaskInfo pairedTaskInfo = getTaskInfo(SplitLayout.reversePosition(position)); final ComponentName pairedActivity = pairedTaskInfo != null diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java new file mode 100644 index 000000000000..681d9647d154 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2022 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.wm.shell.splitscreen; + +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; + +import com.android.wm.shell.sysui.ShellCommandHandler; + +import java.io.PrintWriter; + +/** + * Handles the shell commands for the SplitscreenController. + */ +public class SplitScreenShellCommandHandler implements + ShellCommandHandler.ShellCommandActionHandler { + + private final SplitScreenController mController; + + public SplitScreenShellCommandHandler(SplitScreenController controller) { + mController = controller; + } + + @Override + public boolean onShellCommand(String[] args, PrintWriter pw) { + switch (args[0]) { + case "moveToSideStage": + return runMoveToSideStage(args, pw); + case "removeFromSideStage": + return runRemoveFromSideStage(args, pw); + case "setSideStagePosition": + return runSetSideStagePosition(args, pw); + default: + pw.println("Invalid command: " + args[0]); + return false; + } + } + + private boolean runMoveToSideStage(String[] args, PrintWriter pw) { + if (args.length < 3) { + // First argument is the action name. + pw.println("Error: task id should be provided as arguments"); + return false; + } + final int taskId = new Integer(args[1]); + final int sideStagePosition = args.length > 3 + ? new Integer(args[2]) : SPLIT_POSITION_BOTTOM_OR_RIGHT; + mController.moveToSideStage(taskId, sideStagePosition); + return true; + } + + private boolean runRemoveFromSideStage(String[] args, PrintWriter pw) { + if (args.length < 2) { + // First argument is the action name. + pw.println("Error: task id should be provided as arguments"); + return false; + } + final int taskId = new Integer(args[1]); + mController.removeFromSideStage(taskId); + return true; + } + + private boolean runSetSideStagePosition(String[] args, PrintWriter pw) { + if (args.length < 2) { + // First argument is the action name. + pw.println("Error: side stage position should be provided as arguments"); + return false; + } + final int position = new Integer(args[1]); + mController.setSideStagePosition(position); + return true; + } + + @Override + public void printShellCommandHelp(PrintWriter pw, String prefix) { + pw.println(prefix + "moveToSideStage <taskId> <SideStagePosition>"); + pw.println(prefix + " Move a task with given id in split-screen mode."); + pw.println(prefix + "removeFromSideStage <taskId>"); + pw.println(prefix + " Remove a task with given id in split-screen mode."); + pw.println(prefix + "setSideStagePosition <SideStagePosition>"); + pw.println(prefix + " Sets the position of the side-stage."); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 80ef74e63940..8e1ae397ea64 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -452,10 +452,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, IRemoteAnimationFinishedCallback finishedCallback, SurfaceControl.Transaction t) { if (apps == null || apps.length == 0) { - // Switch the split position if launching as MULTIPLE_TASK failed. - if ((fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) { - setSideStagePosition(SplitLayout.reversePosition( - getSideStagePosition()), null); + if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) { + mMainExecutor.execute(() -> + exitSplitScreen(mMainStage.getChildCount() == 0 + ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN)); + } else { + // Switch the split position if launching as MULTIPLE_TASK failed. + if ((fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) { + setSideStagePosition(SplitLayout.reversePosition( + getSideStagePosition()), null); + } } // Do nothing when the animation was cancelled. @@ -651,7 +657,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mShouldUpdateRecents = true; // If any stage has no child after animation finished, it means that split will display // nothing, such status will happen if task and intent is same app but not support - // multi-instagce, we should exit split and expand that app as full screen. + // multi-instance, we should exit split and expand that app as full screen. if (!cancel && (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0)) { mMainExecutor.execute(() -> exitSplitScreen(mMainStage.getChildCount() == 0 diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java index f4fc0c4c36bc..2e6ddc363906 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java @@ -16,19 +16,14 @@ package com.android.wm.shell.sysui; -import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_INIT; -import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController; -import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer; -import com.android.wm.shell.onehanded.OneHandedController; -import com.android.wm.shell.pip.Pip; -import com.android.wm.shell.recents.RecentTasksController; -import com.android.wm.shell.splitscreen.SplitScreenController; +import com.android.internal.protolog.common.ProtoLog; import java.io.PrintWriter; -import java.util.Optional; +import java.util.Arrays; +import java.util.TreeMap; +import java.util.function.BiConsumer; /** * An entry point into the shell for dumping shell internal state and running adb commands. @@ -38,52 +33,61 @@ import java.util.Optional; public final class ShellCommandHandler { private static final String TAG = ShellCommandHandler.class.getSimpleName(); - private final Optional<SplitScreenController> mSplitScreenOptional; - private final Optional<Pip> mPipOptional; - private final Optional<OneHandedController> mOneHandedOptional; - private final Optional<HideDisplayCutoutController> mHideDisplayCutout; - private final Optional<RecentTasksController> mRecentTasks; - private final ShellTaskOrganizer mShellTaskOrganizer; - private final KidsModeTaskOrganizer mKidsModeTaskOrganizer; - - public ShellCommandHandler( - ShellController shellController, - ShellTaskOrganizer shellTaskOrganizer, - KidsModeTaskOrganizer kidsModeTaskOrganizer, - Optional<SplitScreenController> splitScreenOptional, - Optional<Pip> pipOptional, - Optional<OneHandedController> oneHandedOptional, - Optional<HideDisplayCutoutController> hideDisplayCutout, - Optional<RecentTasksController> recentTasks, - ShellExecutor mainExecutor) { - mShellTaskOrganizer = shellTaskOrganizer; - mKidsModeTaskOrganizer = kidsModeTaskOrganizer; - mRecentTasks = recentTasks; - mSplitScreenOptional = splitScreenOptional; - mPipOptional = pipOptional; - mOneHandedOptional = oneHandedOptional; - mHideDisplayCutout = hideDisplayCutout; - // TODO(238217847): To be removed once the command handler dependencies are inverted - shellController.setShellCommandHandler(this); + // We're using a TreeMap to keep them sorted by command name + private final TreeMap<String, BiConsumer<PrintWriter, String>> mDumpables = new TreeMap<>(); + private final TreeMap<String, ShellCommandActionHandler> mCommands = new TreeMap<>(); + + public interface ShellCommandActionHandler { + /** + * Handles the given command. + * + * @param args the arguments starting with the action name, then the action arguments + * @param pw the write to print output to + */ + boolean onShellCommand(String[] args, PrintWriter pw); + + /** + * Prints the help for this class of commands. Implementations do not need to print the + * command class. + */ + void printShellCommandHelp(PrintWriter pw, String prefix); + } + + + /** + * Adds a callback to run when the Shell is being dumped. + * + * @param callback the callback to be made when Shell is dumped, takes the print writer and + * a prefix + * @param instance used for debugging only + */ + public <T> void addDumpCallback(BiConsumer<PrintWriter, String> callback, T instance) { + mDumpables.put(instance.getClass().getSimpleName(), callback); + ProtoLog.v(WM_SHELL_INIT, "Adding dump callback for %s", + instance.getClass().getSimpleName()); + } + + /** + * Adds an action callback to be invoked when the user runs that particular command from adb. + * + * @param commandClass the top level class of command to invoke + * @param actions the interface to callback when an action of this class is invoked + * @param instance used for debugging only + */ + public <T> void addCommandCallback(String commandClass, ShellCommandActionHandler actions, + T instance) { + mCommands.put(commandClass, actions); + ProtoLog.v(WM_SHELL_INIT, "Adding command class %s for %s", commandClass, + instance.getClass().getSimpleName()); } /** Dumps WM Shell internal state. */ public void dump(PrintWriter pw) { - mShellTaskOrganizer.dump(pw, ""); - pw.println(); - pw.println(); - mPipOptional.ifPresent(pip -> pip.dump(pw)); - mOneHandedOptional.ifPresent(oneHanded -> oneHanded.dump(pw)); - mHideDisplayCutout.ifPresent(hideDisplayCutout -> hideDisplayCutout.dump(pw)); - pw.println(); - pw.println(); - mSplitScreenOptional.ifPresent(splitScreen -> splitScreen.dump(pw, "")); - pw.println(); - pw.println(); - mRecentTasks.ifPresent(recentTasks -> recentTasks.dump(pw, "")); - pw.println(); - pw.println(); - mKidsModeTaskOrganizer.dump(pw, ""); + for (String key : mDumpables.keySet()) { + final BiConsumer<PrintWriter, String> r = mDumpables.get(key); + r.accept(pw, ""); + pw.println(); + } } @@ -93,72 +97,32 @@ public final class ShellCommandHandler { // Argument at position 0 is "WMShell". return false; } - switch (args[1]) { - case "moveToSideStage": - return runMoveToSideStage(args, pw); - case "removeFromSideStage": - return runRemoveFromSideStage(args, pw); - case "setSideStagePosition": - return runSetSideStagePosition(args, pw); - case "help": - return runHelp(pw); - default: - return false; - } - } - private boolean runMoveToSideStage(String[] args, PrintWriter pw) { - if (args.length < 3) { - // First arguments are "WMShell" and command name. - pw.println("Error: task id should be provided as arguments"); - return false; + final String cmdClass = args[1]; + if (cmdClass.toLowerCase().equals("help")) { + return runHelp(pw); } - final int taskId = new Integer(args[2]); - final int sideStagePosition = args.length > 3 - ? new Integer(args[3]) : SPLIT_POSITION_BOTTOM_OR_RIGHT; - mSplitScreenOptional.ifPresent(split -> split.moveToSideStage(taskId, sideStagePosition)); - return true; - } - - private boolean runRemoveFromSideStage(String[] args, PrintWriter pw) { - if (args.length < 3) { - // First arguments are "WMShell" and command name. - pw.println("Error: task id should be provided as arguments"); + if (!mCommands.containsKey(cmdClass)) { return false; } - final int taskId = new Integer(args[2]); - mSplitScreenOptional.ifPresent(split -> split.removeFromSideStage(taskId)); - return true; - } - private boolean runSetSideStagePosition(String[] args, PrintWriter pw) { - if (args.length < 3) { - // First arguments are "WMShell" and command name. - pw.println("Error: side stage position should be provided as arguments"); - return false; - } - final int position = new Integer(args[2]); - mSplitScreenOptional.ifPresent(split -> split.setSideStagePosition(position)); + // Only pass the actions onwards as arguments to the callback + final ShellCommandActionHandler actions = mCommands.get(args[1]); + final String[] cmdClassArgs = Arrays.copyOfRange(args, 2, args.length); + actions.onShellCommand(cmdClassArgs, pw); return true; } private boolean runHelp(PrintWriter pw) { pw.println("Window Manager Shell commands:"); + for (String commandClass : mCommands.keySet()) { + pw.println(" " + commandClass); + mCommands.get(commandClass).printShellCommandHelp(pw, " "); + } pw.println(" help"); pw.println(" Print this help text."); pw.println(" <no arguments provided>"); - pw.println(" Dump Window Manager Shell internal state"); - pw.println(" pair <taskId1> <taskId2>"); - pw.println(" unpair <taskId>"); - pw.println(" Pairs/unpairs tasks with given ids."); - pw.println(" moveToSideStage <taskId> <SideStagePosition>"); - pw.println(" Move a task with given id in split-screen mode."); - pw.println(" removeFromSideStage <taskId>"); - pw.println(" Remove a task with given id in split-screen mode."); - pw.println(" setSideStageOutline <true/false>"); - pw.println(" Enable/Disable outline on the side-stage."); - pw.println(" setSideStagePosition <SideStagePosition>"); - pw.println(" Sets the position of the side-stage."); + pw.println(" Dump all Window Manager Shell internal state"); return true; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java index f1f317f65ba9..52ffb46bb39c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java @@ -45,11 +45,10 @@ public class ShellController { private static final String TAG = ShellController.class.getSimpleName(); private final ShellInit mShellInit; + private final ShellCommandHandler mShellCommandHandler; private final ShellExecutor mMainExecutor; private final ShellInterfaceImpl mImpl = new ShellInterfaceImpl(); - private ShellCommandHandler mShellCommandHandler; - private final CopyOnWriteArrayList<ConfigurationChangeListener> mConfigChangeListeners = new CopyOnWriteArrayList<>(); private final CopyOnWriteArrayList<KeyguardChangeListener> mKeyguardChangeListeners = @@ -57,8 +56,10 @@ public class ShellController { private Configuration mLastConfiguration; - public ShellController(ShellInit shellInit, ShellExecutor mainExecutor) { + public ShellController(ShellInit shellInit, ShellCommandHandler shellCommandHandler, + ShellExecutor mainExecutor) { mShellInit = shellInit; + mShellCommandHandler = shellCommandHandler; mMainExecutor = mainExecutor; } @@ -70,15 +71,6 @@ public class ShellController { } /** - * Sets the command handler to call back to. - * TODO(238217847): This is only exposed this way until we can remove the dependencies from the - * command handler to other classes. - */ - public void setShellCommandHandler(ShellCommandHandler shellCommandHandler) { - mShellCommandHandler = shellCommandHandler; - } - - /** * Adds a new configuration listener. The configuration change callbacks are not made in any * particular order. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java index c250e0313255..ac52235375c4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java @@ -52,6 +52,9 @@ public class ShellInit { * Adds a callback to the ordered list of callbacks be made when Shell is first started. This * can be used in class constructors when dagger is used to ensure that the initialization order * matches the dependency order. + * + * @param r the callback to be made when Shell is initialized + * @param instance used for debugging only */ public <T extends Object> void addInitCallback(Runnable r, T instance) { if (mHasInitialized) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index 087304b0d00b..506a4c0f90f3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -233,7 +233,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f; startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(), taskBounds.height()) .setShadowRadius(mTaskBackgroundSurface, shadowRadius) - .setColor(mTaskBackgroundSurface, mTmpColor); + .setColor(mTaskBackgroundSurface, mTmpColor) + .setLayer(mTaskBackgroundSurface, -1) + .show(mTaskBackgroundSurface); // Caption view mCaptionWindowManager.setConfiguration(taskConfig); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java index 7517e8ab6826..f865649b6bbc 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java @@ -59,6 +59,7 @@ import androidx.test.filters.SmallTest; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.compatui.CompatUIController; +import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellInit; import org.junit.Before; @@ -85,10 +86,12 @@ public class ShellTaskOrganizerTests extends ShellTestCase { @Mock private CompatUIController mCompatUI; @Mock - private ShellInit mShellInit; + private ShellExecutor mTestExecutor; + @Mock + private ShellCommandHandler mShellCommandHandler; - ShellTaskOrganizer mOrganizer; - private final ShellExecutor mTestExecutor = mock(ShellExecutor.class); + private ShellTaskOrganizer mOrganizer; + private ShellInit mShellInit; private class TrackingTaskListener implements ShellTaskOrganizer.TaskListener { final ArrayList<RunningTaskInfo> appeared = new ArrayList<>(); @@ -132,8 +135,11 @@ public class ShellTaskOrganizerTests extends ShellTestCase { doReturn(ParceledListSlice.<TaskAppearedInfo>emptyList()) .when(mTaskOrganizerController).registerTaskOrganizer(any()); } catch (RemoteException e) {} - mOrganizer = spy(new ShellTaskOrganizer(mShellInit, mTaskOrganizerController, - mCompatUI, Optional.empty(), Optional.empty(), mTestExecutor)); + mShellInit = spy(new ShellInit(mTestExecutor)); + mOrganizer = spy(new ShellTaskOrganizer(mShellInit, mShellCommandHandler, + mTaskOrganizerController, mCompatUI, Optional.empty(), Optional.empty(), + mTestExecutor)); + mShellInit.init(); } @Test @@ -142,9 +148,12 @@ public class ShellTaskOrganizerTests extends ShellTestCase { } @Test - public void testRegisterOrganizer_sendRegisterTaskOrganizer() throws RemoteException { - mOrganizer.registerOrganizer(); + public void instantiate_addDumpCallback() { + verify(mShellCommandHandler, times(1)).addDumpCallback(any(), any()); + } + @Test + public void testInit_sendRegisterTaskOrganizer() throws RemoteException { verify(mTaskOrganizerController).registerTaskOrganizer(any(ITaskOrganizer.class)); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index ba81602054a8..b0dd7811c53c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -29,6 +29,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -61,6 +62,7 @@ import androidx.test.platform.app.InstrumentationRegistry; import com.android.internal.util.test.FakeSettingsProvider; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; +import com.android.wm.shell.sysui.ShellInit; import org.junit.Before; import org.junit.Ignore; @@ -81,6 +83,7 @@ public class BackAnimationControllerTest extends ShellTestCase { private static final String ANIMATION_ENABLED = "1"; private final TestShellExecutor mShellExecutor = new TestShellExecutor(); + private ShellInit mShellInit; @Rule public TestableContext mContext = @@ -110,10 +113,12 @@ public class BackAnimationControllerTest extends ShellTestCase { Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION, ANIMATION_ENABLED); mTestableLooper = TestableLooper.get(this); - mController = new BackAnimationController( + mShellInit = spy(new ShellInit(mShellExecutor)); + mController = new BackAnimationController(mShellInit, mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction, mActivityTaskManager, mContext, mContentResolver); + mShellInit.init(); mEventTime = 0; mShellExecutor.flushAll(); } @@ -160,6 +165,11 @@ public class BackAnimationControllerTest extends ShellTestCase { } @Test + public void instantiateController_addInitCallback() { + verify(mShellInit, times(1)).addInitCallback(any(), any()); + } + + @Test @Ignore("b/207481538") public void crossActivity_screenshotAttachedAndVisible() { SurfaceControl screenshotSurface = new SurfaceControl(); @@ -233,10 +243,12 @@ public class BackAnimationControllerTest extends ShellTestCase { public void animationDisabledFromSettings() throws RemoteException { // Toggle the setting off Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION, "0"); - mController = new BackAnimationController( + ShellInit shellInit = new ShellInit(mShellExecutor); + mController = new BackAnimationController(shellInit, mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction, mActivityTaskManager, mContext, mContentResolver); + shellInit.init(); mController.setBackToLauncherCallback(mIOnBackInvokedCallback); RemoteAnimationTarget animationTarget = createAnimationTarget(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java index 828c13ecfda6..6292130ddec9 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java @@ -29,6 +29,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -54,6 +55,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager; import com.android.wm.shell.sysui.ShellController; +import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; import org.junit.Before; @@ -79,6 +81,7 @@ public class CompatUIControllerTest extends ShellTestCase { private static final int TASK_ID = 12; private CompatUIController mController; + private ShellInit mShellInit; private @Mock ShellController mMockShellController; private @Mock DisplayController mMockDisplayController; private @Mock DisplayInsetsController mMockDisplayInsetsController; @@ -107,9 +110,10 @@ public class CompatUIControllerTest extends ShellTestCase { doReturn(TASK_ID).when(mMockLetterboxEduLayout).getTaskId(); doReturn(true).when(mMockLetterboxEduLayout).createLayout(anyBoolean()); doReturn(true).when(mMockLetterboxEduLayout).updateCompatInfo(any(), any(), anyBoolean()); - mController = new CompatUIController(mContext, mMockShellController, mMockDisplayController, - mMockDisplayInsetsController, mMockImeController, mMockSyncQueue, mMockExecutor, - mMockTransitionsLazy) { + mShellInit = spy(new ShellInit(mMockExecutor)); + mController = new CompatUIController(mContext, mShellInit, mMockShellController, + mMockDisplayController, mMockDisplayInsetsController, mMockImeController, + mMockSyncQueue, mMockExecutor, mMockTransitionsLazy) { @Override CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) { @@ -122,10 +126,16 @@ public class CompatUIControllerTest extends ShellTestCase { return mMockLetterboxEduLayout; } }; + mShellInit.init(); spyOn(mController); } @Test + public void instantiateController_addInitCallback() { + verify(mShellInit, times(1)).addInitCallback(any(), any()); + } + + @Test public void instantiateController_registerKeyguardChangeListener() { verify(mMockShellController, times(1)).addKeyguardChangeListener(any()); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java index dcc504ad0cdb..6c301bbbc7f1 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java @@ -17,7 +17,9 @@ package com.android.wm.shell.hidedisplaycutout; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -29,7 +31,10 @@ import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; +import com.android.wm.shell.sysui.ShellInit; import org.junit.Before; import org.junit.Test; @@ -45,17 +50,32 @@ public class HideDisplayCutoutControllerTest extends ShellTestCase { InstrumentationRegistry.getInstrumentation().getTargetContext(), null); @Mock + private ShellCommandHandler mShellCommandHandler; + @Mock private ShellController mShellController; @Mock private HideDisplayCutoutOrganizer mMockDisplayAreaOrganizer; private HideDisplayCutoutController mHideDisplayCutoutController; + private ShellInit mShellInit; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mHideDisplayCutoutController = new HideDisplayCutoutController( - mContext, mShellController, mMockDisplayAreaOrganizer); + mShellInit = spy(new ShellInit(mock(ShellExecutor.class))); + mHideDisplayCutoutController = new HideDisplayCutoutController(mContext, mShellInit, + mShellCommandHandler, mShellController, mMockDisplayAreaOrganizer); + mShellInit.init(); + } + + @Test + public void instantiateController_addInitCallback() { + verify(mShellInit, times(1)).addInitCallback(any(), any()); + } + + @Test + public void instantiateController_registerDumpCallback() { + verify(mShellCommandHandler, times(1)).addDumpCallback(any(), any()); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java index a919ad0b4765..ecfb427dbced 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java @@ -49,6 +49,7 @@ import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellInit; import org.junit.Before; @@ -74,6 +75,7 @@ public class KidsModeTaskOrganizerTest extends ShellTestCase { @Mock private WindowContainerTransaction mTransaction; @Mock private KidsModeSettingsObserver mObserver; @Mock private ShellInit mShellInit; + @Mock private ShellCommandHandler mShellCommandHandler; @Mock private DisplayInsetsController mDisplayInsetsController; KidsModeTaskOrganizer mOrganizer; @@ -87,14 +89,20 @@ public class KidsModeTaskOrganizerTest extends ShellTestCase { } catch (RemoteException e) { } // NOTE: KidsModeTaskOrganizer should have a null CompatUIController. - mOrganizer = spy(new KidsModeTaskOrganizer(mContext, mTaskOrganizerController, - mSyncTransactionQueue, mDisplayController, mDisplayInsetsController, - Optional.empty(), Optional.empty(), mObserver, mTestExecutor, mHandler)); + mOrganizer = spy(new KidsModeTaskOrganizer(mContext, mShellInit, mShellCommandHandler, + mTaskOrganizerController, mSyncTransactionQueue, mDisplayController, + mDisplayInsetsController, Optional.empty(), Optional.empty(), mObserver, + mTestExecutor, mHandler)); doReturn(mTransaction).when(mOrganizer).getWindowContainerTransaction(); doReturn(new InsetsState()).when(mDisplayController).getInsetsState(DEFAULT_DISPLAY); } @Test + public void instantiateController_addInitCallback() { + verify(mShellInit, times(1)).addInitCallback(any(), any()); + } + + @Test public void testKidsModeOn() { doReturn(true).when(mObserver).isEnabled(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java index dbf93ae35c18..90645ce4747d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java @@ -36,7 +36,6 @@ import static org.mockito.Mockito.when; import android.graphics.Rect; import android.os.Handler; -import android.os.UserHandle; import android.testing.AndroidTestingRunner; import android.util.ArrayMap; import android.view.Display; @@ -49,7 +48,9 @@ import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TaskStackListenerImpl; +import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; +import com.android.wm.shell.sysui.ShellInit; import org.junit.Before; import org.junit.Test; @@ -61,18 +62,20 @@ import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidTestingRunner.class) public class OneHandedControllerTest extends OneHandedTestCase { - private int mCurrentUser = UserHandle.myUserId(); Display mDisplay; OneHandedAccessibilityUtil mOneHandedAccessibilityUtil; OneHandedController mSpiedOneHandedController; OneHandedTimeoutHandler mSpiedTimeoutHandler; OneHandedState mSpiedTransitionState; + ShellInit mShellInit; @Mock + ShellCommandHandler mMockShellCommandHandler; + @Mock ShellController mMockShellController; @Mock - DisplayLayout mDisplayLayout; + DisplayLayout mMockDisplayLayout; @Mock DisplayController mMockDisplayController; @Mock @@ -102,7 +105,7 @@ public class OneHandedControllerTest extends OneHandedTestCase { public void setUp() { MockitoAnnotations.initMocks(this); mDisplay = mContext.getDisplay(); - mDisplayLayout = Mockito.mock(DisplayLayout.class); + mMockDisplayLayout = Mockito.mock(DisplayLayout.class); mSpiedTimeoutHandler = spy(new OneHandedTimeoutHandler(mMockShellMainExecutor)); mSpiedTransitionState = spy(new OneHandedState()); @@ -122,11 +125,14 @@ public class OneHandedControllerTest extends OneHandedTestCase { when(mMockDisplayAreaOrganizer.getLastDisplayBounds()).thenReturn( new Rect(0, 0, 1080, 2400)); - when(mMockDisplayAreaOrganizer.getDisplayLayout()).thenReturn(mDisplayLayout); + when(mMockDisplayAreaOrganizer.getDisplayLayout()).thenReturn(mMockDisplayLayout); + mShellInit = spy(new ShellInit(mMockShellMainExecutor)); mOneHandedAccessibilityUtil = new OneHandedAccessibilityUtil(mContext); mSpiedOneHandedController = spy(new OneHandedController( mContext, + mShellInit, + mMockShellCommandHandler, mMockShellController, mMockDisplayController, mMockDisplayAreaOrganizer, @@ -141,6 +147,17 @@ public class OneHandedControllerTest extends OneHandedTestCase { mMockShellMainExecutor, mMockShellMainHandler) ); + mShellInit.init(); + } + + @Test + public void instantiateController_addInitCallback() { + verify(mShellInit, times(1)).addInitCallback(any(), any()); + } + + @Test + public void instantiateController_registerDumpCallback() { + verify(mMockShellCommandHandler, times(1)).addDumpCallback(any(), any()); } @Test @@ -308,9 +325,9 @@ public class OneHandedControllerTest extends OneHandedTestCase { @Test public void testRotation90CanNotStartOneHanded() { - mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90); + mMockDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_90); mSpiedTransitionState.setState(STATE_NONE); - when(mDisplayLayout.isLandscape()).thenReturn(true); + when(mMockDisplayLayout.isLandscape()).thenReturn(true); mSpiedOneHandedController.setOneHandedEnabled(true); mSpiedOneHandedController.setLockedDisabled(false /* locked */, false /* enabled */); mSpiedOneHandedController.startOneHanded(); @@ -320,10 +337,10 @@ public class OneHandedControllerTest extends OneHandedTestCase { @Test public void testRotation180CanStartOneHanded() { - mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_180); + mMockDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_180); mSpiedTransitionState.setState(STATE_NONE); when(mMockDisplayAreaOrganizer.isReady()).thenReturn(true); - when(mDisplayLayout.isLandscape()).thenReturn(false); + when(mMockDisplayLayout.isLandscape()).thenReturn(false); mSpiedOneHandedController.setOneHandedEnabled(true); mSpiedOneHandedController.setLockedDisabled(false /* locked */, false /* enabled */); mSpiedOneHandedController.startOneHanded(); @@ -333,9 +350,9 @@ public class OneHandedControllerTest extends OneHandedTestCase { @Test public void testRotation270CanNotStartOneHanded() { - mDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_270); + mMockDisplayLayout.rotateTo(mContext.getResources(), Surface.ROTATION_270); mSpiedTransitionState.setState(STATE_NONE); - when(mDisplayLayout.isLandscape()).thenReturn(true); + when(mMockDisplayLayout.isLandscape()).thenReturn(true); mSpiedOneHandedController.setOneHandedEnabled(true); mSpiedOneHandedController.setLockedDisabled(false /* locked */, false /* enabled */); mSpiedOneHandedController.startOneHanded(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java index e6a8220e081b..a39bdf04bf56 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java @@ -41,7 +41,9 @@ import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TaskStackListenerImpl; +import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; +import com.android.wm.shell.sysui.ShellInit; import org.junit.Before; import org.junit.Test; @@ -61,6 +63,10 @@ public class OneHandedStateTest extends OneHandedTestCase { OneHandedState mSpiedState; @Mock + ShellInit mMockShellInit; + @Mock + ShellCommandHandler mMockShellCommandHandler; + @Mock ShellController mMockShellController; @Mock DisplayController mMockDisplayController; @@ -111,6 +117,8 @@ public class OneHandedStateTest extends OneHandedTestCase { mOneHandedAccessibilityUtil = new OneHandedAccessibilityUtil(mContext); mSpiedOneHandedController = spy(new OneHandedController( mContext, + mMockShellInit, + mMockShellCommandHandler, mMockShellController, mMockDisplayController, mMockDisplayAreaOrganizer, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java index f192514c37ab..9ed8d84d665f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java @@ -55,7 +55,9 @@ import com.android.wm.shell.pip.PipSnapAlgorithm; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; +import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; +import com.android.wm.shell.sysui.ShellInit; import org.junit.Before; import org.junit.Test; @@ -74,7 +76,9 @@ import java.util.Set; @TestableLooper.RunWithLooper public class PipControllerTest extends ShellTestCase { private PipController mPipController; + private ShellInit mShellInit; + @Mock private ShellCommandHandler mMockShellCommandHandler; @Mock private ShellController mMockShellController; @Mock private DisplayController mMockDisplayController; @Mock private PhonePipMenuController mMockPhonePipMenuController; @@ -105,19 +109,31 @@ public class PipControllerTest extends ShellTestCase { ((Runnable) invocation.getArgument(0)).run(); return null; }).when(mMockExecutor).execute(any()); - mPipController = new PipController(mContext, mMockShellController, mMockDisplayController, - mMockPipAppOpsListener, mMockPipBoundsAlgorithm, - mMockPipKeepClearAlgorithm, + mShellInit = spy(new ShellInit(mMockExecutor)); + mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler, + mMockShellController, mMockDisplayController, mMockPipAppOpsListener, + mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm, mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState, mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper, mMockTaskStackListener, mPipParamsChangedForwarder, mMockOneHandedController, mMockExecutor); + mShellInit.init(); when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm); when(mMockPipTouchHandler.getMotionHelper()).thenReturn(mMockPipMotionHelper); } @Test + public void instantiatePipController_addInitCallback() { + verify(mShellInit, times(1)).addInitCallback(any(), any()); + } + + @Test + public void instantiateController_registerDumpCallback() { + verify(mMockShellCommandHandler, times(1)).addDumpCallback(any(), any()); + } + + @Test public void instantiatePipController_registerConfigChangeListener() { verify(mMockShellController, times(1)).addConfigurationChangeListener(any()); } @@ -149,9 +165,10 @@ public class PipControllerTest extends ShellTestCase { when(mockPackageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)).thenReturn(false); when(spyContext.getPackageManager()).thenReturn(mockPackageManager); - assertNull(PipController.create(spyContext, mMockShellController, mMockDisplayController, - mMockPipAppOpsListener, mMockPipBoundsAlgorithm, - mMockPipKeepClearAlgorithm, + ShellInit shellInit = new ShellInit(mMockExecutor); + assertNull(PipController.create(spyContext, shellInit, mMockShellCommandHandler, + mMockShellController, mMockDisplayController, mMockPipAppOpsListener, + mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm, mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState, mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper, @@ -217,7 +234,7 @@ public class PipControllerTest extends ShellTestCase { mPipController.mDisplaysChangedListener.onDisplayConfigurationChanged( displayId, new Configuration()); - verify(mMockPipMotionHelper).movePip(any(Rect.class)); + verify(mMockPipTaskOrganizer).scheduleFinishResizePip(any(Rect.class)); } @Test @@ -233,7 +250,7 @@ public class PipControllerTest extends ShellTestCase { mPipController.mDisplaysChangedListener.onDisplayConfigurationChanged( displayId, new Configuration()); - verify(mMockPipMotionHelper, never()).movePip(any(Rect.class)); + verify(mMockPipTaskOrganizer, never()).scheduleFinishResizePip(any(Rect.class)); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java index d406a4ed3fd7..81bb609cc711 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java @@ -23,7 +23,9 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; @@ -48,6 +50,7 @@ import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TaskStackListenerImpl; +import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.util.GroupedRecentTaskInfo; import com.android.wm.shell.util.SplitBounds; @@ -73,21 +76,35 @@ public class RecentTasksControllerTest extends ShellTestCase { @Mock private TaskStackListenerImpl mTaskStackListener; @Mock - private ShellInit mShellInit; + private ShellCommandHandler mShellCommandHandler; private ShellTaskOrganizer mShellTaskOrganizer; private RecentTasksController mRecentTasksController; + private ShellInit mShellInit; private ShellExecutor mMainExecutor; @Before public void setUp() { mMainExecutor = new TestShellExecutor(); when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class)); + mShellInit = spy(new ShellInit(mMainExecutor)); mRecentTasksController = spy(new RecentTasksController(mContext, mShellInit, - mTaskStackListener, mMainExecutor)); - mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, + mShellCommandHandler, mTaskStackListener, mMainExecutor)); + mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler, null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController), mMainExecutor); + mShellInit.init(); + } + + @Test + public void instantiateController_addInitCallback() { + verify(mShellInit, times(1)).addInitCallback(any(), isA(RecentTasksController.class)); + } + + @Test + public void instantiateController_addDumpCallback() { + verify(mShellCommandHandler, times(1)).addDumpCallback(any(), + isA(RecentTasksController.class)); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java index 10788f9df32f..5a68361c595c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java @@ -20,12 +20,14 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -53,6 +55,7 @@ import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.recents.RecentTasksController; +import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; @@ -72,8 +75,9 @@ import java.util.Optional; @RunWith(AndroidJUnit4.class) public class SplitScreenControllerTests extends ShellTestCase { - @Mock ShellController mShellController; @Mock ShellInit mShellInit; + @Mock ShellController mShellController; + @Mock ShellCommandHandler mShellCommandHandler; @Mock ShellTaskOrganizer mTaskOrganizer; @Mock SyncTransactionQueue mSyncQueue; @Mock RootTaskDisplayAreaOrganizer mRootTDAOrganizer; @@ -93,9 +97,10 @@ public class SplitScreenControllerTests extends ShellTestCase { public void setup() { MockitoAnnotations.initMocks(this); mSplitScreenController = spy(new SplitScreenController(mContext, mShellInit, - mShellController, mTaskOrganizer, mSyncQueue, mRootTDAOrganizer, mDisplayController, - mDisplayImeController, mDisplayInsetsController, mDragAndDropController, - mTransitions, mTransactionPool, mIconProvider, mRecentTasks, mMainExecutor)); + mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue, + mRootTDAOrganizer, mDisplayController, mDisplayImeController, + mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool, + mIconProvider, mRecentTasks, mMainExecutor)); } @Test @@ -104,14 +109,31 @@ public class SplitScreenControllerTests extends ShellTestCase { } @Test + public void instantiateController_registerDumpCallback() { + doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor(); + when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout()); + mSplitScreenController.onInit(); + verify(mShellCommandHandler, times(1)).addDumpCallback(any(), any()); + } + + @Test + public void instantiateController_registerCommandCallback() { + doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor(); + when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout()); + mSplitScreenController.onInit(); + verify(mShellCommandHandler, times(1)).addCommandCallback(eq("splitscreen"), any(), any()); + } + + @Test public void testControllerRegistersKeyguardChangeListener() { + doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor(); when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout()); mSplitScreenController.onInit(); verify(mShellController, times(1)).addKeyguardChangeListener(any()); } @Test - public void testIsLaunchingAdjacently_notInSplitScreen() { + public void testShouldAddMultipleTaskFlag_notInSplitScreen() { doReturn(false).when(mSplitScreenController).isSplitScreenVisible(); doReturn(true).when(mSplitScreenController).isValidToEnterSplitScreen(any()); @@ -120,7 +142,7 @@ public class SplitScreenControllerTests extends ShellTestCase { ActivityManager.RunningTaskInfo focusTaskInfo = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent); doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo(); - assertTrue(mSplitScreenController.isLaunchingAdjacently( + assertTrue(mSplitScreenController.shouldAddMultipleTaskFlag( startIntent, SPLIT_POSITION_TOP_OR_LEFT)); // Verify launching different activity returns false. @@ -128,28 +150,40 @@ public class SplitScreenControllerTests extends ShellTestCase { focusTaskInfo = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, diffIntent); doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo(); - assertFalse(mSplitScreenController.isLaunchingAdjacently( + assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag( startIntent, SPLIT_POSITION_TOP_OR_LEFT)); } @Test - public void testIsLaunchingAdjacently_inSplitScreen() { + public void testShouldAddMultipleTaskFlag_inSplitScreen() { doReturn(true).when(mSplitScreenController).isSplitScreenVisible(); - - // Verify launching the same activity returns true. Intent startIntent = createStartIntent("startActivity"); - ActivityManager.RunningTaskInfo pairingTaskInfo = + ActivityManager.RunningTaskInfo sameTaskInfo = createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, startIntent); - doReturn(pairingTaskInfo).when(mSplitScreenController).getTaskInfo(anyInt()); - assertTrue(mSplitScreenController.isLaunchingAdjacently( - startIntent, SPLIT_POSITION_TOP_OR_LEFT)); - - // Verify launching different activity returns false. Intent diffIntent = createStartIntent("diffActivity"); - pairingTaskInfo = + ActivityManager.RunningTaskInfo differentTaskInfo = createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, diffIntent); - doReturn(pairingTaskInfo).when(mSplitScreenController).getTaskInfo(anyInt()); - assertFalse(mSplitScreenController.isLaunchingAdjacently( + + // Verify launching the same activity return false. + doReturn(sameTaskInfo).when(mSplitScreenController) + .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT); + assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag( + startIntent, SPLIT_POSITION_TOP_OR_LEFT)); + + // Verify launching the same activity as adjacent returns true. + doReturn(differentTaskInfo).when(mSplitScreenController) + .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT); + doReturn(sameTaskInfo).when(mSplitScreenController) + .getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT); + assertTrue(mSplitScreenController.shouldAddMultipleTaskFlag( + startIntent, SPLIT_POSITION_TOP_OR_LEFT)); + + // Verify launching different activity from adjacent returns false. + doReturn(differentTaskInfo).when(mSplitScreenController) + .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT); + doReturn(differentTaskInfo).when(mSplitScreenController) + .getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT); + assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag( startIntent, SPLIT_POSITION_TOP_OR_LEFT)); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java index 02311bab2e4d..39e58ffcf9c7 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java @@ -45,6 +45,8 @@ public class ShellControllerTest extends ShellTestCase { @Mock private ShellInit mShellInit; @Mock + private ShellCommandHandler mShellCommandHandler; + @Mock private ShellExecutor mExecutor; private ShellController mController; @@ -56,7 +58,7 @@ public class ShellControllerTest extends ShellTestCase { MockitoAnnotations.initMocks(this); mKeyguardChangeListener = new TestKeyguardChangeListener(); mConfigChangeListener = new TestConfigurationChangeListener(); - mController = new ShellController(mShellInit, mExecutor); + mController = new ShellController(mShellInit, mShellCommandHandler, mExecutor); mController.onConfigurationChanged(getConfigurationCopy()); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java index 1e7d5fe95229..226843eca64e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java @@ -204,6 +204,8 @@ public class WindowDecorationTests extends ShellTestCase { verify(mMockSurfaceControlStartT) .setColor(taskBackgroundSurface, new float[] {1.f, 1.f, 0.f}); verify(mMockSurfaceControlStartT).setShadowRadius(taskBackgroundSurface, 10); + verify(mMockSurfaceControlStartT).setLayer(taskBackgroundSurface, -1); + verify(mMockSurfaceControlStartT).show(taskBackgroundSurface); verify(mMockSurfaceControlViewHostFactory) .create(any(), eq(defaultDisplay), any(), anyBoolean()); diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp index 0ef80ee10708..132234b38003 100644 --- a/libs/hwui/jni/android_graphics_Canvas.cpp +++ b/libs/hwui/jni/android_graphics_Canvas.cpp @@ -407,14 +407,28 @@ static void drawVertices(JNIEnv* env, jobject, jlong canvasHandle, indices = (const uint16_t*)(indexA.ptr() + indexIndex); } - SkVertices::VertexMode mode = static_cast<SkVertices::VertexMode>(modeHandle); + SkVertices::VertexMode vertexMode = static_cast<SkVertices::VertexMode>(modeHandle); const Paint* paint = reinterpret_cast<Paint*>(paintHandle); - get_canvas(canvasHandle)->drawVertices(SkVertices::MakeCopy(mode, vertexCount, - reinterpret_cast<const SkPoint*>(verts), - reinterpret_cast<const SkPoint*>(texs), - reinterpret_cast<const SkColor*>(colors), - indexCount, indices).get(), - SkBlendMode::kModulate, *paint); + + // Preserve legacy Skia behavior: ignore the shader if there are no texs set. + Paint noShaderPaint; + if (jtexs == NULL) { + noShaderPaint = Paint(*paint); + noShaderPaint.setShader(nullptr); + paint = &noShaderPaint; + } + // Since https://skia-review.googlesource.com/c/skia/+/473676, Skia will blend paint and vertex + // colors when no shader is provided. This ternary uses kDst to mimic the old behavior of + // ignoring the paint and using the vertex colors directly when no shader is provided. + SkBlendMode blendMode = paint->getShader() ? SkBlendMode::kModulate : SkBlendMode::kDst; + + get_canvas(canvasHandle) + ->drawVertices(SkVertices::MakeCopy( + vertexMode, vertexCount, reinterpret_cast<const SkPoint*>(verts), + reinterpret_cast<const SkPoint*>(texs), + reinterpret_cast<const SkColor*>(colors), indexCount, indices) + .get(), + blendMode, *paint); } static void drawNinePatch(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHandle, diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 322e1be250a6..5795fd439fe2 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1610,6 +1610,9 @@ <string name="dream_complication_title_cast_info">Cast Info</string> <!-- Screensaver overlay which displays home controls. [CHAR LIMIT=20] --> <string name="dream_complication_title_home_controls">Home Controls</string> + <!-- Screensaver overlay which displays smartspace. [CHAR LIMIT=20] --> + <string name="dream_complication_title_smartspace">Smartspace</string> + <!-- Title for a screen allowing the user to choose a profile picture. [CHAR LIMIT=NONE] --> <string name="avatar_picker_title">Choose a profile picture</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java index a46e23235843..22586171e5cf 100644 --- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java +++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java @@ -92,7 +92,8 @@ public class DreamBackend { COMPLICATION_TYPE_WEATHER, COMPLICATION_TYPE_AIR_QUALITY, COMPLICATION_TYPE_CAST_INFO, - COMPLICATION_TYPE_HOME_CONTROLS + COMPLICATION_TYPE_HOME_CONTROLS, + COMPLICATION_TYPE_SMARTSPACE }) @Retention(RetentionPolicy.SOURCE) public @interface ComplicationType {} @@ -103,6 +104,7 @@ public class DreamBackend { public static final int COMPLICATION_TYPE_AIR_QUALITY = 4; public static final int COMPLICATION_TYPE_CAST_INFO = 5; public static final int COMPLICATION_TYPE_HOME_CONTROLS = 6; + public static final int COMPLICATION_TYPE_SMARTSPACE = 7; private final Context mContext; private final IDreamManager mDreamManager; @@ -351,6 +353,9 @@ public class DreamBackend { case COMPLICATION_TYPE_HOME_CONTROLS: res = R.string.dream_complication_title_home_controls; break; + case COMPLICATION_TYPE_SMARTSPACE: + res = R.string.dream_complication_title_smartspace; + break; default: return null; } diff --git a/packages/SystemUI/compose/core/Android.bp b/packages/SystemUI/compose/core/Android.bp new file mode 100644 index 000000000000..4cfe39225a9b --- /dev/null +++ b/packages/SystemUI/compose/core/Android.bp @@ -0,0 +1,38 @@ +// Copyright (C) 2022 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], +} + +android_library { + name: "SystemUIComposeCore", + manifest: "AndroidManifest.xml", + + srcs: [ + "src/**/*.kt", + ], + + static_libs: [ + "androidx.compose.runtime_runtime", + "androidx.compose.material3_material3", + ], + + kotlincflags: ["-Xjvm-default=all"], +} diff --git a/packages/SystemUI/compose/core/AndroidManifest.xml b/packages/SystemUI/compose/core/AndroidManifest.xml new file mode 100644 index 000000000000..83c442d2d6f2 --- /dev/null +++ b/packages/SystemUI/compose/core/AndroidManifest.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2022 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.systemui.compose.core"> + + +</manifest> diff --git a/packages/SystemUI/compose/core/TEST_MAPPING b/packages/SystemUI/compose/core/TEST_MAPPING new file mode 100644 index 000000000000..dc243d2fd8f5 --- /dev/null +++ b/packages/SystemUI/compose/core/TEST_MAPPING @@ -0,0 +1,37 @@ +{ + "presubmit": [ + { + "name": "SystemUIComposeCoreTests", + "options": [ + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "SystemUIComposeFeaturesTests", + "options": [ + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "SystemUIComposeGalleryTests", + "options": [ + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + } + ] +}
\ No newline at end of file diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/SystemUiController.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/SystemUiController.kt new file mode 100644 index 000000000000..c9470c8dbe3a --- /dev/null +++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/SystemUiController.kt @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2022 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.systemui.compose + +import android.app.Activity +import android.content.Context +import android.content.ContextWrapper +import android.os.Build +import android.view.View +import android.view.Window +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.compositeOver +import androidx.compose.ui.graphics.luminance +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.window.DialogWindowProvider +import androidx.core.view.ViewCompat +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsCompat + +/** + * ************************************************************************************************* + * This file was forked from + * https://github.com/google/accompanist/blob/main/systemuicontroller/src/main/java/com/google/accompanist/systemuicontroller/SystemUiController.kt + * and will be removed once it lands in AndroidX. + */ + +/** + * A class which provides easy-to-use utilities for updating the System UI bar colors within Jetpack + * Compose. + * + * @sample com.google.accompanist.sample.systemuicontroller.SystemUiControllerSample + */ +@Stable +interface SystemUiController { + + /** + * Property which holds the status bar visibility. If set to true, show the status bar, + * otherwise hide the status bar. + */ + var isStatusBarVisible: Boolean + + /** + * Property which holds the navigation bar visibility. If set to true, show the navigation bar, + * otherwise hide the navigation bar. + */ + var isNavigationBarVisible: Boolean + + /** + * Property which holds the status & navigation bar visibility. If set to true, show both bars, + * otherwise hide both bars. + */ + var isSystemBarsVisible: Boolean + get() = isNavigationBarVisible && isStatusBarVisible + set(value) { + isStatusBarVisible = value + isNavigationBarVisible = value + } + + /** + * Set the status bar color. + * + * @param color The **desired** [Color] to set. This may require modification if running on an + * API level that only supports white status bar icons. + * @param darkIcons Whether dark status bar icons would be preferable. + * @param transformColorForLightContent A lambda which will be invoked to transform [color] if + * dark icons were requested but are not available. Defaults to applying a black scrim. + * + * @see statusBarDarkContentEnabled + */ + fun setStatusBarColor( + color: Color, + darkIcons: Boolean = color.luminance() > 0.5f, + transformColorForLightContent: (Color) -> Color = BlackScrimmed + ) + + /** + * Set the navigation bar color. + * + * @param color The **desired** [Color] to set. This may require modification if running on an + * API level that only supports white navigation bar icons. Additionally this will be ignored + * and [Color.Transparent] will be used on API 29+ where gesture navigation is preferred or the + * system UI automatically applies background protection in other navigation modes. + * @param darkIcons Whether dark navigation bar icons would be preferable. + * @param navigationBarContrastEnforced Whether the system should ensure that the navigation bar + * has enough contrast when a fully transparent background is requested. Only supported on API + * 29+. + * @param transformColorForLightContent A lambda which will be invoked to transform [color] if + * dark icons were requested but are not available. Defaults to applying a black scrim. + * + * @see navigationBarDarkContentEnabled + * @see navigationBarContrastEnforced + */ + fun setNavigationBarColor( + color: Color, + darkIcons: Boolean = color.luminance() > 0.5f, + navigationBarContrastEnforced: Boolean = true, + transformColorForLightContent: (Color) -> Color = BlackScrimmed + ) + + /** + * Set the status and navigation bars to [color]. + * + * @see setStatusBarColor + * @see setNavigationBarColor + */ + fun setSystemBarsColor( + color: Color, + darkIcons: Boolean = color.luminance() > 0.5f, + isNavigationBarContrastEnforced: Boolean = true, + transformColorForLightContent: (Color) -> Color = BlackScrimmed + ) { + setStatusBarColor(color, darkIcons, transformColorForLightContent) + setNavigationBarColor( + color, + darkIcons, + isNavigationBarContrastEnforced, + transformColorForLightContent + ) + } + + /** Property which holds whether the status bar icons + content are 'dark' or not. */ + var statusBarDarkContentEnabled: Boolean + + /** Property which holds whether the navigation bar icons + content are 'dark' or not. */ + var navigationBarDarkContentEnabled: Boolean + + /** + * Property which holds whether the status & navigation bar icons + content are 'dark' or not. + */ + var systemBarsDarkContentEnabled: Boolean + get() = statusBarDarkContentEnabled && navigationBarDarkContentEnabled + set(value) { + statusBarDarkContentEnabled = value + navigationBarDarkContentEnabled = value + } + + /** + * Property which holds whether the system is ensuring that the navigation bar has enough + * contrast when a fully transparent background is requested. Only has an affect when running on + * Android API 29+ devices. + */ + var isNavigationBarContrastEnforced: Boolean +} + +/** + * Remembers a [SystemUiController] for the given [window]. + * + * If no [window] is provided, an attempt to find the correct [Window] is made. + * + * First, if the [LocalView]'s parent is a [DialogWindowProvider], then that dialog's [Window] will + * be used. + * + * Second, we attempt to find [Window] for the [Activity] containing the [LocalView]. + * + * If none of these are found (such as may happen in a preview), then the functionality of the + * returned [SystemUiController] will be degraded, but won't throw an exception. + */ +@Composable +fun rememberSystemUiController( + window: Window? = findWindow(), +): SystemUiController { + val view = LocalView.current + return remember(view, window) { AndroidSystemUiController(view, window) } +} + +@Composable +private fun findWindow(): Window? = + (LocalView.current.parent as? DialogWindowProvider)?.window + ?: LocalView.current.context.findWindow() + +private tailrec fun Context.findWindow(): Window? = + when (this) { + is Activity -> window + is ContextWrapper -> baseContext.findWindow() + else -> null + } + +/** + * A helper class for setting the navigation and status bar colors for a [View], gracefully + * degrading behavior based upon API level. + * + * Typically you would use [rememberSystemUiController] to remember an instance of this. + */ +internal class AndroidSystemUiController(private val view: View, private val window: Window?) : + SystemUiController { + private val windowInsetsController = window?.let { WindowCompat.getInsetsController(it, view) } + + override fun setStatusBarColor( + color: Color, + darkIcons: Boolean, + transformColorForLightContent: (Color) -> Color + ) { + statusBarDarkContentEnabled = darkIcons + + window?.statusBarColor = + when { + darkIcons && windowInsetsController?.isAppearanceLightStatusBars != true -> { + // If we're set to use dark icons, but our windowInsetsController call didn't + // succeed (usually due to API level), we instead transform the color to + // maintain contrast + transformColorForLightContent(color) + } + else -> color + }.toArgb() + } + + override fun setNavigationBarColor( + color: Color, + darkIcons: Boolean, + navigationBarContrastEnforced: Boolean, + transformColorForLightContent: (Color) -> Color + ) { + navigationBarDarkContentEnabled = darkIcons + isNavigationBarContrastEnforced = navigationBarContrastEnforced + + window?.navigationBarColor = + when { + darkIcons && windowInsetsController?.isAppearanceLightNavigationBars != true -> { + // If we're set to use dark icons, but our windowInsetsController call didn't + // succeed (usually due to API level), we instead transform the color to + // maintain contrast + transformColorForLightContent(color) + } + else -> color + }.toArgb() + } + + override var isStatusBarVisible: Boolean + get() { + return ViewCompat.getRootWindowInsets(view) + ?.isVisible(WindowInsetsCompat.Type.statusBars()) == true + } + set(value) { + if (value) { + windowInsetsController?.show(WindowInsetsCompat.Type.statusBars()) + } else { + windowInsetsController?.hide(WindowInsetsCompat.Type.statusBars()) + } + } + + override var isNavigationBarVisible: Boolean + get() { + return ViewCompat.getRootWindowInsets(view) + ?.isVisible(WindowInsetsCompat.Type.navigationBars()) == true + } + set(value) { + if (value) { + windowInsetsController?.show(WindowInsetsCompat.Type.navigationBars()) + } else { + windowInsetsController?.hide(WindowInsetsCompat.Type.navigationBars()) + } + } + + override var statusBarDarkContentEnabled: Boolean + get() = windowInsetsController?.isAppearanceLightStatusBars == true + set(value) { + windowInsetsController?.isAppearanceLightStatusBars = value + } + + override var navigationBarDarkContentEnabled: Boolean + get() = windowInsetsController?.isAppearanceLightNavigationBars == true + set(value) { + windowInsetsController?.isAppearanceLightNavigationBars = value + } + + override var isNavigationBarContrastEnforced: Boolean + get() = Build.VERSION.SDK_INT >= 29 && window?.isNavigationBarContrastEnforced == true + set(value) { + if (Build.VERSION.SDK_INT >= 29) { + window?.isNavigationBarContrastEnforced = value + } + } +} + +private val BlackScrim = Color(0f, 0f, 0f, 0.3f) // 30% opaque black +private val BlackScrimmed: (Color) -> Color = { original -> BlackScrim.compositeOver(original) } diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/AndroidColorScheme.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/AndroidColorScheme.kt new file mode 100644 index 000000000000..b8639e64e002 --- /dev/null +++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/AndroidColorScheme.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2022 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.systemui.compose.theme + +import android.annotation.ColorInt +import android.content.Context +import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.graphics.Color +import com.android.internal.R + +/** CompositionLocal used to pass [AndroidColorScheme] down the tree. */ +val LocalAndroidColorScheme = + staticCompositionLocalOf<AndroidColorScheme> { + throw IllegalStateException( + "No AndroidColorScheme configured. Make sure to use LocalAndroidColorScheme in a " + + "Composable surrounded by a SystemUITheme {}." + ) + } + +/** + * The Android color scheme. + * + * Important: Use M3 colors from MaterialTheme.colorScheme whenever possible instead. In the future, + * most of the colors in this class will be removed in favor of their M3 counterpart. + */ +class AndroidColorScheme internal constructor(context: Context) { + val colorPrimary = getColor(context, R.attr.colorPrimary) + val colorPrimaryDark = getColor(context, R.attr.colorPrimaryDark) + val colorAccent = getColor(context, R.attr.colorAccent) + val colorAccentPrimary = getColor(context, R.attr.colorAccentPrimary) + val colorAccentSecondary = getColor(context, R.attr.colorAccentSecondary) + val colorAccentTertiary = getColor(context, R.attr.colorAccentTertiary) + val colorAccentPrimaryVariant = getColor(context, R.attr.colorAccentPrimaryVariant) + val colorAccentSecondaryVariant = getColor(context, R.attr.colorAccentSecondaryVariant) + val colorAccentTertiaryVariant = getColor(context, R.attr.colorAccentTertiaryVariant) + val colorSurface = getColor(context, R.attr.colorSurface) + val colorSurfaceHighlight = getColor(context, R.attr.colorSurfaceHighlight) + val colorSurfaceVariant = getColor(context, R.attr.colorSurfaceVariant) + val colorSurfaceHeader = getColor(context, R.attr.colorSurfaceHeader) + val colorError = getColor(context, R.attr.colorError) + val colorBackground = getColor(context, R.attr.colorBackground) + val colorBackgroundFloating = getColor(context, R.attr.colorBackgroundFloating) + val panelColorBackground = getColor(context, R.attr.panelColorBackground) + val textColorPrimary = getColor(context, R.attr.textColorPrimary) + val textColorSecondary = getColor(context, R.attr.textColorSecondary) + val textColorTertiary = getColor(context, R.attr.textColorTertiary) + val textColorPrimaryInverse = getColor(context, R.attr.textColorPrimaryInverse) + val textColorSecondaryInverse = getColor(context, R.attr.textColorSecondaryInverse) + val textColorTertiaryInverse = getColor(context, R.attr.textColorTertiaryInverse) + val textColorOnAccent = getColor(context, R.attr.textColorOnAccent) + val colorForeground = getColor(context, R.attr.colorForeground) + val colorForegroundInverse = getColor(context, R.attr.colorForegroundInverse) + + private fun getColor(context: Context, attr: Int): Color { + val ta = context.obtainStyledAttributes(intArrayOf(attr)) + @ColorInt val color = ta.getColor(0, 0) + ta.recycle() + return Color(color) + } +} diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/SystemUITheme.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/SystemUITheme.kt new file mode 100644 index 000000000000..79e3d3d475a8 --- /dev/null +++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/SystemUITheme.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2022 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.systemui.compose.theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Typography +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.platform.LocalContext + +/** The Material 3 theme that should wrap all SystemUI Composables. */ +@Composable +fun SystemUITheme( + isDarkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit, +) { + val context = LocalContext.current + + // TODO(b/230605885): Define our typography and color scheme. + val colorScheme = + if (isDarkTheme) { + dynamicDarkColorScheme(context) + } else { + dynamicLightColorScheme(context) + } + val androidColorScheme = AndroidColorScheme(context) + val typography = Typography() + + MaterialTheme(colorScheme, typography = typography) { + CompositionLocalProvider( + LocalAndroidColorScheme provides androidColorScheme, + ) { + content() + } + } +} diff --git a/packages/SystemUI/compose/core/tests/Android.bp b/packages/SystemUI/compose/core/tests/Android.bp new file mode 100644 index 000000000000..f8023e2519f8 --- /dev/null +++ b/packages/SystemUI/compose/core/tests/Android.bp @@ -0,0 +1,48 @@ +// Copyright (C) 2022 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], +} + +// TODO(b/230606318): Make those host tests instead of device tests. +android_test { + name: "SystemUIComposeCoreTests", + manifest: "AndroidManifest.xml", + test_suites: ["device-tests"], + sdk_version: "current", + certificate: "platform", + + srcs: [ + "src/**/*.kt", + ], + + static_libs: [ + "SystemUIComposeCore", + + "androidx.test.runner", + "androidx.test.ext.junit", + + "androidx.compose.runtime_runtime", + "androidx.compose.ui_ui-test-junit4", + "androidx.compose.ui_ui-test-manifest", + ], + + kotlincflags: ["-Xjvm-default=enable"], +} diff --git a/packages/SystemUI/compose/core/tests/AndroidManifest.xml b/packages/SystemUI/compose/core/tests/AndroidManifest.xml new file mode 100644 index 000000000000..729ab989cdc0 --- /dev/null +++ b/packages/SystemUI/compose/core/tests/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2022 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.systemui.compose.core.tests" > + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.systemui.compose.core.tests" + android:label="Tests for SystemUIComposeCore"/> + +</manifest>
\ No newline at end of file diff --git a/packages/SystemUI/compose/core/tests/src/com/android/systemui/compose/theme/SystemUIThemeTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/systemui/compose/theme/SystemUIThemeTest.kt new file mode 100644 index 000000000000..20249f66d0d0 --- /dev/null +++ b/packages/SystemUI/compose/core/tests/src/com/android/systemui/compose/theme/SystemUIThemeTest.kt @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2022 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.systemui.compose.theme + +import androidx.compose.material3.Text +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Assert.assertThrows +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class SystemUIThemeTest { + @get:Rule val composeRule = createComposeRule() + + @Test + fun testThemeShowsContent() { + composeRule.setContent { SystemUITheme { Text("foo") } } + + composeRule.onNodeWithText("foo").assertIsDisplayed() + } + + @Test + fun testAndroidColorsAreAvailableInsideTheme() { + composeRule.setContent { + SystemUITheme { Text("foo", color = LocalAndroidColorScheme.current.colorAccent) } + } + + composeRule.onNodeWithText("foo").assertIsDisplayed() + } + + @Test + fun testAccessingAndroidColorsWithoutThemeThrows() { + assertThrows(IllegalStateException::class.java) { + composeRule.setContent { + Text("foo", color = LocalAndroidColorScheme.current.colorAccent) + } + } + } +} diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp new file mode 100644 index 000000000000..40218de94258 --- /dev/null +++ b/packages/SystemUI/compose/features/Android.bp @@ -0,0 +1,40 @@ +// Copyright (C) 2022 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], +} + +android_library { + name: "SystemUIComposeFeatures", + manifest: "AndroidManifest.xml", + + srcs: [ + "src/**/*.kt", + ], + + static_libs: [ + "SystemUIComposeCore", + + "androidx.compose.runtime_runtime", + "androidx.compose.material3_material3", + ], + + kotlincflags: ["-Xjvm-default=all"], +} diff --git a/packages/SystemUI/compose/features/AndroidManifest.xml b/packages/SystemUI/compose/features/AndroidManifest.xml new file mode 100644 index 000000000000..0aea99d4e960 --- /dev/null +++ b/packages/SystemUI/compose/features/AndroidManifest.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2022 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.systemui.compose.features"> + + +</manifest> diff --git a/packages/SystemUI/compose/features/TEST_MAPPING b/packages/SystemUI/compose/features/TEST_MAPPING new file mode 100644 index 000000000000..7430acb2e900 --- /dev/null +++ b/packages/SystemUI/compose/features/TEST_MAPPING @@ -0,0 +1,26 @@ +{ + "presubmit": [ + { + "name": "SystemUIComposeFeaturesTests", + "options": [ + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "SystemUIComposeGalleryTests", + "options": [ + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + } + ] +}
\ No newline at end of file diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt b/packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt new file mode 100644 index 000000000000..c58c16259abe --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2022 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.systemui + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import kotlin.math.roundToInt + +/** + * This is an example Compose feature, which shows a text and a count that is incremented when + * clicked. We also show the max width available to this component, which is displayed either next + * to or below the text depending on that max width. + */ +@Composable +fun ExampleFeature(text: String, modifier: Modifier = Modifier) { + BoxWithConstraints(modifier) { + val maxWidth = maxWidth + if (maxWidth < 600.dp) { + Column { + CounterTile(text) + Spacer(Modifier.size(16.dp)) + MaxWidthTile(maxWidth) + } + } else { + Row { + CounterTile(text) + Spacer(Modifier.size(16.dp)) + MaxWidthTile(maxWidth) + } + } + } +} + +@Composable +private fun CounterTile(text: String, modifier: Modifier = Modifier) { + Surface( + modifier, + color = MaterialTheme.colorScheme.primaryContainer, + shape = RoundedCornerShape(28.dp), + ) { + var count by remember { mutableStateOf(0) } + Column( + Modifier.clickable { count++ }.padding(16.dp), + ) { + Text(text) + Text("I was clicked $count times.") + } + } +} + +@Composable +private fun MaxWidthTile(maxWidth: Dp, modifier: Modifier = Modifier) { + Surface( + modifier, + color = MaterialTheme.colorScheme.tertiaryContainer, + shape = RoundedCornerShape(28.dp), + ) { + Text( + "The max available width to me is: ${maxWidth.value.roundToInt()}dp", + Modifier.padding(16.dp) + ) + } +} diff --git a/packages/SystemUI/compose/features/tests/Android.bp b/packages/SystemUI/compose/features/tests/Android.bp new file mode 100644 index 000000000000..ff534bd01fd3 --- /dev/null +++ b/packages/SystemUI/compose/features/tests/Android.bp @@ -0,0 +1,48 @@ +// Copyright (C) 2022 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], +} + +// TODO(b/230606318): Make those host tests instead of device tests. +android_test { + name: "SystemUIComposeFeaturesTests", + manifest: "AndroidManifest.xml", + test_suites: ["device-tests"], + sdk_version: "current", + certificate: "platform", + + srcs: [ + "src/**/*.kt", + ], + + static_libs: [ + "SystemUIComposeFeatures", + + "androidx.test.runner", + "androidx.test.ext.junit", + + "androidx.compose.runtime_runtime", + "androidx.compose.ui_ui-test-junit4", + "androidx.compose.ui_ui-test-manifest", + ], + + kotlincflags: ["-Xjvm-default=enable"], +} diff --git a/packages/SystemUI/compose/features/tests/AndroidManifest.xml b/packages/SystemUI/compose/features/tests/AndroidManifest.xml new file mode 100644 index 000000000000..5e54c1f353d2 --- /dev/null +++ b/packages/SystemUI/compose/features/tests/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2022 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.systemui.compose.features.tests" > + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.systemui.compose.features.tests" + android:label="Tests for SystemUIComposeFeatures"/> + +</manifest>
\ No newline at end of file diff --git a/packages/SystemUI/compose/features/tests/src/com/android/systemui/ExampleFeatureTest.kt b/packages/SystemUI/compose/features/tests/src/com/android/systemui/ExampleFeatureTest.kt new file mode 100644 index 000000000000..1c2e8fab0337 --- /dev/null +++ b/packages/SystemUI/compose/features/tests/src/com/android/systemui/ExampleFeatureTest.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 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.systemui + +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ExampleFeatureTest { + @get:Rule val composeRule = createComposeRule() + + @Test + fun testProvidedTextIsDisplayed() { + composeRule.setContent { ExampleFeature("foo") } + + composeRule.onNodeWithText("foo").assertIsDisplayed() + } + + @Test + fun testCountIsIncreasedWhenClicking() { + composeRule.setContent { ExampleFeature("foo") } + + composeRule.onNodeWithText("I was clicked 0 times.").assertIsDisplayed().performClick() + composeRule.onNodeWithText("I was clicked 1 times.").assertIsDisplayed() + } +} diff --git a/packages/SystemUI/compose/gallery/Android.bp b/packages/SystemUI/compose/gallery/Android.bp new file mode 100644 index 000000000000..40504dc30c33 --- /dev/null +++ b/packages/SystemUI/compose/gallery/Android.bp @@ -0,0 +1,72 @@ +// Copyright (C) 2022 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], +} + +android_library { + name: "SystemUIComposeGalleryLib", + manifest: "AndroidManifest.xml", + + srcs: [ + "src/**/*.kt", + ], + + resource_dirs: [ + "res", + ], + + static_libs: [ + "SystemUI-core", + "SystemUIComposeCore", + "SystemUIComposeFeatures", + + "androidx.compose.runtime_runtime", + "androidx.compose.material3_material3", + "androidx.compose.material_material-icons-extended", + "androidx.activity_activity-compose", + "androidx.navigation_navigation-compose", + + "androidx.appcompat_appcompat", + ], + + kotlincflags: ["-Xjvm-default=all"], +} + +android_app { + name: "SystemUIComposeGallery", + defaults: ["platform_app_defaults"], + manifest: "app/AndroidManifest.xml", + + static_libs: [ + "SystemUIComposeGalleryLib", + ], + + platform_apis: true, + system_ext_specific: true, + certificate: "platform", + privileged: true, + + optimize: { + proguard_flags_files: ["proguard-rules.pro"], + }, + + dxflags: ["--multi-dex"], +} diff --git a/packages/SystemUI/compose/gallery/AndroidManifest.xml b/packages/SystemUI/compose/gallery/AndroidManifest.xml new file mode 100644 index 000000000000..2f30651a6acf --- /dev/null +++ b/packages/SystemUI/compose/gallery/AndroidManifest.xml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2022 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + package="com.android.systemui.compose.gallery"> + <!-- To emulate a display size and density. --> + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> + + <application + android:name="android.app.Application" + android:appComponentFactory="androidx.core.app.AppComponentFactory" + tools:replace="android:name,android:appComponentFactory"> + <!-- Disable providers from SystemUI --> + <provider android:name="com.android.systemui.keyguard.KeyguardSliceProvider" + android:authorities="com.android.systemui.test.keyguard.disabled" + android:enabled="false" + tools:replace="android:authorities" + tools:node="remove" /> + <provider android:name="com.google.android.systemui.keyguard.KeyguardSliceProviderGoogle" + android:authorities="com.android.systemui.test.keyguard.disabled" + android:enabled="false" + tools:replace="android:authorities" + tools:node="remove" /> + <provider android:name="com.android.keyguard.clock.ClockOptionsProvider" + android:authorities="com.android.systemui.test.keyguard.clock.disabled" + android:enabled="false" + tools:replace="android:authorities" + tools:node="remove" /> + <provider android:name="com.android.systemui.people.PeopleProvider" + android:authorities="com.android.systemui.test.people.disabled" + android:enabled="false" + tools:replace="android:authorities" + tools:node="remove" /> + <provider android:name="androidx.core.content.FileProvider" + android:authorities="com.android.systemui.test.fileprovider.disabled" + android:enabled="false" + tools:replace="android:authorities" + tools:node="remove"/> + </application> +</manifest> diff --git a/packages/SystemUI/compose/gallery/TEST_MAPPING b/packages/SystemUI/compose/gallery/TEST_MAPPING new file mode 100644 index 000000000000..c7f8a9216418 --- /dev/null +++ b/packages/SystemUI/compose/gallery/TEST_MAPPING @@ -0,0 +1,15 @@ +{ + "presubmit": [ + { + "name": "SystemUIComposeGalleryTests", + "options": [ + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + } + ] +}
\ No newline at end of file diff --git a/packages/SystemUI/compose/gallery/app/AndroidManifest.xml b/packages/SystemUI/compose/gallery/app/AndroidManifest.xml new file mode 100644 index 000000000000..1f3fd8c312d9 --- /dev/null +++ b/packages/SystemUI/compose/gallery/app/AndroidManifest.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2022 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + package="com.android.systemui.compose.gallery.app"> + <application + android:allowBackup="true" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:roundIcon="@mipmap/ic_launcher_round" + android:supportsRtl="true" + android:theme="@style/Theme.SystemUI.Gallery" + tools:replace="android:icon,android:theme,android:label"> + <activity + android:name="com.android.systemui.compose.gallery.GalleryActivity" + android:exported="true" + android:label="@string/app_name"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/packages/SystemUI/compose/gallery/proguard-rules.pro b/packages/SystemUI/compose/gallery/proguard-rules.pro new file mode 100644 index 000000000000..481bb4348141 --- /dev/null +++ b/packages/SystemUI/compose/gallery/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile
\ No newline at end of file diff --git a/packages/SystemUI/compose/gallery/res/drawable-v24/ic_launcher_foreground.xml b/packages/SystemUI/compose/gallery/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 000000000000..966abaff2074 --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt" + android:width="108dp" + android:height="108dp" + android:viewportHeight="108" + android:viewportWidth="108"> + <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z"> + <aapt:attr name="android:fillColor"> + <gradient + android:endX="85.84757" + android:endY="92.4963" + android:startX="42.9492" + android:startY="49.59793" + android:type="linear"> + <item + android:color="#44000000" + android:offset="0.0" /> + <item + android:color="#00000000" + android:offset="1.0" /> + </gradient> + </aapt:attr> + </path> + <path + android:fillColor="#FFFFFF" + android:fillType="nonZero" + android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z" + android:strokeColor="#00000000" + android:strokeWidth="1" /> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/compose/gallery/res/drawable/ic_launcher_background.xml b/packages/SystemUI/compose/gallery/res/drawable/ic_launcher_background.xml new file mode 100644 index 000000000000..61bb79edb709 --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="108dp" + android:height="108dp" + android:viewportHeight="108" + android:viewportWidth="108"> + <path + android:fillColor="#3DDC84" + android:pathData="M0,0h108v108h-108z" /> + <path + android:fillColor="#00000000" + android:pathData="M9,0L9,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,0L19,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M29,0L29,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M39,0L39,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M49,0L49,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M59,0L59,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M69,0L69,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M79,0L79,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M89,0L89,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M99,0L99,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,9L108,9" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,19L108,19" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,29L108,29" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,39L108,39" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,49L108,49" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,59L108,59" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,69L108,69" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,79L108,79" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,89L108,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,99L108,99" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,29L89,29" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,39L89,39" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,49L89,49" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,59L89,59" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,69L89,69" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,79L89,79" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M29,19L29,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M39,19L39,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M49,19L49,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M59,19L59,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M69,19L69,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M79,19L79,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> +</vector> diff --git a/packages/SystemUI/compose/gallery/res/mipmap-anydpi-v26/ic_launcher.xml b/packages/SystemUI/compose/gallery/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 000000000000..03eed2533da2 --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@drawable/ic_launcher_background" /> + <foreground android:drawable="@drawable/ic_launcher_foreground" /> +</adaptive-icon>
\ No newline at end of file diff --git a/packages/SystemUI/compose/gallery/res/mipmap-anydpi-v26/ic_launcher_round.xml b/packages/SystemUI/compose/gallery/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 000000000000..03eed2533da2 --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@drawable/ic_launcher_background" /> + <foreground android:drawable="@drawable/ic_launcher_foreground" /> +</adaptive-icon>
\ No newline at end of file diff --git a/packages/SystemUI/compose/gallery/res/mipmap-hdpi/ic_launcher.webp b/packages/SystemUI/compose/gallery/res/mipmap-hdpi/ic_launcher.webp Binary files differnew file mode 100644 index 000000000000..c209e78ecd37 --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/mipmap-hdpi/ic_launcher.webp diff --git a/packages/SystemUI/compose/gallery/res/mipmap-hdpi/ic_launcher_round.webp b/packages/SystemUI/compose/gallery/res/mipmap-hdpi/ic_launcher_round.webp Binary files differnew file mode 100644 index 000000000000..b2dfe3d1ba5c --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/mipmap-hdpi/ic_launcher_round.webp diff --git a/packages/SystemUI/compose/gallery/res/mipmap-mdpi/ic_launcher.webp b/packages/SystemUI/compose/gallery/res/mipmap-mdpi/ic_launcher.webp Binary files differnew file mode 100644 index 000000000000..4f0f1d64e58b --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/mipmap-mdpi/ic_launcher.webp diff --git a/packages/SystemUI/compose/gallery/res/mipmap-mdpi/ic_launcher_round.webp b/packages/SystemUI/compose/gallery/res/mipmap-mdpi/ic_launcher_round.webp Binary files differnew file mode 100644 index 000000000000..62b611da0816 --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/mipmap-mdpi/ic_launcher_round.webp diff --git a/packages/SystemUI/compose/gallery/res/mipmap-xhdpi/ic_launcher.webp b/packages/SystemUI/compose/gallery/res/mipmap-xhdpi/ic_launcher.webp Binary files differnew file mode 100644 index 000000000000..948a3070fe34 --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/mipmap-xhdpi/ic_launcher.webp diff --git a/packages/SystemUI/compose/gallery/res/mipmap-xhdpi/ic_launcher_round.webp b/packages/SystemUI/compose/gallery/res/mipmap-xhdpi/ic_launcher_round.webp Binary files differnew file mode 100644 index 000000000000..1b9a6956b3ac --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/mipmap-xhdpi/ic_launcher_round.webp diff --git a/packages/SystemUI/compose/gallery/res/mipmap-xxhdpi/ic_launcher.webp b/packages/SystemUI/compose/gallery/res/mipmap-xxhdpi/ic_launcher.webp Binary files differnew file mode 100644 index 000000000000..28d4b77f9f03 --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/mipmap-xxhdpi/ic_launcher.webp diff --git a/packages/SystemUI/compose/gallery/res/mipmap-xxhdpi/ic_launcher_round.webp b/packages/SystemUI/compose/gallery/res/mipmap-xxhdpi/ic_launcher_round.webp Binary files differnew file mode 100644 index 000000000000..9287f5083623 --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/mipmap-xxhdpi/ic_launcher_round.webp diff --git a/packages/SystemUI/compose/gallery/res/mipmap-xxxhdpi/ic_launcher.webp b/packages/SystemUI/compose/gallery/res/mipmap-xxxhdpi/ic_launcher.webp Binary files differnew file mode 100644 index 000000000000..aa7d6427e6fa --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/mipmap-xxxhdpi/ic_launcher.webp diff --git a/packages/SystemUI/compose/gallery/res/mipmap-xxxhdpi/ic_launcher_round.webp b/packages/SystemUI/compose/gallery/res/mipmap-xxxhdpi/ic_launcher_round.webp Binary files differnew file mode 100644 index 000000000000..9126ae37cbc3 --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/mipmap-xxxhdpi/ic_launcher_round.webp diff --git a/packages/SystemUI/compose/gallery/res/values/colors.xml b/packages/SystemUI/compose/gallery/res/values/colors.xml new file mode 100644 index 000000000000..a2fcbffc26c0 --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/values/colors.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2022 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. +--> +<resources> + <color name="ic_launcher_background">#FFFFFF</color> +</resources>
\ No newline at end of file diff --git a/packages/SystemUI/compose/gallery/res/values/strings.xml b/packages/SystemUI/compose/gallery/res/values/strings.xml new file mode 100644 index 000000000000..86bdb0568837 --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/values/strings.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2022 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. +--> +<resources> + <!-- Application name [CHAR LIMIT=NONE] --> + <string name="app_name">SystemUI Gallery</string> +</resources>
\ No newline at end of file diff --git a/packages/SystemUI/compose/gallery/res/values/themes.xml b/packages/SystemUI/compose/gallery/res/values/themes.xml new file mode 100644 index 000000000000..45fa1f5dfb5c --- /dev/null +++ b/packages/SystemUI/compose/gallery/res/values/themes.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2022 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. +--> +<resources xmlns:tools="http://schemas.android.com/tools"> + <style name="Theme.SystemUI.Gallery"> + <item name="android:windowActionBar">false</item> + <item name="android:windowNoTitle">true</item> + + <item name="android:statusBarColor" tools:targetApi="l"> + @android:color/transparent + </item> + <item name="android:navigationBarColor" tools:targetApi="l"> + @android:color/transparent + </item> + <item name="android:windowLightStatusBar">true</item> + </style> +</resources> diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ColorsScreen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ColorsScreen.kt new file mode 100644 index 000000000000..dfa1b26f464e --- /dev/null +++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ColorsScreen.kt @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2022 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.systemui.compose.gallery + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.android.systemui.compose.theme.LocalAndroidColorScheme + +/** The screen that shows all the Material 3 colors. */ +@Composable +fun MaterialColorsScreen() { + val colors = MaterialTheme.colorScheme + ColorsScreen( + listOf( + "primary" to colors.primary, + "onPrimary" to colors.onPrimary, + "primaryContainer" to colors.primaryContainer, + "onPrimaryContainer" to colors.onPrimaryContainer, + "inversePrimary" to colors.inversePrimary, + "secondary" to colors.secondary, + "onSecondary" to colors.onSecondary, + "secondaryContainer" to colors.secondaryContainer, + "onSecondaryContainer" to colors.onSecondaryContainer, + "tertiary" to colors.tertiary, + "onTertiary" to colors.onTertiary, + "tertiaryContainer" to colors.tertiaryContainer, + "onTertiaryContainer" to colors.onTertiaryContainer, + "background" to colors.background, + "onBackground" to colors.onBackground, + "surface" to colors.surface, + "onSurface" to colors.onSurface, + "surfaceVariant" to colors.surfaceVariant, + "onSurfaceVariant" to colors.onSurfaceVariant, + "inverseSurface" to colors.inverseSurface, + "inverseOnSurface" to colors.inverseOnSurface, + "error" to colors.error, + "onError" to colors.onError, + "errorContainer" to colors.errorContainer, + "onErrorContainer" to colors.onErrorContainer, + "outline" to colors.outline, + ) + ) +} + +/** The screen that shows all the Android colors. */ +@Composable +fun AndroidColorsScreen() { + val colors = LocalAndroidColorScheme.current + ColorsScreen( + listOf( + "colorPrimary" to colors.colorPrimary, + "colorPrimaryDark" to colors.colorPrimaryDark, + "colorAccent" to colors.colorAccent, + "colorAccentPrimary" to colors.colorAccentPrimary, + "colorAccentSecondary" to colors.colorAccentSecondary, + "colorAccentTertiary" to colors.colorAccentTertiary, + "colorAccentPrimaryVariant" to colors.colorAccentPrimaryVariant, + "colorAccentSecondaryVariant" to colors.colorAccentSecondaryVariant, + "colorAccentTertiaryVariant" to colors.colorAccentTertiaryVariant, + "colorSurface" to colors.colorSurface, + "colorSurfaceHighlight" to colors.colorSurfaceHighlight, + "colorSurfaceVariant" to colors.colorSurfaceVariant, + "colorSurfaceHeader" to colors.colorSurfaceHeader, + "colorError" to colors.colorError, + "colorBackground" to colors.colorBackground, + "colorBackgroundFloating" to colors.colorBackgroundFloating, + "panelColorBackground" to colors.panelColorBackground, + "textColorPrimary" to colors.textColorPrimary, + "textColorSecondary" to colors.textColorSecondary, + "textColorTertiary" to colors.textColorTertiary, + "textColorPrimaryInverse" to colors.textColorPrimaryInverse, + "textColorSecondaryInverse" to colors.textColorSecondaryInverse, + "textColorTertiaryInverse" to colors.textColorTertiaryInverse, + "textColorOnAccent" to colors.textColorOnAccent, + "colorForeground" to colors.colorForeground, + "colorForegroundInverse" to colors.colorForegroundInverse, + ) + ) +} + +@Composable +private fun ColorsScreen( + colors: List<Pair<String, Color>>, +) { + LazyColumn( + Modifier.fillMaxWidth(), + ) { + colors.forEach { (name, color) -> item { ColorTile(color, name) } } + } +} + +@Composable +private fun ColorTile( + color: Color, + name: String, +) { + Row( + Modifier.padding(16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + val shape = RoundedCornerShape(16.dp) + Spacer( + Modifier.border(1.dp, MaterialTheme.colorScheme.onBackground, shape) + .background(color, shape) + .size(64.dp) + ) + Spacer(Modifier.width(16.dp)) + Text(name) + } +} diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ConfigurationControls.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ConfigurationControls.kt new file mode 100644 index 000000000000..990d060207df --- /dev/null +++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ConfigurationControls.kt @@ -0,0 +1,210 @@ +package com.android.systemui.compose.gallery + +import android.graphics.Point +import android.os.UserHandle +import android.view.Display +import android.view.WindowManagerGlobal +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.DarkMode +import androidx.compose.material.icons.filled.FormatSize +import androidx.compose.material.icons.filled.FormatTextdirectionLToR +import androidx.compose.material.icons.filled.FormatTextdirectionRToL +import androidx.compose.material.icons.filled.InvertColors +import androidx.compose.material.icons.filled.LightMode +import androidx.compose.material.icons.filled.Smartphone +import androidx.compose.material.icons.filled.Tablet +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import kotlin.math.max +import kotlin.math.min + +enum class FontScale(val scale: Float) { + Small(0.85f), + Normal(1f), + Big(1.15f), + Bigger(1.30f), +} + +/** A configuration panel that allows to toggle the theme, font scale and layout direction. */ +@Composable +fun ConfigurationControls( + theme: Theme, + fontScale: FontScale, + layoutDirection: LayoutDirection, + onChangeTheme: () -> Unit, + onChangeLayoutDirection: () -> Unit, + onChangeFontScale: () -> Unit, + modifier: Modifier = Modifier, +) { + // The display we are emulating, if any. + var emulatedDisplayName by rememberSaveable { mutableStateOf<String?>(null) } + val emulatedDisplay = + emulatedDisplayName?.let { name -> EmulatedDisplays.firstOrNull { it.name == name } } + + LaunchedEffect(emulatedDisplay) { + val wm = WindowManagerGlobal.getWindowManagerService() + + val defaultDisplayId = Display.DEFAULT_DISPLAY + if (emulatedDisplay == null) { + wm.clearForcedDisplayDensityForUser(defaultDisplayId, UserHandle.myUserId()) + wm.clearForcedDisplaySize(defaultDisplayId) + } else { + val density = emulatedDisplay.densityDpi + + // Emulate the display and make sure that we use the maximum available space possible. + val initialSize = Point() + wm.getInitialDisplaySize(defaultDisplayId, initialSize) + val width = emulatedDisplay.width + val height = emulatedDisplay.height + val minOfSize = min(width, height) + val maxOfSize = max(width, height) + if (initialSize.x < initialSize.y) { + wm.setForcedDisplaySize(defaultDisplayId, minOfSize, maxOfSize) + } else { + wm.setForcedDisplaySize(defaultDisplayId, maxOfSize, minOfSize) + } + wm.setForcedDisplayDensityForUser(defaultDisplayId, density, UserHandle.myUserId()) + } + } + + // TODO(b/231131244): Fork FlowRow from Accompanist and use that instead to make sure that users + // don't miss any available configuration. + LazyRow(modifier) { + // Dark/light theme. + item { + TextButton(onChangeTheme) { + val text: String + val icon: ImageVector + + when (theme) { + Theme.System -> { + icon = Icons.Default.InvertColors + text = "System" + } + Theme.Dark -> { + icon = Icons.Default.DarkMode + text = "Dark" + } + Theme.Light -> { + icon = Icons.Default.LightMode + text = "Light" + } + } + + Icon(icon, null) + Spacer(Modifier.width(8.dp)) + Text(text) + } + } + + // Font scale. + item { + TextButton(onChangeFontScale) { + Icon(Icons.Default.FormatSize, null) + Spacer(Modifier.width(8.dp)) + + Text(fontScale.name) + } + } + + // Layout direction. + item { + TextButton(onChangeLayoutDirection) { + when (layoutDirection) { + LayoutDirection.Ltr -> { + Icon(Icons.Default.FormatTextdirectionLToR, null) + Spacer(Modifier.width(8.dp)) + Text("LTR") + } + LayoutDirection.Rtl -> { + Icon(Icons.Default.FormatTextdirectionRToL, null) + Spacer(Modifier.width(8.dp)) + Text("RTL") + } + } + } + } + + // Display emulation. + EmulatedDisplays.forEach { display -> + item { + DisplayButton( + display, + emulatedDisplay == display, + { emulatedDisplayName = it?.name }, + ) + } + } + } +} + +@Composable +private fun DisplayButton( + display: EmulatedDisplay, + selected: Boolean, + onChangeEmulatedDisplay: (EmulatedDisplay?) -> Unit, +) { + val onClick = { + if (selected) { + onChangeEmulatedDisplay(null) + } else { + onChangeEmulatedDisplay(display) + } + } + + val content: @Composable RowScope.() -> Unit = { + Icon(display.icon, null) + Spacer(Modifier.width(8.dp)) + Text(display.name) + } + + if (selected) { + Button(onClick, contentPadding = ButtonDefaults.TextButtonContentPadding, content = content) + } else { + TextButton(onClick, content = content) + } +} + +/** The displays that can be emulated from this Gallery app. */ +private val EmulatedDisplays = + listOf( + EmulatedDisplay( + "Phone", + Icons.Default.Smartphone, + width = 1440, + height = 3120, + densityDpi = 560, + ), + EmulatedDisplay( + "Tablet", + Icons.Default.Tablet, + width = 2560, + height = 1600, + densityDpi = 320, + ), + ) + +private data class EmulatedDisplay( + val name: String, + val icon: ImageVector, + val width: Int, + val height: Int, + val densityDpi: Int, +) diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ExampleFeatureScreen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ExampleFeatureScreen.kt new file mode 100644 index 000000000000..6e1721490f98 --- /dev/null +++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ExampleFeatureScreen.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2022 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.systemui.compose.gallery + +import androidx.compose.foundation.layout.Column +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.android.systemui.ExampleFeature + +/** The screen that shows ExampleFeature. */ +@Composable +fun ExampleFeatureScreen(modifier: Modifier = Modifier) { + Column(modifier) { ExampleFeature("This is an example feature!") } +} diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryActivity.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryActivity.kt new file mode 100644 index 000000000000..bb2d2feba39f --- /dev/null +++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryActivity.kt @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2022 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.systemui.compose.gallery + +import android.app.UiModeManager +import android.content.Context +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.graphics.Color +import androidx.core.view.WindowCompat +import com.android.systemui.compose.rememberSystemUiController + +class GalleryActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + WindowCompat.setDecorFitsSystemWindows(window, false) + val uiModeManager = getSystemService(Context.UI_MODE_SERVICE) as UiModeManager + + setContent { + var theme by rememberSaveable { mutableStateOf(Theme.System) } + val onChangeTheme = { + // Change to the next theme for a toggle behavior. + theme = + when (theme) { + Theme.System -> Theme.Dark + Theme.Dark -> Theme.Light + Theme.Light -> Theme.System + } + } + + val isSystemInDarkTheme = isSystemInDarkTheme() + val isDark = theme == Theme.Dark || (theme == Theme.System && isSystemInDarkTheme) + val useDarkIcons = !isDark + val systemUiController = rememberSystemUiController() + SideEffect { + systemUiController.setSystemBarsColor( + color = Color.Transparent, + darkIcons = useDarkIcons, + ) + + uiModeManager.setApplicationNightMode( + when (theme) { + Theme.System -> UiModeManager.MODE_NIGHT_AUTO + Theme.Dark -> UiModeManager.MODE_NIGHT_YES + Theme.Light -> UiModeManager.MODE_NIGHT_NO + } + ) + } + + GalleryApp(theme, onChangeTheme) + } + } +} + +enum class Theme { + System, + Dark, + Light, +} diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt new file mode 100644 index 000000000000..c341867bfb59 --- /dev/null +++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt @@ -0,0 +1,125 @@ +package com.android.systemui.compose.gallery + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.rememberNavController +import com.android.systemui.compose.theme.SystemUITheme + +/** The gallery app screens. */ +object GalleryAppScreens { + val Typography = ChildScreen("typography") { TypographyScreen() } + val MaterialColors = ChildScreen("material_colors") { MaterialColorsScreen() } + val AndroidColors = ChildScreen("android_colors") { AndroidColorsScreen() } + val ExampleFeature = ChildScreen("example_feature") { ExampleFeatureScreen() } + + val Home = + ParentScreen( + "home", + mapOf( + "Typography" to Typography, + "Material colors" to MaterialColors, + "Android colors" to AndroidColors, + "Example feature" to ExampleFeature, + ) + ) +} + +/** The main content of the app, that shows [GalleryAppScreens.Home] by default. */ +@Composable +private fun MainContent() { + Box(Modifier.fillMaxSize()) { + val navController = rememberNavController() + NavHost( + navController = navController, + startDestination = GalleryAppScreens.Home.identifier, + ) { + screen(GalleryAppScreens.Home, navController) + } + } +} + +/** + * The top-level composable shown when starting the app. This composable always shows a + * [ConfigurationControls] at the top of the screen, above the [MainContent]. + */ +@Composable +fun GalleryApp( + theme: Theme, + onChangeTheme: () -> Unit, +) { + val systemFontScale = LocalDensity.current.fontScale + var fontScale: FontScale by remember { + mutableStateOf( + FontScale.values().firstOrNull { it.scale == systemFontScale } ?: FontScale.Normal + ) + } + val context = LocalContext.current + val density = Density(context.resources.displayMetrics.density, fontScale.scale) + val onChangeFontScale = { + fontScale = + when (fontScale) { + FontScale.Small -> FontScale.Normal + FontScale.Normal -> FontScale.Big + FontScale.Big -> FontScale.Bigger + FontScale.Bigger -> FontScale.Small + } + } + + val systemLayoutDirection = LocalLayoutDirection.current + var layoutDirection by remember { mutableStateOf(systemLayoutDirection) } + val onChangeLayoutDirection = { + layoutDirection = + when (layoutDirection) { + LayoutDirection.Ltr -> LayoutDirection.Rtl + LayoutDirection.Rtl -> LayoutDirection.Ltr + } + } + + CompositionLocalProvider( + LocalDensity provides density, + LocalLayoutDirection provides layoutDirection, + ) { + SystemUITheme { + Surface( + Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background, + ) { + Column(Modifier.fillMaxSize().systemBarsPadding().padding(16.dp)) { + ConfigurationControls( + theme, + fontScale, + layoutDirection, + onChangeTheme, + onChangeLayoutDirection, + onChangeFontScale, + ) + + Spacer(Modifier.height(4.dp)) + + MainContent() + } + } + } + } +} diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt new file mode 100644 index 000000000000..467dac044b79 --- /dev/null +++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2022 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.systemui.compose.gallery + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.compose.composable +import androidx.navigation.compose.navigation + +/** + * A screen in an app. It is either an [ParentScreen] which lists its child screens to navigate to + * them or a [ChildScreen] which shows some content. + */ +sealed class Screen(val identifier: String) + +class ParentScreen( + identifier: String, + val children: Map<String, Screen>, +) : Screen(identifier) + +class ChildScreen( + identifier: String, + val content: @Composable (NavController) -> Unit, +) : Screen(identifier) + +/** Create the navigation graph for [screen]. */ +fun NavGraphBuilder.screen(screen: Screen, navController: NavController) { + when (screen) { + is ChildScreen -> composable(screen.identifier) { screen.content(navController) } + is ParentScreen -> { + val menuRoute = "${screen.identifier}_menu" + navigation(startDestination = menuRoute, route = screen.identifier) { + // The menu to navigate to one of the children screens. + composable(menuRoute) { ScreenMenu(screen, navController) } + + // The content of the child screens. + screen.children.forEach { (_, child) -> screen(child, navController) } + } + } + } +} + +@Composable +private fun ScreenMenu( + screen: ParentScreen, + navController: NavController, +) { + LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) { + screen.children.forEach { (name, child) -> + item { + Surface( + Modifier.fillMaxWidth(), + color = MaterialTheme.colorScheme.secondaryContainer, + shape = CircleShape, + ) { + Column( + Modifier.clickable { navController.navigate(child.identifier) } + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text(name) + } + } + } + } + } +} diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/TypographyScreen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/TypographyScreen.kt new file mode 100644 index 000000000000..147025ed1d60 --- /dev/null +++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/TypographyScreen.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2022 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.systemui.compose.gallery + +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.TextOverflow + +/** The screen that shows the Material text styles. */ +@Composable +fun TypographyScreen() { + val typography = MaterialTheme.typography + + Column( + Modifier.fillMaxSize() + .horizontalScroll(rememberScrollState()) + .verticalScroll(rememberScrollState()), + ) { + FontLine("displayLarge", typography.displayLarge) + FontLine("displayMedium", typography.displayMedium) + FontLine("displaySmall", typography.displaySmall) + FontLine("headlineLarge", typography.headlineLarge) + FontLine("headlineMedium", typography.headlineMedium) + FontLine("headlineSmall", typography.headlineSmall) + FontLine("titleLarge", typography.titleLarge) + FontLine("titleMedium", typography.titleMedium) + FontLine("titleSmall", typography.titleSmall) + FontLine("bodyLarge", typography.bodyLarge) + FontLine("bodyMedium", typography.bodyMedium) + FontLine("bodySmall", typography.bodySmall) + FontLine("labelLarge", typography.labelLarge) + FontLine("labelMedium", typography.labelMedium) + FontLine("labelSmall", typography.labelSmall) + } +} + +@Composable +private fun FontLine(name: String, style: TextStyle) { + Text( + "$name (${style.fontSize}/${style.lineHeight}, W${style.fontWeight?.weight})", + style = style, + maxLines = 1, + overflow = TextOverflow.Visible, + ) +} diff --git a/packages/SystemUI/compose/gallery/tests/Android.bp b/packages/SystemUI/compose/gallery/tests/Android.bp new file mode 100644 index 000000000000..3e01f7d2c431 --- /dev/null +++ b/packages/SystemUI/compose/gallery/tests/Android.bp @@ -0,0 +1,47 @@ +// Copyright (C) 2022 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], +} + +android_test { + name: "SystemUIComposeGalleryTests", + manifest: "AndroidManifest.xml", + test_suites: ["device-tests"], + sdk_version: "current", + certificate: "platform", + + srcs: [ + "src/**/*.kt", + ], + + static_libs: [ + "SystemUIComposeGalleryLib", + + "androidx.test.runner", + "androidx.test.ext.junit", + + "androidx.compose.runtime_runtime", + "androidx.compose.ui_ui-test-junit4", + "androidx.compose.ui_ui-test-manifest", + ], + + kotlincflags: ["-Xjvm-default=enable"], +} diff --git a/packages/SystemUI/compose/gallery/tests/AndroidManifest.xml b/packages/SystemUI/compose/gallery/tests/AndroidManifest.xml new file mode 100644 index 000000000000..5eeb3ad24e5a --- /dev/null +++ b/packages/SystemUI/compose/gallery/tests/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2022 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.systemui.compose.gallery.tests" > + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.systemui.compose.gallery.tests" + android:label="Tests for SystemUIComposeGallery"/> + +</manifest>
\ No newline at end of file diff --git a/packages/SystemUI/compose/gallery/tests/src/com/android/systemui/compose/gallery/ScreenshotsTests.kt b/packages/SystemUI/compose/gallery/tests/src/com/android/systemui/compose/gallery/ScreenshotsTests.kt new file mode 100644 index 000000000000..66ecc8d4fde5 --- /dev/null +++ b/packages/SystemUI/compose/gallery/tests/src/com/android/systemui/compose/gallery/ScreenshotsTests.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2022 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.systemui.compose.gallery + +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.systemui.compose.theme.SystemUITheme +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ScreenshotsTests { + @get:Rule val composeRule = createComposeRule() + + @Test + fun exampleFeatureScreenshotTest() { + // TODO(b/230832101): Wire this with the screenshot diff testing infra. We should reuse the + // configuration of the features in the gallery app to populate the UIs. + composeRule.setContent { SystemUITheme { ExampleFeatureScreen() } } + } +} diff --git a/packages/SystemUI/compose/testing/Android.bp b/packages/SystemUI/compose/testing/Android.bp new file mode 100644 index 000000000000..293e51f68b98 --- /dev/null +++ b/packages/SystemUI/compose/testing/Android.bp @@ -0,0 +1,43 @@ +// Copyright (C) 2022 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], +} + +android_library { + name: "SystemUIComposeTesting", + manifest: "AndroidManifest.xml", + + srcs: [ + "src/**/*.kt", + ], + + static_libs: [ + "SystemUIComposeCore", + "SystemUIScreenshotLib", + + "androidx.compose.runtime_runtime", + "androidx.compose.material3_material3", + "androidx.compose.ui_ui-test-junit4", + "androidx.compose.ui_ui-test-manifest", + ], + + kotlincflags: ["-Xjvm-default=all"], +} diff --git a/packages/SystemUI/compose/testing/AndroidManifest.xml b/packages/SystemUI/compose/testing/AndroidManifest.xml new file mode 100644 index 000000000000..b1f7c3be2796 --- /dev/null +++ b/packages/SystemUI/compose/testing/AndroidManifest.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2022 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + package="com.android.systemui.testing.compose"> + <application + android:appComponentFactory="androidx.core.app.AppComponentFactory" + tools:replace="android:appComponentFactory"> + </application> +</manifest> diff --git a/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt b/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt new file mode 100644 index 000000000000..e611e8bf0068 --- /dev/null +++ b/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2022 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.systemui.testing.compose + +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.ViewRootForTest +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onRoot +import com.android.systemui.compose.theme.SystemUITheme +import com.android.systemui.testing.screenshot.ScreenshotActivity +import com.android.systemui.testing.screenshot.SystemUIGoldenImagePathManager +import com.android.systemui.testing.screenshot.UnitTestBitmapMatcher +import com.android.systemui.testing.screenshot.drawIntoBitmap +import org.junit.rules.RuleChain +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement +import platform.test.screenshot.DeviceEmulationRule +import platform.test.screenshot.DeviceEmulationSpec +import platform.test.screenshot.MaterialYouColorsRule +import platform.test.screenshot.ScreenshotTestRule +import platform.test.screenshot.getEmulatedDevicePathConfig + +/** A rule for Compose screenshot diff tests. */ +class ComposeScreenshotTestRule(emulationSpec: DeviceEmulationSpec) : TestRule { + private val colorsRule = MaterialYouColorsRule() + private val deviceEmulationRule = DeviceEmulationRule(emulationSpec) + private val screenshotRule = + ScreenshotTestRule( + SystemUIGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec)) + ) + private val composeRule = createAndroidComposeRule<ScreenshotActivity>() + private val delegateRule = + RuleChain.outerRule(colorsRule) + .around(deviceEmulationRule) + .around(screenshotRule) + .around(composeRule) + private val matcher = UnitTestBitmapMatcher + + override fun apply(base: Statement, description: Description): Statement { + return delegateRule.apply(base, description) + } + + /** + * Compare [content] with the golden image identified by [goldenIdentifier] in the context of + * [testSpec]. + */ + fun screenshotTest( + goldenIdentifier: String, + content: @Composable () -> Unit, + ) { + // Make sure that the activity draws full screen and fits the whole display instead of the + // system bars. + val activity = composeRule.activity + activity.mainExecutor.execute { activity.window.setDecorFitsSystemWindows(false) } + + // Set the content using the AndroidComposeRule to make sure that the Activity is set up + // correctly. + composeRule.setContent { + SystemUITheme { + Surface( + color = MaterialTheme.colorScheme.background, + ) { + content() + } + } + } + composeRule.waitForIdle() + + val view = (composeRule.onRoot().fetchSemanticsNode().root as ViewRootForTest).view + screenshotRule.assertBitmapAgainstGolden(view.drawIntoBitmap(), goldenIdentifier, matcher) + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt index 89d6fb5f062f..acbea1beeae3 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt @@ -29,7 +29,7 @@ import javax.inject.Inject /** * Translates items away/towards the hinge when the device is opened/closed. This is controlled by - * the set of ids, which also dictact which direction to move and when, via a filter function. + * the set of ids, which also dictate which direction to move and when, via a filter function. */ @SysUIUnfoldScope class KeyguardUnfoldTransition @@ -55,7 +55,9 @@ constructor( ViewIdToTranslate(R.id.lockscreen_clock_view, LEFT, filterNever), ViewIdToTranslate( R.id.notification_stack_scroller, RIGHT, filterSplitShadeOnly), - ViewIdToTranslate(R.id.wallet_button, RIGHT, filterNever)), + ViewIdToTranslate(R.id.wallet_button, RIGHT, filterNever), + ViewIdToTranslate(R.id.start_button, LEFT, filterNever), + ViewIdToTranslate(R.id.end_button, RIGHT, filterNever)), progressProvider = unfoldProgressProvider) } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index f07d9ce21563..98946ac5b0ee 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -214,7 +214,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * If no cancel signal has been received after this amount of time, set the biometric running * state to stopped to allow Keyguard to retry authentication. */ - private static final int DEFAULT_CANCEL_SIGNAL_TIMEOUT = 3000; + @VisibleForTesting + protected static final int DEFAULT_CANCEL_SIGNAL_TIMEOUT = 3000; private static final ComponentName FALLBACK_HOME_COMPONENT = new ComponentName( "com.android.settings", "com.android.settings.FallbackHome"); @@ -332,10 +333,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private static final int HAL_ERROR_RETRY_TIMEOUT = 500; // ms private static final int HAL_ERROR_RETRY_MAX = 20; - private final Runnable mFpCancelNotReceived = this::onFingerprintCancelNotReceived; + @VisibleForTesting + protected final Runnable mFpCancelNotReceived = this::onFingerprintCancelNotReceived; private final Runnable mFaceCancelNotReceived = this::onFaceCancelNotReceived; + @VisibleForTesting + protected Handler getHandler() { + return mHandler; + } private final Handler mHandler; private SparseBooleanArray mBiometricEnabledForUser = new SparseBooleanArray(); @@ -723,6 +729,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private void handleFingerprintAuthFailed() { Assert.isMainThread(); + if (mHandler.hasCallbacks(mFpCancelNotReceived)) { + Log.d(TAG, "handleFingerprintAuthFailed()" + + " triggered while waiting for cancellation, removing watchdog"); + mHandler.removeCallbacks(mFpCancelNotReceived); + } for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { @@ -753,6 +764,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private void handleFingerprintAuthenticated(int authUserId, boolean isStrongBiometric) { Trace.beginSection("KeyGuardUpdateMonitor#handlerFingerPrintAuthenticated"); + if (mHandler.hasCallbacks(mFpCancelNotReceived)) { + Log.d(TAG, "handleFingerprintAuthenticated()" + + " triggered while waiting for cancellation, removing watchdog"); + mHandler.removeCallbacks(mFpCancelNotReceived); + } try { final int userId; try { diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java index 835025bbfc88..e82d0ea85490 100644 --- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java +++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java @@ -16,8 +16,6 @@ package com.android.systemui.charging; -import static com.android.systemui.charging.WirelessChargingLayout.UNKNOWN_BATTERY_LEVEL; - import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; @@ -32,13 +30,14 @@ import android.view.WindowManager; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; +import com.android.systemui.ripple.RippleShader.RippleShape; /** * A WirelessChargingAnimation is a view containing view + animation for wireless charging. * @hide */ public class WirelessChargingAnimation { - + public static final int UNKNOWN_BATTERY_LEVEL = -1; public static final long DURATION = 1500; private static final String TAG = "WirelessChargingView"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -58,11 +57,12 @@ public class WirelessChargingAnimation { * before calling {@link #show} - can be done through {@link #makeWirelessChargingAnimation}. * @hide */ - public WirelessChargingAnimation(@NonNull Context context, @Nullable Looper looper, + private WirelessChargingAnimation(@NonNull Context context, @Nullable Looper looper, int transmittingBatteryLevel, int batteryLevel, Callback callback, boolean isDozing, - UiEventLogger uiEventLogger) { + RippleShape rippleShape, UiEventLogger uiEventLogger) { mCurrentWirelessChargingView = new WirelessChargingView(context, looper, - transmittingBatteryLevel, batteryLevel, callback, isDozing, uiEventLogger); + transmittingBatteryLevel, batteryLevel, callback, isDozing, + rippleShape, uiEventLogger); } /** @@ -72,9 +72,10 @@ public class WirelessChargingAnimation { */ public static WirelessChargingAnimation makeWirelessChargingAnimation(@NonNull Context context, @Nullable Looper looper, int transmittingBatteryLevel, int batteryLevel, - Callback callback, boolean isDozing, UiEventLogger uiEventLogger) { + Callback callback, boolean isDozing, RippleShape rippleShape, + UiEventLogger uiEventLogger) { return new WirelessChargingAnimation(context, looper, transmittingBatteryLevel, - batteryLevel, callback, isDozing, uiEventLogger); + batteryLevel, callback, isDozing, rippleShape, uiEventLogger); } /** @@ -82,9 +83,10 @@ public class WirelessChargingAnimation { * battery level without charging number shown. */ public static WirelessChargingAnimation makeChargingAnimationWithNoBatteryLevel( - @NonNull Context context, UiEventLogger uiEventLogger) { + @NonNull Context context, RippleShape rippleShape, UiEventLogger uiEventLogger) { return makeWirelessChargingAnimation(context, null, - UNKNOWN_BATTERY_LEVEL, UNKNOWN_BATTERY_LEVEL, null, false, uiEventLogger); + UNKNOWN_BATTERY_LEVEL, UNKNOWN_BATTERY_LEVEL, null, false, + rippleShape, uiEventLogger); } /** @@ -121,10 +123,10 @@ public class WirelessChargingAnimation { public WirelessChargingView(Context context, @Nullable Looper looper, int transmittingBatteryLevel, int batteryLevel, Callback callback, - boolean isDozing, UiEventLogger uiEventLogger) { + boolean isDozing, RippleShape rippleShape, UiEventLogger uiEventLogger) { mCallback = callback; mNextView = new WirelessChargingLayout(context, transmittingBatteryLevel, batteryLevel, - isDozing); + isDozing, rippleShape); mGravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER; mUiEventLogger = uiEventLogger; diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java index 65400c22ebd7..47ea27ff8ccb 100644 --- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java +++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java @@ -33,7 +33,7 @@ import android.widget.TextView; import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; -import com.android.systemui.ripple.RippleShader; +import com.android.systemui.ripple.RippleShader.RippleShape; import com.android.systemui.ripple.RippleView; import java.text.NumberFormat; @@ -41,37 +41,36 @@ import java.text.NumberFormat; /** * @hide */ -public class WirelessChargingLayout extends FrameLayout { - public static final int UNKNOWN_BATTERY_LEVEL = -1; +final class WirelessChargingLayout extends FrameLayout { private static final long RIPPLE_ANIMATION_DURATION = 1500; private static final int SCRIM_COLOR = 0x4C000000; private static final int SCRIM_FADE_DURATION = 300; private RippleView mRippleView; - public WirelessChargingLayout(Context context) { + WirelessChargingLayout(Context context, int transmittingBatteryLevel, int batteryLevel, + boolean isDozing, RippleShape rippleShape) { super(context); - init(context, null, false); + init(context, null, transmittingBatteryLevel, batteryLevel, isDozing, rippleShape); } - public WirelessChargingLayout(Context context, int transmittingBatteryLevel, int batteryLevel, - boolean isDozing) { + private WirelessChargingLayout(Context context) { super(context); - init(context, null, transmittingBatteryLevel, batteryLevel, isDozing); + init(context, null, /* isDozing= */ false, RippleShape.CIRCLE); } - public WirelessChargingLayout(Context context, AttributeSet attrs) { + private WirelessChargingLayout(Context context, AttributeSet attrs) { super(context, attrs); - init(context, attrs, false); + init(context, attrs, /* isDozing= */false, RippleShape.CIRCLE); } - private void init(Context c, AttributeSet attrs, boolean isDozing) { - init(c, attrs, -1, -1, false); + private void init(Context c, AttributeSet attrs, boolean isDozing, RippleShape rippleShape) { + init(c, attrs, -1, -1, isDozing, rippleShape); } private void init(Context context, AttributeSet attrs, int transmittingBatteryLevel, - int batteryLevel, boolean isDozing) { + int batteryLevel, boolean isDozing, RippleShape rippleShape) { final boolean showTransmittingBatteryLevel = - (transmittingBatteryLevel != UNKNOWN_BATTERY_LEVEL); + (transmittingBatteryLevel != WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL); // set style based on background int style = R.style.ChargingAnim_WallpaperBackground; @@ -84,7 +83,7 @@ public class WirelessChargingLayout extends FrameLayout { // amount of battery: final TextView percentage = findViewById(R.id.wireless_charging_percentage); - if (batteryLevel != UNKNOWN_BATTERY_LEVEL) { + if (batteryLevel != WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL) { percentage.setText(NumberFormat.getPercentInstance().format(batteryLevel / 100f)); percentage.setAlpha(0); } @@ -138,8 +137,7 @@ public class WirelessChargingLayout extends FrameLayout { animatorSetScrim.start(); mRippleView = findViewById(R.id.wireless_charging_ripple); - // TODO: Make rounded box shape if the device is tablet. - mRippleView.setupShader(RippleShader.RippleShape.CIRCLE); + mRippleView.setupShader(rippleShape); OnAttachStateChangeListener listener = new OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View view) { @@ -233,8 +231,12 @@ public class WirelessChargingLayout extends FrameLayout { int width = getMeasuredWidth(); int height = getMeasuredHeight(); mRippleView.setCenter(width * 0.5f, height * 0.5f); - float maxSize = Math.max(width, height); - mRippleView.setMaxSize(maxSize, maxSize); + if (mRippleView.getRippleShape() == RippleShape.ROUNDED_BOX) { + mRippleView.setMaxSize(width * 1.5f, height * 1.5f); + } else { + float maxSize = Math.max(width, height); + mRippleView.setMaxSize(maxSize, maxSize); + } mRippleView.setColor(Utils.getColorAttr(mRippleView.getContext(), android.R.attr.colorAccent).getDefaultColor()); } diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java index e16ac08c88f8..c21e36ab6ecc 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java @@ -97,6 +97,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.NonNull; import androidx.core.view.ViewCompat; import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; @@ -132,7 +133,7 @@ public class ClipboardOverlayController { private static final int FONT_SEARCH_STEP_PX = 4; private final Context mContext; - private final UiEventLogger mUiEventLogger; + private final ClipboardLogger mClipboardLogger; private final BroadcastDispatcher mBroadcastDispatcher; private final DisplayManager mDisplayManager; private final DisplayMetrics mDisplayMetrics; @@ -181,7 +182,7 @@ public class ClipboardOverlayController { final Context displayContext = context.createDisplayContext(getDefaultDisplay()); mContext = displayContext.createWindowContext(TYPE_SCREENSHOT, null); - mUiEventLogger = uiEventLogger; + mClipboardLogger = new ClipboardLogger(uiEventLogger); mAccessibilityManager = AccessibilityManager.getInstance(mContext); mTextClassifier = requireNonNull(context.getSystemService(TextClassificationManager.class)) @@ -231,7 +232,7 @@ public class ClipboardOverlayController { @Override public void onSwipeDismissInitiated(Animator animator) { - mUiEventLogger.log(CLIPBOARD_OVERLAY_SWIPE_DISMISSED); + mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SWIPE_DISMISSED); mExitAnimator = animator; } @@ -249,7 +250,7 @@ public class ClipboardOverlayController { }); mDismissButton.setOnClickListener(view -> { - mUiEventLogger.log(CLIPBOARD_OVERLAY_DISMISS_TAPPED); + mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISS_TAPPED); animateOut(); }); @@ -285,7 +286,8 @@ public class ClipboardOverlayController { int newDisplayId) { if (mContext.getResources().getConfiguration().orientation != mOrientation) { - mUiEventLogger.log(CLIPBOARD_OVERLAY_DISMISSED_OTHER); + mClipboardLogger.logSessionComplete( + CLIPBOARD_OVERLAY_DISMISSED_OTHER); hideImmediate(); } } @@ -300,7 +302,7 @@ public class ClipboardOverlayController { }); mTimeoutHandler.setOnTimeoutRunnable(() -> { - mUiEventLogger.log(CLIPBOARD_OVERLAY_TIMED_OUT); + mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TIMED_OUT); animateOut(); }); @@ -308,7 +310,7 @@ public class ClipboardOverlayController { @Override public void onReceive(Context context, Intent intent) { if (ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) { - mUiEventLogger.log(CLIPBOARD_OVERLAY_DISMISSED_OTHER); + mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER); animateOut(); } } @@ -320,7 +322,7 @@ public class ClipboardOverlayController { @Override public void onReceive(Context context, Intent intent) { if (SCREENSHOT_ACTION.equals(intent.getAction())) { - mUiEventLogger.log(CLIPBOARD_OVERLAY_DISMISSED_OTHER); + mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_DISMISSED_OTHER); animateOut(); } } @@ -390,7 +392,7 @@ public class ClipboardOverlayController { mContext.getString(R.string.clipboard_send_nearby_description)); mRemoteCopyChip.setVisibility(View.VISIBLE); mRemoteCopyChip.setOnClickListener((v) -> { - mUiEventLogger.log(CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED); + mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_REMOTE_COPY_TAPPED); mContext.startActivity(remoteCopyIntent); animateOut(); }); @@ -450,7 +452,7 @@ public class ClipboardOverlayController { chip.setContentDescription(action.getTitle()); chip.setIcon(action.getIcon(), false); chip.setPendingIntent(action.getActionIntent(), () -> { - mUiEventLogger.log(CLIPBOARD_OVERLAY_ACTION_TAPPED); + mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED); animateOut(); }); chip.setAlpha(1); @@ -486,7 +488,7 @@ public class ClipboardOverlayController { touchRegion.op(tmpRect, Region.Op.UNION); if (!touchRegion.contains( (int) motionEvent.getRawX(), (int) motionEvent.getRawY())) { - mUiEventLogger.log(CLIPBOARD_OVERLAY_TAP_OUTSIDE); + mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_TAP_OUTSIDE); animateOut(); } } @@ -497,7 +499,7 @@ public class ClipboardOverlayController { } private void editImage(Uri uri) { - mUiEventLogger.log(CLIPBOARD_OVERLAY_EDIT_TAPPED); + mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED); String editorPackage = mContext.getString(R.string.config_screenshotEditor); Intent editIntent = new Intent(Intent.ACTION_EDIT); if (!TextUtils.isEmpty(editorPackage)) { @@ -512,7 +514,7 @@ public class ClipboardOverlayController { } private void editText() { - mUiEventLogger.log(CLIPBOARD_OVERLAY_EDIT_TAPPED); + mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_EDIT_TAPPED); Intent editIntent = new Intent(mContext, EditTextActivity.class); editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); mContext.startActivity(editIntent); @@ -520,13 +522,15 @@ public class ClipboardOverlayController { } private void shareContent(ClipData clip) { - mUiEventLogger.log(CLIPBOARD_OVERLAY_SHARE_TAPPED); + mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_SHARE_TAPPED); Intent shareIntent = new Intent(Intent.ACTION_SEND); - shareIntent.putExtra(Intent.EXTRA_TEXT, clip.getItemAt(0).getText().toString()); shareIntent.setDataAndType( clip.getItemAt(0).getUri(), clip.getDescription().getMimeType(0)); - shareIntent.putExtra(Intent.EXTRA_STREAM, clip.getItemAt(0).getUri()); - shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + shareIntent.putExtra(Intent.EXTRA_TEXT, clip.getItemAt(0).getText().toString()); + if (clip.getItemAt(0).getUri() != null) { + shareIntent.putExtra(Intent.EXTRA_STREAM, clip.getItemAt(0).getUri()); + shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + } Intent chooserIntent = Intent.createChooser(shareIntent, null) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK) .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); @@ -864,6 +868,7 @@ public class ClipboardOverlayController { mRemoteCopyChip.setVisibility(View.GONE); resetActionChips(); mTimeoutHandler.cancelTimeout(); + mClipboardLogger.reset(); } @MainThread @@ -969,4 +974,24 @@ public class ClipboardOverlayController { mWindowManager.updateViewLayout(decorView, mWindowLayoutParams); } } + + static class ClipboardLogger { + private final UiEventLogger mUiEventLogger; + private boolean mGuarded = false; + + ClipboardLogger(UiEventLogger uiEventLogger) { + mUiEventLogger = uiEventLogger; + } + + void logSessionComplete(@NonNull UiEventLogger.UiEventEnum event) { + if (!mGuarded) { + mGuarded = true; + mUiEventLogger.log(event); + } + } + + void reset() { + mGuarded = false; + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java index de7bf28c01d6..55c1806e1899 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java @@ -215,16 +215,15 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve final boolean cameraBlocked = mSensorPrivacyController .isSensorBlocked(SensorPrivacyManager.Sensors.CAMERA); @DreamOverlayStatusBarView.StatusIconType int iconType = Resources.ID_NULL; - if (micBlocked && cameraBlocked) { - iconType = DreamOverlayStatusBarView.STATUS_ICON_MIC_CAMERA_DISABLED; - } else if (!micBlocked && cameraBlocked) { - iconType = DreamOverlayStatusBarView.STATUS_ICON_CAMERA_DISABLED; - } else if (micBlocked && !cameraBlocked) { - iconType = DreamOverlayStatusBarView.STATUS_ICON_MIC_DISABLED; - } - if (iconType != Resources.ID_NULL) { - showIcon(iconType, true); - } + showIcon( + DreamOverlayStatusBarView.STATUS_ICON_CAMERA_DISABLED, + !micBlocked && cameraBlocked); + showIcon( + DreamOverlayStatusBarView.STATUS_ICON_MIC_DISABLED, + micBlocked && !cameraBlocked); + showIcon( + DreamOverlayStatusBarView.STATUS_ICON_MIC_CAMERA_DISABLED, + micBlocked && cameraBlocked); } private String buildNotificationsContentDescription(int notificationCount) { diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java index 54571448c981..29bb2f42cca5 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java @@ -163,7 +163,8 @@ public interface Complication { COMPLICATION_TYPE_WEATHER, COMPLICATION_TYPE_AIR_QUALITY, COMPLICATION_TYPE_CAST_INFO, - COMPLICATION_TYPE_HOME_CONTROLS + COMPLICATION_TYPE_HOME_CONTROLS, + COMPLICATION_TYPE_SMARTSPACE }) @Retention(RetentionPolicy.SOURCE) @interface ComplicationType {} @@ -175,6 +176,7 @@ public interface Complication { int COMPLICATION_TYPE_AIR_QUALITY = 1 << 3; int COMPLICATION_TYPE_CAST_INFO = 1 << 4; int COMPLICATION_TYPE_HOME_CONTROLS = 1 << 5; + int COMPLICATION_TYPE_SMARTSPACE = 1 << 6; /** * The {@link Host} interface specifies a way a {@link Complication} to communicate with its diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java index dcab90fe7ab9..d5db63dc9093 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java @@ -21,6 +21,7 @@ import static com.android.systemui.dreams.complication.Complication.COMPLICATION import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_DATE; import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_HOME_CONTROLS; import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_NONE; +import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_SMARTSPACE; import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_TIME; import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_WEATHER; @@ -51,6 +52,8 @@ public class ComplicationUtils { return COMPLICATION_TYPE_CAST_INFO; case DreamBackend.COMPLICATION_TYPE_HOME_CONTROLS: return COMPLICATION_TYPE_HOME_CONTROLS; + case DreamBackend.COMPLICATION_TYPE_SMARTSPACE: + return COMPLICATION_TYPE_SMARTSPACE; default: return COMPLICATION_TYPE_NONE; } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java index ac6edba6b3fa..567bdbc01170 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/SmartSpaceComplication.java @@ -52,6 +52,11 @@ public class SmartSpaceComplication implements Complication { return mViewHolderProvider.get(); } + @Override + public int getRequiredTypeAvailability() { + return COMPLICATION_TYPE_SMARTSPACE; + } + /** * {@link CoreStartable} responsbile for registering {@link SmartSpaceComplication} with * SystemUI. diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index 0dc07ac35e3d..a65aed23f5b3 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -185,6 +185,7 @@ public class Flags { new ReleasedFlag(1000); public static final ReleasedFlag DOCK_SETUP_ENABLED = new ReleasedFlag(1001); + public static final UnreleasedFlag ROUNDED_BOX_RIPPLE = new UnreleasedFlag(1002, false); // 1100 - windowing @Keep @@ -207,6 +208,14 @@ public class Flags { public static final SysPropBooleanFlag HIDE_NAVBAR_WINDOW = new SysPropBooleanFlag(1103, "persist.wm.debug.hide_navbar_window", false); + @Keep + public static final SysPropBooleanFlag WM_DESKTOP_WINDOWING = + new SysPropBooleanFlag(1104, "persist.wm.debug.desktop_mode", false); + + @Keep + public static final SysPropBooleanFlag WM_CAPTION_ON_SHELL = + new SysPropBooleanFlag(1105, "persist.wm.debug.caption_on_shell", false); + // 1200 - predictive back @Keep public static final SysPropBooleanFlag WM_ENABLE_PREDICTIVE_BACK = new SysPropBooleanFlag( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt index 3202ecb9a287..df44957ec591 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt @@ -36,6 +36,7 @@ import com.android.systemui.util.kotlin.getOrNull import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf /** Home controls quick affordance data source. */ @@ -50,7 +51,13 @@ constructor( private val appContext = context.applicationContext override val state: Flow<KeyguardQuickAffordanceConfig.State> = - stateInternal(component.getControlsListingController().getOrNull()) + component.canShowWhileLockedSetting.flatMapLatest { canShowWhileLocked -> + if (canShowWhileLocked) { + stateInternal(component.getControlsListingController().getOrNull()) + } else { + flowOf(KeyguardQuickAffordanceConfig.State.Hidden) + } + } override fun onQuickAffordanceClicked( animationController: ActivityLaunchAnimator.Controller?, diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt index dc23684dd517..6124e10144f2 100644 --- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt +++ b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt @@ -22,17 +22,11 @@ import com.android.systemui.log.dagger.LogModule import com.android.systemui.util.collection.RingBuffer import com.google.errorprone.annotations.CompileTimeConstant import java.io.PrintWriter -import java.text.SimpleDateFormat -import java.util.Arrays.stream -import java.util.Locale import java.util.concurrent.ArrayBlockingQueue import java.util.concurrent.BlockingQueue import kotlin.concurrent.thread import kotlin.math.max -const val UNBOUNDED_STACK_TRACE = -1 -const val NESTED_TRACE_DEPTH = 10 - /** * A simple ring buffer of recyclable log messages * @@ -74,18 +68,12 @@ const val NESTED_TRACE_DEPTH = 10 * @param maxSize The maximum number of messages to keep in memory at any one time. Buffers start * out empty and grow up to [maxSize] as new messages are logged. Once the buffer's size reaches * the maximum, it behaves like a ring buffer. - * @param rootStackTraceDepth The number of stack trace elements to be logged for an exception when - * the logBuffer is dumped. Defaulted to -1 [UNBOUNDED_STACK_TRACE] to print the entire stack trace. - * @param nestedStackTraceDepth The number of stack trace elements to be logged for any nested - * exceptions present in [Throwable.cause] or [Throwable.suppressedExceptions]. */ class LogBuffer @JvmOverloads constructor( private val name: String, private val maxSize: Int, private val logcatEchoTracker: LogcatEchoTracker, private val systrace: Boolean = true, - private val rootStackTraceDepth: Int = UNBOUNDED_STACK_TRACE, - private val nestedStackTraceDepth: Int = NESTED_TRACE_DEPTH, ) { private val buffer = RingBuffer(maxSize) { LogMessageImpl.create() } @@ -236,7 +224,7 @@ class LogBuffer @JvmOverloads constructor( val iterationStart = if (tailLength <= 0) { 0 } else { max(0, buffer.size - tailLength) } for (i in iterationStart until buffer.size) { - dumpMessage(buffer[i], pw) + buffer[i].dump(pw) } } @@ -264,76 +252,6 @@ class LogBuffer @JvmOverloads constructor( } } - private fun dumpMessage( - message: LogMessage, - pw: PrintWriter - ) { - val formattedTimestamp = DATE_FORMAT.format(message.timestamp) - val shortLevel = message.level.shortString - val messageToPrint = message.messagePrinter(message) - val tag = message.tag - printLikeLogcat(pw, formattedTimestamp, shortLevel, tag, messageToPrint) - message.exception?.let { ex -> - printException( - pw, - formattedTimestamp, - shortLevel, - ex, - tag, - stackTraceDepth = rootStackTraceDepth) - } - } - - private fun printException( - pw: PrintWriter, - timestamp: String, - level: String, - exception: Throwable, - tag: String, - exceptionMessagePrefix: String = "", - stackTraceDepth: Int = UNBOUNDED_STACK_TRACE - ) { - val message = "$exceptionMessagePrefix$exception" - printLikeLogcat(pw, timestamp, level, tag, message) - var stacktraceStream = stream(exception.stackTrace) - if (stackTraceDepth != UNBOUNDED_STACK_TRACE) { - stacktraceStream = stacktraceStream.limit(stackTraceDepth.toLong()) - } - stacktraceStream.forEach { line -> - printLikeLogcat(pw, timestamp, level, tag, "\tat $line") - } - exception.cause?.let { cause -> - printException(pw, timestamp, level, cause, tag, "Caused by: ", nestedStackTraceDepth) - } - exception.suppressedExceptions.forEach { suppressed -> - printException( - pw, - timestamp, - level, - suppressed, - tag, - "Suppressed: ", - nestedStackTraceDepth - ) - } - } - - private fun printLikeLogcat( - pw: PrintWriter, - formattedTimestamp: String, - shortLogLevel: String, - tag: String, - message: String - ) { - pw.print(formattedTimestamp) - pw.print(" ") - pw.print(shortLogLevel) - pw.print(" ") - pw.print(tag) - pw.print(": ") - pw.println(message) - } - private fun echo(message: LogMessage, toLogcat: Boolean, toSystrace: Boolean) { if (toLogcat || toSystrace) { val strMessage = message.messagePrinter(message) @@ -370,5 +288,4 @@ class LogBuffer @JvmOverloads constructor( typealias MessageInitializer = LogMessage.() -> Unit private const val TAG = "LogBuffer" -private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US) private val FROZEN_MESSAGE = LogMessageImpl.create() diff --git a/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt b/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt index 987aea8bff08..dae2592e116c 100644 --- a/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt +++ b/packages/SystemUI/src/com/android/systemui/log/LogMessage.kt @@ -16,6 +16,10 @@ package com.android.systemui.log +import java.io.PrintWriter +import java.text.SimpleDateFormat +import java.util.Locale + /** * Generic data class for storing messages logged to a [LogBuffer] * @@ -50,6 +54,17 @@ interface LogMessage { var bool2: Boolean var bool3: Boolean var bool4: Boolean + + /** + * Function that dumps the [LogMessage] to the provided [writer]. + */ + fun dump(writer: PrintWriter) { + val formattedTimestamp = DATE_FORMAT.format(timestamp) + val shortLevel = level.shortString + val messageToPrint = messagePrinter(this) + printLikeLogcat(writer, formattedTimestamp, shortLevel, tag, messageToPrint) + exception?.printStackTrace(writer) + } } /** @@ -61,3 +76,21 @@ interface LogMessage { * of the printer for each call, thwarting our attempts at avoiding any sort of allocation. */ typealias MessagePrinter = LogMessage.() -> String + +private fun printLikeLogcat( + pw: PrintWriter, + formattedTimestamp: String, + shortLogLevel: String, + tag: String, + message: String +) { + pw.print(formattedTimestamp) + pw.print(" ") + pw.print(shortLogLevel) + pw.print(" ") + pw.print(tag) + pw.print(": ") + pw.println(message) +} + +private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt index 81efdf591b41..e0c8d66cb6fd 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt @@ -266,11 +266,11 @@ class MediaDataFilter @Inject constructor( } /** - * Are there any media notifications active, including the recommendation? + * Are there any active media entries, including the recommendation? */ - fun hasActiveMediaOrRecommendation() = - userEntries.any { it.value.active } || - (smartspaceMediaData.isActive && smartspaceMediaData.isValid()) + fun hasActiveMediaOrRecommendation() = userEntries.any { it.value.active } || + (smartspaceMediaData.isActive && + (smartspaceMediaData.isValid() || reactivatedKey != null)) /** * Are there any media entries we should display? diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java index e9b6af44ddf3..e360d10d9362 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java @@ -263,6 +263,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { } private void onGroupActionTriggered(boolean isChecked, MediaDevice device) { + disableSeekBar(); if (isChecked && isDeviceIncluded(mController.getSelectableMediaDevice(), device)) { mController.addDeviceToPlayMedia(device); } else if (!isChecked && isDeviceIncluded(mController.getDeselectableMediaDevice(), diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java index bec67397a926..3b4ca48046eb 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java @@ -273,6 +273,8 @@ public abstract class MediaOutputBaseAdapter extends void initSeekbar(MediaDevice device, boolean isCurrentSeekbarInvisible) { if (!mController.isVolumeControlEnabled(device)) { disableSeekBar(); + } else { + enableSeekBar(); } mSeekBar.setMaxVolume(device.getMaxVolume()); final int currentVolume = device.getCurrentVolume(); @@ -417,11 +419,16 @@ public abstract class MediaOutputBaseAdapter extends return drawable; } - private void disableSeekBar() { + protected void disableSeekBar() { mSeekBar.setEnabled(false); mSeekBar.setOnTouchListener((v, event) -> true); } + private void enableSeekBar() { + mSeekBar.setEnabled(true); + mSeekBar.setOnTouchListener((v, event) -> false); + } + protected void setUpDeviceIcon(MediaDevice device) { ThreadUtils.postOnBackgroundThread(() -> { Icon icon = mController.getDeviceIconCompat(device).toIcon(mContext); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java index 5d7af522176a..6fe06e085556 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputMetricLogger.java @@ -194,6 +194,11 @@ public class MediaOutputMetricLogger { } private int getLoggingDeviceType(MediaDevice device, boolean isSourceDevice) { + if (device == null) { + return isSourceDevice + ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__UNKNOWN_TYPE + : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__UNKNOWN_TYPE; + } switch (device.getDeviceType()) { case MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE: return isSourceDevice @@ -229,6 +234,9 @@ public class MediaOutputMetricLogger { } private int getInteractionDeviceType(MediaDevice device) { + if (device == null) { + return SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__UNKNOWN_TYPE; + } switch (device.getDeviceType()) { case MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE: return SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__BUILTIN_SPEAKER; diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt index 5f478ce32590..9ab83b84277e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt @@ -56,7 +56,7 @@ abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>( internal val logger: MediaTttLogger, internal val windowManager: WindowManager, private val viewUtil: ViewUtil, - @Main private val mainExecutor: DelayableExecutor, + @Main internal val mainExecutor: DelayableExecutor, private val accessibilityManager: AccessibilityManager, private val configurationController: ConfigurationController, private val powerManager: PowerManager, @@ -205,13 +205,15 @@ abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>( * * @param appPackageName the package name of the app playing the media. Will be used to fetch * the app icon and app name if overrides aren't provided. + * + * @return the content description of the icon. */ internal fun setIcon( currentChipView: ViewGroup, appPackageName: String?, appIconDrawableOverride: Drawable? = null, appNameOverride: CharSequence? = null, - ) { + ): CharSequence { val appIconView = currentChipView.requireViewById<CachingIconView>(R.id.app_icon) val iconInfo = getIconInfo(appPackageName) @@ -224,6 +226,7 @@ abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>( appIconView.contentDescription = appNameOverride ?: iconInfo.iconName appIconView.setImageDrawable(appIconDrawableOverride ?: iconInfo.icon) + return appIconView.contentDescription.toString() } /** diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt index 3ea11b8aa4dd..b94b8bfabfc1 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt @@ -122,13 +122,12 @@ class MediaTttChipControllerSender @Inject constructor( val chipState = newChipInfo.state // App icon - setIcon(currentChipView, newChipInfo.routeInfo.packageName) + val iconName = setIcon(currentChipView, newChipInfo.routeInfo.packageName) // Text val otherDeviceName = newChipInfo.routeInfo.name.toString() - currentChipView.requireViewById<TextView>(R.id.text).apply { - text = chipState.getChipTextString(context, otherDeviceName) - } + val chipText = chipState.getChipTextString(context, otherDeviceName) + currentChipView.requireViewById<TextView>(R.id.text).text = chipText // Loading currentChipView.requireViewById<View>(R.id.loading).visibility = @@ -145,17 +144,29 @@ class MediaTttChipControllerSender @Inject constructor( // Failure currentChipView.requireViewById<View>(R.id.failure_icon).visibility = chipState.isTransferFailure.visibleIfTrue() + + // For accessibility + currentChipView.requireViewById<ViewGroup>( + R.id.media_ttt_sender_chip_inner + ).contentDescription = "$iconName $chipText" } override fun animateChipIn(chipView: ViewGroup) { + val chipInnerView = chipView.requireViewById<ViewGroup>(R.id.media_ttt_sender_chip_inner) ViewHierarchyAnimator.animateAddition( - chipView.requireViewById<ViewGroup>(R.id.media_ttt_sender_chip_inner), + chipInnerView, ViewHierarchyAnimator.Hotspot.TOP, Interpolators.EMPHASIZED_DECELERATE, - duration = 500L, + duration = ANIMATION_DURATION, includeMargins = true, includeFadeIn = true, ) + + // We can only request focus once the animation finishes. + mainExecutor.executeDelayed( + { chipInnerView.requestAccessibilityFocus() }, + ANIMATION_DURATION + ) } override fun removeChip(removalReason: String) { @@ -186,3 +197,4 @@ data class ChipSenderInfo( } const val SENDER_TAG = "MediaTapToTransferSender" +private const val ANIMATION_DURATION = 500L diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java index 2d7a809644c0..3789cbb1fb65 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java @@ -302,10 +302,6 @@ public class NavigationBarController implements */ @VisibleForTesting void createNavigationBar(Display display, Bundle savedState, RegisterStatusBarResult result) { - if (initializeTaskbarIfNecessary()) { - return; - } - if (display == null) { return; } @@ -315,7 +311,7 @@ public class NavigationBarController implements // We may show TaskBar on the default display for large screen device. Don't need to create // navigation bar for this case. - if (mIsTablet && isOnDefaultDisplay) { + if (isOnDefaultDisplay && initializeTaskbarIfNecessary()) { return; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java index 4552abd402b0..77652c9ddf2f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java @@ -110,6 +110,11 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D private Context mUserContext; private UserTracker mUserTracker; private SecureSettings mSecureSettings; + // Keep track of whether mTilesList contains the same information as the Settings value. + // This is a performance optimization to reduce the number of blocking calls to Settings from + // main thread. + // This is enforced by only cleaning the flag at the end of a successful run of #onTuningChanged + private boolean mTilesListDirty = true; private final TileServiceRequestController mTileServiceRequestController; private TileLifecycleManager.Factory mTileLifeCycleManagerFactory; @@ -374,6 +379,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D // the ones that are in the setting, update the Setting. saveTilesToSettings(mTileSpecs); } + mTilesListDirty = false; for (int i = 0; i < mCallbacks.size(); i++) { mCallbacks.get(i).onTilesChanged(); } @@ -436,6 +442,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D ); } + // When calling this, you may want to modify mTilesListDirty accordingly. @MainThread private void saveTilesToSettings(List<String> tileSpecs) { mSecureSettings.putStringForUser(TILES_SETTING, TextUtils.join(",", tileSpecs), @@ -445,9 +452,15 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D @MainThread private void changeTileSpecs(Predicate<List<String>> changeFunction) { - final String setting = mSecureSettings.getStringForUser(TILES_SETTING, mCurrentUser); - final List<String> tileSpecs = loadTileSpecs(mContext, setting); + final List<String> tileSpecs; + if (!mTilesListDirty) { + tileSpecs = new ArrayList<>(mTileSpecs); + } else { + tileSpecs = loadTileSpecs(mContext, + mSecureSettings.getStringForUser(TILES_SETTING, mCurrentUser)); + } if (changeFunction.test(tileSpecs)) { + mTilesListDirty = true; saveTilesToSettings(tileSpecs); } } @@ -507,6 +520,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D } } if (DEBUG) Log.d(TAG, "saveCurrentTiles " + newTiles); + mTilesListDirty = true; saveTilesToSettings(newTiles); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt index 86ef85824eb0..ab795faf57e6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt @@ -69,36 +69,63 @@ class QSLogger @Inject constructor( }) } - fun logTileClick(tileSpec: String, statusBarState: Int, state: Int) { + fun logTileClick(tileSpec: String, statusBarState: Int, state: Int, eventId: Int) { log(DEBUG, { str1 = tileSpec - int1 = statusBarState + int1 = eventId str2 = StatusBarState.toString(statusBarState) str3 = toStateString(state) }, { - "[$str1] Tile clicked. StatusBarState=$str2. TileState=$str3" + "[$str1][$int1] Tile clicked. StatusBarState=$str2. TileState=$str3" }) } - fun logTileSecondaryClick(tileSpec: String, statusBarState: Int, state: Int) { + fun logHandleClick(tileSpec: String, eventId: Int) { log(DEBUG, { str1 = tileSpec - int1 = statusBarState + int1 = eventId + }, { + "[$str1][$int1] Tile handling click." + }) + } + + fun logTileSecondaryClick(tileSpec: String, statusBarState: Int, state: Int, eventId: Int) { + log(DEBUG, { + str1 = tileSpec + int1 = eventId str2 = StatusBarState.toString(statusBarState) str3 = toStateString(state) }, { - "[$str1] Tile long clicked. StatusBarState=$str2. TileState=$str3" + "[$str1][$int1] Tile secondary clicked. StatusBarState=$str2. TileState=$str3" + }) + } + + fun logHandleSecondaryClick(tileSpec: String, eventId: Int) { + log(DEBUG, { + str1 = tileSpec + int1 = eventId + }, { + "[$str1][$int1] Tile handling secondary click." }) } - fun logTileLongClick(tileSpec: String, statusBarState: Int, state: Int) { + fun logTileLongClick(tileSpec: String, statusBarState: Int, state: Int, eventId: Int) { log(DEBUG, { str1 = tileSpec - int1 = statusBarState + int1 = eventId str2 = StatusBarState.toString(statusBarState) str3 = toStateString(state) }, { - "[$str1] Tile long clicked. StatusBarState=$str2. TileState=$str3" + "[$str1][$int1] Tile long clicked. StatusBarState=$str2. TileState=$str3" + }) + } + + fun logHandleLongClick(tileSpec: String, eventId: Int) { + log(DEBUG, { + str1 = tileSpec + int1 = eventId + }, { + "[$str1][$int1] Tile handling long click." }) } @@ -144,4 +171,4 @@ class QSLogger @Inject constructor( ) { buffer.log(TAG, logLevel, initializer, printer) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java index 740e12ab5839..2cffe8951b56 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -105,6 +105,9 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy private final FalsingManager mFalsingManager; protected final QSLogger mQSLogger; private volatile int mReadyState; + // Keeps track of the click event, to match it with the handling in the background thread + // Only read and modified in main thread (where click events come through). + private int mClickEventId = 0; private final ArrayList<Callback> mCallbacks = new ArrayList<>(); private final Object mStaleListener = new Object(); @@ -295,9 +298,11 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy mStatusBarStateController.getState()))); mUiEventLogger.logWithInstanceId(QSEvent.QS_ACTION_CLICK, 0, getMetricsSpec(), getInstanceId()); - mQSLogger.logTileClick(mTileSpec, mStatusBarStateController.getState(), mState.state); + final int eventId = mClickEventId++; + mQSLogger.logTileClick(mTileSpec, mStatusBarStateController.getState(), mState.state, + eventId); if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { - mHandler.obtainMessage(H.CLICK, view).sendToTarget(); + mHandler.obtainMessage(H.CLICK, eventId, 0, view).sendToTarget(); } } @@ -307,9 +312,10 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy mStatusBarStateController.getState()))); mUiEventLogger.logWithInstanceId(QSEvent.QS_ACTION_SECONDARY_CLICK, 0, getMetricsSpec(), getInstanceId()); + final int eventId = mClickEventId++; mQSLogger.logTileSecondaryClick(mTileSpec, mStatusBarStateController.getState(), - mState.state); - mHandler.obtainMessage(H.SECONDARY_CLICK, view).sendToTarget(); + mState.state, eventId); + mHandler.obtainMessage(H.SECONDARY_CLICK, eventId, 0, view).sendToTarget(); } @Override @@ -319,8 +325,10 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy mStatusBarStateController.getState()))); mUiEventLogger.logWithInstanceId(QSEvent.QS_ACTION_LONG_PRESS, 0, getMetricsSpec(), getInstanceId()); - mQSLogger.logTileLongClick(mTileSpec, mStatusBarStateController.getState(), mState.state); - mHandler.obtainMessage(H.LONG_CLICK, view).sendToTarget(); + final int eventId = mClickEventId++; + mQSLogger.logTileLongClick(mTileSpec, mStatusBarStateController.getState(), mState.state, + eventId); + mHandler.obtainMessage(H.LONG_CLICK, eventId, 0, view).sendToTarget(); } public LogMaker populate(LogMaker logMaker) { @@ -590,13 +598,16 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy mContext, mEnforcedAdmin); mActivityStarter.postStartActivityDismissingKeyguard(intent, 0); } else { + mQSLogger.logHandleClick(mTileSpec, msg.arg1); handleClick((View) msg.obj); } } else if (msg.what == SECONDARY_CLICK) { name = "handleSecondaryClick"; + mQSLogger.logHandleSecondaryClick(mTileSpec, msg.arg1); handleSecondaryClick((View) msg.obj); } else if (msg.what == LONG_CLICK) { name = "handleLongClick"; + mQSLogger.logHandleLongClick(mTileSpec, msg.arg1); handleLongClick((View) msg.obj); } else if (msg.what == REFRESH_STATE) { name = "handleRefreshState"; diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt index 0a8e6e21d5b3..56a187429af6 100644 --- a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt +++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt @@ -39,7 +39,7 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C ROUNDED_BOX, ELLIPSE } - + //language=AGSL companion object { private const val SHADER_UNIFORMS = """uniform vec2 in_center; uniform vec2 in_size; diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt index 0cacbc2819c5..6de46483892b 100644 --- a/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt +++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleShaderUtilLibrary.kt @@ -17,6 +17,7 @@ package com.android.systemui.ripple /** A common utility functions that are used for computing [RippleShader]. */ class RippleShaderUtilLibrary { + //language=AGSL companion object { const val SHADER_LIB = """ float triangleNoise(vec2 n) { diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt index 83d9f2da1db1..8b0120177268 100644 --- a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt @@ -39,7 +39,9 @@ private const val RIPPLE_DEFAULT_COLOR: Int = 0xffffffff.toInt() open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) { private lateinit var rippleShader: RippleShader - private lateinit var rippleShape: RippleShape + lateinit var rippleShape: RippleShape + private set + private val ripplePaint = Paint() var rippleInProgress: Boolean = false diff --git a/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt b/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt index 7f26146f541d..5e256c653992 100644 --- a/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt +++ b/packages/SystemUI/src/com/android/systemui/ripple/SdfShaderLibrary.kt @@ -17,6 +17,7 @@ package com.android.systemui.ripple /** Library class that contains 2D signed distance functions. */ class SdfShaderLibrary { + //language=AGSL companion object { const val CIRCLE_SDF = """ float sdCircle(vec2 p, float r) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java index 68d35f9679ed..824d3a3f5af1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java @@ -34,6 +34,8 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.statusbar.dagger.CentralSurfacesModule; import com.android.systemui.statusbar.notification.collection.NotifCollection; +import com.android.systemui.statusbar.notification.collection.PipelineDumpable; +import com.android.systemui.statusbar.notification.collection.PipelineDumper; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins; import com.android.systemui.util.time.SystemClock; @@ -52,7 +54,8 @@ import javax.inject.Inject; */ @SysUISingleton @SuppressLint("OverrideAbstract") -public class NotificationListener extends NotificationListenerWithPlugins { +public class NotificationListener extends NotificationListenerWithPlugins implements + PipelineDumpable { private static final String TAG = "NotificationListener"; private static final boolean DEBUG = CentralSurfaces.DEBUG; private static final long MAX_RANKING_DELAY_MILLIS = 500L; @@ -255,6 +258,11 @@ public class NotificationListener extends NotificationListenerWithPlugins { } } + @Override + public void dumpPipeline(@NonNull PipelineDumper d) { + d.dump("notificationHandlers", mNotificationHandlers); + } + private static Ranking getRankingOrTemporaryStandIn(RankingMap rankingMap, String key) { Ranking ranking = new Ranking(); if (!rankingMap.getRanking(key, ranking)) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index 351a4bea2947..68bf69a3ea9d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -142,7 +142,7 @@ import javax.inject.Inject; */ @MainThread @SysUISingleton -public class NotifCollection implements Dumpable { +public class NotifCollection implements Dumpable, PipelineDumpable { private final IStatusBarService mStatusBarService; private final SystemClock mClock; private final NotifPipelineFlags mNotifPipelineFlags; @@ -870,6 +870,14 @@ public class NotifCollection implements Dumpable { } } + @Override + public void dumpPipeline(@NonNull PipelineDumper d) { + d.dump("notifCollectionListeners", mNotifCollectionListeners); + d.dump("lifetimeExtenders", mLifetimeExtenders); + d.dump("dismissInterceptors", mDismissInterceptors); + d.dump("buildListener", mBuildListener); + } + private final BatchableNotificationHandler mNotifHandler = new BatchableNotificationHandler() { @Override public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineDumpable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineDumpable.kt new file mode 100644 index 000000000000..a1aec3f382bf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineDumpable.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2022 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.systemui.statusbar.notification.collection + +interface PipelineDumpable { + fun dumpPipeline(d: PipelineDumper) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineDumper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineDumper.kt new file mode 100644 index 000000000000..a10c74523015 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineDumper.kt @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2022 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.systemui.statusbar.notification.collection + +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender +import com.android.systemui.util.asIndenting +import com.android.systemui.util.withIncreasedIndent +import java.io.PrintWriter + +class PipelineDumper(pw: PrintWriter) { + private val ipw = pw.asIndenting() + + fun print(a: Any?) = ipw.print(a) + fun println(a: Any?) = ipw.println(a) + fun withIncreasedIndent(b: () -> Unit) = ipw.withIncreasedIndent(b) + fun withIncreasedIndent(r: Runnable) = ipw.withIncreasedIndent(r) + + fun dump(label: String, value: Any?) { + ipw.print("$label: ") + dump(value) + } + + private fun dump(value: Any?) = when (value) { + null, is String, is Int -> println(value) + is Collection<*> -> dumpCollection(value) + else -> { + println(value.fullPipelineName) + withIncreasedIndent { (value as? PipelineDumpable)?.dumpPipeline(this) } + } + } + + private fun dumpCollection(values: Collection<Any?>) { + println(values.size) + withIncreasedIndent { values.forEach { dump(it) } } + } +} + +private val Any.bareClassName: String get() { + val className = javaClass.name + val packageName = javaClass.`package`.name + return className.substring(packageName.length + 1) +} + +private val Any.barePipelineName: String? get() = when (this) { + is NotifLifetimeExtender -> name + is NotifDismissInterceptor -> name + is Pluggable<*> -> name + else -> null +} + +private val Any.fullPipelineName: String get() = + barePipelineName?.let { "\"$it\" ($bareClassName)" } ?: bareClassName diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index 075a0dc7555e..420f21db2c73 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java @@ -87,7 +87,7 @@ import javax.inject.Inject; */ @MainThread @SysUISingleton -public class ShadeListBuilder implements Dumpable { +public class ShadeListBuilder implements Dumpable, PipelineDumpable { private final SystemClock mSystemClock; private final ShadeListBuilderLogger mLogger; private final NotificationInteractionTracker mInteractionTracker; @@ -1396,6 +1396,21 @@ public class ShadeListBuilder implements Dumpable { "\t\t")); } + @Override + public void dumpPipeline(@NonNull PipelineDumper d) { + d.dump("choreographer", mChoreographer); + d.dump("notifPreGroupFilters", mNotifPreGroupFilters); + d.dump("onBeforeTransformGroupsListeners", mOnBeforeTransformGroupsListeners); + d.dump("notifPromoters", mNotifPromoters); + d.dump("onBeforeSortListeners", mOnBeforeSortListeners); + d.dump("notifSections", mNotifSections); + d.dump("notifComparators", mNotifComparators); + d.dump("onBeforeFinalizeFilterListeners", mOnBeforeFinalizeFilterListeners); + d.dump("notifFinalizeFilters", mNotifFinalizeFilters); + d.dump("onBeforeRenderListListeners", mOnBeforeRenderListListeners); + d.dump("onRenderListListener", mOnRenderListListener); + } + /** See {@link #setOnRenderListListener(OnRenderListListener)} */ public interface OnRenderListListener { /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java index 050b4c113231..98f2167ebfa6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java @@ -32,6 +32,8 @@ import com.android.systemui.Dumpable; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationListener.NotificationHandler; +import com.android.systemui.statusbar.notification.collection.PipelineDumpable; +import com.android.systemui.statusbar.notification.collection.PipelineDumper; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.time.SystemClock; @@ -63,7 +65,7 @@ import javax.inject.Inject; * passed along to the NotifCollection. */ @MainThread -public class GroupCoalescer implements Dumpable { +public class GroupCoalescer implements Dumpable, PipelineDumpable { private final DelayableExecutor mMainExecutor; private final SystemClock mClock; private final GroupCoalescerLogger mLogger; @@ -314,6 +316,11 @@ public class GroupCoalescer implements Dumpable { } } + @Override + public void dumpPipeline(@NonNull PipelineDumper d) { + d.dump("handler", mHandler); + } + private final Comparator<CoalescedEvent> mEventComparator = (o1, o2) -> { int cmp = Boolean.compare( o2.getSbn().getNotification().isGroupSummary(), diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt index 891e25ef6c25..1399385e7654 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt @@ -15,24 +15,22 @@ */ package com.android.systemui.statusbar.notification.collection.coordinator -import com.android.systemui.Dumpable -import com.android.systemui.dump.DumpManager import com.android.systemui.statusbar.notification.NotifPipelineFlags import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.PipelineDumpable +import com.android.systemui.statusbar.notification.collection.PipelineDumper import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner -import java.io.PrintWriter import javax.inject.Inject /** * Handles the attachment of [Coordinator]s to the [NotifPipeline] so that the * Coordinators can register their respective callbacks. */ -interface NotifCoordinators : Coordinator, Dumpable +interface NotifCoordinators : Coordinator, PipelineDumpable @CoordinatorScope class NotifCoordinatorsImpl @Inject constructor( - dumpManager: DumpManager, notifPipelineFlags: NotifPipelineFlags, dataStoreCoordinator: DataStoreCoordinator, hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator, @@ -66,8 +64,6 @@ class NotifCoordinatorsImpl @Inject constructor( * Creates all the coordinators. */ init { - dumpManager.registerDumpable(TAG, this) - // TODO(b/208866714): formalize the system by which some coordinators may be required by the // pipeline, such as this DataStoreCoordinator which cannot be removed, as it's a critical // glue between the pipeline and parts of SystemUI which depend on pipeline output via the @@ -121,15 +117,12 @@ class NotifCoordinatorsImpl @Inject constructor( pipeline.setSections(mOrderedSections) } - override fun dump(pw: PrintWriter, args: Array<String>) { - pw.println() - pw.println("$TAG:") - for (c in mCoordinators) { - pw.println("\t${c.javaClass}") - } - for (s in mOrderedSections) { - pw.println("\t${s.name}") - } + /* + * As part of the NotifPipeline dumpable, dumps the list of coordinators; sections are omitted + * as they are dumped in the RenderStageManager instead. + */ + override fun dumpPipeline(d: PipelineDumper) = with(d) { + dump("coordinators", mCoordinators) } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java index 24ef5808b2e3..a34d033afcaa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java @@ -18,6 +18,8 @@ package com.android.systemui.statusbar.notification.collection.init; import android.util.Log; +import androidx.annotation.NonNull; + import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; @@ -25,12 +27,15 @@ import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl; import com.android.systemui.statusbar.notification.collection.NotifPipeline; +import com.android.systemui.statusbar.notification.collection.PipelineDumpable; +import com.android.systemui.statusbar.notification.collection.PipelineDumper; import com.android.systemui.statusbar.notification.collection.ShadeListBuilder; import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer; import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinators; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; import com.android.systemui.statusbar.notification.collection.render.NotifStackController; import com.android.systemui.statusbar.notification.collection.render.RenderStageManager; +import com.android.systemui.statusbar.notification.collection.render.ShadeViewManager; import com.android.systemui.statusbar.notification.collection.render.ShadeViewManagerFactory; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; @@ -42,7 +47,7 @@ import javax.inject.Inject; * Initialization code for the new notification pipeline. */ @SysUISingleton -public class NotifPipelineInitializer implements Dumpable { +public class NotifPipelineInitializer implements Dumpable, PipelineDumpable { private final NotifPipeline mPipelineWrapper; private final GroupCoalescer mGroupCoalescer; private final NotifCollection mNotifCollection; @@ -53,6 +58,9 @@ public class NotifPipelineInitializer implements Dumpable { private final DumpManager mDumpManager; private final ShadeViewManagerFactory mShadeViewManagerFactory; + /* These are saved just for dumping. */ + private ShadeViewManager mShadeViewManager; + private NotificationListener mNotificationService; @Inject public NotifPipelineInitializer( @@ -83,9 +91,10 @@ public class NotifPipelineInitializer implements Dumpable { NotificationRowBinderImpl rowBinder, NotificationListContainer listContainer, NotifStackController stackController) { - mDumpManager.registerDumpable("NotifPipeline", this); + mNotificationService = notificationService; + // Setup inflation mNotifInflater.setRowBinder(rowBinder); @@ -93,13 +102,12 @@ public class NotifPipelineInitializer implements Dumpable { mNotifPluggableCoordinators.attach(mPipelineWrapper); // Wire up pipeline - mShadeViewManagerFactory - .create(listContainer, stackController) - .attach(mRenderStageManager); + mShadeViewManager = mShadeViewManagerFactory.create(listContainer, stackController); + mShadeViewManager.attach(mRenderStageManager); mRenderStageManager.attach(mListBuilder); mListBuilder.attach(mNotifCollection); mNotifCollection.attach(mGroupCoalescer); - mGroupCoalescer.attach(notificationService); + mGroupCoalescer.attach(mNotificationService); Log.d(TAG, "Notif pipeline initialized." + " rendering=" + true); @@ -107,8 +115,37 @@ public class NotifPipelineInitializer implements Dumpable { @Override public void dump(PrintWriter pw, String[] args) { - mNotifPluggableCoordinators.dump(pw, args); - mGroupCoalescer.dump(pw, args); + dumpPipeline(new PipelineDumper(pw)); + } + + @Override + public void dumpPipeline(@NonNull PipelineDumper d) { + d.println("STAGE 0: SETUP"); + d.dump("notifPluggableCoordinators", mNotifPluggableCoordinators); + d.println(""); + + d.println("STAGE 1: LISTEN"); + d.dump("notificationService", mNotificationService); + d.println(""); + + d.println("STAGE 2: BATCH EVENTS"); + d.dump("groupCoalescer", mGroupCoalescer); + d.println(""); + + d.println("STAGE 3: COLLECT"); + d.dump("notifCollection", mNotifCollection); + d.println(""); + + d.println("STAGE 4: BUILD LIST"); + d.dump("listBuilder", mListBuilder); + d.println(""); + + d.println("STAGE 5: DISPATCH RENDER"); + d.dump("renderStageManager", mRenderStageManager); + d.println(""); + + d.println("STAGE 6: UPDATE SHADE"); + d.dump("shadeViewManager", mShadeViewManager); } private static final String TAG = "NotifPipeline"; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt index ea66f3b6dd42..9765e8f1e4fa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification.collection.listbuilder +import com.android.systemui.statusbar.notification.collection.PipelineDumpable +import com.android.systemui.statusbar.notification.collection.PipelineDumper import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner import com.android.systemui.statusbar.notification.collection.render.NodeController @@ -24,10 +26,18 @@ import com.android.systemui.statusbar.notification.stack.PriorityBucket data class NotifSection( val sectioner: NotifSectioner, val index: Int -) { +) : PipelineDumpable { @PriorityBucket val bucket: Int = sectioner.bucket val label: String = "$index:$bucket:${sectioner.name}" val headerController: NodeController? = sectioner.headerNodeController val comparator: NotifComparator? = sectioner.comparator + + override fun dumpPipeline(d: PipelineDumper) = with(d) { + dump("index", index) + dump("bucket", bucket) + dump("sectioner", sectioner) + dump("headerController", headerController) + dump("comparator", comparator) + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt index a9c398726138..7a37846ac97b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt @@ -20,6 +20,8 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.PipelineDumpable +import com.android.systemui.statusbar.notification.collection.PipelineDumper import com.android.systemui.statusbar.notification.collection.ShadeListBuilder import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderEntryListener import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener @@ -33,7 +35,7 @@ import javax.inject.Inject * provided to [setViewRenderer]. */ @SysUISingleton -class RenderStageManager @Inject constructor() { +class RenderStageManager @Inject constructor() : PipelineDumpable { private val onAfterRenderListListeners = mutableListOf<OnAfterRenderListListener>() private val onAfterRenderGroupListeners = mutableListOf<OnAfterRenderGroupListener>() private val onAfterRenderEntryListeners = mutableListOf<OnAfterRenderEntryListener>() @@ -75,6 +77,13 @@ class RenderStageManager @Inject constructor() { onAfterRenderEntryListeners.add(listener) } + override fun dumpPipeline(d: PipelineDumper) = with(d) { + dump("ViewRenderer", viewRenderer) + dump("OnAfterRenderListListeners", onAfterRenderListListeners) + dump("OnAfterRenderGroupListeners", onAfterRenderGroupListeners) + dump("OnAfterRenderEntryListeners", onAfterRenderEntryListeners) + } + private fun dispatchOnAfterRenderList( viewRenderer: NotifViewRenderer, entries: List<ListEntry> @@ -139,4 +148,4 @@ class RenderStageManager @Inject constructor() { } } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt index b76169f111db..2073e92cd45b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt @@ -17,6 +17,8 @@ package com.android.systemui.statusbar.notification.collection.render import android.view.View +import com.android.systemui.statusbar.notification.collection.PipelineDumpable +import com.android.systemui.statusbar.notification.collection.PipelineDumper import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView import com.android.systemui.statusbar.notification.stack.NotificationListContainer @@ -28,7 +30,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationListContain class RootNodeController( private val listContainer: NotificationListContainer, override val view: View -) : NodeController { +) : NodeController, PipelineDumpable { override val nodeLabel: String = "<root>" override fun getChildAt(index: Int): View? { @@ -59,4 +61,8 @@ class RootNodeController( listContainer.setChildTransferInProgress(false) } } + + override fun dumpPipeline(d: PipelineDumper) = with(d) { + dump("listContainer", listContainer) + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt index 51dc72848d9e..df8e87fa413b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt @@ -22,6 +22,8 @@ import com.android.systemui.statusbar.notification.NotificationSectionsFeatureMa import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.PipelineDumpable +import com.android.systemui.statusbar.notification.collection.PipelineDumper import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider import com.android.systemui.statusbar.notification.stack.NotificationListContainer import com.android.systemui.util.traceSection @@ -43,12 +45,12 @@ class ShadeViewManager @AssistedInject constructor( nodeSpecBuilderLogger: NodeSpecBuilderLogger, shadeViewDifferLogger: ShadeViewDifferLogger, private val viewBarn: NotifViewBarn -) { +) : PipelineDumpable { // We pass a shim view here because the listContainer may not actually have a view associated // with it and the differ never actually cares about the root node's view. private val rootController = RootNodeController(listContainer, View(context)) private val specBuilder = NodeSpecBuilder(mediaContainerController, featureManager, - sectionHeaderVisibilityProvider, viewBarn, nodeSpecBuilderLogger) + sectionHeaderVisibilityProvider, viewBarn, nodeSpecBuilderLogger) private val viewDiffer = ShadeViewDiffer(rootController, shadeViewDifferLogger) /** Method for attaching this manager to the pipeline. */ @@ -56,6 +58,12 @@ class ShadeViewManager @AssistedInject constructor( renderStageManager.setViewRenderer(viewRenderer) } + override fun dumpPipeline(d: PipelineDumper) = with(d) { + dump("rootController", rootController) + dump("specBuilder", specBuilder) + dump("viewDiffer", viewDiffer) + } + private val viewRenderer = object : NotifViewRenderer { override fun onRenderList(notifList: List<ListEntry>) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index e5835a836d39..cc539b01b894 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -86,6 +86,8 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.PipelineDumpable; +import com.android.systemui.statusbar.notification.collection.PipelineDumper; import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy; import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy.OnGroupChangeListener; import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager; @@ -1560,7 +1562,8 @@ public class NotificationStackScrollLayoutController { } } - private class NotificationListContainerImpl implements NotificationListContainer { + private class NotificationListContainerImpl implements NotificationListContainer, + PipelineDumpable { @Override public void setChildTransferInProgress(boolean childTransferInProgress) { @@ -1706,6 +1709,12 @@ public class NotificationStackScrollLayoutController { public void setWillExpand(boolean willExpand) { mView.setWillExpand(willExpand); } + + @Override + public void dumpPipeline(@NonNull PipelineDumper d) { + d.dump("NotificationStackScrollLayoutController.this", + NotificationStackScrollLayoutController.this); + } } class TouchHandler implements Gefingerpoken { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index ee45c42b74fc..896e3e53946a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -31,7 +31,7 @@ import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_ import static androidx.lifecycle.Lifecycle.State.RESUMED; import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME; -import static com.android.systemui.charging.WirelessChargingLayout.UNKNOWN_BATTERY_LEVEL; +import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL; import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP; import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT; @@ -172,6 +172,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSFragment; import com.android.systemui.qs.QSPanelController; import com.android.systemui.recents.ScreenPinningRequest; +import com.android.systemui.ripple.RippleShader.RippleShape; import com.android.systemui.scrim.ScrimView; import com.android.systemui.settings.brightness.BrightnessSliderController; import com.android.systemui.shade.NotificationPanelViewController; @@ -2211,7 +2212,8 @@ public class CentralSurfacesImpl extends CoreStartable implements public void onAnimationEnded() { mNotificationShadeWindowController.setRequestTopUi(false, TAG); } - }, false, sUiEventLogger).show(animationDelay); + }, /* isDozing= */ false, RippleShape.CIRCLE, + sUiEventLogger).show(animationDelay); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java index e22a896227ef..4c762702892a 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java @@ -405,8 +405,8 @@ public class BubblesManager implements Dumpable { } @Override - public void onEntryUpdated(NotificationEntry entry) { - BubblesManager.this.onEntryUpdated(entry); + public void onEntryUpdated(NotificationEntry entry, boolean fromSystem) { + BubblesManager.this.onEntryUpdated(entry, fromSystem); } @Override @@ -444,9 +444,10 @@ public class BubblesManager implements Dumpable { } } - void onEntryUpdated(NotificationEntry entry) { + void onEntryUpdated(NotificationEntry entry, boolean fromSystem) { + boolean shouldBubble = mNotificationInterruptStateProvider.shouldBubbleUp(entry); mBubbles.onEntryUpdated(notifToBubbleEntry(entry), - mNotificationInterruptStateProvider.shouldBubbleUp(entry)); + shouldBubble, fromSystem); } void onEntryRemoved(NotificationEntry entry) { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 8d27f2422f66..c67737136b3b 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -22,6 +22,7 @@ import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT; +import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT; import static com.google.common.truth.Truth.assertThat; @@ -109,6 +110,7 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.MockitoSession; +import org.mockito.internal.util.reflection.FieldSetter; import java.util.ArrayList; import java.util.Arrays; @@ -200,9 +202,10 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { private ArgumentCaptor<CancellationSignal> mCancellationSignalCaptor; // Direct executor - private Executor mBackgroundExecutor = Runnable::run; - private Executor mMainExecutor = Runnable::run; + private final Executor mBackgroundExecutor = Runnable::run; + private final Executor mMainExecutor = Runnable::run; private TestableLooper mTestableLooper; + private Handler mHandler; private TestableKeyguardUpdateMonitor mKeyguardUpdateMonitor; private TestableContext mSpiedContext; private MockitoSession mMockitoSession; @@ -291,6 +294,13 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mBiometricEnabledOnKeyguardCallback = mBiometricEnabledCallbackArgCaptor.getValue(); biometricsEnabledForCurrentUser(); + mHandler = spy(mKeyguardUpdateMonitor.getHandler()); + try { + FieldSetter.setField(mKeyguardUpdateMonitor, + KeyguardUpdateMonitor.class.getDeclaredField("mHandler"), mHandler); + } catch (NoSuchFieldException e) { + + } verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture()); mStatusBarStateListener = mStatusBarStateListenerCaptor.getValue(); mKeyguardUpdateMonitor.registerCallback(mTestCallback); @@ -330,7 +340,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { when(mTelephonyManager.getActiveModemCount()).thenReturn(1); when(mTelephonyManager.getSimState(anyInt())).thenReturn(state); - when(mSubscriptionManager.getSubscriptionIds(anyInt())).thenReturn(new int[] { subId }); + when(mSubscriptionManager.getSubscriptionIds(anyInt())).thenReturn(new int[]{subId}); KeyguardUpdateMonitor testKUM = new TestableKeyguardUpdateMonitor(mSpiedContext); @@ -505,7 +515,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { // Even SimState Loaded, still need ACTION_SERVICE_STATE turn on mTelephonyCapable assertThat(mKeyguardUpdateMonitor.mTelephonyCapable).isFalse(); - Intent intentServiceState = new Intent(Intent.ACTION_SERVICE_STATE); + Intent intentServiceState = new Intent(Intent.ACTION_SERVICE_STATE); intentSimState.putExtra(Intent.EXTRA_SIM_STATE , Intent.SIM_STATE_LOADED); mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext() @@ -520,7 +530,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mTestableLooper.processAllMessages(); verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(), - anyInt()); + anyInt()); verify(mFingerprintManager, never()).detectFingerprint(any(), any(), anyInt()); } @@ -791,7 +801,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { public void testBiometricsCleared_whenUserSwitches() throws Exception { final IRemoteCallback reply = new IRemoteCallback.Stub() { @Override - public void sendResult(Bundle data) {} // do nothing + public void sendResult(Bundle data) { + } // do nothing }; final BiometricAuthenticated dummyAuthentication = new BiometricAuthenticated(true /* authenticated */, true /* strong */); @@ -809,7 +820,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { public void testMultiUserJankMonitor_whenUserSwitches() throws Exception { final IRemoteCallback reply = new IRemoteCallback.Stub() { @Override - public void sendResult(Bundle data) {} // do nothing + public void sendResult(Bundle data) { + } // do nothing }; mKeyguardUpdateMonitor.handleUserSwitchComplete(10 /* user */); verify(mInteractionJankMonitor).end(InteractionJankMonitor.CUJ_USER_SWITCH); @@ -1499,6 +1511,34 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { assertThat(cancelSignal.isCanceled()).isTrue(); } + @Test + public void testFingerprintCanAuth_whenCancellationNotReceivedAndAuthFailed() { + mKeyguardUpdateMonitor.dispatchStartedWakingUp(); + mTestableLooper.processAllMessages(); + mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true); + + verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean()); + verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(), + anyInt()); + + mKeyguardUpdateMonitor.onFaceAuthenticated(0, false); + // Make sure keyguard is going away after face auth attempt, and that it calls + // updateBiometricStateListeningState. + mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(false); + mTestableLooper.processAllMessages(); + + verify(mHandler).postDelayed(mKeyguardUpdateMonitor.mFpCancelNotReceived, + DEFAULT_CANCEL_SIGNAL_TIMEOUT); + + mKeyguardUpdateMonitor.onFingerprintAuthenticated(0, true); + mTestableLooper.processAllMessages(); + + verify(mHandler, times(1)).removeCallbacks(mKeyguardUpdateMonitor.mFpCancelNotReceived); + mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */); + mTestableLooper.processAllMessages(); + assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(anyBoolean())).isEqualTo(true); + } + private void fingerprintIsNotEnrolled() { when(mFingerprintManager.hasEnrolledTemplates(mCurrentUserId)).thenReturn(false); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java index 2915f5a504d7..e099c9269d3f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java @@ -20,6 +20,7 @@ import static com.android.systemui.dreams.complication.Complication.COMPLICATION import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_CAST_INFO; import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_DATE; import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_HOME_CONTROLS; +import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_SMARTSPACE; import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_TIME; import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_WEATHER; import static com.android.systemui.dreams.complication.ComplicationUtils.convertComplicationType; @@ -60,6 +61,8 @@ public class ComplicationUtilsTest extends SysuiTestCase { .isEqualTo(COMPLICATION_TYPE_CAST_INFO); assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_HOME_CONTROLS)) .isEqualTo(COMPLICATION_TYPE_HOME_CONTROLS); + assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_SMARTSPACE)) + .isEqualTo(COMPLICATION_TYPE_SMARTSPACE); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt index bcc76abc89ba..810c6dc4776d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.keyguard.data.repository +package com.android.systemui.keyguard.data.quickaffordance import androidx.test.filters.SmallTest import com.android.systemui.R @@ -22,11 +22,10 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.controls.controller.ControlsController import com.android.systemui.controls.dagger.ControlsComponent import com.android.systemui.controls.management.ControlsListingController -import com.android.systemui.keyguard.data.quickaffordance.HomeControlsKeyguardQuickAffordanceConfig -import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import java.util.Optional +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.test.runBlockingTest @@ -50,18 +49,19 @@ class HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTes companion object { @Parameters( name = - "feature enabled = {0}, has favorites = {1}, has service infos = {2} - expected" + - " visible = {3}" + "feature enabled = {0}, has favorites = {1}, has service infos = {2}, can show" + + " while locked = {3} - expected visible = {4}" ) @JvmStatic fun data() = - (0 until 8) + (0 until 16) .map { combination -> arrayOf( - /* isFeatureEnabled= */ combination and 0b100 != 0, - /* hasFavorites= */ combination and 0b010 != 0, - /* hasServiceInfos= */ combination and 0b001 != 0, - /* isVisible= */ combination == 0b111, + /* isFeatureEnabled= */ combination and 0b1000 != 0, + /* hasFavorites= */ combination and 0b0100 != 0, + /* hasServiceInfos= */ combination and 0b0010 != 0, + /* canShowWhileLocked= */ combination and 0b0001 != 0, + /* isVisible= */ combination == 0b1111, ) } .toList() @@ -79,7 +79,8 @@ class HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTes @JvmField @Parameter(0) var isFeatureEnabled: Boolean = false @JvmField @Parameter(1) var hasFavorites: Boolean = false @JvmField @Parameter(2) var hasServiceInfos: Boolean = false - @JvmField @Parameter(3) var isVisible: Boolean = false + @JvmField @Parameter(3) var canShowWhileLocked: Boolean = false + @JvmField @Parameter(4) var isVisible: Boolean = false @Before fun setUp() { @@ -89,6 +90,8 @@ class HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTes whenever(component.getControlsController()).thenReturn(Optional.of(controlsController)) whenever(component.getControlsListingController()) .thenReturn(Optional.of(controlsListingController)) + whenever(component.canShowWhileLockedSetting) + .thenReturn(MutableStateFlow(canShowWhileLocked)) underTest = HomeControlsKeyguardQuickAffordanceConfig( @@ -111,14 +114,16 @@ class HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTes val values = mutableListOf<KeyguardQuickAffordanceConfig.State>() val job = underTest.state.onEach(values::add).launchIn(this) - verify(controlsListingController).addCallback(callbackCaptor.capture()) - callbackCaptor.value.onServicesUpdated( - if (hasServiceInfos) { - listOf(mock()) - } else { - emptyList() - } - ) + if (canShowWhileLocked) { + verify(controlsListingController).addCallback(callbackCaptor.capture()) + callbackCaptor.value.onServicesUpdated( + if (hasServiceInfos) { + listOf(mock()) + } else { + emptyList() + } + ) + } assertThat(values.last()) .isInstanceOf( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt index 592e80b9e7d9..ef588f5ce255 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt @@ -51,6 +51,7 @@ class HomeControlsKeyguardQuickAffordanceConfigTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) + whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(true)) underTest = HomeControlsKeyguardQuickAffordanceConfig( @@ -60,7 +61,26 @@ class HomeControlsKeyguardQuickAffordanceConfigTest : SysuiTestCase() { } @Test - fun `state - when listing controller is missing - returns None`() = runBlockingTest { + fun `state - when cannot show while locked - returns Hidden`() = runBlockingTest { + whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(false)) + whenever(component.isEnabled()).thenReturn(true) + whenever(component.getTileImageId()).thenReturn(R.drawable.controls_icon) + whenever(component.getTileTitleId()).thenReturn(R.string.quick_controls_title) + val controlsController = mock<ControlsController>() + whenever(component.getControlsController()).thenReturn(Optional.of(controlsController)) + whenever(component.getControlsListingController()).thenReturn(Optional.empty()) + whenever(controlsController.getFavorites()).thenReturn(listOf(mock())) + + val values = mutableListOf<KeyguardQuickAffordanceConfig.State>() + val job = underTest.state.onEach(values::add).launchIn(this) + + assertThat(values.last()) + .isInstanceOf(KeyguardQuickAffordanceConfig.State.Hidden::class.java) + job.cancel() + } + + @Test + fun `state - when listing controller is missing - returns Hidden`() = runBlockingTest { whenever(component.isEnabled()).thenReturn(true) whenever(component.getTileImageId()).thenReturn(R.drawable.controls_icon) whenever(component.getTileTitleId()).thenReturn(R.string.quick_controls_title) diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt index 4abb973817b1..56aff3c2fc8b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt @@ -24,16 +24,11 @@ class LogBufferTest : SysuiTestCase() { @Before fun setup() { outputWriter = StringWriter() - buffer = createBuffer(UNBOUNDED_STACK_TRACE, NESTED_TRACE_DEPTH) + buffer = createBuffer() } - private fun createBuffer(rootTraceDepth: Int, nestedTraceDepth: Int): LogBuffer { - return LogBuffer("TestBuffer", - 1, - logcatEchoTracker, - false, - rootStackTraceDepth = rootTraceDepth, - nestedStackTraceDepth = nestedTraceDepth) + private fun createBuffer(): LogBuffer { + return LogBuffer("TestBuffer", 1, logcatEchoTracker, false) } @Test @@ -56,95 +51,83 @@ class LogBufferTest : SysuiTestCase() { } @Test - fun dump_writesExceptionAndStacktraceLimitedToGivenDepth() { - buffer = createBuffer(rootTraceDepth = 2, nestedTraceDepth = -1) - // stack trace depth of 5 - val exception = createTestException("Exception message", "TestClass", 5) + fun dump_writesExceptionAndStacktrace() { + buffer = createBuffer() + val exception = createTestException("Exception message", "TestClass") buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception) val dumpedString = dumpBuffer() - // logs are limited to depth 2 - assertThat(dumpedString).contains("E Tag: Extra message") - assertThat(dumpedString).contains("E Tag: java.lang.RuntimeException: Exception message") - assertThat(dumpedString).contains("E Tag: \tat TestClass.TestMethod(TestClass.java:1)") - assertThat(dumpedString).contains("E Tag: \tat TestClass.TestMethod(TestClass.java:2)") - assertThat(dumpedString) - .doesNotContain("E Tag: \tat TestClass.TestMethod(TestClass.java:3)") + assertThat(dumpedString).contains("Extra message") + assertThat(dumpedString).contains("java.lang.RuntimeException: Exception message") + assertThat(dumpedString).contains("at TestClass.TestMethod(TestClass.java:1)") + assertThat(dumpedString).contains("at TestClass.TestMethod(TestClass.java:2)") } @Test - fun dump_writesCauseAndStacktraceLimitedToGivenDepth() { - buffer = createBuffer(rootTraceDepth = 0, nestedTraceDepth = 2) + fun dump_writesCauseAndStacktrace() { + buffer = createBuffer() val exception = createTestException("Exception message", "TestClass", - 1, - cause = createTestException("The real cause!", "TestClass", 5)) + cause = createTestException("The real cause!", "TestClass")) buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception) val dumpedString = dumpBuffer() - // logs are limited to depth 2 - assertThat(dumpedString) - .contains("E Tag: Caused by: java.lang.RuntimeException: The real cause!") - assertThat(dumpedString).contains("E Tag: \tat TestClass.TestMethod(TestClass.java:1)") - assertThat(dumpedString).contains("E Tag: \tat TestClass.TestMethod(TestClass.java:2)") assertThat(dumpedString) - .doesNotContain("E Tag: \tat TestClass.TestMethod(TestClass.java:3)") + .contains("Caused by: java.lang.RuntimeException: The real cause!") + assertThat(dumpedString).contains("at TestClass.TestMethod(TestClass.java:1)") + assertThat(dumpedString).contains("at TestClass.TestMethod(TestClass.java:2)") } @Test - fun dump_writesSuppressedExceptionAndStacktraceLimitedToGivenDepth() { - buffer = createBuffer(rootTraceDepth = 0, nestedTraceDepth = 2) + fun dump_writesSuppressedExceptionAndStacktrace() { + buffer = createBuffer() val exception = RuntimeException("Root exception message") exception.addSuppressed( createTestException( "First suppressed exception", "FirstClass", - 5, - createTestException("Cause of suppressed exp", "ThirdClass", 5) + createTestException("Cause of suppressed exp", "ThirdClass") )) exception.addSuppressed( - createTestException("Second suppressed exception", "SecondClass", 5)) + createTestException("Second suppressed exception", "SecondClass")) buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception) val dumpedStr = dumpBuffer() - // logs are limited to depth 2 // first suppressed exception assertThat(dumpedStr) - .contains("E Tag: Suppressed: " + + .contains("Suppressed: " + "java.lang.RuntimeException: First suppressed exception") - assertThat(dumpedStr).contains("E Tag: \tat FirstClass.TestMethod(FirstClass.java:1)") - assertThat(dumpedStr).contains("E Tag: \tat FirstClass.TestMethod(FirstClass.java:2)") - assertThat(dumpedStr) - .doesNotContain("E Tag: \tat FirstClass.TestMethod(FirstClass.java:3)") + assertThat(dumpedStr).contains("at FirstClass.TestMethod(FirstClass.java:1)") + assertThat(dumpedStr).contains("at FirstClass.TestMethod(FirstClass.java:2)") assertThat(dumpedStr) - .contains("E Tag: Caused by: java.lang.RuntimeException: Cause of suppressed exp") - assertThat(dumpedStr).contains("E Tag: \tat ThirdClass.TestMethod(ThirdClass.java:1)") - assertThat(dumpedStr).contains("E Tag: \tat ThirdClass.TestMethod(ThirdClass.java:2)") - assertThat(dumpedStr) - .doesNotContain("E Tag: \tat ThirdClass.TestMethod(ThirdClass.java:3)") + .contains("Caused by: java.lang.RuntimeException: Cause of suppressed exp") + assertThat(dumpedStr).contains("at ThirdClass.TestMethod(ThirdClass.java:1)") + assertThat(dumpedStr).contains("at ThirdClass.TestMethod(ThirdClass.java:2)") // second suppressed exception assertThat(dumpedStr) - .contains("E Tag: Suppressed: " + + .contains("Suppressed: " + "java.lang.RuntimeException: Second suppressed exception") - assertThat(dumpedStr).contains("E Tag: \tat SecondClass.TestMethod(SecondClass.java:1)") - assertThat(dumpedStr).contains("E Tag: \tat SecondClass.TestMethod(SecondClass.java:2)") - assertThat(dumpedStr) - .doesNotContain("E Tag: \tat SecondClass.TestMethod(SecondClass.java:3)") + assertThat(dumpedStr).contains("at SecondClass.TestMethod(SecondClass.java:1)") + assertThat(dumpedStr).contains("at SecondClass.TestMethod(SecondClass.java:2)") } private fun createTestException( - message: String, - errorClass: String, - stackTraceLength: Int, - cause: Throwable? = null + message: String, + errorClass: String, + cause: Throwable? = null, ): Exception { val exception = RuntimeException(message, cause) - exception.stackTrace = createStackTraceElements(errorClass, stackTraceLength) + exception.stackTrace = (1..5).map { lineNumber -> + StackTraceElement(errorClass, + "TestMethod", + "$errorClass.java", + lineNumber) + }.toTypedArray() return exception } @@ -152,16 +135,4 @@ class LogBufferTest : SysuiTestCase() { buffer.dump(PrintWriter(outputWriter), tailLength = 100) return outputWriter.toString() } - - private fun createStackTraceElements( - errorClass: String, - stackTraceLength: Int - ): Array<StackTraceElement> { - return (1..stackTraceLength).map { lineNumber -> - StackTraceElement(errorClass, - "TestMethod", - "$errorClass.java", - lineNumber) - }.toTypedArray() - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt index 6a532d74967f..6468fe1a81d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt @@ -17,9 +17,9 @@ package com.android.systemui.media import android.app.smartspace.SmartspaceAction -import androidx.test.filters.SmallTest import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher @@ -29,18 +29,18 @@ import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat +import java.util.concurrent.Executor import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations -import java.util.concurrent.Executor private const val KEY = "TEST_KEY" private const val KEY_ALT = "TEST_KEY_2" @@ -433,7 +433,7 @@ class MediaDataFilterTest : SysuiTestCase() { val dataCurrentAndActive = dataCurrent.copy(active = true) verify(listener).onMediaDataLoaded(eq(KEY), eq(KEY), eq(dataCurrentAndActive), eq(true), eq(100), eq(true)) - assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isFalse() + assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue() // Smartspace update shouldn't be propagated for the empty rec list. verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean()) verify(logger, never()).logRecommendationAdded(any(), any()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java index 568e0cb22f18..260bb8760f1c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java @@ -254,4 +254,30 @@ public class MediaOutputAdapterTest extends SysuiTestCase { verify(mMediaOutputController).connectDevice(mMediaDevice2); } + + @Test + public void onItemClick_onGroupActionTriggered_verifySeekbarDisabled() { + when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(mMediaDevices); + List<MediaDevice> selectableDevices = new ArrayList<>(); + selectableDevices.add(mMediaDevice1); + when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(selectableDevices); + when(mMediaOutputController.hasAdjustVolumeUserRestriction()).thenReturn(true); + mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); + + mViewHolder.mContainerLayout.performClick(); + + assertThat(mViewHolder.mSeekBar.isEnabled()).isFalse(); + } + + @Test + public void onBindViewHolder_volumeControlChangeToEnabled_enableSeekbarAgain() { + when(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(false); + mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); + assertThat(mViewHolder.mSeekBar.isEnabled()).isFalse(); + + when(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(true); + mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); + + assertThat(mViewHolder.mSeekBar.isEnabled()).isTrue(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java index 5336ef09f368..ba49f3fa66ee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java @@ -37,6 +37,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Matchers.argThat; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -78,6 +79,7 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatcher; import org.mockito.Captor; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -144,7 +146,25 @@ public class QSTileImplTest extends SysuiTestCase { when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE); mTile.click(null /* view */); - verify(mQsLogger).logTileClick(SPEC, StatusBarState.SHADE, Tile.STATE_ACTIVE); + verify(mQsLogger).logTileClick(eq(SPEC), eq(StatusBarState.SHADE), eq(Tile.STATE_ACTIVE), + anyInt()); + } + + @Test + public void testHandleClick_log() { + mTile.click(null); + mTile.click(null); + mTestableLooper.processAllMessages(); + mTile.click(null); + mTestableLooper.processAllMessages(); + + InOrder inOrder = inOrder(mQsLogger); + inOrder.verify(mQsLogger).logTileClick(eq(SPEC), anyInt(), anyInt(), eq(0)); + inOrder.verify(mQsLogger).logTileClick(eq(SPEC), anyInt(), anyInt(), eq(1)); + inOrder.verify(mQsLogger).logHandleClick(SPEC, 0); + inOrder.verify(mQsLogger).logHandleClick(SPEC, 1); + inOrder.verify(mQsLogger).logTileClick(eq(SPEC), anyInt(), anyInt(), eq(2)); + inOrder.verify(mQsLogger).logHandleClick(SPEC, 2); } @Test @@ -183,7 +203,25 @@ public class QSTileImplTest extends SysuiTestCase { when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE); mTile.secondaryClick(null /* view */); - verify(mQsLogger).logTileSecondaryClick(SPEC, StatusBarState.SHADE, Tile.STATE_ACTIVE); + verify(mQsLogger).logTileSecondaryClick(eq(SPEC), eq(StatusBarState.SHADE), + eq(Tile.STATE_ACTIVE), anyInt()); + } + + @Test + public void testHandleSecondaryClick_log() { + mTile.secondaryClick(null); + mTile.secondaryClick(null); + mTestableLooper.processAllMessages(); + mTile.secondaryClick(null); + mTestableLooper.processAllMessages(); + + InOrder inOrder = inOrder(mQsLogger); + inOrder.verify(mQsLogger).logTileSecondaryClick(eq(SPEC), anyInt(), anyInt(), eq(0)); + inOrder.verify(mQsLogger).logTileSecondaryClick(eq(SPEC), anyInt(), anyInt(), eq(1)); + inOrder.verify(mQsLogger).logHandleSecondaryClick(SPEC, 0); + inOrder.verify(mQsLogger).logHandleSecondaryClick(SPEC, 1); + inOrder.verify(mQsLogger).logTileSecondaryClick(eq(SPEC), anyInt(), anyInt(), eq(2)); + inOrder.verify(mQsLogger).logHandleSecondaryClick(SPEC, 2); } @Test @@ -210,7 +248,25 @@ public class QSTileImplTest extends SysuiTestCase { when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE); mTile.longClick(null /* view */); - verify(mQsLogger).logTileLongClick(SPEC, StatusBarState.SHADE, Tile.STATE_ACTIVE); + verify(mQsLogger).logTileLongClick(eq(SPEC), eq(StatusBarState.SHADE), + eq(Tile.STATE_ACTIVE), anyInt()); + } + + @Test + public void testHandleLongClick_log() { + mTile.longClick(null); + mTile.longClick(null); + mTestableLooper.processAllMessages(); + mTile.longClick(null); + mTestableLooper.processAllMessages(); + + InOrder inOrder = inOrder(mQsLogger); + inOrder.verify(mQsLogger).logTileLongClick(eq(SPEC), anyInt(), anyInt(), eq(0)); + inOrder.verify(mQsLogger).logTileLongClick(eq(SPEC), anyInt(), anyInt(), eq(1)); + inOrder.verify(mQsLogger).logHandleLongClick(SPEC, 0); + inOrder.verify(mQsLogger).logHandleLongClick(SPEC, 1); + inOrder.verify(mQsLogger).logTileLongClick(eq(SPEC), anyInt(), anyInt(), eq(2)); + inOrder.verify(mQsLogger).logHandleLongClick(SPEC, 2); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index fee17c785ed2..18acf3f6ce53 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -33,6 +33,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; @@ -621,7 +622,7 @@ public class BubblesTest extends SysuiTestCase { assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot()); // Send update - mEntryListener.onEntryUpdated(mRow); + mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ true); // Nothing should have changed // Notif is suppressed after expansion @@ -789,7 +790,7 @@ public class BubblesTest extends SysuiTestCase { @Test public void testAddNotif_notBubble() { mEntryListener.onEntryAdded(mNonBubbleNotifRow.getEntry()); - mEntryListener.onEntryUpdated(mNonBubbleNotifRow.getEntry()); + mEntryListener.onEntryUpdated(mNonBubbleNotifRow.getEntry(), /* fromSystem= */ true); assertThat(mBubbleController.hasBubbles()).isFalse(); } @@ -827,7 +828,7 @@ public class BubblesTest extends SysuiTestCase { NotificationListenerService.Ranking ranking = new RankingBuilder( mRow.getRanking()).setCanBubble(false).build(); mRow.setRanking(ranking); - mEntryListener.onEntryUpdated(mRow); + mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ true); assertFalse(mBubbleController.hasBubbles()); verify(mDeleteIntent, never()).send(); @@ -1432,6 +1433,100 @@ public class BubblesTest extends SysuiTestCase { assertThat(mBubbleData.hasBubbleInStackWithKey(mBubbleEntry.getKey())).isFalse(); } + /** + * Verifies that if a bubble is in the overflow and a non-interruptive notification update + * comes in for it, it stays in the overflow but the entry is updated. + */ + @Test + public void testNonInterruptiveUpdate_doesntBubbleFromOverflow() { + mEntryListener.onEntryAdded(mRow); + mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ true); + assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry); + + // Dismiss the bubble so it's in the overflow + mBubbleController.removeBubble( + mRow.getKey(), Bubbles.DISMISS_USER_GESTURE); + assertThat(mBubbleData.hasOverflowBubbleWithKey(mRow.getKey())).isTrue(); + + // Update the entry to not show in shade + setMetadataFlags(mRow, + Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, /* enableFlag= */ true); + mBubbleController.updateBubble(mBubbleEntry, + /* suppressFlyout= */ false, /* showInShade= */ true); + + // Check that the update was applied - shouldn't be show in shade + assertBubbleNotificationSuppressedFromShade(mBubbleEntry); + // Check that it wasn't inflated (1 because it would've been inflated via onEntryAdded) + verify(mBubbleController, times(1)).inflateAndAdd( + any(Bubble.class), anyBoolean(), anyBoolean()); + } + + /** + * Verifies that if a bubble is active, and a non-interruptive notification update comes in for + * it, it doesn't trigger a new inflate and add for that bubble. + */ + @Test + public void testNonInterruptiveUpdate_doesntTriggerInflate() { + mEntryListener.onEntryAdded(mRow); + mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ true); + assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry); + + // Update the entry to not show in shade + setMetadataFlags(mRow, + Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, /* enableFlag= */ true); + mBubbleController.updateBubble(mBubbleEntry, + /* suppressFlyout= */ false, /* showInShade= */ true); + + // Check that the update was applied - shouldn't be show in shade + assertBubbleNotificationSuppressedFromShade(mBubbleEntry); + // Check that it wasn't inflated (1 because it would've been inflated via onEntryAdded) + verify(mBubbleController, times(1)).inflateAndAdd( + any(Bubble.class), anyBoolean(), anyBoolean()); + } + + /** + * Verifies that if a bubble is in the overflow and a non-interruptive notification update + * comes in for it with FLAG_BUBBLE that the flag is removed. + */ + @Test + public void testNonInterruptiveUpdate_doesntOverrideOverflowFlagBubble() { + mEntryListener.onEntryAdded(mRow); + mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ true); + assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry); + + // Dismiss the bubble so it's in the overflow + mBubbleController.removeBubble( + mRow.getKey(), Bubbles.DISMISS_USER_GESTURE); + assertThat(mBubbleData.hasOverflowBubbleWithKey(mRow.getKey())).isTrue(); + // Once it's in the overflow it's not actively a bubble (doesn't have FLAG_BUBBLE) + Bubble b = mBubbleData.getOverflowBubbleWithKey(mBubbleEntry.getKey()); + assertThat(b.isBubble()).isFalse(); + + // Send a non-notifying update that has FLAG_BUBBLE + mRow.getSbn().getNotification().flags = FLAG_BUBBLE; + assertThat(mRow.getSbn().getNotification().isBubbleNotification()).isTrue(); + mBubbleController.updateBubble(mBubbleEntry, + /* suppressFlyout= */ false, /* showInShade= */ true); + + // Verify that it still doesn't have FLAG_BUBBLE because it's in the overflow. + b = mBubbleData.getOverflowBubbleWithKey(mBubbleEntry.getKey()); + assertThat(b.isBubble()).isFalse(); + } + + @Test + public void testNonSystemUpdatesIgnored() { + mEntryListener.onEntryAdded(mRow); + assertThat(mBubbleController.hasBubbles()).isTrue(); + + mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ false); + mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ false); + mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ false); + + // Check that it wasn't inflated (1 because it would've been inflated via onEntryAdded) + verify(mBubbleController, times(1)).inflateAndAdd( + any(Bubble.class), anyBoolean(), anyBoolean()); + } + /** Creates a bubble using the userId and package. */ private Bubble createBubble(int userId, String pkg) { final UserHandle userHandle = new UserHandle(userId); diff --git a/services/Android.bp b/services/Android.bp index 70692a63ff0f..1e4ce19f1541 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -102,6 +102,7 @@ filegroup { ":services.profcollect-sources", ":services.restrictions-sources", ":services.searchui-sources", + ":services.selectiontoolbar-sources", ":services.smartspace-sources", ":services.speech-sources", ":services.systemcaptions-sources", @@ -157,6 +158,7 @@ java_library { "services.profcollect", "services.restrictions", "services.searchui", + "services.selectiontoolbar", "services.smartspace", "services.speech", "services.systemcaptions", diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java index e1a0bfd25c9f..1fab28efb1ac 100644 --- a/services/core/java/com/android/server/Watchdog.java +++ b/services/core/java/com/android/server/Watchdog.java @@ -160,6 +160,7 @@ public class Watchdog implements Dumpable { public static final String[] AIDL_INTERFACE_PREFIXES_OF_INTEREST = new String[] { "android.hardware.biometrics.face.IFace/", "android.hardware.biometrics.fingerprint.IFingerprint/", + "android.hardware.graphics.composer3.IComposer/", "android.hardware.input.processor.IInputProcessor/", "android.hardware.light.ILights/", "android.hardware.power.IPower/", diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java index 0cdf7bf62f55..339d5d4fe021 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -43,7 +43,6 @@ import android.content.pm.PackageChangeEvent; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.SharedLibraryInfo; -import android.content.pm.UserInfo; import android.content.pm.VersionedPackage; import android.net.Uri; import android.os.Binder; @@ -164,17 +163,6 @@ final class DeletePackageHelper { return PackageManager.DELETE_FAILED_INTERNAL_ERROR; } - if (PackageManagerServiceUtils.isSystemApp(uninstalledPs)) { - UserInfo userInfo = mUserManagerInternal.getUserInfo(userId); - if (userInfo == null || (!userInfo.isAdmin() && !mUserManagerInternal.getUserInfo( - mUserManagerInternal.getProfileParentId(userId)).isAdmin())) { - Slog.w(TAG, "Not removing package " + packageName - + " as only admin user (or their profile) may downgrade system apps"); - EventLog.writeEvent(0x534e4554, "170646036", -1, packageName); - return PackageManager.DELETE_FAILED_USER_RESTRICTED; - } - } - disabledSystemPs = mPm.mSettings.getDisabledSystemPkgLPr(packageName); // Static shared libs can be declared by any package, so let us not // allow removing a package if it provides a lib others depend on. diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 3b25f2876de4..8d94324a1414 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -586,7 +586,10 @@ class TaskFragment extends WindowContainer<WindowContainer> { } // Cannot embed activity across TaskFragments for activity result. - if (a.resultTo != null && a.resultTo.getTaskFragment() != this) { + // If the activity that started for result is finishing, it's likely that this start mode + // is used to place an activity in the same task. Since the finishing activity won't be + // able to get the results, so it's OK to embed in a different TaskFragment. + if (a.resultTo != null && !a.resultTo.finishing && a.resultTo.getTaskFragment() != this) { return EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT; } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 66c9f55b0403..8fd4b5aa6bee 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -338,6 +338,8 @@ public final class SystemServer implements Dumpable { "com.android.server.contentcapture.ContentCaptureManagerService"; private static final String TRANSLATION_MANAGER_SERVICE_CLASS = "com.android.server.translation.TranslationManagerService"; + private static final String SELECTION_TOOLBAR_MANAGER_SERVICE_CLASS = + "com.android.server.selectiontoolbar.SelectionToolbarManagerService"; private static final String MUSIC_RECOGNITION_MANAGER_SERVICE_CLASS = "com.android.server.musicrecognition.MusicRecognitionManagerService"; private static final String SYSTEM_CAPTIONS_MANAGER_SERVICE_CLASS = @@ -2634,6 +2636,11 @@ public final class SystemServer implements Dumpable { Slog.d(TAG, "TranslationService not defined by OEM"); } + // Selection toolbar service + t.traceBegin("StartSelectionToolbarManagerService"); + mSystemServiceManager.startService(SELECTION_TOOLBAR_MANAGER_SERVICE_CLASS); + t.traceEnd(); + // NOTE: ClipboardService depends on ContentCapture and Autofill t.traceBegin("StartClipboardService"); mSystemServiceManager.startService(ClipboardService.class); diff --git a/services/selectiontoolbar/Android.bp b/services/selectiontoolbar/Android.bp new file mode 100644 index 000000000000..cc6405f97bc3 --- /dev/null +++ b/services/selectiontoolbar/Android.bp @@ -0,0 +1,22 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +filegroup { + name: "services.selectiontoolbar-sources", + srcs: ["java/**/*.java"], + path: "java", + visibility: ["//frameworks/base/services"], +} + +java_library_static { + name: "services.selectiontoolbar", + defaults: ["platform_service_defaults"], + srcs: [":services.selectiontoolbar-sources"], + libs: ["services.core"], +} diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt deleted file mode 100644 index e30f3d26119f..000000000000 --- a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2022 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.pm - -import android.content.pm.PackageManager -import android.content.pm.UserInfo -import android.os.Build -import android.util.Log -import com.android.server.testutils.any -import com.android.server.testutils.spy -import com.android.server.testutils.whenever -import com.google.common.truth.Truth.assertThat -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import org.mockito.Mockito.doAnswer - -@RunWith(JUnit4::class) -class DeletePackageHelperTest { - - @Rule - @JvmField - val rule = MockSystemRule() - - private lateinit var mPms: PackageManagerService - private lateinit var mUserManagerInternal: UserManagerInternal - - @Before - @Throws(Exception::class) - fun setup() { - Log.i("system.out", "setup", Exception()) - rule.system().stageNominalSystemState() - rule.system().stageScanExistingPackage( - "a.data.package", 1L, rule.system().dataAppDirectory) - - mUserManagerInternal = rule.mocks().injector.userManagerInternal - whenever(mUserManagerInternal.getUserIds()).thenReturn(intArrayOf(0, 1)) - - mPms = createPackageManagerService() - doAnswer { false }.`when`(mPms).isPackageDeviceAdmin(any(), any()) - doAnswer { null }.`when`(mPms).freezePackageForDelete(any(), any(), any(), any()) - } - - private fun createPackageManagerService(): PackageManagerService { - return spy(PackageManagerService(rule.mocks().injector, - false /*coreOnly*/, - false /*factoryTest*/, - MockSystem.DEFAULT_VERSION_INFO.fingerprint, - false /*isEngBuild*/, - false /*isUserDebugBuild*/, - Build.VERSION_CODES.CUR_DEVELOPMENT, - Build.VERSION.INCREMENTAL)) - } - - @Test - fun deleteSystemPackageFailsIfNotAdminAndNotProfile() { - val ps = mPms.mSettings.getPackageLPr("a.data.package") - whenever(PackageManagerServiceUtils.isSystemApp(ps)).thenReturn(true) - whenever(mUserManagerInternal.getUserInfo(1)).thenReturn(UserInfo(1, "test", 0)) - whenever(mUserManagerInternal.getProfileParentId(1)).thenReturn(1) - - val dph = DeletePackageHelper(mPms) - val result = dph.deletePackageX("a.data.package", 1L, 1, - PackageManager.DELETE_SYSTEM_APP, false) - - assertThat(result).isEqualTo(PackageManager.DELETE_FAILED_USER_RESTRICTED) - } - - @Test - fun deleteSystemPackageFailsIfProfileOfNonAdmin() { - val userId = 1 - val parentId = 5 - val ps = mPms.mSettings.getPackageLPr("a.data.package") - whenever(PackageManagerServiceUtils.isSystemApp(ps)).thenReturn(true) - whenever(mUserManagerInternal.getUserInfo(userId)).thenReturn( - UserInfo(userId, "test", UserInfo.FLAG_PROFILE)) - whenever(mUserManagerInternal.getProfileParentId(userId)).thenReturn(parentId) - whenever(mUserManagerInternal.getUserInfo(parentId)).thenReturn( - UserInfo(userId, "testparent", 0)) - - val dph = DeletePackageHelper(mPms) - val result = dph.deletePackageX("a.data.package", 1L, userId, - PackageManager.DELETE_SYSTEM_APP, false) - - assertThat(result).isEqualTo(PackageManager.DELETE_FAILED_USER_RESTRICTED) - } - - @Test - fun deleteSystemPackageSucceedsIfAdmin() { - val ps = mPms.mSettings.getPackageLPr("a.data.package") - whenever(PackageManagerServiceUtils.isSystemApp(ps)).thenReturn(true) - whenever(mUserManagerInternal.getUserInfo(1)).thenReturn( - UserInfo(1, "test", UserInfo.FLAG_ADMIN)) - - val dph = DeletePackageHelper(mPms) - val result = dph.deletePackageX("a.data.package", 1L, 1, - PackageManager.DELETE_SYSTEM_APP, false) - - assertThat(result).isEqualTo(PackageManager.DELETE_SUCCEEDED) - } - - @Test - fun deleteSystemPackageSucceedsIfProfileOfAdmin() { - val userId = 1 - val parentId = 5 - val ps = mPms.mSettings.getPackageLPr("a.data.package") - whenever(PackageManagerServiceUtils.isSystemApp(ps)).thenReturn(true) - whenever(mUserManagerInternal.getUserInfo(userId)).thenReturn( - UserInfo(userId, "test", UserInfo.FLAG_PROFILE)) - whenever(mUserManagerInternal.getProfileParentId(userId)).thenReturn(parentId) - whenever(mUserManagerInternal.getUserInfo(parentId)).thenReturn( - UserInfo(userId, "testparent", UserInfo.FLAG_ADMIN)) - - val dph = DeletePackageHelper(mPms) - val result = dph.deletePackageX("a.data.package", 1L, userId, - PackageManager.DELETE_SYSTEM_APP, false) - - assertThat(result).isEqualTo(PackageManager.DELETE_SUCCEEDED) - } -}
\ No newline at end of file diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java index 1096351524d7..88eadfceedb6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java @@ -32,6 +32,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.wm.ActivityRecord.State.RESUMED; +import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED; import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION; import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT; import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST; @@ -468,6 +469,10 @@ public class TaskFragmentTest extends WindowTestsBase { newActivity.resultTo = activity; assertEquals(EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT, newTaskFragment.isAllowedToEmbedActivity(newActivity)); + + // Allow embedding if the resultTo activity is finishing. + activity.finishing = true; + assertEquals(EMBEDDING_ALLOWED, newTaskFragment.isAllowedToEmbedActivity(newActivity)); } @Test diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortHalInstance.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortHalInstance.java index 41f9faef99df..6fc4b6707e9e 100644 --- a/services/usb/java/com/android/server/usb/hal/port/UsbPortHalInstance.java +++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortHalInstance.java @@ -31,15 +31,14 @@ public final class UsbPortHalInstance { public static UsbPortHal getInstance(UsbPortManager portManager, IndentingPrintWriter pw) { logAndPrint(Log.DEBUG, null, "Querying USB HAL version"); - if (UsbPortHidl.isServicePresent(null)) { - logAndPrint(Log.INFO, null, "USB HAL HIDL present"); - return new UsbPortHidl(portManager, pw); - } if (UsbPortAidl.isServicePresent(null)) { logAndPrint(Log.INFO, null, "USB HAL AIDL present"); return new UsbPortAidl(portManager, pw); } - + if (UsbPortHidl.isServicePresent(null)) { + logAndPrint(Log.INFO, null, "USB HAL HIDL present"); + return new UsbPortHidl(portManager, pw); + } return null; } } |