diff options
238 files changed, 7189 insertions, 1455 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/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java index 58ddd49ec6d1..13934e592d40 100644 --- a/core/java/android/app/PropertyInvalidatedCache.java +++ b/core/java/android/app/PropertyInvalidatedCache.java @@ -310,6 +310,21 @@ public class PropertyInvalidatedCache<Query, Result> {      public static final String MODULE_TELEPHONY = "telephony";      /** +     * Constants that affect retries when the process is unable to write the property. +     * The first constant is the number of times the process will attempt to set the +     * property.  The second constant is the delay between attempts. +     */ + +    /** +     * Wait 200ms between retry attempts and the retry limit is 5.  That gives a total possible +     * delay of 1s, which should be less than ANR timeouts.  The goal is to have the system crash +     * because the property could not be set (which is a condition that is easily recognized) and +     * not crash because of an ANR (which can be confusing to debug). +     */ +    private static final int PROPERTY_FAILURE_RETRY_DELAY_MILLIS = 200; +    private static final int PROPERTY_FAILURE_RETRY_LIMIT = 5; + +    /**       * Construct a system property that matches the rules described above.  The module is       * one of the permitted values above.  The API is a string that is a legal Java simple       * identifier.  The api is modified to conform to the system property style guide by @@ -670,7 +685,33 @@ public class PropertyInvalidatedCache<Query, Result> {                  }              }          } -        SystemProperties.set(name, Long.toString(val)); +        RuntimeException failure = null; +        for (int attempt = 0; attempt < PROPERTY_FAILURE_RETRY_LIMIT; attempt++) { +            try { +                SystemProperties.set(name, Long.toString(val)); +                if (attempt > 0) { +                    // This log is not guarded.  Based on known bug reports, it should +                    // occur once a week or less.  The purpose of the log message is to +                    // identify the retries as a source of delay that might be otherwise +                    // be attributed to the cache itself. +                    Log.w(TAG, "Nonce set after " + attempt + " tries"); +                } +                return; +            } catch (RuntimeException e) { +                if (failure == null) { +                    failure = e; +                } +                try { +                    Thread.sleep(PROPERTY_FAILURE_RETRY_DELAY_MILLIS); +                } catch (InterruptedException x) { +                    // Ignore this exception.  The desired delay is only approximate and +                    // there is no issue if the sleep sometimes terminates early. +                } +            } +        } +        // This point is reached only if SystemProperties.set() fails at least once. +        // Rethrow the first exception that was received. +        throw failure;      }      // Set the nonce in a static context.  No handle is available. 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/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index 0f1b39c01289..df1c0d7c63bc 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -892,7 +892,7 @@ public abstract class CameraDevice implements AutoCloseable {       * <tr><th colspan="7">Preview stabilization guaranteed stream configurations</th></tr>       * <tr><th colspan="2" id="rb">Target 1</th><th colspan="2" id="rb">Target 2</th><th rowspan="2">Sample use case(s)</th> </tr>       * <tr><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th></tr> -     * <tr> <td>{@code PRIV / YUV}</td><td id="rb">{@code s1440p}</td><td colspan="4" id="rb"></td> <td>Stabilized preview, GPU video processing, or no-preview stabilized video recording.</td> </tr> +     * <tr> <td>{@code PRIV / YUV}</td><td id="rb">{@code s1440p}</td><td colspan="2" id="rb"></td> <td>Stabilized preview, GPU video processing, or no-preview stabilized video recording.</td> </tr>       * <tr> <td>{@code PRIV / YUV}</td><td id="rb">{@code s1440p}</td> <td>{@code JPEG / YUV}</td><td id="rb">{@code MAXIMUM }</td><td>Standard still imaging with stabilized preview.</td> </tr>       * <tr> <td>{@code PRIV / YUV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code PRIV / YUV}</td><td id="rb">{@code s1440p }</td><td>High-resolution recording with stabilized preview and recording stream.</td> </tr>       * </table><br> diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index 13a3ec8c0562..fd4b94ac5f1f 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -623,7 +623,7 @@ public final class DeviceConfig {      private static final List<String> PUBLIC_NAMESPACES =              Arrays.asList(NAMESPACE_TEXTCLASSIFIER, NAMESPACE_RUNTIME, NAMESPACE_STATSD_JAVA,                      NAMESPACE_STATSD_JAVA_BOOT, NAMESPACE_SELECTION_TOOLBAR, NAMESPACE_AUTOFILL, -                    NAMESPACE_DEVICE_POLICY_MANAGER); +                    NAMESPACE_DEVICE_POLICY_MANAGER, NAMESPACE_CONTENT_CAPTURE);      /**       * Privacy related properties definitions.       * diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java index d75ff2fc7dc2..5721fa6dd11a 100644 --- a/core/java/android/view/SurfaceControlViewHost.java +++ b/core/java/android/view/SurfaceControlViewHost.java @@ -422,7 +422,7 @@ public class SurfaceControlViewHost {      public void relayout(WindowManager.LayoutParams attrs,              WindowlessWindowManager.ResizeCompleteCallback callback) {          mViewRoot.setLayoutParams(attrs, false); -        mViewRoot.setReportNextDraw(true /* syncBuffer */); +        mViewRoot.setReportNextDraw(true /* syncBuffer */, "scvh_relayout");          mWm.setCompletionCallback(mViewRoot.mWindow.asBinder(), callback);      } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 7690af693148..fff6c6036601 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. @@ -586,8 +586,21 @@ public final class ViewRootImpl implements ViewParent,      int mContentCaptureEnabled = CONTENT_CAPTURE_ENABLED_NOT_CHECKED;      boolean mPerformContentCapture; -      boolean mReportNextDraw; +    /** Set only while mReportNextDraw=true, indicating the last reason that was triggered */ +    String mLastReportNextDrawReason; +    /** The reaason the last call to performDraw() returned false */ +    String mLastPerformDrawSkippedReason; +    /** The reason the last call to performTraversals() returned without drawing */ +    String mLastPerformTraversalsSkipDrawReason; +    /** The state of the local sync, if one is in progress. Can be one of the states below. */ +    int mLocalSyncState; + +    // The possible states of the local sync, see createSyncIfNeeded() +    private final int LOCAL_SYNC_NONE = 0; +    private final int LOCAL_SYNC_PENDING = 1; +    private final int LOCAL_SYNC_RETURNED = 2; +    private final int LOCAL_SYNC_MERGED = 3;      /**       * Set whether the draw should send the buffer to system server. When set to true, VRI will @@ -1811,7 +1824,7 @@ public final class ViewRootImpl implements ViewParent,          mSyncSeqId = args.argi4 > mSyncSeqId ? args.argi4 : mSyncSeqId;          if (msg == MSG_RESIZED_REPORT) { -            reportNextDraw(); +            reportNextDraw("resized");          }          if (mView != null && (frameChanged || configChanged)) { @@ -2716,6 +2729,8 @@ public final class ViewRootImpl implements ViewParent,      }      private void performTraversals() { +        mLastPerformTraversalsSkipDrawReason = null; +          // cache mView since it is used so much below...          final View host = mView;          if (DBG) { @@ -2725,12 +2740,14 @@ public final class ViewRootImpl implements ViewParent,          }          if (host == null || !mAdded) { +            mLastPerformTraversalsSkipDrawReason = host == null ? "no_host" : "not_added";              return;          }          mIsInTraversal = true;          mWillDrawSoon = true;          boolean cancelDraw = false; +        String cancelReason = null;          boolean isSyncRequest = false;          boolean windowSizeMayChange = false; @@ -3013,13 +3030,14 @@ public final class ViewRootImpl implements ViewParent,                  relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);                  cancelDraw = (relayoutResult & RELAYOUT_RES_CANCEL_AND_REDRAW)                          == RELAYOUT_RES_CANCEL_AND_REDRAW; +                cancelReason = "relayout";                  final boolean dragResizing = mPendingDragResizing;                  if (mSyncSeqId > mLastSyncSeqId) {                      mLastSyncSeqId = mSyncSeqId;                      if (DEBUG_BLAST) {                          Log.d(mTag, "Relayout called with blastSync");                      } -                    reportNextDraw(); +                    reportNextDraw("relayout");                      mSyncBuffer = true;                      isSyncRequest = true;                      if (!cancelDraw) { @@ -3117,6 +3135,7 @@ public final class ViewRootImpl implements ViewParent,                              }                          } catch (OutOfResourcesException e) {                              handleOutOfResourcesException(e); +                            mLastPerformTraversalsSkipDrawReason = "oom_initialize_renderer";                              return;                          }                      } @@ -3154,6 +3173,7 @@ public final class ViewRootImpl implements ViewParent,                          mAttachInfo.mThreadedRenderer.updateSurface(mSurface);                      } catch (OutOfResourcesException e) {                          handleOutOfResourcesException(e); +                        mLastPerformTraversalsSkipDrawReason = "oom_update_surface";                          return;                      }                  } @@ -3319,6 +3339,7 @@ public final class ViewRootImpl implements ViewParent,              if (mCheckIfCanDraw) {                  try {                      cancelDraw = mWindowSession.cancelDraw(mWindow); +                    cancelReason = "wm_sync";                      if (DEBUG_BLAST) {                          Log.d(mTag, "cancelDraw returned " + cancelDraw);                      } @@ -3541,19 +3562,21 @@ public final class ViewRootImpl implements ViewParent,          mImeFocusController.onTraversal(hasWindowFocus, mWindowAttributes);          if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) { -            reportNextDraw(); +            reportNextDraw("first_relayout");          }          mCheckIfCanDraw = isSyncRequest || cancelDraw; -        boolean cancelAndRedraw = -                mAttachInfo.mTreeObserver.dispatchOnPreDraw() || (cancelDraw && mDrewOnceForSync); +        boolean cancelDueToPreDrawListener = mAttachInfo.mTreeObserver.dispatchOnPreDraw(); +        boolean cancelAndRedraw = cancelDueToPreDrawListener +                 || (cancelDraw && mDrewOnceForSync);          if (!cancelAndRedraw) {              createSyncIfNeeded();              mDrewOnceForSync = true;          }          if (!isViewVisible) { +            mLastPerformTraversalsSkipDrawReason = "view_not_visible";              if (mPendingTransitions != null && mPendingTransitions.size() > 0) {                  for (int i = 0; i < mPendingTransitions.size(); ++i) {                      mPendingTransitions.get(i).endChangingAnimations(); @@ -3565,6 +3588,9 @@ public final class ViewRootImpl implements ViewParent,                  mSyncBufferCallback.onBufferReady(null);              }          } else if (cancelAndRedraw) { +            mLastPerformTraversalsSkipDrawReason = cancelDueToPreDrawListener +                ? "predraw_" + mAttachInfo.mTreeObserver.getLastDispatchOnPreDrawCanceledReason() +                : "cancel_" + cancelReason;              // Try again              scheduleTraversals();          } else { @@ -3588,11 +3614,13 @@ public final class ViewRootImpl implements ViewParent,          if (!cancelAndRedraw) {              mReportNextDraw = false; +            mLastReportNextDrawReason = null;              mSyncBufferCallback = null;              mSyncBuffer = false;              if (isInLocalSync()) {                  mSurfaceSyncer.markSyncReady(mSyncId);                  mSyncId = UNSET_SYNC_ID; +                mLocalSyncState = LOCAL_SYNC_NONE;              }          }      } @@ -3604,9 +3632,12 @@ public final class ViewRootImpl implements ViewParent,          }          final int seqId = mSyncSeqId; +        mLocalSyncState = LOCAL_SYNC_PENDING;          mSyncId = mSurfaceSyncer.setupSync(transaction -> { +            mLocalSyncState = LOCAL_SYNC_RETURNED;              // Callback will be invoked on executor thread so post to main thread.              mHandler.postAtFrontOfQueue(() -> { +                mLocalSyncState = LOCAL_SYNC_MERGED;                  mSurfaceChangedTransaction.merge(transaction);                  reportDrawFinished(seqId);              }); @@ -4321,9 +4352,12 @@ public final class ViewRootImpl implements ViewParent,      }      private boolean performDraw() { +        mLastPerformDrawSkippedReason = null;          if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) { +            mLastPerformDrawSkippedReason = "screen_off";              return false;          } else if (mView == null) { +            mLastPerformDrawSkippedReason = "no_root_view";              return false;          } @@ -8390,6 +8424,21 @@ public final class ViewRootImpl implements ViewParent,          if (mTraversalScheduled) {              writer.println(innerPrefix + " (barrier=" + mTraversalBarrier + ")");          } +        writer.println(innerPrefix + "mReportNextDraw=" + mReportNextDraw); +        if (mReportNextDraw) { +            writer.println(innerPrefix + " (reason=" + mLastReportNextDrawReason + ")"); +        } +        if (mLastPerformTraversalsSkipDrawReason != null) { +            writer.println(innerPrefix + "mLastPerformTraversalsFailedReason=" +                + mLastPerformTraversalsSkipDrawReason); +        } +        if (mLastPerformDrawSkippedReason != null) { +            writer.println(innerPrefix + "mLastPerformDrawFailedReason=" +                + mLastPerformDrawSkippedReason); +        } +        if (mLocalSyncState != LOCAL_SYNC_NONE) { +            writer.println(innerPrefix + "mLocalSyncState=" + mLocalSyncState); +        }          writer.println(innerPrefix + "mIsAmbientMode="  + mIsAmbientMode);          writer.println(innerPrefix + "mUnbufferedInputSource="                  + Integer.toHexString(mUnbufferedInputSource)); @@ -9890,11 +9939,12 @@ public final class ViewRootImpl implements ViewParent,          }      } -    private void reportNextDraw() { +    private void reportNextDraw(String reason) {          if (DEBUG_BLAST) {              Log.d(mTag, "reportNextDraw " + Debug.getCallers(5));          }          mReportNextDraw = true; +        mLastReportNextDrawReason = reason;      }      /** @@ -9907,11 +9957,12 @@ public final class ViewRootImpl implements ViewParent,       * @param syncBuffer If true, the transaction that contains the buffer from the draw should be       *                   sent to system to be synced. If false, VRI will not try to sync the buffer,       *                   but only report back that a buffer was drawn. +     * @param reason A debug string indicating the reason for reporting the next draw       * @hide       */ -    public void setReportNextDraw(boolean syncBuffer) { +    public void setReportNextDraw(boolean syncBuffer, String reason) {          mSyncBuffer = syncBuffer; -        reportNextDraw(); +        reportNextDraw(reason);          invalidate();      } diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java index e2466345ff4a..2c2ae06e9186 100644 --- a/core/java/android/view/ViewStructure.java +++ b/core/java/android/view/ViewStructure.java @@ -45,6 +45,30 @@ import java.util.List;  public abstract class ViewStructure {      /** +     * Key used for writing active child view information to the content capture bundle. +     * +     * The value stored under this key will be an ordered list of Autofill IDs of child views. +     * +     * TODO(b/241498401): Add @TestApi in Android U +     * @hide +     */ +    public static final String EXTRA_ACTIVE_CHILDREN_IDS = +            "android.view.ViewStructure.extra.ACTIVE_CHILDREN_IDS"; + +    /** +     * Key used for writing the first active child's position to the content capture bundle. +     * +     * When active child view information is provided under the +     * {@link #EXTRA_ACTIVE_CHILDREN_IDS}, the value stored under this key will be the +     * 0-based position of the first child view in the list relative to the positions of child views +     * in the containing View's dataset. +     * +     * TODO(b/241498401): Add @TestApi in Android U +     * @hide */ +    public static final String EXTRA_FIRST_ACTIVE_POSITION = +            "android.view.ViewStructure.extra.FIRST_ACTIVE_POSITION"; + +    /**       * Set the identifier for this view.       *       * @param id The view's identifier, as per {@link View#getId View.getId()}. diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java index ed8350afc109..fd62ecd11e13 100644 --- a/core/java/android/view/ViewTreeObserver.java +++ b/core/java/android/view/ViewTreeObserver.java @@ -74,6 +74,9 @@ public final class ViewTreeObserver {       * that the listener will be immediately called. */      private boolean mWindowShown; +    // The reason that the last call to dispatchOnPreDraw() returned true to cancel and redraw +    private String mLastDispatchOnPreDrawCanceledReason; +      private boolean mAlive = true;      /** @@ -1167,6 +1170,7 @@ public final class ViewTreeObserver {       */      @SuppressWarnings("unchecked")      public final boolean dispatchOnPreDraw() { +        mLastDispatchOnPreDrawCanceledReason = null;          boolean cancelDraw = false;          final CopyOnWriteArray<OnPreDrawListener> listeners = mOnPreDrawListeners;          if (listeners != null && listeners.size() > 0) { @@ -1174,7 +1178,11 @@ public final class ViewTreeObserver {              try {                  int count = access.size();                  for (int i = 0; i < count; i++) { -                    cancelDraw |= !(access.get(i).onPreDraw()); +                    final OnPreDrawListener preDrawListener = access.get(i); +                    cancelDraw |= !(preDrawListener.onPreDraw()); +                    if (cancelDraw) { +                        mLastDispatchOnPreDrawCanceledReason = preDrawListener.getClass().getName(); +                    }                  }              } finally {                  listeners.end(); @@ -1184,6 +1192,15 @@ public final class ViewTreeObserver {      }      /** +     * @return the reason that the last call to dispatchOnPreDraw() returned true to cancel the +     *         current draw, or null if the last call did not cancel. +     * @hide +     */ +    final String getLastDispatchOnPreDrawCanceledReason() { +        return mLastDispatchOnPreDrawCanceledReason; +    } + +    /**       * Notifies registered listeners that the window is now shown       * @hide       */ diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 63d42c0ca915..67352c02ce18 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -4396,15 +4396,42 @@ public interface WindowManager extends ViewManager {                  changes |= LAYOUT_CHANGED;              } -            if (!Arrays.equals(paramsForRotation, o.paramsForRotation)) { +            if (paramsForRotation != o.paramsForRotation) { +                if ((changes & LAYOUT_CHANGED) == 0) { +                    if (paramsForRotation != null && o.paramsForRotation != null +                            && paramsForRotation.length == o.paramsForRotation.length) { +                        for (int i = paramsForRotation.length - 1; i >= 0; i--) { +                            if (hasLayoutDiff(paramsForRotation[i], o.paramsForRotation[i])) { +                                changes |= LAYOUT_CHANGED; +                                break; +                            } +                        } +                    } else { +                        changes |= LAYOUT_CHANGED; +                    } +                }                  paramsForRotation = o.paramsForRotation;                  checkNonRecursiveParams(); -                changes |= LAYOUT_CHANGED;              }              return changes;          } +        /** +         * Returns {@code true} if the 2 params may have difference results of +         * {@link WindowLayout#computeFrames}. +         */ +        private static boolean hasLayoutDiff(LayoutParams a, LayoutParams b) { +            return a.width != b.width || a.height != b.height || a.x != b.x || a.y != b.y +                    || a.horizontalMargin != b.horizontalMargin +                    || a.verticalMargin != b.verticalMargin +                    || a.layoutInDisplayCutoutMode != b.layoutInDisplayCutoutMode +                    || a.gravity != b.gravity || !Arrays.equals(a.providedInsets, b.providedInsets) +                    || a.mFitInsetsTypes != b.mFitInsetsTypes +                    || a.mFitInsetsSides != b.mFitInsetsSides +                    || a.mFitInsetsIgnoringVisibility != b.mFitInsetsIgnoringVisibility; +        } +          @Override          public String debug(String output) {              output += "Contents of " + this + ":"; diff --git a/core/java/android/view/contentcapture/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java index ba4176faa283..db4ac5de0b49 100644 --- a/core/java/android/view/contentcapture/ContentCaptureEvent.java +++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java @@ -55,6 +55,15 @@ public final class ContentCaptureEvent implements Parcelable {      /**       * Called when a node has been added to the screen and is visible to the user.       * +     * On API level 33, this event may be re-sent with additional information if a view's children +     * have changed, e.g. scrolling Views inside of a ListView. This information will be stored in +     * the extras Bundle associated with the event's ViewNode. Within the Bundle, the +     * "android.view.ViewStructure.extra.ACTIVE_CHILDREN_IDS" key may be used to get a list of +     * Autofill IDs of active child views, and the +     * "android.view.ViewStructure.extra.FIRST_ACTIVE_POSITION" key may be used to get the 0-based +     * position of the first active child view in the list relative to the positions of child views +     * in the container View's dataset. +     *       * <p>The metadata of the node is available through {@link #getViewNode()}.       */      public static final int TYPE_VIEW_APPEARED = 1; diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java index 48d29706a1e9..1664637eac56 100644 --- a/core/java/android/view/contentcapture/ContentCaptureManager.java +++ b/core/java/android/view/contentcapture/ContentCaptureManager.java @@ -280,6 +280,15 @@ public final class ContentCaptureManager {              "service_explicitly_enabled";      /** +     * Device config property used by {@code android.widget.AbsListView} to determine whether or +     * not it should report the positions of its children to Content Capture. +     * +     * @hide +     */ +    public static final String DEVICE_CONFIG_PROPERTY_REPORT_LIST_VIEW_CHILDREN = +            "report_list_view_children"; + +    /**       * Maximum number of events that are buffered before sent to the app.       *       * @hide diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 231ae084dd6c..0b0bfb1ddbe9 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -20,6 +20,7 @@ import android.annotation.ColorInt;  import android.annotation.DrawableRes;  import android.annotation.NonNull;  import android.annotation.TestApi; +import android.app.ActivityThread;  import android.compat.annotation.UnsupportedAppUsage;  import android.content.Context;  import android.content.Intent; @@ -37,6 +38,7 @@ import android.os.Parcel;  import android.os.Parcelable;  import android.os.StrictMode;  import android.os.Trace; +import android.provider.DeviceConfig;  import android.text.Editable;  import android.text.InputType;  import android.text.TextUtils; @@ -65,6 +67,7 @@ import android.view.ViewDebug;  import android.view.ViewGroup;  import android.view.ViewHierarchyEncoder;  import android.view.ViewParent; +import android.view.ViewStructure;  import android.view.ViewTreeObserver;  import android.view.accessibility.AccessibilityEvent;  import android.view.accessibility.AccessibilityManager; @@ -73,6 +76,9 @@ import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;  import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;  import android.view.animation.Interpolator;  import android.view.animation.LinearInterpolator; +import android.view.autofill.AutofillId; +import android.view.contentcapture.ContentCaptureManager; +import android.view.contentcapture.ContentCaptureSession;  import android.view.inputmethod.BaseInputConnection;  import android.view.inputmethod.CompletionInfo;  import android.view.inputmethod.CorrectionInfo; @@ -634,6 +640,23 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te      private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE;      /** +     * Indicates that reporting positions of child views to content capture is enabled via +     * DeviceConfig. +     */ +    private static boolean sContentCaptureReportingEnabledByDeviceConfig = false; + +    /** +     * Listens for changes to DeviceConfig properties and updates stored values accordingly. +     */ +    private static DeviceConfig.OnPropertiesChangedListener sDeviceConfigChangeListener = null; + +    /** +     * Indicates that child positions of views should be reported to Content Capture the next time +     * that active views are refreshed. +     */ +    private boolean mReportChildrenToContentCaptureOnNextUpdate = true; + +    /**       * Helper object that renders and controls the fast scroll thumb.       */      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768941) @@ -850,8 +873,44 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te          public void adjustListItemSelectionBounds(Rect bounds);      } +    private static class DeviceConfigChangeListener +            implements DeviceConfig.OnPropertiesChangedListener { +        @Override +        public void onPropertiesChanged( +                @NonNull DeviceConfig.Properties properties) { +            if (!DeviceConfig.NAMESPACE_CONTENT_CAPTURE.equals(properties.getNamespace())) { +                return; +            } + +            for (String key : properties.getKeyset()) { +                if (!ContentCaptureManager.DEVICE_CONFIG_PROPERTY_REPORT_LIST_VIEW_CHILDREN +                        .equals(key)) { +                    continue; +                } + +                sContentCaptureReportingEnabledByDeviceConfig = properties.getBoolean(key, +                        false); +            } +        } +    } + +    private static void setupDeviceConfigProperties() { +        if (sDeviceConfigChangeListener == null) { +            sContentCaptureReportingEnabledByDeviceConfig = DeviceConfig.getBoolean( +                    DeviceConfig.NAMESPACE_CONTENT_CAPTURE, +                    ContentCaptureManager.DEVICE_CONFIG_PROPERTY_REPORT_LIST_VIEW_CHILDREN, +                    false); +            sDeviceConfigChangeListener = new DeviceConfigChangeListener(); +            DeviceConfig.addOnPropertiesChangedListener( +                    DeviceConfig.NAMESPACE_CONTENT_CAPTURE, +                    ActivityThread.currentApplication().getMainExecutor(), +                    sDeviceConfigChangeListener); +        } +    } +      public AbsListView(Context context) {          super(context); +        setupDeviceConfigProperties();          mEdgeGlowBottom = new EdgeEffect(context);          mEdgeGlowTop = new EdgeEffect(context);          initAbsListView(); @@ -874,6 +933,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te      public AbsListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {          super(context, attrs, defStyleAttr, defStyleRes); +        setupDeviceConfigProperties();          mEdgeGlowBottom = new EdgeEffect(context, attrs);          mEdgeGlowTop = new EdgeEffect(context, attrs);          initAbsListView(); @@ -4699,6 +4759,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te                  mOnScrollListener.onScrollStateChanged(this, newState);              }          } + +        // When scrolling, we want to report changes in the active children to Content Capture, +        // so set the flag to report on the next update only when scrolling has stopped or a fling +        // scroll is performed. +        if (newState == OnScrollListener.SCROLL_STATE_IDLE +                || newState == OnScrollListener.SCROLL_STATE_FLING) { +            mReportChildrenToContentCaptureOnNextUpdate = true; +        }      }      /** @@ -6654,10 +6722,77 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te          mRecycler.mRecyclerListener = listener;      } +    /** +     * {@inheritDoc} +     * +     * This method will initialize the fields of the {@link ViewStructure} +     * using the base implementation in {@link View}. On API level 33 and higher, it may also +     * write information about the positions of active views to the extras bundle provided by the +     * {@link ViewStructure}. +     * +     * NOTE: When overriding this method on API level 33, if not calling super() or if changing the +     * logic for child views, be sure to provide values for the first active child view position and +     * the list of active child views in the {@link ViewStructure}'s extras {@link Bundle} using the +     * "android.view.ViewStructure.extra.ACTIVE_CHILDREN_IDS" and +     * "android.view.ViewStructure.extra.FIRST_ACTIVE_POSITION" keys. +     * +     * @param structure {@link ViewStructure} to be filled in with structured view data. +     * @param flags optional flags. +     * +     * @see View#AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS +     */ +    @Override +    public void onProvideContentCaptureStructure( +            @NonNull ViewStructure structure, int flags) { +        super.onProvideContentCaptureStructure(structure, flags); +        if (!sContentCaptureReportingEnabledByDeviceConfig) { +            return; +        } + +        Bundle extras = structure.getExtras(); + +        if (extras == null) { +            Log.wtf(TAG, "Unexpected null extras Bundle in ViewStructure"); +            return; +        } + +        int childCount = getChildCount(); +        ArrayList<AutofillId> idsList = new ArrayList<>(childCount); + +        for (int i = 0; i < childCount; ++i) { +            View activeView = getChildAt(i); +            if (activeView == null) { +                continue; +            } + +            idsList.add(activeView.getAutofillId()); +        } + +        extras.putParcelableArrayList(ViewStructure.EXTRA_ACTIVE_CHILDREN_IDS, +                idsList); + +        extras.putInt(ViewStructure.EXTRA_FIRST_ACTIVE_POSITION, +                getFirstVisiblePosition()); +    } + +    private void reportActiveViewsToContentCapture() { +        if (!sContentCaptureReportingEnabledByDeviceConfig) { +            return; +        } + +        ContentCaptureSession session = getContentCaptureSession(); +        if (session != null) { +            ViewStructure structure = session.newViewStructure(this); +            onProvideContentCaptureStructure(structure, /* flags= */ 0); +            session.notifyViewAppeared(structure); +        } +    } +      class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {          @Override          public void onChanged() {              super.onChanged(); +            mReportChildrenToContentCaptureOnNextUpdate = true;              if (mFastScroll != null) {                  mFastScroll.onSectionsChanged();              } @@ -6666,6 +6801,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te          @Override          public void onInvalidated() {              super.onInvalidated(); +            mReportChildrenToContentCaptureOnNextUpdate = true;              if (mFastScroll != null) {                  mFastScroll.onSectionsChanged();              } @@ -6984,6 +7120,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te                      lp.scrappedFromPosition = firstActivePosition + i;                  }              } + +            if (mReportChildrenToContentCaptureOnNextUpdate && childCount > 0) { +                AbsListView.this.reportActiveViewsToContentCapture(); +                mReportChildrenToContentCaptureOnNextUpdate = false; +            }          }          /** 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/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java index 3bffa890122a..e3430a686864 100644 --- a/core/java/android/window/WindowContainerTransaction.java +++ b/core/java/android/window/WindowContainerTransaction.java @@ -269,6 +269,20 @@ public final class WindowContainerTransaction implements Parcelable {      }      /** +     * Sets whether a task should be translucent. When {@code false}, the existing translucent of +     * the task applies, but when {@code true} the task will be forced to be translucent. +     * @hide +     */ +    @NonNull +    public WindowContainerTransaction setForceTranslucent( +            @NonNull WindowContainerToken container, boolean forceTranslucent) { +        Change chg = getOrCreateChange(container.asBinder()); +        chg.mForceTranslucent = forceTranslucent; +        chg.mChangeMask |= Change.CHANGE_FORCE_TRANSLUCENT; +        return this; +    } + +    /**       * Used in conjunction with a shell-transition call (usually finishTransition). This is       * basically a message to the transition system that a particular task should NOT go into       * PIP even though it normally would. This is to deal with some edge-case situations where @@ -834,11 +848,13 @@ public final class WindowContainerTransaction implements Parcelable {          public static final int CHANGE_BOUNDS_TRANSACTION_RECT = 1 << 4;          public static final int CHANGE_IGNORE_ORIENTATION_REQUEST = 1 << 5;          public static final int CHANGE_FORCE_NO_PIP = 1 << 6; +        public static final int CHANGE_FORCE_TRANSLUCENT = 1 << 7;          private final Configuration mConfiguration = new Configuration();          private boolean mFocusable = true;          private boolean mHidden = false;          private boolean mIgnoreOrientationRequest = false; +        private boolean mForceTranslucent = false;          private int mChangeMask = 0;          private @ActivityInfo.Config int mConfigSetMask = 0; @@ -858,6 +874,7 @@ public final class WindowContainerTransaction implements Parcelable {              mFocusable = in.readBoolean();              mHidden = in.readBoolean();              mIgnoreOrientationRequest = in.readBoolean(); +            mForceTranslucent = in.readBoolean();              mChangeMask = in.readInt();              mConfigSetMask = in.readInt();              mWindowSetMask = in.readInt(); @@ -903,6 +920,9 @@ public final class WindowContainerTransaction implements Parcelable {              if ((other.mChangeMask & CHANGE_IGNORE_ORIENTATION_REQUEST) != 0) {                  mIgnoreOrientationRequest = other.mIgnoreOrientationRequest;              } +            if ((other.mChangeMask & CHANGE_FORCE_TRANSLUCENT) != 0) { +                mForceTranslucent = other.mForceTranslucent; +            }              mChangeMask |= other.mChangeMask;              if (other.mActivityWindowingMode >= 0) {                  mActivityWindowingMode = other.mActivityWindowingMode; @@ -953,6 +973,15 @@ public final class WindowContainerTransaction implements Parcelable {              return mIgnoreOrientationRequest;          } +        /** Gets the requested force translucent state. */ +        public boolean getForceTranslucent() { +            if ((mChangeMask & CHANGE_FORCE_TRANSLUCENT) == 0) { +                throw new RuntimeException("Force translucent not set. " +                        + "Check CHANGE_FORCE_TRANSLUCENT first"); +            } +            return mForceTranslucent; +        } +          public int getChangeMask() {              return mChangeMask;          } @@ -1030,6 +1059,7 @@ public final class WindowContainerTransaction implements Parcelable {              dest.writeBoolean(mFocusable);              dest.writeBoolean(mHidden);              dest.writeBoolean(mIgnoreOrientationRequest); +            dest.writeBoolean(mForceTranslucent);              dest.writeInt(mChangeMask);              dest.writeInt(mConfigSetMask);              dest.writeInt(mWindowSetMask); 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/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index 9b583be547c3..00b01051adae 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -339,6 +339,8 @@ public class SystemConfig {      // A map from package name of vendor APEXes that can be updated to an installer package name      // allowed to install updates for it.      private final ArrayMap<String, String> mAllowedVendorApexes = new ArrayMap<>(); +    // A set of package names that are allowed to use <install-constraints> manifest tag. +    private final Set<String> mInstallConstraintsAllowlist = new ArraySet<>();      private String mModulesInstallerPackageName; @@ -535,6 +537,10 @@ public class SystemConfig {          return mAllowedVendorApexes;      } +    public Set<String> getInstallConstraintsAllowlist() { +        return mInstallConstraintsAllowlist; +    } +      public String getModulesInstallerPackageName() {          return mModulesInstallerPackageName;      } @@ -1455,6 +1461,20 @@ public class SystemConfig {                          }                          XmlUtils.skipCurrentTag(parser);                      } break; +                    case "install-constraints-allowed": { +                        if (allowAppConfigs) { +                            String packageName = parser.getAttributeValue(null, "package"); +                            if (packageName == null) { +                                Slog.w(TAG, "<" + name + "> without package in " + permFile +                                        + " at " + parser.getPositionDescription()); +                            } else { +                                mInstallConstraintsAllowlist.add(packageName); +                            } +                        } else { +                            logNotAllowedInPartition(name, permFile, parser); +                        } +                        XmlUtils.skipCurrentTag(parser); +                    } break;                      default: {                          Slog.w(TAG, "Tag " + name + " is unknown in "                                  + permFile + " at " + parser.getPositionDescription()); diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 7562b9aa0ead..a62f6ad4681a 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -3602,4 +3602,19 @@               false, the application cannot be profiled at all. Defaults to true. -->          <attr name="enabled" format="boolean" />      </declare-styleable> + +    <!-- <code>install-constraints</code> tag rejects installs unless one the constraints defined by +         its child elements is true. +         It is possible to have multiple <code>install-constraints</code> tags in a single manifest, +         where each tag is evaluated independently. +         @hide --> +    <declare-styleable name="AndroidManifestInstallConstraints" parent="AndroidManifest" /> + +    <!-- A constraint for <code>install-constraints</code>. Checks that the device fingerprint +         starts with the given prefix. +         @hide --> +    <declare-styleable name="AndroidManifestInstallConstraintsFingerprintPrefix" +                       parent="AndroidManifestInstallConstraints"> +        <attr name="value" /> +    </declare-styleable>  </resources> diff --git a/core/tests/coretests/src/android/view/WindowParamsTest.java b/core/tests/coretests/src/android/view/WindowParamsTest.java new file mode 100644 index 000000000000..49d4872922e2 --- /dev/null +++ b/core/tests/coretests/src/android/view/WindowParamsTest.java @@ -0,0 +1,100 @@ +/* + * 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 android.view; + +import static org.junit.Assert.assertEquals; + +import android.os.Parcel; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; + +@Presubmit +@SmallTest +public class WindowParamsTest { + +    @Test +    public void testParamsForRotation() { +        final WindowManager.LayoutParams paramsA = new WindowManager.LayoutParams(); +        initParamsForRotation(paramsA); +        final Parcel parcel = Parcel.obtain(); +        paramsA.writeToParcel(parcel, 0); +        parcel.setDataPosition(0); + +        final WindowManager.LayoutParams paramsB = new WindowManager.LayoutParams(parcel); +        assertEquals(0, paramsA.copyFrom(paramsB)); + +        for (int i = 1; i <= 12; i++) { +            initParamsForRotation(paramsA); +            changeField(i, paramsA.paramsForRotation[0]); +            assertEquals("Change not found for case " + i, +                    WindowManager.LayoutParams.LAYOUT_CHANGED, paramsA.copyFrom(paramsB)); +        } + +        parcel.recycle(); +    } + +    private static void initParamsForRotation(WindowManager.LayoutParams params) { +        params.paramsForRotation = new WindowManager.LayoutParams[4]; +        for (int i = 0; i < 4; i++) { +            params.paramsForRotation[i] = new WindowManager.LayoutParams(); +        } +    } + +    private static void changeField(int fieldCase, WindowManager.LayoutParams params) { +        switch (fieldCase) { +            case 1: +                params.width++; +                break; +            case 2: +                params.height++; +                break; +            case 3: +                params.x++; +                break; +            case 4: +                params.y++; +                break; +            case 5: +                params.horizontalMargin++; +                break; +            case 6: +                params.verticalMargin++; +                break; +            case 7: +                params.layoutInDisplayCutoutMode++; +                break; +            case 8: +                params.gravity++; +                break; +            case 9: +                params.providedInsets = new InsetsFrameProvider[0]; +                break; +            case 10: +                params.setFitInsetsTypes(0); +                break; +            case 11: +                params.setFitInsetsSides(0); +                break; +            case 12: +                params.setFitInsetsIgnoringVisibility(!params.isFitInsetsIgnoringVisibility()); +                break; +        } +    } +} 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..d3e46f82efe5 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,13 +91,14 @@ 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;      /** Tracks if an uninterruptible transition is in progress */      private boolean mTransitionInProgress = false; +    /** Tracks if we should start the back gesture on the next motion move event */ +    private boolean mShouldStartOnNextMoveEvent = false;      /** @see #setTriggerBack(boolean) */      private boolean mTriggerBack; @@ -105,6 +107,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 +138,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 +158,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( @@ -286,12 +300,17 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont          if (mTransitionInProgress) {              return;          } -        if (keyAction == MotionEvent.ACTION_MOVE) { +        if (keyAction == MotionEvent.ACTION_DOWN) {              if (!mBackGestureStarted) { +                mShouldStartOnNextMoveEvent = true; +            } +        } else if (keyAction == MotionEvent.ACTION_MOVE) { +            if (!mBackGestureStarted && mShouldStartOnNextMoveEvent) {                  // Let the animation initialized here to make sure the onPointerDownOutsideFocus                  // could be happened when ACTION_DOWN, it may change the current focus that we                  // would access it when startBackNavigation.                  onGestureStarted(touchX, touchY); +                mShouldStartOnNextMoveEvent = false;              }              onMove(touchX, touchY, swipeEdge);          } else if (keyAction == MotionEvent.ACTION_UP || keyAction == MotionEvent.ACTION_CANCEL) { @@ -425,6 +444,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont      private void onGestureFinished(boolean fromTouch) {          ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", mTriggerBack); +        if (!mBackGestureStarted) { +            finishAnimation(); +            return; +        } +          if (fromTouch) {              // Let touch reset the flag otherwise it will start a new back navigation and refresh              // the info when received a new move event. @@ -540,6 +564,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont          boolean triggerBack = mTriggerBack;          mBackNavigationInfo = null;          mTriggerBack = false; +        mShouldStartOnNextMoveEvent = false;          if (backNavigationInfo == null) {              return;          } 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/common/split/SplitScreenConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java index afc706ee9c8e..b8204d013105 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java @@ -19,6 +19,7 @@ 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 android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;  import android.annotation.IntDef; @@ -55,4 +56,7 @@ public class SplitScreenConstants {              {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};      public static final int[] CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE =              {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW}; + +    /** Flag applied to a transition change to identify it as a divider bar for animation. */ +    public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM;  } 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..a6a04cf67b3c 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 @@ -128,15 +128,9 @@ public abstract class WMShellBaseModule {                  mainExecutor);      } -    // Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride} -    @BindsOptionalOf -    @DynamicOverride -    abstract DisplayImeController optionalDisplayImeController(); -      @WMSingleton      @Provides      static DisplayImeController provideDisplayImeController( -            @DynamicOverride Optional<DisplayImeController> overrideDisplayImeController,              IWindowManager wmService,              ShellInit shellInit,              DisplayController displayController, @@ -144,9 +138,6 @@ public abstract class WMShellBaseModule {              TransactionPool transactionPool,              @ShellMainThread ShellExecutor mainExecutor      ) { -        if (overrideDisplayImeController.isPresent()) { -            return overrideDisplayImeController.get(); -        }          return new DisplayImeController(wmService, shellInit, displayController,                  displayInsetsController, transactionPool, mainExecutor);      } @@ -174,13 +165,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 +180,7 @@ public abstract class WMShellBaseModule {      static KidsModeTaskOrganizer provideKidsModeTaskOrganizer(              Context context,              ShellInit shellInit, +            ShellCommandHandler shellCommandHandler,              SyncTransactionQueue syncTransactionQueue,              DisplayController displayController,              DisplayInsetsController displayInsetsController, @@ -196,19 +189,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 +262,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 +380,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 +465,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 +649,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 +677,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 +701,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 de7e7bd1c506..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 @@ -21,6 +21,7 @@ import static android.app.ActivityManager.START_TASK_TO_FRONT;  import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;  import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;  import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.RemoteAnimationTarget.MODE_OPENING;  import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;  import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; @@ -83,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; @@ -90,7 +92,6 @@ import com.android.wm.shell.transition.Transitions;  import java.io.PrintWriter;  import java.lang.annotation.Retention;  import java.lang.annotation.RetentionPolicy; -import java.util.Arrays;  import java.util.Objects;  import java.util.Optional;  import java.util.concurrent.Executor; @@ -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 @@ -453,12 +493,12 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,              mStageCoordinator.prepareEvictInvisibleChildTasks(wct);              mSyncQueue.queue(wct);          } -        return reparentSplitTasksForAnimation(apps, true /*splitExpectedToBeVisible*/); +        return reparentSplitTasksForAnimation(apps, false /* enterSplitScreen */);      }      RemoteAnimationTarget[] onStartingSplitLegacy(RemoteAnimationTarget[] apps) {          try { -            return reparentSplitTasksForAnimation(apps, false /*splitExpectedToBeVisible*/); +            return reparentSplitTasksForAnimation(apps, true /* enterSplitScreen */);          } finally {              for (RemoteAnimationTarget appTarget : apps) {                  if (appTarget.leash != null) { @@ -469,14 +509,23 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,      }      private RemoteAnimationTarget[] reparentSplitTasksForAnimation(RemoteAnimationTarget[] apps, -            boolean splitExpectedToBeVisible) { +            boolean enterSplitScreen) {          if (ENABLE_SHELL_TRANSITIONS) return null; -        // TODO(b/206487881): Integrate this with shell transition. -        if (splitExpectedToBeVisible && !isSplitScreenVisible()) return null; -        // Split not visible, but not enough apps to have split, also return null -        if (!splitExpectedToBeVisible && apps.length < 2) return null; -        SurfaceControl.Transaction transaction = new SurfaceControl.Transaction(); +        if (enterSplitScreen) { +            int openingApps = 0; +            for (int i = 0; i < apps.length; ++i) { +                if (apps[i].mode == MODE_OPENING) openingApps++; +            } +            if (openingApps < 2) { +                // Not having enough apps to enter split screen +                return null; +            } +        } else if (!isSplitScreenVisible()) { +            return null; +        } + +        final SurfaceControl.Transaction transaction = mTransactionPool.acquire();          if (mSplitTasksContainerLayer != null) {              // Remove the previous layer before recreating              transaction.remove(mSplitTasksContainerLayer); @@ -489,17 +538,14 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,          mRootTDAOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, builder);          mSplitTasksContainerLayer = builder.build(); -        // Ensure that we order these in the parent in the right z-order as their previous order -        Arrays.sort(apps, (a1, a2) -> a1.prefixOrderIndex - a2.prefixOrderIndex); -        int layer = 1; -        for (RemoteAnimationTarget appTarget : apps) { +        for (int i = 0; i < apps.length; ++i) { +            final RemoteAnimationTarget appTarget = apps[i];              transaction.reparent(appTarget.leash, mSplitTasksContainerLayer);              transaction.setPosition(appTarget.leash, appTarget.screenSpaceBounds.left,                      appTarget.screenSpaceBounds.top); -            transaction.setLayer(appTarget.leash, layer++);          }          transaction.apply(); -        transaction.close(); +        mTransactionPool.release(transaction);          return new RemoteAnimationTarget[]{mStageCoordinator.getDividerBarLegacyTarget()};      }      /** 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..4bc8e913ec4e 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 @@ -32,13 +32,13 @@ import static android.view.WindowManager.TRANSIT_CHANGE;  import static android.view.WindowManager.TRANSIT_TO_BACK;  import static android.view.WindowManager.TRANSIT_TO_FRONT;  import static android.view.WindowManager.transitTypeToString; -import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;  import static android.window.TransitionInfo.FLAG_IS_DISPLAY;  import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;  import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTER;  import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;  import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES; +import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;  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 com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; @@ -147,9 +147,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,      private static final String TAG = StageCoordinator.class.getSimpleName(); -    /** Flag applied to a transition change to identify it as a divider bar for animation. */ -    public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM; -      private final SurfaceSession mSurfaceSession = new SurfaceSession();      private final MainStage mMainStage; @@ -452,10 +449,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 +654,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 @@ -888,6 +891,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,              }          });          mShouldUpdateRecents = false; +        mIsDividerRemoteAnimating = false;          if (childrenToTop == null) {              mSideStage.removeAllTasks(wct, false /* toTop */); @@ -1802,7 +1806,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,          boolean shouldAnimate = true;          if (mSplitTransitions.isPendingEnter(transition)) { -            shouldAnimate = startPendingEnterAnimation(transition, info, startTransaction); +            shouldAnimate = startPendingEnterAnimation( +                    transition, info, startTransaction, finishTransaction);          } else if (mSplitTransitions.isPendingRecent(transition)) {              shouldAnimate = startPendingRecentAnimation(transition, info, startTransaction);          } else if (mSplitTransitions.isPendingDismiss(transition)) { @@ -1830,7 +1835,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,      }      private boolean startPendingEnterAnimation(@NonNull IBinder transition, -            @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) { +            @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, +            @NonNull SurfaceControl.Transaction finishT) {          // First, verify that we actually have opened apps in both splits.          TransitionInfo.Change mainChild = null;          TransitionInfo.Change sideChild = null; @@ -1877,8 +1883,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,                      + " before startAnimation().");          } -        finishEnterSplitScreen(t); -        addDividerBarToTransition(info, t, true /* show */); +        finishEnterSplitScreen(finishT); +        addDividerBarToTransition(info, finishT, true /* show */);          return true;      } @@ -1963,7 +1969,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,              return false;          } -        addDividerBarToTransition(info, t, false /* show */); +        addDividerBarToTransition(info, finishT, false /* show */);          return true;      } @@ -1974,23 +1980,25 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,      }      private void addDividerBarToTransition(@NonNull TransitionInfo info, -            @NonNull SurfaceControl.Transaction t, boolean show) { +            @NonNull SurfaceControl.Transaction finishT, boolean show) {          final SurfaceControl leash = mSplitLayout.getDividerLeash();          final TransitionInfo.Change barChange = new TransitionInfo.Change(null /* token */, leash); -        final Rect bounds = mSplitLayout.getDividerBounds(); -        barChange.setStartAbsBounds(bounds); -        barChange.setEndAbsBounds(bounds); +        mSplitLayout.getRefDividerBounds(mTempRect1); +        barChange.setStartAbsBounds(mTempRect1); +        barChange.setEndAbsBounds(mTempRect1);          barChange.setMode(show ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK);          barChange.setFlags(FLAG_IS_DIVIDER_BAR);          // Technically this should be order-0, but this is running after layer assignment          // and it's a special case, so just add to end.          info.addChange(barChange); -        // Be default, make it visible. The remote animator can adjust alpha if it plans to animate. +          if (show) { -            t.setAlpha(leash, 1.f); -            t.setLayer(leash, Integer.MAX_VALUE); -            t.setPosition(leash, bounds.left, bounds.top); -            t.show(leash); +            finishT.setLayer(leash, Integer.MAX_VALUE); +            finishT.setPosition(leash, mTempRect1.left, mTempRect1.top); +            finishT.show(leash); +            // Ensure divider surface are re-parented back into the hierarchy at the end of the +            // transition. See Transition#buildFinishTransaction for more detail. +            finishT.reparent(leash, mRootTaskLeash);          }      } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index f414d69d37ec..1af9415fca3a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -33,6 +33,7 @@ import android.content.Context;  import android.graphics.Point;  import android.graphics.Rect;  import android.os.IBinder; +import android.util.Slog;  import android.util.SparseArray;  import android.view.RemoteAnimationTarget;  import android.view.SurfaceControl; @@ -376,7 +377,13 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {              SurfaceControl leash, boolean firstAppeared) {          final Point taskPositionInParent = taskInfo.positionInParent;          mSyncQueue.runInSync(t -> { -            t.setWindowCrop(leash, null); +            // The task surface might be released before running in the sync queue for the case like +            // trampoline launch, so check if the surface is valid before processing it. +            if (!leash.isValid()) { +                Slog.w(TAG, "Skip updating invalid child task surface of task#" + taskInfo.taskId); +                return; +            } +            t.setCrop(leash, null);              t.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y);              if (firstAppeared && !ENABLE_SHELL_TRANSITIONS) {                  t.setAlpha(leash, 1f); 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/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index 5cce6b99fb11..e26c259b2397 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -20,9 +20,9 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;  import static android.view.WindowManager.TRANSIT_TO_BACK;  import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; +import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;  import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;  import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP; -import static com.android.wm.shell.splitscreen.StageCoordinator.FLAG_IS_DIVIDER_BAR;  import android.annotation.NonNull;  import android.annotation.Nullable; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java index a843b2a0ac39..45b69f17a861 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java @@ -162,13 +162,12 @@ class ScreenRotationAnimation {                      .setParent(mAnimLeash)                      .setBLASTLayer()                      .setSecure(screenshotBuffer.containsSecureLayers()) +                    .setOpaque(true)                      .setCallsite("ShellRotationAnimation")                      .setName("RotationLayer")                      .build();              t.setLayer(mAnimLeash, SCREEN_FREEZE_LAYER_BASE); -            t.setPosition(mAnimLeash, 0, 0); -            t.setAlpha(mAnimLeash, 1);              t.show(mAnimLeash);              final ColorSpace colorSpace = screenshotBuffer.getColorSpace(); @@ -181,6 +180,7 @@ class ScreenRotationAnimation {                  mBackColorSurface = new SurfaceControl.Builder(session)                          .setParent(rootLeash)                          .setColorLayer() +                        .setOpaque(true)                          .setCallsite("ShellRotationAnimation")                          .setName("BackColorSurface")                          .build(); @@ -189,7 +189,6 @@ class ScreenRotationAnimation {                  t.setLayer(mBackColorSurface, -1);                  t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma}); -                t.setAlpha(mBackColorSurface, 1);                  t.show(mBackColorSurface);              } @@ -242,7 +241,6 @@ class ScreenRotationAnimation {          t.setMatrix(mScreenshotLayer,                  mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],                  mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]); -        t.setAlpha(mScreenshotLayer, (float) 1.0);      }      /** 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..5b3b8fd7ad71 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(); @@ -272,9 +284,14 @@ public class BackAnimationControllerTest extends ShellTestCase {          // the previous transition is finished.          doMotionEvent(MotionEvent.ACTION_DOWN, 0);          verifyNoMoreInteractions(mIOnBackInvokedCallback); +        mController.onBackToLauncherAnimationFinished(); + +        // Verify that more events from a rejected swipe cannot start animation. +        doMotionEvent(MotionEvent.ACTION_MOVE, 100); +        doMotionEvent(MotionEvent.ACTION_UP, 0); +        verifyNoMoreInteractions(mIOnBackInvokedCallback);          // Verify that we start accepting gestures again once transition finishes. -        mController.onBackToLauncherAnimationFinished();          doMotionEvent(MotionEvent.ACTION_DOWN, 0);          doMotionEvent(MotionEvent.ACTION_MOVE, 100);          verify(mIOnBackInvokedCallback).onBackStarted(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java index 514390fa52f9..d467b399ebbb 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java @@ -47,6 +47,7 @@ import org.junit.After;  import org.junit.Before;  import org.junit.Test;  import org.mockito.MockitoSession; +import org.mockito.quality.Strictness;  /**   * Tests for {@link DisplayLayout}. @@ -62,6 +63,7 @@ public class DisplayLayoutTest extends ShellTestCase {      public void setup() {          mMockitoSession = mockitoSession()                  .initMocks(this) +                .strictness(Strictness.WARN)                  .mockStatic(SystemBarUtils.class)                  .startMocking();      } 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/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt index 2f36ab9aa93d..8f9ced6956ca 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt @@ -23,7 +23,6 @@ import android.app.Dialog  import android.graphics.Color  import android.graphics.Rect  import android.os.Looper -import android.service.dreams.IDreamManager  import android.util.Log  import android.util.MathUtils  import android.view.GhostView @@ -54,7 +53,7 @@ private const val TAG = "DialogLaunchAnimator"  class DialogLaunchAnimator  @JvmOverloads  constructor( -    private val dreamManager: IDreamManager, +    private val callback: Callback,      private val interactionJankMonitor: InteractionJankMonitor,      private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS),      private val isForTesting: Boolean = false @@ -126,7 +125,7 @@ constructor(          val animatedDialog =              AnimatedDialog(                  launchAnimator, -                dreamManager, +                callback,                  interactionJankMonitor,                  animateFrom,                  onDialogDismissed = { openedDialogs.remove(it) }, @@ -194,8 +193,12 @@ constructor(          val dialog = animatedDialog.dialog -        // Don't animate if the dialog is not showing. -        if (!dialog.isShowing) { +        // Don't animate if the dialog is not showing or if we are locked and going to show the +        // bouncer. +        if ( +            !dialog.isShowing || +            (!callback.isUnlocked() && !callback.isShowingAlternateAuthOnUnlock()) +        ) {              return null          } @@ -285,6 +288,23 @@ constructor(              ?.let { it.touchSurface = it.prepareForStackDismiss() }          dialog.dismiss()      } + +    interface Callback { +        /** Whether the device is currently in dreaming (screensaver) mode. */ +        fun isDreaming(): Boolean + +        /** +         * Whether the device is currently unlocked, i.e. if it is *not* on the keyguard or if the +         * keyguard can be dismissed. +         */ +        fun isUnlocked(): Boolean + +        /** +         * Whether we are going to show alternate authentication (like UDFPS) instead of the +         * traditional bouncer when unlocking the device. +         */ +        fun isShowingAlternateAuthOnUnlock(): Boolean +    }  }  /** @@ -296,7 +316,7 @@ data class DialogCuj(@CujType val cujType: Int, val tag: String? = null)  private class AnimatedDialog(      private val launchAnimator: LaunchAnimator, -    private val dreamManager: IDreamManager, +    private val callback: DialogLaunchAnimator.Callback,      private val interactionJankMonitor: InteractionJankMonitor,      /** The view that triggered the dialog after being tapped. */ @@ -850,7 +870,7 @@ private class AnimatedDialog(          // If we are dreaming, the dialog was probably closed because of that so we don't animate          // into the touchSurface. -        if (dreamManager.isDreaming) { +        if (callback.isDreaming()) {              return false          } 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/res-keyguard/layout/keyguard_bouncer_user_switcher.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml index 01e3de2315af..898935fc7e99 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml @@ -35,7 +35,7 @@      <!-- need to keep this outer view in order to have a correctly sized anchor           for the dropdown menu, as well as dropdown background in the right place --> -    <LinearLayout +    <com.android.keyguard.KeyguardUserSwitcherAnchor          android:id="@+id/user_switcher_anchor"          android:orientation="horizontal"          android:layout_height="wrap_content" @@ -48,7 +48,7 @@            android:textDirection="locale"            android:layout_width="@dimen/bouncer_user_switcher_width"            android:layout_height="wrap_content" /> -    </LinearLayout>> +    </com.android.keyguard.KeyguardUserSwitcherAnchor>  </LinearLayout> diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml index 70a770912c7f..c972624c04b1 100644 --- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml +++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml @@ -35,10 +35,20 @@          app:layout_constraintBottom_toBottomOf="parent" />      <LinearLayout +        android:id="@+id/dream_overlay_extra_items" +        android:layout_width="wrap_content" +        android:layout_height="match_parent" +        android:orientation="horizontal" +        android:gravity="center_vertical" +        android:layout_marginEnd="@dimen/dream_overlay_status_bar_extra_margin" +        app:layout_constraintEnd_toStartOf="@+id/dream_overlay_system_status" /> + +    <LinearLayout          android:id="@+id/dream_overlay_system_status"          android:layout_width="wrap_content"          android:layout_height="match_parent"          android:orientation="horizontal" +        android:layout_marginStart="@dimen/dream_overlay_status_bar_extra_margin"          app:layout_constraintEnd_toEndOf="parent">          <com.android.systemui.statusbar.AlphaOptimizedImageView diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 3fb00a33e6b6..8ea2c0cbe8f5 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1455,6 +1455,7 @@      <dimen name="dream_overlay_camera_mic_off_indicator_size">8dp</dimen>      <dimen name="dream_overlay_notification_indicator_size">6dp</dimen>      <dimen name="dream_overlay_grey_chip_width">56dp</dimen> +    <dimen name="dream_overlay_status_bar_extra_margin">16dp</dimen>      <!-- Dream overlay complications related dimensions -->      <dimen name="dream_overlay_complication_clock_time_text_size">100sp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 9c2542cbd05f..1bf30377d7ff 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -887,7 +887,8 @@      <!-- Accessibility label for the button that opens the user switcher. -->      <string name="accessibility_multi_user_switch_switcher">Switch user</string> -    <!-- Accessibility label for the button that opens the user switcher and announces the current user. --> +    <!-- Accessibility role description for the element that opens the user switcher list. --> +    <string name="accessibility_multi_user_list_switcher">pulldown menu</string>      <!-- Accessibility label for the user icon on the lock screen. --> diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml index ee0c4fb6bab8..a82684d0358b 100644 --- a/packages/SystemUI/res/xml/qqs_header.xml +++ b/packages/SystemUI/res/xml/qqs_header.xml @@ -43,7 +43,7 @@          android:id="@+id/date">          <Layout              android:layout_width="0dp" -            android:layout_height="0dp" +            android:layout_height="@dimen/qs_header_non_clickable_element_height"              app:layout_constrainedWidth="true"              app:layout_constraintStart_toEndOf="@id/clock"              app:layout_constraintEnd_toStartOf="@id/barrier" @@ -61,7 +61,7 @@              app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height"              app:layout_constraintStart_toEndOf="@id/date"              app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon" -            app:layout_constraintTop_toTopOf="@id/date" +            app:layout_constraintTop_toTopOf="parent"              app:layout_constraintBottom_toBottomOf="parent"              app:layout_constraintHorizontal_bias="1"              /> @@ -76,7 +76,7 @@              app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height"              app:layout_constraintStart_toEndOf="@id/statusIcons"              app:layout_constraintEnd_toEndOf="@id/end_guide" -            app:layout_constraintTop_toTopOf="@id/date" +            app:layout_constraintTop_toTopOf="parent"              app:layout_constraintBottom_toBottomOf="parent"              app:layout_constraintHorizontal_bias="1"              /> @@ -100,8 +100,8 @@              android:layout_height="0dp"              app:layout_constraintStart_toEndOf="@id/date"              app:layout_constraintEnd_toEndOf="@id/end_guide" -            app:layout_constraintTop_toTopOf="@id/date" -            app:layout_constraintBottom_toBottomOf="@id/date" +            app:layout_constraintTop_toTopOf="parent" +            app:layout_constraintBottom_toBottomOf="parent"              app:layout_constraintHorizontal_bias="1"          />      </Constraint> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl index 01497516e0b1..4613e8b1060f 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl @@ -100,4 +100,14 @@ oneway interface IOverviewProxy {       * Sent when the desired dark intensity of the nav buttons has changed       */      void onNavButtonsDarkIntensityChanged(float darkIntensity) = 22; + +     /** +      * Sent when screen started turning on. +      */ +     void onScreenTurningOn() = 23; + +     /** +      * Sent when screen started turning off. +      */ +     void onScreenTurningOff() = 24;  } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java index 9265f07ad284..33e8e3555eba 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java @@ -122,12 +122,13 @@ public class RemoteAnimationAdapterCompat {                      IRemoteTransitionFinishedCallback finishCallback) {                  final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>();                  final RemoteAnimationTargetCompat[] appsCompat = -                        RemoteAnimationTargetCompat.wrap(info, false /* wallpapers */, t, leashMap); +                        RemoteAnimationTargetCompat.wrapApps(info, t, leashMap);                  final RemoteAnimationTargetCompat[] wallpapersCompat = -                        RemoteAnimationTargetCompat.wrap(info, true /* wallpapers */, t, leashMap); -                // TODO(bc-unlock): Build wrapped object for non-apps target. +                        RemoteAnimationTargetCompat.wrapNonApps( +                                info, true /* wallpapers */, t, leashMap);                  final RemoteAnimationTargetCompat[] nonAppsCompat = -                        new RemoteAnimationTargetCompat[0]; +                        RemoteAnimationTargetCompat.wrapNonApps( +                                info, false /* wallpapers */, t, leashMap);                  // TODO(b/177438007): Move this set-up logic into launcher's animation impl.                  boolean isReturnToHome = false; diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java index ef9e0951abf0..7c3b5fc52f0a 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java @@ -16,7 +16,9 @@  package com.android.systemui.shared.system; +import static android.app.ActivityTaskManager.INVALID_TASK_ID;  import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; +import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;  import static android.view.WindowManager.TRANSIT_CLOSE;  import static android.view.WindowManager.TRANSIT_OPEN;  import static android.view.WindowManager.TRANSIT_TO_BACK; @@ -24,6 +26,8 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT;  import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;  import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; +import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; +  import android.annotation.NonNull;  import android.annotation.SuppressLint;  import android.app.ActivityManager; @@ -76,7 +80,7 @@ public class RemoteAnimationTargetCompat {      private final SurfaceControl mStartLeash; -    // Fields used only to unrap into RemoteAnimationTarget +    // Fields used only to unwrap into RemoteAnimationTarget      private final Rect startBounds;      public final boolean willShowImeOnTarget; @@ -203,8 +207,19 @@ public class RemoteAnimationTargetCompat {      public RemoteAnimationTargetCompat(TransitionInfo.Change change, int order,              TransitionInfo info, SurfaceControl.Transaction t) { -        taskId = change.getTaskInfo() != null ? change.getTaskInfo().taskId : -1;          mode = newModeToLegacyMode(change.getMode()); +        taskInfo = change.getTaskInfo(); +        if (taskInfo != null) { +            taskId = taskInfo.taskId; +            isNotInRecents = !taskInfo.isRunning; +            activityType = taskInfo.getActivityType(); +            windowConfiguration = taskInfo.configuration.windowConfiguration; +        } else { +            taskId = INVALID_TASK_ID; +            isNotInRecents = true; +            activityType = ACTIVITY_TYPE_UNDEFINED; +            windowConfiguration = new WindowConfiguration(); +        }          // TODO: once we can properly sync transactions across process, then get rid of this leash.          leash = createLeash(info, change, order, t); @@ -221,22 +236,12 @@ public class RemoteAnimationTargetCompat {          prefixOrderIndex = order;          // TODO(shell-transitions): I guess we need to send content insets? evaluate how its used.          contentInsets = new Rect(0, 0, 0, 0); -        if (change.getTaskInfo() != null) { -            isNotInRecents = !change.getTaskInfo().isRunning; -            activityType = change.getTaskInfo().getActivityType(); -        } else { -            isNotInRecents = true; -            activityType = ACTIVITY_TYPE_UNDEFINED; -        } -        taskInfo = change.getTaskInfo();          allowEnterPip = change.getAllowEnterPip();          mStartLeash = null;          rotationChange = change.getEndRotation() - change.getStartRotation(); -        windowType = INVALID_WINDOW_TYPE; +        windowType = (change.getFlags() & FLAG_IS_DIVIDER_BAR) != 0 +                ? TYPE_DOCK_DIVIDER : INVALID_WINDOW_TYPE; -        windowConfiguration = change.getTaskInfo() != null -            ? change.getTaskInfo().configuration.windowConfiguration -            : new WindowConfiguration();          startBounds = change.getStartAbsBounds();          willShowImeOnTarget = (change.getFlags() & TransitionInfo.FLAG_WILL_IME_SHOWN) != 0;      } @@ -251,37 +256,62 @@ public class RemoteAnimationTargetCompat {      }      /** -     * Represents a TransitionInfo object as an array of old-style targets +     * Represents a TransitionInfo object as an array of old-style app targets +     * +     * @param leashMap Temporary map of change leash -> launcher leash. Is an output, so should be +     *                 populated by this function. If null, it is ignored. +     */ +    public static RemoteAnimationTargetCompat[] wrapApps(TransitionInfo info, +            SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap) { +        final ArrayList<RemoteAnimationTargetCompat> out = new ArrayList<>(); +        final SparseArray<TransitionInfo.Change> childTaskTargets = new SparseArray<>(); +        for (int i = 0; i < info.getChanges().size(); i++) { +            final TransitionInfo.Change change = info.getChanges().get(i); +            if (change.getTaskInfo() == null) continue; + +            final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); +            // Children always come before parent since changes are in top-to-bottom z-order. +            if (taskInfo != null) { +                if (childTaskTargets.contains(taskInfo.taskId)) { +                    // has children, so not a leaf. Skip. +                    continue; +                } +                if (taskInfo.hasParentTask()) { +                    childTaskTargets.put(taskInfo.parentTaskId, change); +                } +            } + +            final RemoteAnimationTargetCompat targetCompat = +                    new RemoteAnimationTargetCompat(change, info.getChanges().size() - i, info, t); +            if (leashMap != null) { +                leashMap.put(change.getLeash(), targetCompat.leash); +            } +            out.add(targetCompat); +        } + +        return out.toArray(new RemoteAnimationTargetCompat[out.size()]); +    } + +    /** +     * Represents a TransitionInfo object as an array of old-style non-app targets       *       * @param wallpapers If true, this will return wallpaper targets; otherwise it returns       *                   non-wallpaper targets.       * @param leashMap Temporary map of change leash -> launcher leash. Is an output, so should be       *                 populated by this function. If null, it is ignored.       */ -    public static RemoteAnimationTargetCompat[] wrap(TransitionInfo info, boolean wallpapers, +    public static RemoteAnimationTargetCompat[] wrapNonApps(TransitionInfo info, boolean wallpapers,              SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap) {          final ArrayList<RemoteAnimationTargetCompat> out = new ArrayList<>(); -        final SparseArray<TransitionInfo.Change> childTaskTargets = new SparseArray<>(); +          for (int i = 0; i < info.getChanges().size(); i++) {              final TransitionInfo.Change change = info.getChanges().get(i); +            if (change.getTaskInfo() != null) continue; +              final boolean changeIsWallpaper =                      (change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0;              if (wallpapers != changeIsWallpaper) continue; -            if (!wallpapers) { -                final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); -                // Children always come before parent since changes are in top-to-bottom z-order. -                if (taskInfo != null) { -                    if (childTaskTargets.contains(taskInfo.taskId)) { -                        // has children, so not a leaf. Skip. -                        continue; -                    } -                    if (taskInfo.hasParentTask()) { -                        childTaskTargets.put(taskInfo.parentTaskId, change); -                    } -                } -            } -              final RemoteAnimationTargetCompat targetCompat =                      new RemoteAnimationTargetCompat(change, info.getChanges().size() - i, info, t);              if (leashMap != null) { diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java index 7c1ef8c76926..f6792251d282 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java @@ -128,9 +128,10 @@ public class RemoteTransitionCompat implements Parcelable {                      IRemoteTransitionFinishedCallback finishedCallback) {                  final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>();                  final RemoteAnimationTargetCompat[] apps = -                        RemoteAnimationTargetCompat.wrap(info, false /* wallpapers */, t, leashMap); +                        RemoteAnimationTargetCompat.wrapApps(info, t, leashMap);                  final RemoteAnimationTargetCompat[] wallpapers = -                        RemoteAnimationTargetCompat.wrap(info, true /* wallpapers */, t, leashMap); +                        RemoteAnimationTargetCompat.wrapNonApps( +                                info, true /* wallpapers */, t, leashMap);                  // TODO(b/177438007): Move this set-up logic into launcher's animation impl.                  mToken = transition;                  // This transition is for opening recents, so recents is on-top. We want to draw diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index f697e256dfb0..3517d22ae50d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -1129,11 +1129,13 @@ public class KeyguardSecurityContainer extends FrameLayout {                  Log.e(TAG, "Current user in user switcher is null.");                  return;              } +            final String currentUserName = mUserSwitcherController.getCurrentUserName();              Drawable userIcon = findUserIcon(currentUser.info.id);              ((ImageView) mView.findViewById(R.id.user_icon)).setImageDrawable(userIcon); -            mUserSwitcher.setText(mUserSwitcherController.getCurrentUserName()); +            mUserSwitcher.setText(currentUserName); + +            KeyguardUserSwitcherAnchor anchor = mView.findViewById(R.id.user_switcher_anchor); -            ViewGroup anchor = mView.findViewById(R.id.user_switcher_anchor);              BaseUserAdapter adapter = new BaseUserAdapter(mUserSwitcherController) {                  @Override                  public View getView(int position, View convertView, ViewGroup parent) { @@ -1213,7 +1215,6 @@ public class KeyguardSecurityContainer extends FrameLayout {              anchor.setOnClickListener((v) -> {                  if (mFalsingManager.isFalseTap(LOW_PENALTY)) return; -                  mPopup = new KeyguardUserSwitcherPopupMenu(v.getContext(), mFalsingManager);                  mPopup.setAnchorView(anchor);                  mPopup.setAdapter(adapter); 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/keyguard/KeyguardUserSwitcherAnchor.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherAnchor.kt new file mode 100644 index 000000000000..5f3ba72d445b --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherAnchor.kt @@ -0,0 +1,39 @@ +/* + * 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.keyguard + +import android.content.Context +import android.util.AttributeSet +import android.view.accessibility.AccessibilityNodeInfo +import android.widget.LinearLayout +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat +import com.android.systemui.R + +/** + * Custom View for the multi-user switcher pull-down menu anchor + */ +class KeyguardUserSwitcherAnchor @JvmOverloads constructor( +        context: Context, +        attrs: AttributeSet? = null +) : LinearLayout(context, attrs) { + +    override fun createAccessibilityNodeInfo(): AccessibilityNodeInfo { +        val info = super.createAccessibilityNodeInfo() +        AccessibilityNodeInfoCompat.wrap(info).roleDescription = +                context.getString(R.string.accessibility_multi_user_list_switcher) +        return info +    } +} 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/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java index 78a45f9d3310..b6923a867507 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java @@ -75,25 +75,12 @@ public interface WMComponent {       * Initializes all the WMShell components before starting any of the SystemUI components.       */      default void init() { -        // TODO(238217847): To be removed once the dependencies are inverted and ShellController can -        // inject these classes directly, otherwise, it's currently needed to ensure that these -        // classes are created and set on the controller before onInit() is called -        getShellInit(); -        getShellCommandHandler();          getShell().onInit();      }      @WMSingleton      ShellInterface getShell(); -    // TODO(238217847): To be removed once ShellController can inject ShellInit directly -    @WMSingleton -    ShellInit getShellInit(); - -    // TODO(238217847): To be removed once ShellController can inject ShellCommandHandler directly -    @WMSingleton -    ShellCommandHandler getShellCommandHandler(); -      @WMSingleton      Optional<OneHanded> getOneHanded(); diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProvider.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProvider.java new file mode 100644 index 000000000000..193d6f5c0061 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProvider.java @@ -0,0 +1,117 @@ +/* + * 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.dreams; + +import android.view.View; + +import androidx.annotation.NonNull; + +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.statusbar.policy.CallbackController; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.Executor; + +import javax.inject.Inject; + +/** + * {@link DreamOverlayStatusBarItemsProvider} provides extra dream overlay status bar items. A + * callback can be registered that will be informed of items being added or removed from the + * provider. + */ +@SysUISingleton +public class DreamOverlayStatusBarItemsProvider implements +        CallbackController<DreamOverlayStatusBarItemsProvider.Callback> { +    /** +     * Represents one item in the dream overlay status bar. +     */ +    public interface StatusBarItem { +        /** +         * Return the {@link View} associated with this item. +         */ +        View getView(); +    } + +    /** +     * A callback to be registered with the provider to be informed of when the list of status bar +     * items has changed. +     */ +    public interface Callback { +        /** +         * Inform the callback that status bar items have changed. +         */ +        void onStatusBarItemsChanged(List<StatusBarItem> newItems); +    } + +    private final Executor mExecutor; +    private final List<StatusBarItem> mItems = new ArrayList<>(); +    private final List<Callback> mCallbacks = new ArrayList<>(); + +    @Inject +    public DreamOverlayStatusBarItemsProvider(@Main Executor executor) { +        mExecutor = executor; +    } + +    @Override +    public void addCallback(@NonNull Callback callback) { +        mExecutor.execute(() -> { +            Objects.requireNonNull(callback, "Callback must not be null."); +            if (mCallbacks.contains(callback)) { +                return; +            } + +            mCallbacks.add(callback); +            if (!mItems.isEmpty()) { +                callback.onStatusBarItemsChanged(mItems); +            } +        }); +    } + +    @Override +    public void removeCallback(@NonNull Callback callback) { +        mExecutor.execute(() -> { +            Objects.requireNonNull(callback, "Callback must not be null."); +            mCallbacks.remove(callback); +        }); +    } + +    /** +     * Adds an item to the dream overlay status bar. +     */ +    public void addStatusBarItem(StatusBarItem item) { +        mExecutor.execute(() -> { +            if (!mItems.contains(item)) { +                mItems.add(item); +                mCallbacks.forEach(callback -> callback.onStatusBarItemsChanged(mItems)); +            } +        }); +    } + +    /** +     * Removes an item from the dream overlay status bar. +     */ +    public void removeStatusBarItem(StatusBarItem item) { +        mExecutor.execute(() -> { +            if (mItems.remove(item)) { +                mCallbacks.forEach(callback -> callback.onStatusBarItemsChanged(mItems)); +            } +        }); +    } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java index a25257d6cf42..7e4a108aadf1 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java @@ -21,6 +21,7 @@ import android.annotation.Nullable;  import android.content.Context;  import android.util.AttributeSet;  import android.view.View; +import android.view.ViewGroup;  import androidx.constraintlayout.widget.ConstraintLayout; @@ -29,6 +30,7 @@ import com.android.systemui.R;  import java.lang.annotation.Retention;  import java.lang.annotation.RetentionPolicy;  import java.util.HashMap; +import java.util.List;  import java.util.Map;  import java.util.Objects; @@ -58,6 +60,7 @@ public class DreamOverlayStatusBarView extends ConstraintLayout {      public static final int STATUS_ICON_PRIORITY_MODE_ON = 6;      private final Map<Integer, View> mStatusIcons = new HashMap<>(); +    private ViewGroup mSystemStatusViewGroup;      public DreamOverlayStatusBarView(Context context) {          this(context, null); @@ -94,6 +97,8 @@ public class DreamOverlayStatusBarView extends ConstraintLayout {                  fetchStatusIconForResId(R.id.dream_overlay_notification_indicator));          mStatusIcons.put(STATUS_ICON_PRIORITY_MODE_ON,                  fetchStatusIconForResId(R.id.dream_overlay_priority_mode)); + +        mSystemStatusViewGroup = findViewById(R.id.dream_overlay_extra_items);      }      void showIcon(@StatusIconType int iconType, boolean show, @Nullable String contentDescription) { @@ -107,6 +112,11 @@ public class DreamOverlayStatusBarView extends ConstraintLayout {          icon.setVisibility(show ? View.VISIBLE : View.GONE);      } +    void setExtraStatusBarItemViews(List<View> views) { +        mSystemStatusViewGroup.removeAllViews(); +        views.forEach(view -> mSystemStatusViewGroup.addView(view)); +    } +      private View fetchStatusIconForResId(int resId) {          final View statusIcon = findViewById(resId);          return Objects.requireNonNull(statusIcon); diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java index de7bf28c01d6..65cfae1ac14b 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java @@ -38,6 +38,7 @@ import android.view.View;  import com.android.systemui.R;  import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.dreams.DreamOverlayStatusBarItemsProvider.StatusBarItem;  import com.android.systemui.dreams.dagger.DreamOverlayComponent;  import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;  import com.android.systemui.statusbar.policy.NextAlarmController; @@ -47,10 +48,13 @@ import com.android.systemui.touch.TouchInsetManager;  import com.android.systemui.util.ViewController;  import com.android.systemui.util.time.DateFormatUtil; +import java.util.ArrayList; +import java.util.List;  import java.util.Locale;  import java.util.Map;  import java.util.Optional;  import java.util.concurrent.Executor; +import java.util.stream.Collectors;  import javax.inject.Inject; @@ -69,7 +73,10 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve      private final Optional<DreamOverlayNotificationCountProvider>              mDreamOverlayNotificationCountProvider;      private final ZenModeController mZenModeController; +    private final DreamOverlayStatusBarItemsProvider mStatusBarItemsProvider;      private final Executor mMainExecutor; +    private final List<DreamOverlayStatusBarItemsProvider.StatusBarItem> mExtraStatusBarItems = +            new ArrayList<>();      private boolean mIsAttached; @@ -116,6 +123,9 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve                              ? buildNotificationsContentDescription(notificationCount)                              : null); +    private final DreamOverlayStatusBarItemsProvider.Callback mStatusBarItemsProviderCallback = +            this::onStatusBarItemsChanged; +      @Inject      public DreamOverlayStatusBarViewController(              DreamOverlayStatusBarView view, @@ -129,7 +139,8 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve              IndividualSensorPrivacyController sensorPrivacyController,              Optional<DreamOverlayNotificationCountProvider> dreamOverlayNotificationCountProvider,              ZenModeController zenModeController, -            StatusBarWindowStateController statusBarWindowStateController) { +            StatusBarWindowStateController statusBarWindowStateController, +            DreamOverlayStatusBarItemsProvider statusBarItemsProvider) {          super(view);          mResources = resources;          mMainExecutor = mainExecutor; @@ -140,6 +151,7 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve          mDateFormatUtil = dateFormatUtil;          mSensorPrivacyController = sensorPrivacyController;          mDreamOverlayNotificationCountProvider = dreamOverlayNotificationCountProvider; +        mStatusBarItemsProvider = statusBarItemsProvider;          mZenModeController = zenModeController;          // Register to receive show/hide updates for the system status bar. Our custom status bar @@ -166,6 +178,8 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve          mDreamOverlayNotificationCountProvider.ifPresent(                  provider -> provider.addCallback(mNotificationCountCallback)); +        mStatusBarItemsProvider.addCallback(mStatusBarItemsProviderCallback); +          mTouchInsetSession.addViewToTracking(mView);      } @@ -177,6 +191,7 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve          mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);          mDreamOverlayNotificationCountProvider.ifPresent(                  provider -> provider.removeCallback(mNotificationCountCallback)); +        mStatusBarItemsProvider.removeCallback(mStatusBarItemsProviderCallback);          mTouchInsetSession.clear();          mIsAttached = false; @@ -215,16 +230,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) { @@ -272,4 +286,16 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve              }          });      } + +    private void onStatusBarItemsChanged(List<StatusBarItem> newItems) { +        mMainExecutor.execute(() -> { +            mExtraStatusBarItems.clear(); +            mExtraStatusBarItems.addAll(newItems); +            mView.setExtraStatusBarItemViews( +                    newItems +                            .stream() +                            .map(StatusBarItem::getView) +                            .collect(Collectors.toList())); +        }); +    }  } 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/LifecycleScreenStatusProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt index 044a57ced3fc..0a55294dfe8a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt @@ -41,4 +41,12 @@ class LifecycleScreenStatusProvider @Inject constructor(screenLifecycle: ScreenL      override fun onScreenTurnedOn() {          listeners.forEach(ScreenListener::onScreenTurnedOn)      } + +    override fun onScreenTurningOff() { +        listeners.forEach(ScreenListener::onScreenTurningOff) +    } + +    override fun onScreenTurningOn(ignored: Runnable) { +        listeners.forEach(ScreenListener::onScreenTurningOn) +    }  } 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/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt index b44b4def33f4..6424256f36ab 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt @@ -42,7 +42,6 @@ import com.android.systemui.plugins.NavigationEdgeBackPlugin  import com.android.systemui.statusbar.VibratorHelper  import com.android.systemui.statusbar.policy.ConfigurationController  import com.android.systemui.util.ViewController -import com.android.wm.shell.back.BackAnimation  import java.io.PrintWriter  import javax.inject.Inject  import kotlin.math.abs @@ -119,7 +118,7 @@ class BackPanelController private constructor(          private val latencyTracker: LatencyTracker      ) {          /** Construct a [BackPanelController].  */ -        fun create(context: Context, backAnimation: BackAnimation?): BackPanelController { +        fun create(context: Context): BackPanelController {              val backPanelController = BackPanelController(                  context,                  windowManager, diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index fc6dcd3e5bb8..6ac3eadb838d 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -574,10 +574,10 @@ public class EdgeBackGestureHandler extends CurrentUserTracker      private void resetEdgeBackPlugin() {          if (mIsNewBackAffordanceEnabled) {              setEdgeBackPlugin( -                    mBackPanelControllerFactory.create(mContext, mBackAnimation)); +                    mBackPanelControllerFactory.create(mContext));          } else {              setEdgeBackPlugin( -                    new NavigationBarEdgePanel(mContext, mBackAnimation, mLatencyTracker)); +                    new NavigationBarEdgePanel(mContext, mLatencyTracker));          }      } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java index eba9d3fdcab8..122852f7d07a 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java @@ -43,7 +43,6 @@ import android.view.View;  import android.view.WindowManager;  import android.view.animation.Interpolator;  import android.view.animation.PathInterpolator; -import android.window.BackEvent;  import androidx.core.graphics.ColorUtils;  import androidx.dynamicanimation.animation.DynamicAnimation; @@ -59,7 +58,6 @@ import com.android.systemui.animation.Interpolators;  import com.android.systemui.plugins.NavigationEdgeBackPlugin;  import com.android.systemui.shared.navigationbar.RegionSamplingHelper;  import com.android.systemui.statusbar.VibratorHelper; -import com.android.wm.shell.back.BackAnimation;  import java.io.PrintWriter;  import java.util.concurrent.Executor; @@ -283,14 +281,11 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl                  }              };      private BackCallback mBackCallback; -    private BackAnimation mBackAnimation; -    public NavigationBarEdgePanel(Context context, -            BackAnimation backAnimation, LatencyTracker latencyTracker) { +    public NavigationBarEdgePanel(Context context, LatencyTracker latencyTracker) {          super(context);          mWindowManager = context.getSystemService(WindowManager.class); -        mBackAnimation = backAnimation;          mVibratorHelper = Dependency.get(VibratorHelper.class);          mDensity = context.getResources().getDisplayMetrics().density; @@ -360,7 +355,6 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl                  .getDimension(R.dimen.navigation_edge_action_drag_threshold);          mSwipeProgressThreshold = context.getResources()                  .getDimension(R.dimen.navigation_edge_action_progress_threshold); -        initializeBackAnimation();          setVisibility(GONE); @@ -388,17 +382,6 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl          mLatencyTracker = latencyTracker;      } -    public void setBackAnimation(BackAnimation backAnimation) { -        mBackAnimation = backAnimation; -        initializeBackAnimation(); -    } - -    private void initializeBackAnimation() { -        if (mBackAnimation != null) { -            mBackAnimation.setSwipeThresholds(mSwipeTriggerThreshold, mSwipeProgressThreshold); -        } -    } -      @Override      public void onDestroy() {          cancelFailsafe(); @@ -484,12 +467,6 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl      @Override      public void onMotionEvent(MotionEvent event) { -        if (mBackAnimation != null) { -            mBackAnimation.onBackMotion( -                    event.getX(), event.getY(), -                    event.getActionMasked(), -                    mIsLeftPanel ? BackEvent.EDGE_LEFT : BackEvent.EDGE_RIGHT); -        }          if (mVelocityTracker == null) {              mVelocityTracker = VelocityTracker.obtain();          } @@ -903,9 +880,6 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl              // Whenever the trigger back state changes the existing translation animation should be              // cancelled              mTranslationAnimation.cancel(); -            if (mBackAnimation != null) { -                mBackAnimation.setTriggerBack(triggerBack); -            }          }      } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java index 4552abd402b0..ac46c85c10a4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java @@ -29,6 +29,7 @@ import android.util.Log;  import androidx.annotation.MainThread;  import androidx.annotation.Nullable; +import com.android.internal.annotations.VisibleForTesting;  import com.android.internal.logging.InstanceId;  import com.android.internal.logging.InstanceIdSequence;  import com.android.internal.logging.UiEventLogger; @@ -47,6 +48,7 @@ import com.android.systemui.qs.external.TileLifecycleManager;  import com.android.systemui.qs.external.TileServiceKey;  import com.android.systemui.qs.external.TileServiceRequestController;  import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.settings.UserFileManager;  import com.android.systemui.settings.UserTracker;  import com.android.systemui.shared.plugins.PluginManager;  import com.android.systemui.statusbar.phone.AutoTileManager; @@ -88,6 +90,10 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D      public static final int POSITION_AT_END = -1;      public static final String TILES_SETTING = Secure.QS_TILES; +    // Shared prefs that hold tile lifecycle info. +    @VisibleForTesting +    static final String TILES = "tiles_prefs"; +      private final Context mContext;      private final LinkedHashMap<String, QSTile> mTiles = new LinkedHashMap<>();      protected final ArrayList<String> mTileSpecs = new ArrayList<>(); @@ -99,6 +105,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D      private final InstanceIdSequence mInstanceIdSequence;      private final CustomTileStatePersister mCustomTileStatePersister;      private final Executor mMainExecutor; +    private final UserFileManager mUserFileManager;      private final List<Callback> mCallbacks = new ArrayList<>();      @Nullable @@ -110,6 +117,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; @@ -130,7 +142,8 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D              SecureSettings secureSettings,              CustomTileStatePersister customTileStatePersister,              TileServiceRequestController.Builder tileServiceRequestControllerBuilder, -            TileLifecycleManager.Factory tileLifecycleManagerFactory +            TileLifecycleManager.Factory tileLifecycleManagerFactory, +            UserFileManager userFileManager      ) {          mIconController = iconController;          mContext = context; @@ -143,6 +156,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D          mMainExecutor = mainExecutor;          mTileServiceRequestController = tileServiceRequestControllerBuilder.create(this);          mTileLifeCycleManagerFactory = tileLifecycleManagerFactory; +        mUserFileManager = userFileManager;          mInstanceIdSequence = new InstanceIdSequence(MAX_QS_INSTANCE_ID);          mCentralSurfacesOptional = centralSurfacesOptional; @@ -374,6 +388,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();              } @@ -386,6 +401,11 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D       */      @Override      public void removeTile(String spec) { +        if (spec.startsWith(CustomTile.PREFIX)) { +            // If the tile is removed (due to it not actually existing), mark it as removed. That +            // way it will be marked as newly added if it appears in the future. +            setTileAdded(CustomTile.getComponentFromSpec(spec), mCurrentUser, false); +        }          mMainExecutor.execute(() -> changeTileSpecs(tileSpecs-> tileSpecs.remove(spec)));      } @@ -436,6 +456,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 +466,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);          }      } @@ -502,11 +529,12 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D                  lifecycleManager.onStopListening();                  lifecycleManager.onTileRemoved();                  mCustomTileStatePersister.removeState(new TileServiceKey(component, mCurrentUser)); -                TileLifecycleManager.setTileAdded(mContext, component, false); +                setTileAdded(component, mCurrentUser, false);                  lifecycleManager.flushMessagesAndUnbind();              }          }          if (DEBUG) Log.d(TAG, "saveCurrentTiles " + newTiles); +        mTilesListDirty = true;          saveTilesToSettings(newTiles);      } @@ -538,6 +566,36 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D          throw new RuntimeException("Default factory didn't create view for " + tile.getTileSpec());      } +    /** +     * Check if a particular {@link CustomTile} has been added for a user and has not been removed +     * since. +     * @param componentName the {@link ComponentName} of the +     *                      {@link android.service.quicksettings.TileService} associated with the +     *                      tile. +     * @param userId the user to check +     */ +    public boolean isTileAdded(ComponentName componentName, int userId) { +        return mUserFileManager +                .getSharedPreferences(TILES, 0, userId) +                .getBoolean(componentName.flattenToString(), false); +    } + +    /** +     * Persists whether a particular {@link CustomTile} has been added and it's currently in the +     * set of selected tiles ({@link #mTiles}. +     * @param componentName the {@link ComponentName} of the +     *                      {@link android.service.quicksettings.TileService} associated +     *                      with the tile. +     * @param userId the user for this tile +     * @param added {@code true} if the tile is being added, {@code false} otherwise +     */ +    public void setTileAdded(ComponentName componentName, int userId, boolean added) { +        mUserFileManager.getSharedPreferences(TILES, 0, userId) +                .edit() +                .putBoolean(componentName.flattenToString(), added) +                .apply(); +    } +      protected static List<String> loadTileSpecs(Context context, String tileList) {          final Resources res = context.getResources(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java index a49d3fd16591..3e445ddfc2a1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java @@ -127,6 +127,10 @@ public class TileLifecycleManager extends BroadcastReceiver implements          TileLifecycleManager create(Intent intent, UserHandle userHandle);      } +    public int getUserId() { +        return mUser.getIdentifier(); +    } +      public ComponentName getComponent() {          return mIntent.getComponent();      } @@ -507,13 +511,4 @@ public class TileLifecycleManager extends BroadcastReceiver implements      public interface TileChangeListener {          void onTileChanged(ComponentName tile);      } - -    public static boolean isTileAdded(Context context, ComponentName component) { -        return context.getSharedPreferences(TILES, 0).getBoolean(component.flattenToString(), false); -    } - -    public static void setTileAdded(Context context, ComponentName component, boolean added) { -        context.getSharedPreferences(TILES, 0).edit().putBoolean(component.flattenToString(), -                added).commit(); -    }  } diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java index cfc57db2eeb8..e86bd7a30490 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java @@ -109,9 +109,9 @@ public class TileServiceManager {      void startLifecycleManagerAndAddTile() {          mStarted = true;          ComponentName component = mStateManager.getComponent(); -        Context context = mServices.getContext(); -        if (!TileLifecycleManager.isTileAdded(context, component)) { -            TileLifecycleManager.setTileAdded(context, component, true); +        final int userId = mStateManager.getUserId(); +        if (!mServices.getHost().isTileAdded(component, userId)) { +            mServices.getHost().setTileAdded(component, userId, true);              mStateManager.onTileAdded();              mStateManager.flushMessagesAndUnbind();          } 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/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 438236d6a63d..30862b78c93f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -638,12 +638,7 @@ public class OverviewProxyService extends CurrentUserTracker implements          // Listen for user setup          startTracking(); -        screenLifecycle.addObserver(new ScreenLifecycle.Observer() { -            @Override -            public void onScreenTurnedOn() { -                notifyScreenTurnedOn(); -            } -        }); +        screenLifecycle.addObserver(mLifecycleObserver);          // Connect to the service          updateEnabledState(); @@ -951,20 +946,55 @@ public class OverviewProxyService extends CurrentUserTracker implements          }      } -    /** -     * Notifies the Launcher that screen turned on and ready to use -     */ -    public void notifyScreenTurnedOn() { -        try { -            if (mOverviewProxy != null) { -                mOverviewProxy.onScreenTurnedOn(); -            } else { -                Log.e(TAG_OPS, "Failed to get overview proxy for screen turned on event."); +    private final ScreenLifecycle.Observer mLifecycleObserver = new ScreenLifecycle.Observer() { +        /** +         * Notifies the Launcher that screen turned on and ready to use +         */ +        @Override +        public void onScreenTurnedOn() { +            try { +                if (mOverviewProxy != null) { +                    mOverviewProxy.onScreenTurnedOn(); +                } else { +                    Log.e(TAG_OPS, "Failed to get overview proxy for screen turned on event."); +                } +            } catch (RemoteException e) { +                Log.e(TAG_OPS, "Failed to call onScreenTurnedOn()", e);              } -        } catch (RemoteException e) { -            Log.e(TAG_OPS, "Failed to call notifyScreenTurnedOn()", e);          } -    } + +        /** +         * Notifies the Launcher that screen is starting to turn on. +         */ +        @Override +        public void onScreenTurningOff() { +            try { +                if (mOverviewProxy != null) { +                    mOverviewProxy.onScreenTurningOff(); +                } else { +                    Log.e(TAG_OPS, "Failed to get overview proxy for screen turning off event."); +                } +            } catch (RemoteException e) { +                Log.e(TAG_OPS, "Failed to call onScreenTurningOff()", e); +            } +        } + +        /** +         * Notifies the Launcher that screen is starting to turn on. +         */ +        @Override +        public void onScreenTurningOn(@NonNull Runnable ignored) { +            try { +                if (mOverviewProxy != null) { +                    mOverviewProxy.onScreenTurningOn(); +                } else { +                    Log.e(TAG_OPS, "Failed to get overview proxy for screen turning on event."); +                } +            } catch (RemoteException e) { +                Log.e(TAG_OPS, "Failed to call onScreenTurningOn()", e); +            } +        } +    };      void notifyToggleRecentApps() {          for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { 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/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt index 13a5615a8b54..2a467763951c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt @@ -6,7 +6,11 @@ import android.view.ViewGroup.LayoutParams.WRAP_CONTENT  import android.view.WindowInsets  import androidx.annotation.VisibleForTesting  import androidx.constraintlayout.widget.ConstraintSet -import androidx.constraintlayout.widget.ConstraintSet.* +import androidx.constraintlayout.widget.ConstraintSet.BOTTOM +import androidx.constraintlayout.widget.ConstraintSet.END +import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID +import androidx.constraintlayout.widget.ConstraintSet.START +import androidx.constraintlayout.widget.ConstraintSet.TOP  import com.android.systemui.R  import com.android.systemui.dagger.qualifiers.Main  import com.android.systemui.flags.FeatureFlags @@ -171,33 +175,23 @@ class NotificationsQSContainerController @Inject constructor(      private fun calculateBottomSpacing(): Paddings {          val containerPadding: Int -        var stackScrollMargin = notificationsBottomMargin -        if (splitShadeEnabled) { -            if (isGestureNavigation) { -                // only default cutout padding, taskbar always hides -                containerPadding = bottomCutoutInsets -            } else if (taskbarVisible) { -                // navigation buttons + visible taskbar means we're NOT on homescreen -                containerPadding = bottomStableInsets -            } else { -                // navigation buttons + hidden taskbar means we're on homescreen -                containerPadding = 0 -                // we need extra margin for notifications as navigation buttons are below them -                stackScrollMargin = bottomStableInsets + notificationsBottomMargin -            } +        val stackScrollMargin: Int +        if (!splitShadeEnabled && (isQSCustomizing || isQSDetailShowing)) { +            // Clear out bottom paddings/margins so the qs customization can be full height. +            containerPadding = 0 +            stackScrollMargin = 0 +        } else if (isGestureNavigation) { +            // only default cutout padding, taskbar always hides +            containerPadding = bottomCutoutInsets +            stackScrollMargin = notificationsBottomMargin +        } else if (taskbarVisible) { +            // navigation buttons + visible taskbar means we're NOT on homescreen +            containerPadding = bottomStableInsets +            stackScrollMargin = notificationsBottomMargin          } else { -            if (isQSCustomizing || isQSDetailShowing) { -                // Clear out bottom paddings/margins so the qs customization can be full height. -                containerPadding = 0 -                stackScrollMargin = 0 -            } else if (isGestureNavigation) { -                containerPadding = bottomCutoutInsets -            } else if (taskbarVisible) { -                containerPadding = bottomStableInsets -            } else { -                containerPadding = 0 -                stackScrollMargin = bottomStableInsets + notificationsBottomMargin -            } +            // navigation buttons + hidden taskbar means we're on homescreen +            containerPadding = 0 +            stackScrollMargin = bottomStableInsets + notificationsBottomMargin          }          val qsContainerPadding = if (!(isQSCustomizing || isQSDetailShowing)) {              // We also want this padding in the bottom in these cases 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/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java index 48e34501ef59..0951e821cdc2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java @@ -19,7 +19,9 @@ package com.android.systemui.statusbar.dagger;  import android.app.IActivityManager;  import android.content.Context;  import android.os.Handler; +import android.os.RemoteException;  import android.service.dreams.IDreamManager; +import android.util.Log;  import com.android.internal.jank.InteractionJankMonitor;  import com.android.internal.statusbar.IStatusBarService; @@ -60,10 +62,12 @@ import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;  import com.android.systemui.statusbar.phone.StatusBarIconController;  import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl;  import com.android.systemui.statusbar.phone.StatusBarIconList; +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;  import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;  import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;  import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallFlags;  import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLogger; +import com.android.systemui.statusbar.policy.KeyguardStateController;  import com.android.systemui.statusbar.policy.RemoteInputUriController;  import com.android.systemui.statusbar.window.StatusBarWindowController;  import com.android.systemui.tracing.ProtoTracer; @@ -274,7 +278,30 @@ public interface CentralSurfacesDependenciesModule {      @Provides      @SysUISingleton      static DialogLaunchAnimator provideDialogLaunchAnimator(IDreamManager dreamManager, +            KeyguardStateController keyguardStateController, +            Lazy<StatusBarKeyguardViewManager> statusBarKeyguardViewManager,              InteractionJankMonitor interactionJankMonitor) { -        return new DialogLaunchAnimator(dreamManager, interactionJankMonitor); +        DialogLaunchAnimator.Callback callback = new DialogLaunchAnimator.Callback() { +            @Override +            public boolean isDreaming() { +                try { +                    return dreamManager.isDreaming(); +                } catch (RemoteException e) { +                    Log.e("DialogLaunchAnimator.Callback", "dreamManager.isDreaming failed", e); +                    return false; +                } +            } + +            @Override +            public boolean isUnlocked() { +                return keyguardStateController.isUnlocked(); +            } + +            @Override +            public boolean isShowingAlternateAuthOnUnlock() { +                return statusBarKeyguardViewManager.get().shouldShowAltAuth(); +            } +        }; +        return new DialogLaunchAnimator(callback, interactionJankMonitor);      }  } 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..14cc6bf1ea41 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 @@ -38,6 +38,7 @@ import android.os.Trace;  import android.service.notification.StatusBarNotification;  import android.util.ArrayMap;  import android.util.ArraySet; +import android.util.Log;  import androidx.annotation.NonNull;  import androidx.annotation.VisibleForTesting; @@ -87,7 +88,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; @@ -126,6 +127,9 @@ public class ShadeListBuilder implements Dumpable {      private List<ListEntry> mReadOnlyNewNotifList = Collections.unmodifiableList(mNewNotifList);      private final NotifPipelineChoreographer mChoreographer; +    private int mConsecutiveReentrantRebuilds = 0; +    @VisibleForTesting public static final int MAX_CONSECUTIVE_REENTRANT_REBUILDS = 3; +      @Inject      public ShadeListBuilder(              DumpManager dumpManager, @@ -310,7 +314,7 @@ public class ShadeListBuilder implements Dumpable {                      mLogger.logOnBuildList(reason);                      mAllEntries = entries; -                    mChoreographer.schedule(); +                    scheduleRebuild(/* reentrant = */ false);                  }              }; @@ -1332,11 +1336,64 @@ public class ShadeListBuilder implements Dumpable {          throw new RuntimeException("Missing default sectioner!");      } -    private void rebuildListIfBefore(@PipelineState.StateName int state) { -        mPipelineState.requireIsBefore(state); -        if (mPipelineState.is(STATE_IDLE)) { +    private void rebuildListIfBefore(@PipelineState.StateName int rebuildState) { +        final @PipelineState.StateName int currentState = mPipelineState.getState(); + +        // If the pipeline is idle, requesting an invalidation is always okay, and starts a new run. +        if (currentState == STATE_IDLE) { +            scheduleRebuild(/* reentrant = */ false, rebuildState); +            return; +        } + +        // If the pipeline is running, it is okay to request an invalidation of a *later* stage. +        // Since the current pipeline run hasn't run it yet, no new pipeline run is needed. +        if (rebuildState > currentState) { +            return; +        } + +        // If the pipeline is running, it is bad to request an invalidation of *earlier* stages or +        // the *current* stage; this will run the pipeline more often than needed, and may even +        // cause an infinite loop of pipeline runs. +        // +        // Unfortunately, there are some unfixed bugs that cause reentrant pipeline runs, so we keep +        // a counter and allow a few reentrant runs in a row between any two non-reentrant runs. +        // +        // It is technically possible for a *pair* of invalidations, one reentrant and one not, to +        // trigger *each other*, alternating responsibility for pipeline runs in an infinite loop +        // but constantly resetting the reentrant run counter. Hopefully that doesn't happen. +        scheduleRebuild(/* reentrant = */ true, rebuildState); +    } + +    private void scheduleRebuild(boolean reentrant) { +        scheduleRebuild(reentrant, STATE_IDLE); +    } + +    private void scheduleRebuild(boolean reentrant, @PipelineState.StateName int rebuildState) { +        if (!reentrant) { +            mConsecutiveReentrantRebuilds = 0;              mChoreographer.schedule(); +            return;          } + +        final @PipelineState.StateName int currentState = mPipelineState.getState(); + +        final String rebuildStateName = PipelineState.getStateName(rebuildState); +        final String currentStateName = PipelineState.getStateName(currentState); +        final IllegalStateException exception = new IllegalStateException( +                "Reentrant notification pipeline rebuild of state " + rebuildStateName +                        + " while pipeline in state " + currentStateName + "."); + +        mConsecutiveReentrantRebuilds++; + +        if (mConsecutiveReentrantRebuilds > MAX_CONSECUTIVE_REENTRANT_REBUILDS) { +            Log.e(TAG, "Crashing after more than " + MAX_CONSECUTIVE_REENTRANT_REBUILDS +                    + " consecutive reentrant notification pipeline rebuilds.", exception); +            throw exception; +        } + +        Log.e(TAG, "Allowing " + mConsecutiveReentrantRebuilds +                + " consecutive reentrant notification pipeline rebuild(s).", exception); +        mChoreographer.schedule();      }      private static int countChildren(List<ListEntry> entries) { @@ -1396,6 +1453,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/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 3880ee36cd1f..d5c6f89bfb6b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -471,7 +471,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb          showBouncer(scrimmed);      } -    private boolean shouldShowAltAuth() { +    /** Whether we should show the alternate authentication instead of the traditional bouncer. */ +    public boolean shouldShowAltAuth() {          return mAlternateAuthInterceptor != null                  && mKeyguardUpdateManager.isUnlockingWithBiometricAllowed(true);      } 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/keyguard/KeyguardUserSwitcherAnchorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUserSwitcherAnchorTest.kt new file mode 100644 index 000000000000..08185af1238e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUserSwitcherAnchorTest.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.keyguard + +import android.testing.AndroidTestingRunner +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class KeyguardUserSwitcherAnchorTest : SysuiTestCase() { + +    private lateinit var keyguardUserSwitcherAnchor: KeyguardUserSwitcherAnchor + +    @Before +    fun setUp() { +        keyguardUserSwitcherAnchor = KeyguardUserSwitcherAnchor(context) +    } + +    @Test +    fun roleDescription_is_set_to_pulldown_menu() { +        // GIVEN +        val roleDescriptionString = +                context.getString(R.string.accessibility_multi_user_list_switcher) + +        // WHEN +        val result = keyguardUserSwitcherAnchor.createAccessibilityNodeInfo() + +        // THEN +        assertThat( +                AccessibilityNodeInfoCompat.wrap(result).roleDescription +        ).isEqualTo(roleDescriptionString) +    } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt index c48cbb19b40a..0f112415df0d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt @@ -26,6 +26,7 @@ import junit.framework.Assert.assertNotNull  import junit.framework.Assert.assertNull  import junit.framework.Assert.assertTrue  import junit.framework.AssertionFailedError +import kotlin.concurrent.thread  import org.junit.After  import org.junit.Before  import org.junit.Rule @@ -34,19 +35,18 @@ import org.junit.runner.RunWith  import org.mockito.ArgumentCaptor  import org.mockito.ArgumentMatchers.anyBoolean  import org.mockito.Mock -import org.mockito.Mockito.`when`  import org.mockito.Mockito.never  import org.mockito.Mockito.verify +import org.mockito.Mockito.`when`  import org.mockito.Spy  import org.mockito.junit.MockitoJUnit -import kotlin.concurrent.thread  @SmallTest  @RunWith(AndroidTestingRunner::class)  @RunWithLooper  class ActivityLaunchAnimatorTest : SysuiTestCase() {      private val launchContainer = LinearLayout(mContext) -    private val testLaunchAnimator = LaunchAnimator(TEST_TIMINGS, TEST_INTERPOLATORS) +    private val testLaunchAnimator = fakeLaunchAnimator()      @Mock lateinit var callback: ActivityLaunchAnimator.Callback      @Mock lateinit var listener: ActivityLaunchAnimator.Listener      @Spy private val controller = TestLaunchAnimatorController(launchContainer) @@ -77,12 +77,13 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {          // We start in a new thread so that we can ensure that the callbacks are called in the main          // thread.          thread { -            animator.startIntentWithAnimation( +                animator.startIntentWithAnimation(                      controller = controller,                      animate = animate,                      intentStarter = intentStarter -            ) -        }.join() +                ) +            } +            .join()      }      @Test @@ -197,14 +198,25 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {          val bounds = Rect(10 /* left */, 20 /* top */, 30 /* right */, 40 /* bottom */)          val taskInfo = ActivityManager.RunningTaskInfo()          taskInfo.topActivity = ComponentName("com.android.systemui", "FakeActivity") -        taskInfo.topActivityInfo = ActivityInfo().apply { -            applicationInfo = ApplicationInfo() -        } +        taskInfo.topActivityInfo = ActivityInfo().apply { applicationInfo = ApplicationInfo() }          return RemoteAnimationTarget( -                0, RemoteAnimationTarget.MODE_OPENING, SurfaceControl(), false, Rect(), Rect(), 0, -                Point(), Rect(), bounds, WindowConfiguration(), false, SurfaceControl(), Rect(), -                taskInfo, false +            0, +            RemoteAnimationTarget.MODE_OPENING, +            SurfaceControl(), +            false, +            Rect(), +            Rect(), +            0, +            Point(), +            Rect(), +            bounds, +            WindowConfiguration(), +            false, +            SurfaceControl(), +            Rect(), +            taskInfo, +            false          )      }  } @@ -213,17 +225,17 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {   * A simple implementation of [ActivityLaunchAnimator.Controller] which throws if it is called   * outside of the main thread.   */ -private class TestLaunchAnimatorController( -    override var launchContainer: ViewGroup -) : ActivityLaunchAnimator.Controller { -    override fun createAnimatorState() = LaunchAnimator.State( +private class TestLaunchAnimatorController(override var launchContainer: ViewGroup) : +    ActivityLaunchAnimator.Controller { +    override fun createAnimatorState() = +        LaunchAnimator.State(              top = 100,              bottom = 200,              left = 300,              right = 400,              topCornerRadius = 10f,              bottomCornerRadius = 20f -    ) +        )      private fun assertOnMainThread() {          if (Looper.myLooper() != Looper.getMainLooper()) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt index 4218e0904c43..7c1e384f8c30 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt @@ -5,7 +5,6 @@ import android.content.Context  import android.graphics.Color  import android.graphics.drawable.ColorDrawable  import android.os.Bundle -import android.service.dreams.IDreamManager  import android.testing.AndroidTestingRunner  import android.testing.TestableLooper  import android.testing.ViewUtils @@ -38,19 +37,16 @@ import org.mockito.junit.MockitoJUnit  @RunWith(AndroidTestingRunner::class)  @TestableLooper.RunWithLooper  class DialogLaunchAnimatorTest : SysuiTestCase() { -    private val launchAnimator = LaunchAnimator(TEST_TIMINGS, TEST_INTERPOLATORS)      private lateinit var dialogLaunchAnimator: DialogLaunchAnimator      private val attachedViews = mutableSetOf<View>() -    @Mock lateinit var dreamManager: IDreamManager      @Mock lateinit var interactionJankMonitor: InteractionJankMonitor      @get:Rule val rule = MockitoJUnit.rule()      @Before      fun setUp() { -        dialogLaunchAnimator = DialogLaunchAnimator( -            dreamManager, interactionJankMonitor, launchAnimator, isForTesting = true -        ) +        dialogLaunchAnimator = +            fakeDialogLaunchAnimator(interactionJankMonitor = interactionJankMonitor)      }      @After @@ -153,6 +149,22 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {      }      @Test +    fun testActivityLaunchWhenLockedWithoutAlternateAuth() { +        val dialogLaunchAnimator = +            fakeDialogLaunchAnimator(isUnlocked = false, isShowingAlternateAuthOnUnlock = false) +        val dialog = createAndShowDialog(dialogLaunchAnimator) +        assertNull(dialogLaunchAnimator.createActivityLaunchController(dialog.contentView)) +    } + +    @Test +    fun testActivityLaunchWhenLockedWithAlternateAuth() { +        val dialogLaunchAnimator = +            fakeDialogLaunchAnimator(isUnlocked = false, isShowingAlternateAuthOnUnlock = true) +        val dialog = createAndShowDialog(dialogLaunchAnimator) +        assertNotNull(dialogLaunchAnimator.createActivityLaunchController(dialog.contentView)) +    } + +    @Test      fun testDialogAnimationIsChangedByAnimator() {          // Important: the power menu animation relies on this behavior to know when to animate (see          // http://ag/16774605). @@ -193,11 +205,13 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {          verify(interactionJankMonitor).end(InteractionJankMonitor.CUJ_USER_DIALOG_OPEN)      } -    private fun createAndShowDialog(): TestDialog { +    private fun createAndShowDialog( +        animator: DialogLaunchAnimator = dialogLaunchAnimator, +    ): TestDialog {          val touchSurface = createTouchSurface()          return runOnMainThreadAndWaitForIdleSync {              val dialog = TestDialog(context) -            dialogLaunchAnimator.showFromView(dialog, touchSurface) +            animator.showFromView(dialog, touchSurface)              dialog          }      } diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TestValues.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TestValues.kt deleted file mode 100644 index dadf94e2a9dd..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/TestValues.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.android.systemui.animation - -/** - * A [LaunchAnimator.Timings] to be used in tests. - * - * Note that all timings except the total duration are non-zero to avoid divide-by-zero exceptions - * when computing the progress of a sub-animation (the contents fade in/out). - */ -val TEST_TIMINGS = LaunchAnimator.Timings( -    totalDuration = 0L, -    contentBeforeFadeOutDelay = 1L, -    contentBeforeFadeOutDuration = 1L, -    contentAfterFadeInDelay = 1L, -    contentAfterFadeInDuration = 1L -) - -/** A [LaunchAnimator.Interpolators] to be used in tests. */ -val TEST_INTERPOLATORS = LaunchAnimator.Interpolators( -    positionInterpolator = Interpolators.STANDARD, -    positionXInterpolator = Interpolators.STANDARD, -    contentBeforeFadeOutInterpolator = Interpolators.STANDARD, -    contentAfterFadeInInterpolator = Interpolators.STANDARD -)
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.java new file mode 100644 index 000000000000..a78886f8d504 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarItemsProviderTest.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.systemui.dreams; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; +import java.util.concurrent.Executor; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class DreamOverlayStatusBarItemsProviderTest extends SysuiTestCase { +    @Mock +    DreamOverlayStatusBarItemsProvider.Callback mCallback; +    @Mock +    DreamOverlayStatusBarItemsProvider.StatusBarItem mStatusBarItem; + +    private final Executor mMainExecutor = Runnable::run; + +    DreamOverlayStatusBarItemsProvider mProvider; + +    @Before +    public void setup() { +        MockitoAnnotations.initMocks(this); +        mProvider = new DreamOverlayStatusBarItemsProvider(mMainExecutor); +    } + +    @Test +    public void addingCallbackCallsOnStatusBarItemsChanged() { +        mProvider.addStatusBarItem(mStatusBarItem); +        mProvider.addCallback(mCallback); +        verify(mCallback).onStatusBarItemsChanged(List.of(mStatusBarItem)); +    } + +    @Test +    public void addingStatusBarItemCallsOnStatusBarItemsChanged() { +        mProvider.addCallback(mCallback); +        mProvider.addStatusBarItem(mStatusBarItem); +        verify(mCallback).onStatusBarItemsChanged(List.of(mStatusBarItem)); +    } + +    @Test +    public void addingDuplicateStatusBarItemDoesNotCallOnStatusBarItemsChanged() { +        mProvider.addCallback(mCallback); +        mProvider.addStatusBarItem(mStatusBarItem); +        mProvider.addStatusBarItem(mStatusBarItem); +        // Called only once for addStatusBarItem. +        verify(mCallback, times(1)) +                .onStatusBarItemsChanged(List.of(mStatusBarItem)); +    } + +    @Test +    public void removingStatusBarItemCallsOnStatusBarItemsChanged() { +        mProvider.addCallback(mCallback); +        mProvider.addStatusBarItem(mStatusBarItem); +        mProvider.removeStatusBarItem(mStatusBarItem); +        // Called once for addStatusBarItem and once for removeStatusBarItem. +        verify(mCallback, times(2)).onStatusBarItemsChanged(any()); +    } + +    @Test +    public void removingNonexistentStatusBarItemDoesNotCallOnStatusBarItemsChanged() { +        mProvider.addCallback(mCallback); +        mProvider.removeStatusBarItem(mStatusBarItem); +        verify(mCallback, never()).onStatusBarItemsChanged(any()); +    } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java index 60e5a9423c61..01309f86a137 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java @@ -57,6 +57,7 @@ import org.mockito.ArgumentCaptor;  import org.mockito.Mock;  import org.mockito.MockitoAnnotations; +import java.util.List;  import java.util.Optional;  import java.util.concurrent.Executor; @@ -94,6 +95,12 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {      DreamOverlayNotificationCountProvider mDreamOverlayNotificationCountProvider;      @Mock      StatusBarWindowStateController mStatusBarWindowStateController; +    @Mock +    DreamOverlayStatusBarItemsProvider mDreamOverlayStatusBarItemsProvider; +    @Mock +    DreamOverlayStatusBarItemsProvider.StatusBarItem mStatusBarItem; +    @Mock +    View mStatusBarItemView;      private final Executor mMainExecutor = Runnable::run; @@ -118,7 +125,8 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {                  mSensorPrivacyController,                  Optional.of(mDreamOverlayNotificationCountProvider),                  mZenModeController, -                mStatusBarWindowStateController); +                mStatusBarWindowStateController, +                mDreamOverlayStatusBarItemsProvider);      }      @Test @@ -128,6 +136,7 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {          verify(mSensorPrivacyController).addCallback(any());          verify(mZenModeController).addCallback(any());          verify(mDreamOverlayNotificationCountProvider).addCallback(any()); +        verify(mDreamOverlayStatusBarItemsProvider).addCallback(any());      }      @Test @@ -256,7 +265,8 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {                  mSensorPrivacyController,                  Optional.empty(),                  mZenModeController, -                mStatusBarWindowStateController); +                mStatusBarWindowStateController, +                mDreamOverlayStatusBarItemsProvider);          controller.onViewAttached();          verify(mView, never()).showIcon(                  eq(DreamOverlayStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(true), any()); @@ -294,6 +304,7 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {          verify(mSensorPrivacyController).removeCallback(any());          verify(mZenModeController).removeCallback(any());          verify(mDreamOverlayNotificationCountProvider).removeCallback(any()); +        verify(mDreamOverlayStatusBarItemsProvider).removeCallback(any());      }      @Test @@ -462,4 +473,18 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {          verify(mView, never()).setVisibility(anyInt());      } + +    @Test +    public void testExtraStatusBarItemSetWhenItemsChange() { +        mController.onViewAttached(); +        when(mStatusBarItem.getView()).thenReturn(mStatusBarItemView); + +        final ArgumentCaptor<DreamOverlayStatusBarItemsProvider.Callback> +                callbackCapture = ArgumentCaptor.forClass( +                        DreamOverlayStatusBarItemsProvider.Callback.class); +        verify(mDreamOverlayStatusBarItemsProvider).addCallback(callbackCapture.capture()); +        callbackCapture.getValue().onStatusBarItemsChanged(List.of(mStatusBarItem)); + +        verify(mView).setExtraStatusBarItemViews(List.of(mStatusBarItemView)); +    }  } 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/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java index 7dbc561fbfc1..3c58b6fc1354 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java @@ -22,6 +22,7 @@ import static junit.framework.Assert.assertFalse;  import static junit.framework.Assert.assertTrue;  import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt;  import static org.mockito.ArgumentMatchers.anyString;  import static org.mockito.ArgumentMatchers.isNull;  import static org.mockito.Mockito.mock; @@ -32,11 +33,13 @@ import static org.mockito.Mockito.when;  import android.content.ComponentName;  import android.content.Context;  import android.content.Intent; +import android.content.SharedPreferences;  import android.database.ContentObserver;  import android.os.Handler;  import android.os.Looper;  import android.os.UserHandle;  import android.testing.AndroidTestingRunner; +import android.util.SparseArray;  import android.view.View;  import androidx.annotation.Nullable; @@ -60,12 +63,14 @@ import com.android.systemui.qs.external.TileServiceKey;  import com.android.systemui.qs.external.TileServiceRequestController;  import com.android.systemui.qs.logging.QSLogger;  import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.settings.UserFileManager;  import com.android.systemui.settings.UserTracker;  import com.android.systemui.shared.plugins.PluginManager;  import com.android.systemui.statusbar.phone.AutoTileManager;  import com.android.systemui.statusbar.phone.CentralSurfaces;  import com.android.systemui.statusbar.phone.StatusBarIconController;  import com.android.systemui.tuner.TunerService; +import com.android.systemui.util.FakeSharedPreferences;  import com.android.systemui.util.concurrency.FakeExecutor;  import com.android.systemui.util.settings.FakeSettings;  import com.android.systemui.util.settings.SecureSettings; @@ -76,6 +81,7 @@ import org.junit.Test;  import org.junit.runner.RunWith;  import org.mockito.Mock;  import org.mockito.MockitoAnnotations; +import org.mockito.stubbing.Answer;  import java.io.PrintWriter;  import java.io.StringWriter; @@ -130,6 +136,10 @@ public class QSTileHostTest extends SysuiTestCase {      private TileLifecycleManager.Factory mTileLifecycleManagerFactory;      @Mock      private TileLifecycleManager mTileLifecycleManager; +    @Mock +    private UserFileManager mUserFileManager; + +    private SparseArray<SharedPreferences> mSharedPreferencesByUser;      private FakeExecutor mMainExecutor; @@ -140,17 +150,29 @@ public class QSTileHostTest extends SysuiTestCase {          MockitoAnnotations.initMocks(this);          mMainExecutor = new FakeExecutor(new FakeSystemClock()); +        mSharedPreferencesByUser = new SparseArray<>(); +          when(mTileServiceRequestControllerBuilder.create(any()))                  .thenReturn(mTileServiceRequestController);          when(mTileLifecycleManagerFactory.create(any(Intent.class), any(UserHandle.class)))                  .thenReturn(mTileLifecycleManager); +        when(mUserFileManager.getSharedPreferences(anyString(), anyInt(), anyInt())) +                .thenAnswer((Answer<SharedPreferences>) invocation -> { +                    assertEquals(QSTileHost.TILES, invocation.getArgument(0)); +                    int userId = invocation.getArgument(2); +                    if (!mSharedPreferencesByUser.contains(userId)) { +                        mSharedPreferencesByUser.put(userId, new FakeSharedPreferences()); +                    } +                    return mSharedPreferencesByUser.get(userId); +                });          mSecureSettings = new FakeSettings();          saveSetting("");          mQSTileHost = new TestQSTileHost(mContext, mIconController, mDefaultFactory, mMainExecutor,                  mPluginManager, mTunerService, mAutoTiles, mDumpManager, mCentralSurfaces,                  mQSLogger, mUiEventLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister, -                mTileServiceRequestControllerBuilder, mTileLifecycleManagerFactory); +                mTileServiceRequestControllerBuilder, mTileLifecycleManagerFactory, +                mUserFileManager);          mSecureSettings.registerContentObserverForUser(SETTING, new ContentObserver(null) {              @Override @@ -528,6 +550,118 @@ public class QSTileHostTest extends SysuiTestCase {          assertEquals("spec1", getSetting());      } +    @Test +    public void testIsTileAdded_true() { +        int user = mUserTracker.getUserId(); +        getSharedPreferenecesForUser(user) +                .edit() +                .putBoolean(CUSTOM_TILE.flattenToString(), true) +                .apply(); + +        assertTrue(mQSTileHost.isTileAdded(CUSTOM_TILE, user)); +    } + +    @Test +    public void testIsTileAdded_false() { +        int user = mUserTracker.getUserId(); +        getSharedPreferenecesForUser(user) +                .edit() +                .putBoolean(CUSTOM_TILE.flattenToString(), false) +                .apply(); + +        assertFalse(mQSTileHost.isTileAdded(CUSTOM_TILE, user)); +    } + +    @Test +    public void testIsTileAdded_notSet() { +        int user = mUserTracker.getUserId(); + +        assertFalse(mQSTileHost.isTileAdded(CUSTOM_TILE, user)); +    } + +    @Test +    public void testIsTileAdded_differentUser() { +        int user = mUserTracker.getUserId(); +        mUserFileManager.getSharedPreferences(QSTileHost.TILES, 0, user) +                .edit() +                .putBoolean(CUSTOM_TILE.flattenToString(), true) +                .apply(); + +        assertFalse(mQSTileHost.isTileAdded(CUSTOM_TILE, user + 1)); +    } + +    @Test +    public void testSetTileAdded_true() { +        int user = mUserTracker.getUserId(); +        mQSTileHost.setTileAdded(CUSTOM_TILE, user, true); + +        assertTrue(getSharedPreferenecesForUser(user) +                .getBoolean(CUSTOM_TILE.flattenToString(), false)); +    } + +    @Test +    public void testSetTileAdded_false() { +        int user = mUserTracker.getUserId(); +        mQSTileHost.setTileAdded(CUSTOM_TILE, user, false); + +        assertFalse(getSharedPreferenecesForUser(user) +                .getBoolean(CUSTOM_TILE.flattenToString(), false)); +    } + +    @Test +    public void testSetTileAdded_differentUser() { +        int user = mUserTracker.getUserId(); +        mQSTileHost.setTileAdded(CUSTOM_TILE, user, true); + +        assertFalse(getSharedPreferenecesForUser(user + 1) +                .getBoolean(CUSTOM_TILE.flattenToString(), false)); +    } + +    @Test +    public void testSetTileRemoved_afterCustomTileChangedByUser() { +        int user = mUserTracker.getUserId(); +        saveSetting(CUSTOM_TILE_SPEC); + +        // This will be done by TileServiceManager +        mQSTileHost.setTileAdded(CUSTOM_TILE, user, true); + +        mQSTileHost.changeTilesByUser(mQSTileHost.mTileSpecs, List.of("spec1")); +        assertFalse(getSharedPreferenecesForUser(user) +                .getBoolean(CUSTOM_TILE.flattenToString(), false)); +    } + +    @Test +    public void testSetTileRemoved_removedByUser() { +        int user = mUserTracker.getUserId(); +        saveSetting(CUSTOM_TILE_SPEC); + +        // This will be done by TileServiceManager +        mQSTileHost.setTileAdded(CUSTOM_TILE, user, true); + +        mQSTileHost.removeTileByUser(CUSTOM_TILE); +        mMainExecutor.runAllReady(); +        assertFalse(getSharedPreferenecesForUser(user) +                .getBoolean(CUSTOM_TILE.flattenToString(), false)); +    } + +    @Test +    public void testSetTileRemoved_removedBySystem() { +        int user = mUserTracker.getUserId(); +        saveSetting("spec1" + CUSTOM_TILE_SPEC); + +        // This will be done by TileServiceManager +        mQSTileHost.setTileAdded(CUSTOM_TILE, user, true); + +        mQSTileHost.removeTile(CUSTOM_TILE_SPEC); +        mMainExecutor.runAllReady(); +        assertFalse(getSharedPreferenecesForUser(user) +                .getBoolean(CUSTOM_TILE.flattenToString(), false)); +    } + +    private SharedPreferences getSharedPreferenecesForUser(int user) { +        return mUserFileManager.getSharedPreferences(QSTileHost.TILES, 0, user); +    } +      private class TestQSTileHost extends QSTileHost {          TestQSTileHost(Context context, StatusBarIconController iconController,                  QSFactory defaultFactory, Executor mainExecutor, @@ -537,11 +671,13 @@ public class QSTileHostTest extends SysuiTestCase {                  UserTracker userTracker, SecureSettings secureSettings,                  CustomTileStatePersister customTileStatePersister,                  TileServiceRequestController.Builder tileServiceRequestControllerBuilder, -                TileLifecycleManager.Factory tileLifecycleManagerFactory) { +                TileLifecycleManager.Factory tileLifecycleManagerFactory, +                UserFileManager userFileManager) {              super(context, iconController, defaultFactory, mainExecutor, pluginManager,                      tunerService, autoTiles, dumpManager, Optional.of(centralSurfaces), qsLogger,                      uiEventLogger, userTracker, secureSettings, customTileStatePersister, -                    tileServiceRequestControllerBuilder, tileLifecycleManagerFactory); +                    tileServiceRequestControllerBuilder, tileLifecycleManagerFactory, +                    userFileManager);          }          @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java index 573980d015a9..8aa625a7ea20 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java @@ -19,6 +19,14 @@ import static junit.framework.Assert.assertEquals;  import static junit.framework.Assert.assertFalse;  import static junit.framework.Assert.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +  import android.content.ComponentName;  import android.content.Context;  import android.content.Intent; @@ -31,6 +39,7 @@ import android.test.suitebuilder.annotation.SmallTest;  import androidx.test.runner.AndroidJUnit4;  import com.android.systemui.SysuiTestCase; +import com.android.systemui.qs.QSTileHost;  import com.android.systemui.settings.UserTracker;  import org.junit.After; @@ -38,37 +47,45 @@ import org.junit.Before;  import org.junit.Test;  import org.junit.runner.RunWith;  import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations;  @SmallTest  @RunWith(AndroidJUnit4.class)  public class TileServiceManagerTest extends SysuiTestCase { +    @Mock      private TileServices mTileServices; +    @Mock      private TileLifecycleManager mTileLifecycle; +    @Mock +    private UserTracker mUserTracker; +    @Mock +    private QSTileHost mQSTileHost; +    @Mock +    private Context mMockContext; +      private HandlerThread mThread;      private Handler mHandler;      private TileServiceManager mTileServiceManager; -    private UserTracker mUserTracker; -    private Context mMockContext; +    private ComponentName mComponentName;      @Before      public void setUp() throws Exception { +        MockitoAnnotations.initMocks(this);          mThread = new HandlerThread("TestThread");          mThread.start();          mHandler = Handler.createAsync(mThread.getLooper()); -        mTileServices = Mockito.mock(TileServices.class); -        mUserTracker = Mockito.mock(UserTracker.class); -        Mockito.when(mUserTracker.getUserId()).thenReturn(UserHandle.USER_SYSTEM); -        Mockito.when(mUserTracker.getUserHandle()).thenReturn(UserHandle.SYSTEM); - -        mMockContext = Mockito.mock(Context.class); -        Mockito.when(mTileServices.getContext()).thenReturn(mMockContext); -        mTileLifecycle = Mockito.mock(TileLifecycleManager.class); -        Mockito.when(mTileLifecycle.isActiveTile()).thenReturn(false); -        ComponentName componentName = new ComponentName(mContext, -                TileServiceManagerTest.class); -        Mockito.when(mTileLifecycle.getComponent()).thenReturn(componentName); +        when(mUserTracker.getUserId()).thenReturn(UserHandle.USER_SYSTEM); +        when(mUserTracker.getUserHandle()).thenReturn(UserHandle.SYSTEM); + +        when(mTileServices.getContext()).thenReturn(mMockContext); +        when(mTileServices.getHost()).thenReturn(mQSTileHost); +        when(mTileLifecycle.getUserId()).thenAnswer(invocation -> mUserTracker.getUserId()); +        when(mTileLifecycle.isActiveTile()).thenReturn(false); + +        mComponentName = new ComponentName(mContext, TileServiceManagerTest.class); +        when(mTileLifecycle.getComponent()).thenReturn(mComponentName);          mTileServiceManager = new TileServiceManager(mTileServices, mHandler, mUserTracker,                  mTileLifecycle);      } @@ -80,17 +97,44 @@ public class TileServiceManagerTest extends SysuiTestCase {      }      @Test +    public void testSetTileAddedIfNotAdded() { +        when(mQSTileHost.isTileAdded(eq(mComponentName), anyInt())).thenReturn(false); +        mTileServiceManager.startLifecycleManagerAndAddTile(); + +        verify(mQSTileHost).setTileAdded(mComponentName, mUserTracker.getUserId(), true); +    } + +    @Test +    public void testNotSetTileAddedIfAdded() { +        when(mQSTileHost.isTileAdded(eq(mComponentName), anyInt())).thenReturn(true); +        mTileServiceManager.startLifecycleManagerAndAddTile(); + +        verify(mQSTileHost, never()).setTileAdded(eq(mComponentName), anyInt(), eq(true)); +    } + +    @Test +    public void testSetTileAddedCorrectUser() { +        int user = 10; +        when(mUserTracker.getUserId()).thenReturn(user); +        when(mQSTileHost.isTileAdded(eq(mComponentName), anyInt())).thenReturn(false); +        mTileServiceManager.startLifecycleManagerAndAddTile(); + +        verify(mQSTileHost).setTileAdded(mComponentName, user, true); +    } + +    @Test      public void testUninstallReceiverExported() { +        mTileServiceManager.startLifecycleManagerAndAddTile();          ArgumentCaptor<IntentFilter> intentFilterCaptor =                  ArgumentCaptor.forClass(IntentFilter.class); -        Mockito.verify(mMockContext).registerReceiverAsUser( -                Mockito.any(), -                Mockito.any(), +        verify(mMockContext).registerReceiverAsUser( +                any(), +                any(),                  intentFilterCaptor.capture(), -                Mockito.any(), -                Mockito.any(), -                Mockito.eq(Context.RECEIVER_EXPORTED) +                any(), +                any(), +                eq(Context.RECEIVER_EXPORTED)          );          IntentFilter filter = intentFilterCaptor.getValue();          assertTrue(filter.hasAction(Intent.ACTION_PACKAGE_REMOVED)); @@ -99,38 +143,41 @@ public class TileServiceManagerTest extends SysuiTestCase {      @Test      public void testSetBindRequested() { +        mTileServiceManager.startLifecycleManagerAndAddTile();          // Request binding.          mTileServiceManager.setBindRequested(true);          mTileServiceManager.setLastUpdate(0);          mTileServiceManager.calculateBindPriority(5); -        Mockito.verify(mTileServices, Mockito.times(2)).recalculateBindAllowance(); +        verify(mTileServices, times(2)).recalculateBindAllowance();          assertEquals(5, mTileServiceManager.getBindPriority());          // Verify same state doesn't trigger recalculating for no reason.          mTileServiceManager.setBindRequested(true); -        Mockito.verify(mTileServices, Mockito.times(2)).recalculateBindAllowance(); +        verify(mTileServices, times(2)).recalculateBindAllowance();          mTileServiceManager.setBindRequested(false);          mTileServiceManager.calculateBindPriority(5); -        Mockito.verify(mTileServices, Mockito.times(3)).recalculateBindAllowance(); +        verify(mTileServices, times(3)).recalculateBindAllowance();          assertEquals(Integer.MIN_VALUE, mTileServiceManager.getBindPriority());      }      @Test      public void testPendingClickPriority() { -        Mockito.when(mTileLifecycle.hasPendingClick()).thenReturn(true); +        mTileServiceManager.startLifecycleManagerAndAddTile(); +        when(mTileLifecycle.hasPendingClick()).thenReturn(true);          mTileServiceManager.calculateBindPriority(0);          assertEquals(Integer.MAX_VALUE, mTileServiceManager.getBindPriority());      }      @Test      public void testBind() { +        mTileServiceManager.startLifecycleManagerAndAddTile();          // Trigger binding requested and allowed.          mTileServiceManager.setBindRequested(true);          mTileServiceManager.setBindAllowed(true);          ArgumentCaptor<Boolean> captor = ArgumentCaptor.forClass(Boolean.class); -        Mockito.verify(mTileLifecycle, Mockito.times(1)).setBindService(captor.capture()); +        verify(mTileLifecycle, times(1)).setBindService(captor.capture());          assertTrue((boolean) captor.getValue());          mTileServiceManager.setBindRequested(false); @@ -141,7 +188,7 @@ public class TileServiceManagerTest extends SysuiTestCase {          mTileServiceManager.setBindAllowed(false);          captor = ArgumentCaptor.forClass(Boolean.class); -        Mockito.verify(mTileLifecycle, Mockito.times(2)).setBindService(captor.capture()); +        verify(mTileLifecycle, times(2)).setBindService(captor.capture());          assertFalse((boolean) captor.getValue());      }  } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java index 471ddfd3f224..213eca895eb9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java @@ -45,6 +45,7 @@ import com.android.systemui.dump.DumpManager;  import com.android.systemui.qs.QSTileHost;  import com.android.systemui.qs.logging.QSLogger;  import com.android.systemui.qs.tileimpl.QSFactoryImpl; +import com.android.systemui.settings.UserFileManager;  import com.android.systemui.settings.UserTracker;  import com.android.systemui.shared.plugins.PluginManager;  import com.android.systemui.statusbar.CommandQueue; @@ -118,6 +119,8 @@ public class TileServicesTest extends SysuiTestCase {      private TileLifecycleManager.Factory mTileLifecycleManagerFactory;      @Mock      private TileLifecycleManager mTileLifecycleManager; +    @Mock +    private UserFileManager mUserFileManager;      @Before      public void setUp() throws Exception { @@ -149,7 +152,8 @@ public class TileServicesTest extends SysuiTestCase {                  mSecureSettings,                  mock(CustomTileStatePersister.class),                  mTileServiceRequestControllerBuilder, -                mTileLifecycleManagerFactory); +                mTileLifecycleManagerFactory, +                mUserFileManager);          mTileService = new TestTileServices(host, provider, mBroadcastDispatcher,                  mUserTracker, mKeyguardStateController, mCommandQueue);      } 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/shared/system/RemoteTransitionTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java index 360eef9d214f..cf5fa87272c7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java @@ -19,12 +19,14 @@ package com.android.systemui.shared.system;  import static android.view.WindowManager.TRANSIT_CHANGE;  import static android.view.WindowManager.TRANSIT_CLOSE;  import static android.view.WindowManager.TRANSIT_OPEN; +import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;  import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;  import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;  import static android.window.TransitionInfo.FLAG_TRANSLUCENT;  import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;  import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME; +import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_STANDARD;  import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CHANGING;  import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;  import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING; @@ -64,12 +66,15 @@ public class RemoteTransitionTest extends SysuiTestCase {      @Test      public void testLegacyTargetExtract() {          TransitionInfo combined = new TransitionInfoBuilder(TRANSIT_CLOSE) -                .addChange(TRANSIT_CHANGE, FLAG_SHOW_WALLPAPER) -                .addChange(TRANSIT_CLOSE, 0 /* flags */) -                .addChange(TRANSIT_OPEN, FLAG_IS_WALLPAPER).build(); -        // Check non-wallpaper extraction -        RemoteAnimationTargetCompat[] wrapped = RemoteAnimationTargetCompat.wrap(combined, -                false /* wallpapers */, mock(SurfaceControl.Transaction.class), null /* leashes */); +                .addChange(TRANSIT_CHANGE, FLAG_SHOW_WALLPAPER, +                        createTaskInfo(1 /* taskId */, ACTIVITY_TYPE_STANDARD)) +                .addChange(TRANSIT_CLOSE, 0 /* flags */, +                        createTaskInfo(2 /* taskId */, ACTIVITY_TYPE_STANDARD)) +                .addChange(TRANSIT_OPEN, FLAG_IS_WALLPAPER, null /* taskInfo */) +                .addChange(TRANSIT_CHANGE, FLAG_FIRST_CUSTOM, null /* taskInfo */).build(); +        // Check apps extraction +        RemoteAnimationTargetCompat[] wrapped = RemoteAnimationTargetCompat.wrapApps(combined, +                mock(SurfaceControl.Transaction.class), null /* leashes */);          assertEquals(2, wrapped.length);          int changeLayer = -1;          int closeLayer = -1; @@ -86,17 +91,25 @@ public class RemoteTransitionTest extends SysuiTestCase {          assertTrue(closeLayer < changeLayer);          // Check wallpaper extraction -        RemoteAnimationTargetCompat[] wallps = RemoteAnimationTargetCompat.wrap(combined, +        RemoteAnimationTargetCompat[] wallps = RemoteAnimationTargetCompat.wrapNonApps(combined,                  true /* wallpapers */, mock(SurfaceControl.Transaction.class), null /* leashes */);          assertEquals(1, wallps.length);          assertTrue(wallps[0].prefixOrderIndex < closeLayer);          assertEquals(MODE_OPENING, wallps[0].mode); + +        // Check non-apps extraction +        RemoteAnimationTargetCompat[] nonApps = RemoteAnimationTargetCompat.wrapNonApps(combined, +                false /* wallpapers */, mock(SurfaceControl.Transaction.class), null /* leashes */); +        assertEquals(1, nonApps.length); +        assertTrue(nonApps[0].prefixOrderIndex < closeLayer); +        assertEquals(MODE_CHANGING, nonApps[0].mode);      }      @Test      public void testLegacyTargetWrapper() {          TransitionInfo tinfo = new TransitionInfoBuilder(TRANSIT_CLOSE) -                .addChange(TRANSIT_CHANGE, FLAG_TRANSLUCENT).build(); +                .addChange(TRANSIT_CHANGE, FLAG_TRANSLUCENT, +                        createTaskInfo(1 /* taskId */, ACTIVITY_TYPE_STANDARD)).build();          final TransitionInfo.Change change = tinfo.getChanges().get(0);          final Rect endBounds = new Rect(40, 60, 140, 200);          change.setTaskInfo(createTaskInfo(1 /* taskId */, ACTIVITY_TYPE_HOME)); @@ -119,11 +132,12 @@ public class RemoteTransitionTest extends SysuiTestCase {          }          TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode, -                @TransitionInfo.ChangeFlags int flags) { +                @TransitionInfo.ChangeFlags int flags, ActivityManager.RunningTaskInfo taskInfo) {              final TransitionInfo.Change change =                      new TransitionInfo.Change(null /* token */, createMockSurface(true));              change.setMode(mode);              change.setFlags(flags); +            change.setTaskInfo(taskInfo);              mInfo.addChange(change);              return this;          } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java index dfa38abc1ff8..9f214097ea55 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java @@ -68,6 +68,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga  import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;  import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;  import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;  import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;  import com.android.systemui.util.time.FakeSystemClock; @@ -1715,66 +1716,201 @@ public class ShadeListBuilderTest extends SysuiTestCase {          assertEquals(GroupEntry.ROOT_ENTRY, group.getPreviousParent());      } +    static class CountingInvalidator { +        CountingInvalidator(Pluggable pluggableToInvalidate) { +            mPluggableToInvalidate = pluggableToInvalidate; +            mInvalidationCount = 0; +        } + +        public void setInvalidationCount(int invalidationCount) { +            mInvalidationCount = invalidationCount; +        } + +        public void maybeInvalidate() { +            if (mInvalidationCount > 0) { +                mPluggableToInvalidate.invalidateList("test invalidation"); +                mInvalidationCount--; +            } +        } + +        private Pluggable mPluggableToInvalidate; +        private int mInvalidationCount; + +        private static final String TAG = "ShadeListBuilderTestCountingInvalidator"; +    } + +    @Test +    public void testOutOfOrderPreGroupFilterInvalidationDoesNotThrowBeforeTooManyRuns() { +        // GIVEN a PreGroupNotifFilter that gets invalidated during the grouping stage, +        NotifFilter filter = new PackageFilter(PACKAGE_1); +        CountingInvalidator invalidator = new CountingInvalidator(filter); +        OnBeforeTransformGroupsListener listener = (list) -> invalidator.maybeInvalidate(); +        mListBuilder.addPreGroupFilter(filter); +        mListBuilder.addOnBeforeTransformGroupsListener(listener); + +        // WHEN we try to run the pipeline and the filter is invalidated exactly +        // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, +        addNotif(0, PACKAGE_2); +        invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS); +        dispatchBuild(); +        runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); + +        // THEN an exception is NOT thrown. +    } +      @Test(expected = IllegalStateException.class) -    public void testOutOfOrderPreGroupFilterInvalidationThrows() { -        // GIVEN a PreGroupNotifFilter that gets invalidated during the grouping stage -        NotifFilter filter = new PackageFilter(PACKAGE_5); -        OnBeforeTransformGroupsListener listener = (list) -> filter.invalidateList(null); +    public void testOutOfOrderPreGroupFilterInvalidationThrowsAfterTooManyRuns() { +        // GIVEN a PreGroupNotifFilter that gets invalidated during the grouping stage, +        NotifFilter filter = new PackageFilter(PACKAGE_1); +        CountingInvalidator invalidator = new CountingInvalidator(filter); +        OnBeforeTransformGroupsListener listener = (list) -> invalidator.maybeInvalidate();          mListBuilder.addPreGroupFilter(filter);          mListBuilder.addOnBeforeTransformGroupsListener(listener); -        // WHEN we try to run the pipeline and the filter is invalidated +        // WHEN we try to run the pipeline and the filter is invalidated more than +        // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, +        addNotif(0, PACKAGE_2); +        invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1); +        dispatchBuild(); +        runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); + +        // THEN an exception IS thrown. +    } + +    @Test +    public void testNonConsecutiveOutOfOrderInvalidationDontThrowAfterTooManyRuns() { +        // GIVEN a PreGroupNotifFilter that gets invalidated during the grouping stage, +        NotifFilter filter = new PackageFilter(PACKAGE_1); +        CountingInvalidator invalidator = new CountingInvalidator(filter); +        OnBeforeTransformGroupsListener listener = (list) -> invalidator.maybeInvalidate(); +        mListBuilder.addPreGroupFilter(filter); +        mListBuilder.addOnBeforeTransformGroupsListener(listener); + +        // WHEN we try to run the pipeline and the filter is invalidated at least +        // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, +        addNotif(0, PACKAGE_2); +        invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS); +        dispatchBuild(); +        runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); +        invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS); +        dispatchBuild(); +        runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); + +        // THEN an exception is NOT thrown. +    } + +    @Test +    public void testOutOfOrderPrompterInvalidationDoesNotThrowBeforeTooManyRuns() { +        // GIVEN a NotifPromoter that gets invalidated during the sorting stage, +        NotifPromoter promoter = new IdPromoter(47); +        CountingInvalidator invalidator = new CountingInvalidator(promoter); +        OnBeforeSortListener listener = (list) -> invalidator.maybeInvalidate(); +        mListBuilder.addPromoter(promoter); +        mListBuilder.addOnBeforeSortListener(listener); + +        // WHEN we try to run the pipeline and the promoter is invalidated exactly +        // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,          addNotif(0, PACKAGE_1); +        invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS);          dispatchBuild(); +        runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); -        // THEN an exception is thrown +        // THEN an exception is NOT thrown.      }      @Test(expected = IllegalStateException.class) -    public void testOutOfOrderPrompterInvalidationThrows() { -        // GIVEN a NotifPromoter that gets invalidated during the sorting stage +    public void testOutOfOrderPrompterInvalidationThrowsAfterTooManyRuns() { +        // GIVEN a NotifPromoter that gets invalidated during the sorting stage,          NotifPromoter promoter = new IdPromoter(47); -        OnBeforeSortListener listener = -                (list) -> promoter.invalidateList(null); +        CountingInvalidator invalidator = new CountingInvalidator(promoter); +        OnBeforeSortListener listener = (list) -> invalidator.maybeInvalidate();          mListBuilder.addPromoter(promoter);          mListBuilder.addOnBeforeSortListener(listener); -        // WHEN we try to run the pipeline and the promoter is invalidated +        // WHEN we try to run the pipeline and the promoter is invalidated more than +        // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,          addNotif(0, PACKAGE_1); +        invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);          dispatchBuild(); +        runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); -        // THEN an exception is thrown +        // THEN an exception IS thrown. +    } + +    @Test +    public void testOutOfOrderComparatorInvalidationDoesNotThrowBeforeTooManyRuns() { +        // GIVEN a NotifComparator that gets invalidated during the finalizing stage, +        NotifComparator comparator = new HypeComparator(PACKAGE_1); +        CountingInvalidator invalidator = new CountingInvalidator(comparator); +        OnBeforeRenderListListener listener = (list) -> invalidator.maybeInvalidate(); +        mListBuilder.setComparators(singletonList(comparator)); +        mListBuilder.addOnBeforeRenderListListener(listener); + +        // WHEN we try to run the pipeline and the comparator is invalidated exactly +        // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, +        addNotif(0, PACKAGE_2); +        invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS); +        dispatchBuild(); +        runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); + +        // THEN an exception is NOT thrown.      }      @Test(expected = IllegalStateException.class) -    public void testOutOfOrderComparatorInvalidationThrows() { -        // GIVEN a NotifComparator that gets invalidated during the finalizing stage -        NotifComparator comparator = new HypeComparator(PACKAGE_5); -        OnBeforeRenderListListener listener = -                (list) -> comparator.invalidateList(null); +    public void testOutOfOrderComparatorInvalidationThrowsAfterTooManyRuns() { +        // GIVEN a NotifComparator that gets invalidated during the finalizing stage, +        NotifComparator comparator = new HypeComparator(PACKAGE_1); +        CountingInvalidator invalidator = new CountingInvalidator(comparator); +        OnBeforeRenderListListener listener = (list) -> invalidator.maybeInvalidate();          mListBuilder.setComparators(singletonList(comparator));          mListBuilder.addOnBeforeRenderListListener(listener); -        // WHEN we try to run the pipeline and the comparator is invalidated -        addNotif(0, PACKAGE_1); +        // WHEN we try to run the pipeline and the comparator is invalidated more than +        // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, +        addNotif(0, PACKAGE_2); +        invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);          dispatchBuild(); +        runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); -        // THEN an exception is thrown +        // THEN an exception IS thrown. +    } + +    @Test +    public void testOutOfOrderPreRenderFilterInvalidationDoesNotThrowBeforeTooManyRuns() { +        // GIVEN a PreRenderNotifFilter that gets invalidated during the finalizing stage, +        NotifFilter filter = new PackageFilter(PACKAGE_1); +        CountingInvalidator invalidator = new CountingInvalidator(filter); +        OnBeforeRenderListListener listener = (list) -> invalidator.maybeInvalidate(); +        mListBuilder.addFinalizeFilter(filter); +        mListBuilder.addOnBeforeRenderListListener(listener); + +        // WHEN we try to run the pipeline and the PreRenderFilter is invalidated exactly +        // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, +        addNotif(0, PACKAGE_2); +        invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS); +        dispatchBuild(); +        runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); + +        // THEN an exception is NOT thrown.      }      @Test(expected = IllegalStateException.class) -    public void testOutOfOrderPreRenderFilterInvalidationThrows() { -        // GIVEN a PreRenderNotifFilter that gets invalidated during the finalizing stage -        NotifFilter filter = new PackageFilter(PACKAGE_5); -        OnBeforeRenderListListener listener = (list) -> filter.invalidateList(null); +    public void testOutOfOrderPreRenderFilterInvalidationThrowsAfterTooManyRuns() { +        // GIVEN a PreRenderNotifFilter that gets invalidated during the finalizing stage, +        NotifFilter filter = new PackageFilter(PACKAGE_1); +        CountingInvalidator invalidator = new CountingInvalidator(filter); +        OnBeforeRenderListListener listener = (list) -> invalidator.maybeInvalidate();          mListBuilder.addFinalizeFilter(filter);          mListBuilder.addOnBeforeRenderListListener(listener); -        // WHEN we try to run the pipeline and the PreRenderFilter is invalidated -        addNotif(0, PACKAGE_1); +        // WHEN we try to run the pipeline and the PreRenderFilter is invalidated more than +        // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, +        addNotif(0, PACKAGE_2); +        invalidator.setInvalidationCount(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);          dispatchBuild(); +        runWhileScheduledUpTo(ShadeListBuilder.MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2); -        // THEN an exception is thrown +        // THEN an exception IS thrown.      }      @Test @@ -2096,6 +2232,18 @@ public class ShadeListBuilderTest extends SysuiTestCase {          mPipelineChoreographer.runIfScheduled();      } +    private void runWhileScheduledUpTo(int maxRuns) { +        int runs = 0; +        while (mPipelineChoreographer.isScheduled()) { +            if (runs > maxRuns) { +                throw new IndexOutOfBoundsException( +                        "Pipeline scheduled itself more than " + maxRuns + "times"); +            } +            runs++; +            mPipelineChoreographer.runIfScheduled(); +        } +    } +      private void verifyBuiltList(ExpectedEntry ...expectedEntries) {          try {              assertEquals( diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt index 87fca1f23f1a..7e0704007700 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt @@ -30,13 +30,13 @@ import com.android.systemui.unfold.updates.screen.ScreenStatusProvider  import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener  import com.android.systemui.util.mockito.any  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.Mock -import org.mockito.MockitoAnnotations -import java.util.concurrent.Executor  import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations  @RunWith(AndroidTestingRunner::class)  @SmallTest @@ -331,6 +331,47 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {          assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)      } +    @Test +    fun screenOff_whileFolded_hingeAngleProviderRemainsOff() { +        setFoldState(folded = true) +        assertThat(testHingeAngleProvider.isStarted).isFalse() + +        screenOnStatusProvider.notifyScreenTurningOff() + +        assertThat(testHingeAngleProvider.isStarted).isFalse() +    } + +    @Test +    fun screenOff_whileUnfolded_hingeAngleProviderStops() { +        setFoldState(folded = false) +        assertThat(testHingeAngleProvider.isStarted).isTrue() + +        screenOnStatusProvider.notifyScreenTurningOff() + +        assertThat(testHingeAngleProvider.isStarted).isFalse() +    } + +    @Test +    fun screenOn_whileUnfoldedAndScreenOff_hingeAngleProviderStarted() { +        setFoldState(folded = false) +        screenOnStatusProvider.notifyScreenTurningOff() +        assertThat(testHingeAngleProvider.isStarted).isFalse() + +        screenOnStatusProvider.notifyScreenTurningOn() + +        assertThat(testHingeAngleProvider.isStarted).isTrue() +    } + +    @Test +    fun screenOn_whileFolded_hingeAngleRemainsOff() { +        setFoldState(folded = true) +        assertThat(testHingeAngleProvider.isStarted).isFalse() + +        screenOnStatusProvider.notifyScreenTurningOn() + +        assertThat(testHingeAngleProvider.isStarted).isFalse() +    } +      private fun setupForegroundActivityType(isHomeActivity: Boolean?) {          whenever(activityTypeProvider.isHomeActivity).thenReturn(isHomeActivity)      } @@ -391,6 +432,14 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {          fun notifyScreenTurnedOn() {              callbacks.forEach { it.onScreenTurnedOn() }          } + +        fun notifyScreenTurningOn() { +            callbacks.forEach { it.onScreenTurningOn() } +        } + +        fun notifyScreenTurningOff() { +            callbacks.forEach { it.onScreenTurningOff() } +        }      }      private class TestHingeAngleProvider : HingeAngleProvider { @@ -398,11 +447,11 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {          var isStarted: Boolean = false          override fun start() { -            isStarted = true; +            isStarted = true          }          override fun stop() { -            isStarted = false; +            isStarted = false          }          override fun addCallback(listener: Consumer<Float>) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/FakeSharedPreferencesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/FakeSharedPreferencesTest.kt new file mode 100644 index 000000000000..d886ffdf0e58 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/FakeSharedPreferencesTest.kt @@ -0,0 +1,259 @@ +/* + * 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.util + +import android.content.SharedPreferences +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.eq +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.anyString +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidJUnit4::class) +class FakeSharedPreferencesTest : SysuiTestCase() { + +    @Mock +    private lateinit var listener: SharedPreferences.OnSharedPreferenceChangeListener + +    private lateinit var sharedPreferences: SharedPreferences + +    @Before +    fun setUp() { +        MockitoAnnotations.initMocks(this) + +        sharedPreferences = FakeSharedPreferences() +    } + +    @Test +    fun testGetString_default() { +        val default = "default" +        val result = sharedPreferences.getString("key", default) +        assertThat(result).isEqualTo(default) +    } + +    @Test +    fun testGetStringSet_default() { +        val default = setOf("one", "two") +        val result = sharedPreferences.getStringSet("key", default) +        assertThat(result).isEqualTo(default) +    } + +    @Test +    fun testGetInt_default() { +        val default = 10 +        val result = sharedPreferences.getInt("key", default) +        assertThat(result).isEqualTo(default) +    } + +    @Test +    fun testGetLong_default() { +        val default = 11L +        val result = sharedPreferences.getLong("key", default) +        assertThat(result).isEqualTo(default) +    } + +    @Test +    fun testGetFloat_default() { +        val default = 1.3f +        val result = sharedPreferences.getFloat("key", default) +        assertThat(result).isEqualTo(default) +    } + +    @Test +    fun testGetBoolean_default() { +        val default = true +        val result = sharedPreferences.getBoolean("key", default) +        assertThat(result).isEqualTo(default) +    } + +    @Test +    fun testPutValuesAndRetrieve() { +        val editor = sharedPreferences.edit() +        val data = listOf<Data<*>>( +            Data( +                "keyString", +                "value", +                SharedPreferences.Editor::putString, +                { getString(it, "") } +            ), +            Data( +                "keyStringSet", +                setOf("one", "two"), +                SharedPreferences.Editor::putStringSet, +                { getStringSet(it, emptySet()) } +            ), +            Data("keyInt", 10, SharedPreferences.Editor::putInt, { getInt(it, 0) }), +            Data("keyLong", 11L, SharedPreferences.Editor::putLong, { getLong(it, 0L) }), +            Data( +                "keyFloat", +                1.3f, +                SharedPreferences.Editor::putFloat, +                { getFloat(it, 0f) } +            ), +            Data( +                "keyBoolean", +                true, +                SharedPreferences.Editor::putBoolean, +                { getBoolean(it, false) } +            ) +        ) + +        data.fold(editor) { ed, d -> +            d.set(ed) +        } +        editor.commit() + +        data.forEach { +            assertThat(it.get(sharedPreferences)).isEqualTo(it.value) +        } +    } + +    @Test +    fun testContains() { +        sharedPreferences.edit().putInt("key", 10).commit() + +        assertThat(sharedPreferences.contains("key")).isTrue() +        assertThat(sharedPreferences.contains("other")).isFalse() +    } + +    @Test +    fun testOverwrite() { +        sharedPreferences.edit().putInt("key", 10).commit() +        sharedPreferences.edit().putInt("key", 11).commit() + +        assertThat(sharedPreferences.getInt("key", 0)).isEqualTo(11) +    } + +    @Test +    fun testDeleteString() { +        sharedPreferences.edit().putString("key", "value").commit() +        sharedPreferences.edit().putString("key", null).commit() + +        assertThat(sharedPreferences.contains("key")).isFalse() +    } + +    @Test +    fun testDeleteAndReplaceString() { +        sharedPreferences.edit().putString("key", "value").commit() +        sharedPreferences.edit().putString("key", "other").putString("key", null).commit() + +        assertThat(sharedPreferences.getString("key", "")).isEqualTo("other") +    } + +    @Test +    fun testDeleteStringSet() { +        sharedPreferences.edit().putStringSet("key", setOf("one")).commit() +        sharedPreferences.edit().putStringSet("key", setOf("two")).commit() + +        assertThat(sharedPreferences.getStringSet("key", emptySet())).isEqualTo(setOf("two")) +    } + +    @Test +    fun testClear() { +        sharedPreferences.edit().putInt("keyInt", 1).putString("keyString", "a").commit() +        sharedPreferences.edit().clear().commit() + +        assertThat(sharedPreferences.contains("keyInt")).isFalse() +        assertThat(sharedPreferences.contains("keyString")).isFalse() +    } + +    @Test +    fun testClearAndWrite() { +        sharedPreferences.edit().putInt("key", 10).commit() +        sharedPreferences.edit().putInt("key", 11).clear().commit() + +        assertThat(sharedPreferences.getInt("key", 0)).isEqualTo(11) +    } + +    @Test +    fun testListenerNotifiedOnChanges() { +        sharedPreferences.registerOnSharedPreferenceChangeListener(listener) + +        sharedPreferences.edit().putInt("keyInt", 10).putString("keyString", "value").commit() + +        verify(listener).onSharedPreferenceChanged(sharedPreferences, "keyInt") +        verify(listener).onSharedPreferenceChanged(sharedPreferences, "keyString") +        verifyNoMoreInteractions(listener) +    } + +    @Test +    fun testListenerNotifiedOnClear() { +        sharedPreferences.edit().putInt("keyInt", 10).commit() +        sharedPreferences.registerOnSharedPreferenceChangeListener(listener) + +        sharedPreferences.edit().clear().commit() + +        verify(listener).onSharedPreferenceChanged(sharedPreferences, null) +        verifyNoMoreInteractions(listener) +    } + +    @Test +    fun testListenerNotifiedOnRemoval() { +        sharedPreferences.edit() +            .putString("keyString", "a") +            .putStringSet("keySet", setOf("a")) +            .commit() + +        sharedPreferences.registerOnSharedPreferenceChangeListener(listener) +        sharedPreferences.edit().putString("keyString", null).putStringSet("keySet", null).commit() + +        verify(listener).onSharedPreferenceChanged(sharedPreferences, "keyString") +        verify(listener).onSharedPreferenceChanged(sharedPreferences, "keySet") +        verifyNoMoreInteractions(listener) +    } + +    @Test +    fun testListenerUnregistered() { +        sharedPreferences.registerOnSharedPreferenceChangeListener(listener) +        sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener) +        sharedPreferences.edit().putInt("key", 10).commit() + +        verify(listener, never()).onSharedPreferenceChanged(eq(sharedPreferences), anyString()) +    } + +    @Test +    fun testSharedPreferencesOnlyModifiedOnCommit() { +        sharedPreferences.edit().putInt("key", 10) + +        assertThat(sharedPreferences.contains("key")).isFalse() +    } + +    private data class Data<T>( +        val key: String, +        val value: T, +        private val setter: SharedPreferences.Editor.(String, T) -> SharedPreferences.Editor, +        private val getter: SharedPreferences.(String) -> T +    ) { +        fun set(editor: SharedPreferences.Editor): SharedPreferences.Editor { +            return editor.setter(key, value) +        } + +        fun get(sharedPreferences: SharedPreferences): T { +            return sharedPreferences.getter(key) +        } +    } +} 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/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java index c52ea60f0bfc..c83189dbc616 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java @@ -15,6 +15,8 @@   */  package com.android.systemui; +import static com.android.systemui.animation.FakeDialogLaunchAnimatorKt.fakeDialogLaunchAnimator; +  import static org.mockito.Mockito.mock;  import static org.mockito.Mockito.spy;  import static org.mockito.Mockito.when; @@ -34,6 +36,7 @@ import androidx.test.uiautomator.UiDevice;  import com.android.keyguard.KeyguardUpdateMonitor;  import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.systemui.animation.DialogLaunchAnimator;  import com.android.systemui.broadcast.BroadcastDispatcher;  import com.android.systemui.broadcast.FakeBroadcastDispatcher;  import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger; @@ -119,6 +122,7 @@ public abstract class SysuiTestCase {          // is missing (constructing the actual one would throw).          // TODO(b/219008720): Remove this.          mDependency.injectMockDependency(SystemUIDialogManager.class); +        mDependency.injectTestDependency(DialogLaunchAnimator.class, fakeDialogLaunchAnimator());      }      @After diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogLaunchAnimator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogLaunchAnimator.kt new file mode 100644 index 000000000000..990db77463f6 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogLaunchAnimator.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.animation + +import com.android.internal.jank.InteractionJankMonitor +import org.mockito.Mockito.mock + +/** A [DialogLaunchAnimator] to be used in tests. */ +@JvmOverloads +fun fakeDialogLaunchAnimator( +    isUnlocked: Boolean = true, +    isShowingAlternateAuthOnUnlock: Boolean = false, +    interactionJankMonitor: InteractionJankMonitor = mock(InteractionJankMonitor::class.java), +): DialogLaunchAnimator { +    return DialogLaunchAnimator( +        FakeCallback( +            isUnlocked = isUnlocked, +            isShowingAlternateAuthOnUnlock = isShowingAlternateAuthOnUnlock, +        ), +        interactionJankMonitor, +        fakeLaunchAnimator(), +        isForTesting = true, +    ) +} + +private class FakeCallback( +    private val isDreaming: Boolean = false, +    private val isUnlocked: Boolean = true, +    private val isShowingAlternateAuthOnUnlock: Boolean = false, +) : DialogLaunchAnimator.Callback { +    override fun isDreaming(): Boolean = isDreaming +    override fun isUnlocked(): Boolean = isUnlocked +    override fun isShowingAlternateAuthOnUnlock() = isShowingAlternateAuthOnUnlock +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeLaunchAnimator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeLaunchAnimator.kt new file mode 100644 index 000000000000..5b431e72e2ac --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeLaunchAnimator.kt @@ -0,0 +1,44 @@ +/* + * 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.animation + +/** A [LaunchAnimator] to be used in tests. */ +fun fakeLaunchAnimator(): LaunchAnimator { +    return LaunchAnimator(TEST_TIMINGS, TEST_INTERPOLATORS) +} + +/** + * A [LaunchAnimator.Timings] to be used in tests. + * + * Note that all timings except the total duration are non-zero to avoid divide-by-zero exceptions + * when computing the progress of a sub-animation (the contents fade in/out). + */ +private val TEST_TIMINGS = +    LaunchAnimator.Timings( +        totalDuration = 0L, +        contentBeforeFadeOutDelay = 1L, +        contentBeforeFadeOutDuration = 1L, +        contentAfterFadeInDelay = 1L, +        contentAfterFadeInDuration = 1L +    ) + +/** A [LaunchAnimator.Interpolators] to be used in tests. */ +private val TEST_INTERPOLATORS = +    LaunchAnimator.Interpolators( +        positionInterpolator = Interpolators.STANDARD, +        positionXInterpolator = Interpolators.STANDARD, +        contentBeforeFadeOutInterpolator = Interpolators.STANDARD, +        contentAfterFadeInInterpolator = Interpolators.STANDARD +    ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSharedPreferences.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSharedPreferences.kt new file mode 100644 index 000000000000..4a881a7ce898 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSharedPreferences.kt @@ -0,0 +1,164 @@ +/* + * 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.util + +import android.content.SharedPreferences + +/** + * Fake [SharedPreferences] to use within tests + * + * This will act in the same way as a real one for a particular file, but will store all the + * data in memory in the instance. + * + * [SharedPreferences.Editor.apply] and [SharedPreferences.Editor.commit] both act in the same way, + * synchronously modifying the stored data. Listeners are dispatched in the same thread, also + * synchronously. + */ +class FakeSharedPreferences : SharedPreferences { +    private val data = mutableMapOf<String, Any>() +    private val listeners = mutableSetOf<SharedPreferences.OnSharedPreferenceChangeListener>() + +    override fun getAll(): Map<String, *> { +        return data +    } + +    override fun getString(key: String, defValue: String?): String? { +        return data.getOrDefault(key, defValue) as? String? +    } + +    override fun getStringSet(key: String, defValues: MutableSet<String>?): MutableSet<String>? { +        return data.getOrDefault(key, defValues) as? MutableSet<String>? +    } + +    override fun getInt(key: String, defValue: Int): Int { +        return data.getOrDefault(key, defValue) as Int +    } + +    override fun getLong(key: String, defValue: Long): Long { +        return data.getOrDefault(key, defValue) as Long +    } + +    override fun getFloat(key: String, defValue: Float): Float { +        return data.getOrDefault(key, defValue) as Float +    } + +    override fun getBoolean(key: String, defValue: Boolean): Boolean { +        return data.getOrDefault(key, defValue) as Boolean +    } + +    override fun contains(key: String): Boolean { +        return key in data +    } + +    override fun edit(): SharedPreferences.Editor { +        return Editor() +    } + +    override fun registerOnSharedPreferenceChangeListener( +        listener: SharedPreferences.OnSharedPreferenceChangeListener +    ) { +        listeners.add(listener) +    } + +    override fun unregisterOnSharedPreferenceChangeListener( +        listener: SharedPreferences.OnSharedPreferenceChangeListener +    ) { +        listeners.remove(listener) +    } + +    private inner class Editor : SharedPreferences.Editor { + +        private var clear = false +        private val changes = mutableMapOf<String, Any>() +        private val removals = mutableSetOf<String>() + +        override fun putString(key: String, value: String?): SharedPreferences.Editor { +            if (value != null) { +                changes[key] = value +            } else { +                removals.add(key) +            } +            return this +        } + +        override fun putStringSet( +            key: String, +            values: MutableSet<String>? +        ): SharedPreferences.Editor { +            if (values != null) { +                changes[key] = values +            } else { +                removals.add(key) +            } +            return this +        } + +        override fun putInt(key: String, value: Int): SharedPreferences.Editor { +            changes[key] = value +            return this +        } + +        override fun putLong(key: String, value: Long): SharedPreferences.Editor { +            changes[key] = value +            return this +        } + +        override fun putFloat(key: String, value: Float): SharedPreferences.Editor { +            changes[key] = value +            return this +        } + +        override fun putBoolean(key: String, value: Boolean): SharedPreferences.Editor { +            changes[key] = value +            return this +        } + +        override fun remove(key: String): SharedPreferences.Editor { +            removals.add(key) +            return this +        } + +        override fun clear(): SharedPreferences.Editor { +            clear = true +            return this +        } + +        override fun commit(): Boolean { +            if (clear) { +                data.clear() +            } +            removals.forEach { data.remove(it) } +            data.putAll(changes) +            val keys = removals + data.keys +            if (clear || removals.isNotEmpty() || data.isNotEmpty()) { +                listeners.forEach { listener -> +                    if (clear) { +                        listener.onSharedPreferenceChanged(this@FakeSharedPreferences, null) +                    } +                    keys.forEach { +                        listener.onSharedPreferenceChanged(this@FakeSharedPreferences, it) +                    } +                } +            } +            return true +        } + +        override fun apply() { +            commit() +        } +    } +} diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt index e8038fd7dfa6..19cfc805d17b 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt @@ -65,6 +65,7 @@ constructor(      private val halfOpenedTimeoutMillis: Int = config.halfFoldedTimeoutMillis      private var isFolded = false +    private var isScreenOn = false      private var isUnfoldHandled = true      override fun start() { @@ -198,6 +199,25 @@ constructor(                  isUnfoldHandled = true              }          } + +        override fun onScreenTurningOn() { +            isScreenOn = true +            updateHingeAngleProviderState() +        } + +        override fun onScreenTurningOff() { +            isScreenOn = false +            updateHingeAngleProviderState() +        } +    } + +    /** While the screen is off or the device is folded, hinge angle updates are not needed. */ +    private fun updateHingeAngleProviderState() { +        if (isScreenOn && !isFolded) { +            hingeAngleProvider.start() +        } else { +            hingeAngleProvider.stop() +        }      }      private inner class HingeAngleListener : Consumer<Float> { diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt index 3fc5d610dc2d..577137ca12f3 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt @@ -30,8 +30,10 @@ internal class HingeSensorAngleProvider(      private val sensorListener = HingeAngleSensorListener()      private val listeners: MutableList<Consumer<Float>> = arrayListOf() +    var started = false      override fun start() = executor.execute { +        if (started) return@execute          Trace.beginSection("HingeSensorAngleProvider#start")          val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_HINGE_ANGLE)          sensorManager.registerListener( @@ -40,10 +42,13 @@ internal class HingeSensorAngleProvider(              SensorManager.SENSOR_DELAY_FASTEST          )          Trace.endSection() +        started = true      }      override fun stop() = executor.execute { +        if (!started) return@execute          sensorManager.unregisterListener(sensorListener) +        started = false      }      override fun removeCallback(listener: Consumer<Float>) { diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt index d95e050474de..f09b53dc8436 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt @@ -25,5 +25,15 @@ interface ScreenStatusProvider : CallbackController<ScreenListener> {           * Called when the screen is on and ready (windows are drawn and screen blocker is removed)           */          fun onScreenTurnedOn() + +        /** +         * Called when the screen is starting to be turned off. +         */ +        fun onScreenTurningOff() + +        /** +         * Called when the screen is starting to be turned on. +         */ +        fun onScreenTurningOn()      }  } 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/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 5eec6e58e925..9a98f545d8d0 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -370,6 +370,10 @@ class StorageManagerService extends IStorageManager.Stub      private static final float DEFAULT_LOW_BATTERY_LEVEL = 20F;      // Decide whether charging is required to turn on the feature      private static final boolean DEFAULT_CHARGING_REQUIRED = true; +    // Minimum GC interval sleep time in ms +    private static final int DEFAULT_MIN_GC_SLEEPTIME = 10000; +    // Target dirty segment ratio to aim to +    private static final int DEFAULT_TARGET_DIRTY_RATIO = 80;      private volatile int mLifetimePercentThreshold;      private volatile int mMinSegmentsThreshold; @@ -377,6 +381,8 @@ class StorageManagerService extends IStorageManager.Stub      private volatile float mSegmentReclaimWeight;      private volatile float mLowBatteryLevel;      private volatile boolean mChargingRequired; +    private volatile int mMinGCSleepTime; +    private volatile int mTargetDirtyRatio;      private volatile boolean mNeedGC;      private volatile boolean mPassedLifetimeThresh; @@ -2712,6 +2718,10 @@ class StorageManagerService extends IStorageManager.Stub                  "low_battery_level", DEFAULT_LOW_BATTERY_LEVEL);              mChargingRequired = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,                  "charging_required", DEFAULT_CHARGING_REQUIRED); +            mMinGCSleepTime = DeviceConfig.getInt(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, +                "min_gc_sleeptime", DEFAULT_MIN_GC_SLEEPTIME); +            mTargetDirtyRatio = DeviceConfig.getInt(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, +                "target_dirty_ratio", DEFAULT_TARGET_DIRTY_RATIO);              // If we use the smart idle maintenance, we need to turn off GC in the traditional idle              // maintenance to avoid the conflict @@ -2829,6 +2839,14 @@ class StorageManagerService extends IStorageManager.Stub          enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);          try { +            int latestWrite = mVold.getWriteAmount(); +            if (latestWrite == -1) { +                Slog.w(TAG, "Failed to get storage write record"); +                return; +            } + +            updateStorageWriteRecords(latestWrite); +              // Block based checkpoint process runs fstrim. So, if checkpoint is in progress              // (first boot after OTA), We skip the smart idle maintenance              if (!needsCheckpoint() || !supportsBlockCheckpoint()) { @@ -2836,13 +2854,6 @@ class StorageManagerService extends IStorageManager.Stub                      return;                  } -                int latestWrite = mVold.getWriteAmount(); -                if (latestWrite == -1) { -                    Slog.w(TAG, "Failed to get storage write record"); -                    return; -                } - -                updateStorageWriteRecords(latestWrite);                  int avgWriteAmount = getAverageWriteAmount();                  Slog.i(TAG, "Set smart idle maintenance: " + "latest write amount: " + @@ -2850,9 +2861,12 @@ class StorageManagerService extends IStorageManager.Stub                              ", min segment threshold: " + mMinSegmentsThreshold +                              ", dirty reclaim rate: " + mDirtyReclaimRate +                              ", segment reclaim weight: " + mSegmentReclaimWeight + -                            ", period: " + sSmartIdleMaintPeriod); +                            ", period(min): " + sSmartIdleMaintPeriod + +                            ", min gc sleep time(ms): " + mMinGCSleepTime + +                            ", target dirty ratio: " + mTargetDirtyRatio);                  mVold.setGCUrgentPace(avgWriteAmount, mMinSegmentsThreshold, mDirtyReclaimRate, -                                      mSegmentReclaimWeight, sSmartIdleMaintPeriod); +                                      mSegmentReclaimWeight, sSmartIdleMaintPeriod, +                                      mMinGCSleepTime, mTargetDirtyRatio);              } else {                  Slog.i(TAG, "Skipping smart idle maintenance - block based checkpoint in progress");              } 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/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index b5aa7b14792b..74ee6800eb63 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -33,6 +33,7 @@ import android.view.DisplayAddress;  import com.android.internal.R;  import com.android.internal.annotations.VisibleForTesting;  import com.android.internal.display.BrightnessSynchronizer; +import com.android.server.display.config.AutoBrightness;  import com.android.server.display.config.BrightnessThresholds;  import com.android.server.display.config.BrightnessThrottlingMap;  import com.android.server.display.config.BrightnessThrottlingPoint; @@ -69,9 +70,8 @@ import java.util.List;  import javax.xml.datatype.DatatypeConfigurationException;  /** - *  Reads and stores display-specific configurations. - *  File format: - *  <pre> + * Reads and stores display-specific configurations. File format: + * <pre>   *  {@code   *    <displayConfiguration>   *      <densityMapping> @@ -147,6 +147,15 @@ import javax.xml.datatype.DatatypeConfigurationException;   *       <quirk>canSetBrightnessViaHwc</quirk>   *      </quirks>   * + *      <autoBrightness> + *           <brighteningLightDebounceMillis> + *              2000 + *           </brighteningLightDebounceMillis> + *          <darkeningLightDebounceMillis> + *              1000 + *          </darkeningLightDebounceMillis> + *      </autoBrightness> + *   *      <screenBrightnessRampFastDecrease>0.01</screenBrightnessRampFastDecrease>   *      <screenBrightnessRampFastIncrease>0.02</screenBrightnessRampFastIncrease>   *      <screenBrightnessRampSlowDecrease>0.03</screenBrightnessRampSlowDecrease> @@ -224,6 +233,9 @@ public class DisplayDeviceConfig {      // Length of the ambient light horizon used to calculate short-term estimate of ambient light.      private static final int AMBIENT_LIGHT_SHORT_HORIZON_MILLIS = 2000; +    // Invalid value of AutoBrightness brightening and darkening light debounce +    private static final int INVALID_AUTO_BRIGHTNESS_LIGHT_DEBOUNCE = -1; +      @VisibleForTesting      static final float HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT = 0.5f; @@ -281,6 +293,14 @@ public class DisplayDeviceConfig {      private String mLoadedFrom = null;      private Spline mSdrToHdrRatioSpline; +    // Represents the auto-brightness brightening light debounce. +    private long mAutoBrightnessBrighteningLightDebounce = +            INVALID_AUTO_BRIGHTNESS_LIGHT_DEBOUNCE; + +    // Represents the auto-brightness darkening light debounce. +    private long mAutoBrightnessDarkeningLightDebounce = +            INVALID_AUTO_BRIGHTNESS_LIGHT_DEBOUNCE; +      // Brightness Throttling data may be updated via the DeviceConfig. Here we store the original      // data, which comes from the ddc, and the current one, which may be the DeviceConfig      // overwritten value. @@ -293,8 +313,8 @@ public class DisplayDeviceConfig {      }      /** -     * Creates an instance for the specified display. -     * Tries to find a file with identifier in the following priority order: +     * Creates an instance for the specified display. Tries to find a file with identifier in the +     * following priority order:       * <ol>       *     <li>physicalDisplayId</li>       *     <li>physicalDisplayId without a stable flag (old system)</li> @@ -314,11 +334,12 @@ public class DisplayDeviceConfig {      }      /** -     * Creates an instance using global values since no display device config xml exists. -     * Uses values from config or PowerManager. +     * Creates an instance using global values since no display device config xml exists. Uses +     * values from config or PowerManager.       * -     * @param context -     * @param useConfigXml +     * @param context      The context from which the DisplayDeviceConfig is to be constructed. +     * @param useConfigXml A flag indicating if values are to be loaded from the configuration file, +     *                     or the default values.       * @return A configuration instance.       */      public static DisplayDeviceConfig create(Context context, boolean useConfigXml) { @@ -450,8 +471,8 @@ public class DisplayDeviceConfig {      }      /** -     * Calculates the backlight value, as recognised by the HAL, from the brightness value -     * given that the rest of the system deals with. +     * Calculates the backlight value, as recognised by the HAL, from the brightness value given +     * that the rest of the system deals with.       *       * @param brightness value on the framework scale of 0-1       * @return backlight value on the HAL scale of 0-1 @@ -502,13 +523,13 @@ public class DisplayDeviceConfig {          if (DEBUG) {              Slog.d(TAG, "getHdrBrightnessFromSdr: sdr brightness " + brightness -                + " backlight " + backlight -                + " nits " + nits -                + " ratio " + ratio -                + " hdrNits " + hdrNits -                + " hdrBacklight " + hdrBacklight -                + " hdrBrightness " + hdrBrightness -                ); +                    + " backlight " + backlight +                    + " nits " + nits +                    + " ratio " + ratio +                    + " hdrNits " + hdrNits +                    + " hdrBacklight " + hdrBacklight +                    + " hdrBrightness " + hdrBrightness +            );          }          return hdrBrightness;      } @@ -590,8 +611,8 @@ public class DisplayDeviceConfig {      /**       * @param quirkValue The quirk to test. -     * @return {@code true} if the specified quirk is present in this configuration, -     * {@code false} otherwise. +     * @return {@code true} if the specified quirk is present in this configuration, {@code false} +     * otherwise.       */      public boolean hasQuirk(String quirkValue) {          return mQuirks != null && mQuirks.contains(quirkValue); @@ -625,6 +646,20 @@ public class DisplayDeviceConfig {          return BrightnessThrottlingData.create(mBrightnessThrottlingData);      } +    /** +     * @return Auto brightness darkening light debounce +     */ +    public long getAutoBrightnessDarkeningLightDebounce() { +        return mAutoBrightnessDarkeningLightDebounce; +    } + +    /** +     * @return Auto brightness brightening light debounce +     */ +    public long getAutoBrightnessBrighteningLightDebounce() { +        return mAutoBrightnessBrighteningLightDebounce; +    } +      @Override      public String toString() {          return "DisplayDeviceConfig{" @@ -663,6 +698,10 @@ public class DisplayDeviceConfig {                  + ", mProximitySensor=" + mProximitySensor                  + ", mRefreshRateLimitations= " + Arrays.toString(mRefreshRateLimitations.toArray())                  + ", mDensityMapping= " + mDensityMapping +                + ", mAutoBrightnessBrighteningLightDebounce= " +                + mAutoBrightnessBrighteningLightDebounce +                + ", mAutoBrightnessDarkeningLightDebounce= " +                + mAutoBrightnessDarkeningLightDebounce                  + "}";      } @@ -719,6 +758,7 @@ public class DisplayDeviceConfig {                  loadProxSensorFromDdc(config);                  loadAmbientHorizonFromDdc(config);                  loadBrightnessChangeThresholds(config); +                loadAutoBrightnessConfigValues(config);              } else {                  Slog.w(TAG, "DisplayDeviceConfig file is null");              } @@ -899,8 +939,8 @@ public class DisplayDeviceConfig {              if (i > 0) {                  if (nits[i] < nits[i - 1]) {                      Slog.e(TAG, "sdrHdrRatioMap must be non-decreasing, ignoring rest " -                                + " of configuration. nits: " + nits[i] + " < " -                                + nits[i - 1]); +                            + " of configuration. nits: " + nits[i] + " < " +                            + nits[i - 1]);                      return null;                  }              } @@ -927,7 +967,7 @@ public class DisplayDeviceConfig {          final List<BrightnessThrottlingPoint> points = map.getBrightnessThrottlingPoint();          // At least 1 point is guaranteed by the display device config schema          List<BrightnessThrottlingData.ThrottlingLevel> throttlingLevels = -            new ArrayList<>(points.size()); +                new ArrayList<>(points.size());          boolean badConfig = false;          for (BrightnessThrottlingPoint point : points) { @@ -938,7 +978,7 @@ public class DisplayDeviceConfig {              }              throttlingLevels.add(new BrightnessThrottlingData.ThrottlingLevel( -                convertThermalStatus(status), point.getBrightness().floatValue())); +                    convertThermalStatus(status), point.getBrightness().floatValue()));          }          if (!badConfig) { @@ -947,6 +987,41 @@ public class DisplayDeviceConfig {          }      } +    private void loadAutoBrightnessConfigValues(DisplayConfiguration config) { +        loadAutoBrightnessBrighteningLightDebounce(config.getAutoBrightness()); +        loadAutoBrightnessDarkeningLightDebounce(config.getAutoBrightness()); +    } + +    /** +     * Loads the auto-brightness brightening light debounce. Internally, this takes care of loading +     * the value from the display config, and if not present, falls back to config.xml. +     */ +    private void loadAutoBrightnessBrighteningLightDebounce(AutoBrightness autoBrightnessConfig) { +        if (autoBrightnessConfig == null +                || autoBrightnessConfig.getBrighteningLightDebounceMillis() == null) { +            mAutoBrightnessBrighteningLightDebounce = mContext.getResources().getInteger( +                    com.android.internal.R.integer.config_autoBrightnessBrighteningLightDebounce); +        } else { +            mAutoBrightnessBrighteningLightDebounce = +                    autoBrightnessConfig.getBrighteningLightDebounceMillis().intValue(); +        } +    } + +    /** +     * Loads the auto-brightness darkening light debounce. Internally, this takes care of loading +     * the value from the display config, and if not present, falls back to config.xml. +     */ +    private void loadAutoBrightnessDarkeningLightDebounce(AutoBrightness autoBrightnessConfig) { +        if (autoBrightnessConfig == null +                || autoBrightnessConfig.getDarkeningLightDebounceMillis() == null) { +            mAutoBrightnessDarkeningLightDebounce = mContext.getResources().getInteger( +                    com.android.internal.R.integer.config_autoBrightnessDarkeningLightDebounce); +        } else { +            mAutoBrightnessDarkeningLightDebounce = +                    autoBrightnessConfig.getDarkeningLightDebounceMillis().intValue(); +        } +    } +      private void loadBrightnessMapFromConfigXml() {          // Use the config.xml mapping          final Resources res = mContext.getResources(); @@ -1058,17 +1133,17 @@ public class DisplayDeviceConfig {                      PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, mBacklight[i]);          }          mBrightnessToBacklightSpline = mInterpolationType == INTERPOLATION_LINEAR -            ? Spline.createLinearSpline(mBrightness, mBacklight) -            : Spline.createSpline(mBrightness, mBacklight); +                ? Spline.createLinearSpline(mBrightness, mBacklight) +                : Spline.createSpline(mBrightness, mBacklight);          mBacklightToBrightnessSpline = mInterpolationType == INTERPOLATION_LINEAR -            ? Spline.createLinearSpline(mBacklight, mBrightness) -            : Spline.createSpline(mBacklight, mBrightness); +                ? Spline.createLinearSpline(mBacklight, mBrightness) +                : Spline.createSpline(mBacklight, mBrightness);          mBacklightToNitsSpline = mInterpolationType == INTERPOLATION_LINEAR -            ? Spline.createLinearSpline(mBacklight, mNits) -            : Spline.createSpline(mBacklight, mNits); +                ? Spline.createLinearSpline(mBacklight, mNits) +                : Spline.createSpline(mBacklight, mNits);          mNitsToBacklightSpline = mInterpolationType == INTERPOLATION_LINEAR -            ? Spline.createLinearSpline(mNits, mBacklight) -            : Spline.createSpline(mNits, mBacklight); +                ? Spline.createLinearSpline(mNits, mBacklight) +                : Spline.createSpline(mNits, mBacklight);      }      private void loadQuirks(DisplayConfiguration config) { @@ -1111,7 +1186,7 @@ public class DisplayDeviceConfig {                  if (mHbmData.minimumHdrPercentOfScreen > 1                          || mHbmData.minimumHdrPercentOfScreen < 0) {                      Slog.w(TAG, "Invalid minimum HDR percent of screen: " -                                    + String.valueOf(mHbmData.minimumHdrPercentOfScreen)); +                            + String.valueOf(mHbmData.minimumHdrPercentOfScreen));                      mHbmData.minimumHdrPercentOfScreen = HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT;                  }              } else { @@ -1235,7 +1310,7 @@ public class DisplayDeviceConfig {                      ambientBrightnessThresholds.getDarkeningThresholds();              final BigDecimal ambientBrighteningThreshold = brighteningAmbientLux.getMinimum(); -            final BigDecimal ambientDarkeningThreshold =  darkeningAmbientLux.getMinimum(); +            final BigDecimal ambientDarkeningThreshold = darkeningAmbientLux.getMinimum();              if (ambientBrighteningThreshold != null) {                  mAmbientLuxBrighteningMinThreshold = ambientBrighteningThreshold.floatValue(); @@ -1330,8 +1405,8 @@ public class DisplayDeviceConfig {          }          /** -         * @return True if the sensor matches both the specified name and type, or one if only -         * one is specified (not-empty). Always returns false if both parameters are null or empty. +         * @return True if the sensor matches both the specified name and type, or one if only one +         * is specified (not-empty). Always returns false if both parameters are null or empty.           */          public boolean matches(String sensorName, String sensorType) {              final boolean isNameSpecified = !TextUtils.isEmpty(sensorName); @@ -1446,6 +1521,7 @@ public class DisplayDeviceConfig {                  return otherThrottlingLevel.thermalStatus == this.thermalStatus                          && otherThrottlingLevel.brightness == this.brightness;              } +              @Override              public int hashCode() {                  int result = 1; @@ -1455,8 +1531,11 @@ public class DisplayDeviceConfig {              }          } -        static public BrightnessThrottlingData create(List<ThrottlingLevel> throttlingLevels) -        { + +        /** +         * Creates multiple teperature based throttling levels of brightness +         */ +        public static BrightnessThrottlingData create(List<ThrottlingLevel> throttlingLevels) {              if (throttlingLevels == null || throttlingLevels.size() == 0) {                  Slog.e(TAG, "BrightnessThrottlingData received null or empty throttling levels");                  return null; @@ -1498,8 +1577,9 @@ public class DisplayDeviceConfig {          }          static public BrightnessThrottlingData create(BrightnessThrottlingData other) { -            if (other == null) +            if (other == null) {                  return null; +            }              return BrightnessThrottlingData.create(other.throttlingLevels);          } @@ -1508,8 +1588,8 @@ public class DisplayDeviceConfig {          @Override          public String toString() {              return "BrightnessThrottlingData{" -                + "throttlingLevels:" + throttlingLevels -                + "} "; +                    + "throttlingLevels:" + throttlingLevels +                    + "} ";          }          @Override diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 8781a8d9b075..75ee447cdf49 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -985,10 +985,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call                      screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels,                      screenDarkeningMinThreshold, screenBrighteningMinThreshold); -            long brighteningLightDebounce = resources.getInteger( -                    com.android.internal.R.integer.config_autoBrightnessBrighteningLightDebounce); -            long darkeningLightDebounce = resources.getInteger( -                    com.android.internal.R.integer.config_autoBrightnessDarkeningLightDebounce); +            long brighteningLightDebounce = mDisplayDeviceConfig +                    .getAutoBrightnessBrighteningLightDebounce(); +            long darkeningLightDebounce = mDisplayDeviceConfig +                    .getAutoBrightnessDarkeningLightDebounce();              boolean autoBrightnessResetAmbientLuxAfterWarmUp = resources.getBoolean(                      com.android.internal.R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp); @@ -1204,6 +1204,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call          }          assert(state != Display.STATE_UNKNOWN); +        boolean skipRampBecauseOfProximityChangeToNegative = false;          // Apply the proximity sensor.          if (mProximitySensor != null) {              if (mPowerRequest.useProximitySensor && state != Display.STATE_OFF) { @@ -1241,6 +1242,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call                  // the screen back on.  Also turn it back on if we've been asked to ignore the                  // prox sensor temporarily.                  mScreenOffBecauseOfProximity = false; +                skipRampBecauseOfProximityChangeToNegative = true;                  sendOnProximityNegativeWithWakelock();              }          } else { @@ -1523,8 +1525,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call              final boolean wasOrWillBeInVr =                      (state == Display.STATE_VR || oldState == Display.STATE_VR); -            final boolean initialRampSkip = -                    state == Display.STATE_ON && mSkipRampState != RAMP_STATE_SKIP_NONE; +            final boolean initialRampSkip = (state == Display.STATE_ON && mSkipRampState +                    != RAMP_STATE_SKIP_NONE) || skipRampBecauseOfProximityChangeToNegative;              // While dozing, sometimes the brightness is split into buckets. Rather than animating              // through the buckets, which is unlikely to be smooth in the first place, just jump              // right to the suggested brightness. 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/pm/pkg/component/InstallConstraintsTagParser.java b/services/core/java/com/android/server/pm/pkg/component/InstallConstraintsTagParser.java new file mode 100644 index 000000000000..14976848fe61 --- /dev/null +++ b/services/core/java/com/android/server/pm/pkg/component/InstallConstraintsTagParser.java @@ -0,0 +1,131 @@ +/* + * 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.pkg.component; + + +import android.content.pm.parsing.result.ParseInput; +import android.content.pm.parsing.result.ParseResult; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.os.Build; +import android.util.ArraySet; + +import com.android.internal.R; +import com.android.server.SystemConfig; +import com.android.server.pm.pkg.parsing.ParsingPackage; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.Set; + +/** + * Utility methods for handling the tag {@code <install-constraints/>} + * + * @hide + */ +public class InstallConstraintsTagParser { + +    private static final String TAG_FINGERPRINT_PREFIX = "fingerprint-prefix"; + +    /** +     * @hide +     */ +    public static ParseResult<ParsingPackage> parseInstallConstraints( +            ParseInput input, ParsingPackage pkg, Resources res, XmlResourceParser parser) +            throws XmlPullParserException, IOException { +        Set<String> allowlist = SystemConfig.getInstance().getInstallConstraintsAllowlist(); +        if (!allowlist.contains(pkg.getPackageName())) { +            return input.skip("install-constraints cannot be used by this package"); +        } + +        ParseResult<Set<String>> prefixes = parseFingerprintPrefixes(input, res, parser); +        if (prefixes.isSuccess()) { +            if (validateFingerprintPrefixes(prefixes.getResult())) { +                return input.success(pkg); +            } else { +                return input.skip( +                        "Install of this package is restricted on this device; device fingerprint" +                                + " does not start with one of the allowed prefixes"); +            } +        } +        return input.skip(prefixes.getErrorMessage()); +    } + +    private static ParseResult<Set<String>> parseFingerprintPrefixes( +            ParseInput input, Resources res, XmlResourceParser parser) +            throws XmlPullParserException, IOException { +        Set<String> prefixes = new ArraySet<>(); +        int type; +        while (true) { +            // move to the tag that contains the next prefix +            type = parser.next(); +            if (type == XmlPullParser.END_TAG) { +                if (prefixes.size() == 0) { +                    return input.error("install-constraints must contain at least one constraint"); +                } +                return input.success(prefixes); +            } else if (type == XmlPullParser.START_TAG) { +                if (parser.getName().equals(TAG_FINGERPRINT_PREFIX)) { +                    ParseResult<String> parsedPrefix = +                            readFingerprintPrefixValue(input, res, parser); +                    if (parsedPrefix.isSuccess()) { +                        prefixes.add(parsedPrefix.getResult()); +                    } else { +                        return input.error(parsedPrefix.getErrorMessage()); +                    } +                } else { +                    return input.error("Unexpected tag: " + parser.getName()); +                } + +                // consume the end tag of this attribute +                type = parser.next(); +                if (type != XmlPullParser.END_TAG) { +                    return input.error("Expected end tag; instead got " + type); +                } +            } +        } +    } + +    private static ParseResult<String> readFingerprintPrefixValue(ParseInput input, Resources res, +            XmlResourceParser parser) { +        TypedArray sa = res.obtainAttributes(parser, +                R.styleable.AndroidManifestInstallConstraintsFingerprintPrefix); +        try { +            String value = sa.getString( +                    R.styleable.AndroidManifestInstallConstraintsFingerprintPrefix_value); +            if (value == null) { +                return input.error("Failed to specify prefix value"); +            } +            return input.success(value); +        } finally { +            sa.recycle(); +        } +    } + +    private static boolean validateFingerprintPrefixes(Set<String> prefixes) { +        String fingerprint = Build.FINGERPRINT; +        for (String prefix : prefixes) { +            if (fingerprint.startsWith(prefix)) { +                return true; +            } +        } +        return false; +    } +} diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java index 9bfb40fe11f7..7ce7f7ebf6cc 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java @@ -95,6 +95,7 @@ import com.android.server.pm.SharedUidMigration;  import com.android.server.pm.permission.CompatibilityPermissionInfo;  import com.android.server.pm.pkg.component.ComponentMutateUtils;  import com.android.server.pm.pkg.component.ComponentParseUtils; +import com.android.server.pm.pkg.component.InstallConstraintsTagParser;  import com.android.server.pm.pkg.component.ParsedActivity;  import com.android.server.pm.pkg.component.ParsedActivityUtils;  import com.android.server.pm.pkg.component.ParsedApexSystemService; @@ -169,9 +170,11 @@ public class ParsingPackageUtils {      public static final String TAG_ADOPT_PERMISSIONS = "adopt-permissions";      public static final String TAG_APPLICATION = "application"; +    public static final String TAG_ATTRIBUTION = "attribution";      public static final String TAG_COMPATIBLE_SCREENS = "compatible-screens";      public static final String TAG_EAT_COMMENT = "eat-comment";      public static final String TAG_FEATURE_GROUP = "feature-group"; +    public static final String TAG_INSTALL_CONSTRAINTS = "install-constraints";      public static final String TAG_INSTRUMENTATION = "instrumentation";      public static final String TAG_KEY_SETS = "key-sets";      public static final String TAG_MANIFEST = "manifest"; @@ -179,15 +182,16 @@ public class ParsingPackageUtils {      public static final String TAG_OVERLAY = "overlay";      public static final String TAG_PACKAGE = "package";      public static final String TAG_PACKAGE_VERIFIER = "package-verifier"; -    public static final String TAG_ATTRIBUTION = "attribution";      public static final String TAG_PERMISSION = "permission";      public static final String TAG_PERMISSION_GROUP = "permission-group";      public static final String TAG_PERMISSION_TREE = "permission-tree"; +    public static final String TAG_PROFILEABLE = "profileable";      public static final String TAG_PROTECTED_BROADCAST = "protected-broadcast";      public static final String TAG_QUERIES = "queries"; +    public static final String TAG_RECEIVER = "receiver";      public static final String TAG_RESTRICT_UPDATE = "restrict-update"; -    public static final String TAG_SUPPORT_SCREENS = "supports-screens";      public static final String TAG_SUPPORTS_INPUT = "supports-input"; +    public static final String TAG_SUPPORT_SCREENS = "supports-screens";      public static final String TAG_USES_CONFIGURATION = "uses-configuration";      public static final String TAG_USES_FEATURE = "uses-feature";      public static final String TAG_USES_GL_TEXTURE = "uses-gl-texture"; @@ -196,8 +200,6 @@ public class ParsingPackageUtils {      public static final String TAG_USES_PERMISSION_SDK_M = "uses-permission-sdk-m";      public static final String TAG_USES_SDK = "uses-sdk";      public static final String TAG_USES_SPLIT = "uses-split"; -    public static final String TAG_PROFILEABLE = "profileable"; -    public static final String TAG_RECEIVER = "receiver";      public static final String METADATA_MAX_ASPECT_RATIO = "android.max_aspect";      public static final String METADATA_SUPPORTS_SIZE_CHANGES = "android.supports_size_changes"; @@ -1040,6 +1042,8 @@ public class ParsingPackageUtils {                  return input.success(pkg);              case TAG_RESTRICT_UPDATE:                  return parseRestrictUpdateHash(flags, input, pkg, res, parser); +            case TAG_INSTALL_CONSTRAINTS: +                return parseInstallConstraints(input, pkg, res, parser);              case TAG_QUERIES:                  return parseQueries(input, pkg, res, parser);              default: @@ -1729,6 +1733,12 @@ public class ParsingPackageUtils {          return input.success(pkg);      } +    private static ParseResult<ParsingPackage> parseInstallConstraints( +            ParseInput input, ParsingPackage pkg, Resources res, XmlResourceParser parser) +            throws IOException, XmlPullParserException { +        return InstallConstraintsTagParser.parseInstallConstraints(input, pkg, res, parser); +    } +      private static ParseResult<ParsingPackage> parseQueries(ParseInput input, ParsingPackage pkg,              Resources res, XmlResourceParser parser) throws IOException, XmlPullParserException {          final int depth = parser.getDepth(); diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 25d187fbf86f..07694065cbd7 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -1725,16 +1725,6 @@ public class DisplayPolicy {                              win.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS,                              new Rect(win.getFrame())));                      mStatusBarColorCheckedBounds.union(sTmpRect); -                    // Check if current activity is letterboxed in order create a LetterboxDetails -                    // component to be passed to SysUI for status bar treatment -                    final ActivityRecord currentActivity = win.getActivityRecord(); -                    if (currentActivity != null) { -                        final LetterboxDetails currentLetterboxDetails = currentActivity -                                .mLetterboxUiController.getLetterboxDetails(); -                        if (currentLetterboxDetails != null) { -                            mLetterboxDetails.add(currentLetterboxDetails); -                        } -                    }                  }              } @@ -1752,6 +1742,17 @@ public class DisplayPolicy {                      mNavBarBackgroundWindow = win;                  }              } + +            // Check if current activity is letterboxed in order create a LetterboxDetails +            // component to be passed to SysUI for status bar treatment +            final ActivityRecord currentActivity = win.getActivityRecord(); +            if (currentActivity != null) { +                final LetterboxDetails currentLetterboxDetails = currentActivity +                        .mLetterboxUiController.getLetterboxDetails(); +                if (currentLetterboxDetails != null) { +                    mLetterboxDetails.add(currentLetterboxDetails); +                } +            }          } else if (win.isDimming()) {              if (mStatusBar != null) {                  addStatusBarAppearanceRegionsForDimmingWindow( diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 2ba0e23f9011..cb90501231c4 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -489,6 +489,7 @@ class Task extends TaskFragment {      static final int FLAG_FORCE_HIDDEN_FOR_PINNED_TASK = 1;      static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1;      private int mForceHiddenFlags = 0; +    private boolean mForceTranslucent = false;      // TODO(b/160201781): Revisit double invocation issue in Task#removeChild.      /** @@ -4348,6 +4349,10 @@ class Task extends TaskFragment {          return true;      } +    void setForceTranslucent(boolean set) { +        mForceTranslucent = set; +    } +      @Override      public boolean isAlwaysOnTop() {          return !isForceHidden() && super.isAlwaysOnTop(); @@ -4366,6 +4371,11 @@ class Task extends TaskFragment {      }      @Override +    protected boolean isForceTranslucent() { +        return mForceTranslucent; +    } + +    @Override      long getProtoFieldId() {          return TASK;      } diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 3b25f2876de4..5f85a1401014 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;          } @@ -737,6 +740,10 @@ class TaskFragment extends WindowContainer<WindowContainer> {          return false;      } +    protected boolean isForceTranslucent() { +        return false; +    } +      boolean isLeafTaskFragment() {          for (int i = mChildren.size() - 1; i >= 0; --i) {              if (mChildren.get(i).asTaskFragment() != null) { @@ -862,7 +869,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {       */      @VisibleForTesting      boolean isTranslucent(ActivityRecord starting) { -        if (!isAttached() || isForceHidden()) { +        if (!isAttached() || isForceHidden() || isForceTranslucent()) {              return true;          }          final PooledPredicate p = PooledLambda.obtainPredicate(TaskFragment::isOpaqueActivity, diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 29e407fccdb7..5a2100d4ecac 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -645,6 +645,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub              }          } +        if ((c.getChangeMask() +                & WindowContainerTransaction.Change.CHANGE_FORCE_TRANSLUCENT) != 0) { +            tr.setForceTranslucent(c.getForceTranslucent()); +            effects = TRANSACT_EFFECTS_LIFECYCLE; +        } +          final int childWindowingMode = c.getActivityWindowingMode();          if (childWindowingMode > -1) {              tr.setActivityWindowingMode(childWindowingMode); diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd index 09044e72f60b..073b131cc819 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -45,6 +45,8 @@                  <xs:element type="highBrightnessMode" name="highBrightnessMode" minOccurs="0"                              maxOccurs="1"/>                  <xs:element type="displayQuirks" name="quirks" minOccurs="0" maxOccurs="1" /> +                <xs:element type="autoBrightness" name="autoBrightness" minOccurs="0" +                            maxOccurs="1" />                  <xs:element type="nonNegativeDecimal" name="screenBrightnessRampFastDecrease">                      <xs:annotation name="final"/>                  </xs:element> @@ -101,6 +103,21 @@      <!-- Type definitions --> +    <xs:complexType name="autoBrightness"> +        <xs:sequence> +            <!-- Sets the debounce for autoBrightness brightening in millis--> +            <xs:element name="brighteningLightDebounceMillis" type="xs:nonNegativeInteger" +                        minOccurs="0" maxOccurs="1"> +                <xs:annotation name="final"/> +            </xs:element> +            <!-- Sets the debounce for autoBrightness darkening in millis--> +            <xs:element name="darkeningLightDebounceMillis" type="xs:nonNegativeInteger" +                        minOccurs="0" maxOccurs="1"> +                <xs:annotation name="final"/> +            </xs:element> +        </xs:sequence> +    </xs:complexType> +      <xs:complexType name="displayQuirks">          <xs:sequence>              <xs:element name="quirk" type="xs:string" minOccurs="0" maxOccurs="unbounded" /> @@ -341,5 +358,4 @@              <xs:annotation name="final"/>          </xs:element>      </xs:complexType> -  </xs:schema> diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt index e8b13ca6356e..e9a926946764 100644 --- a/services/core/xsd/display-device-config/schema/current.txt +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -1,6 +1,14 @@  // Signature format: 2.0  package com.android.server.display.config { +  public class AutoBrightness { +    ctor public AutoBrightness(); +    method public final java.math.BigInteger getBrighteningLightDebounceMillis(); +    method public final java.math.BigInteger getDarkeningLightDebounceMillis(); +    method public final void setBrighteningLightDebounceMillis(java.math.BigInteger); +    method public final void setDarkeningLightDebounceMillis(java.math.BigInteger); +  } +    public class BrightnessThresholds {      ctor public BrightnessThresholds();      method @NonNull public final java.math.BigDecimal getMinimum(); @@ -40,6 +48,7 @@ package com.android.server.display.config {      method @NonNull public final com.android.server.display.config.Thresholds getAmbientBrightnessChangeThresholds();      method public final java.math.BigInteger getAmbientLightHorizonLong();      method public final java.math.BigInteger getAmbientLightHorizonShort(); +    method public com.android.server.display.config.AutoBrightness getAutoBrightness();      method @Nullable public final com.android.server.display.config.DensityMapping getDensityMapping();      method @NonNull public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholds();      method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode(); @@ -58,6 +67,7 @@ package com.android.server.display.config {      method public final void setAmbientBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds);      method public final void setAmbientLightHorizonLong(java.math.BigInteger);      method public final void setAmbientLightHorizonShort(java.math.BigInteger); +    method public void setAutoBrightness(com.android.server.display.config.AutoBrightness);      method public final void setDensityMapping(@Nullable com.android.server.display.config.DensityMapping);      method public final void setDisplayBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds);      method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode); 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/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java index 793930395daa..03ea6137074d 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java @@ -86,6 +86,8 @@ public final class DisplayDeviceConfigTest {                  0.0f);          assertEquals(mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), 0.001, 0.000001f);          assertEquals(mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), 0.002, 0.000001f); +        assertEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLightDebounce(), 2000); +        assertEquals(mDisplayDeviceConfig.getAutoBrightnessDarkeningLightDebounce(), 1000);          // Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping,          // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor. @@ -109,6 +111,10 @@ public final class DisplayDeviceConfigTest {                  +           "<nits>800.0</nits>\n"                  +       "</point>\n"                  +   "</screenBrightnessMap>\n" +                +   "<autoBrightness>\n" +                +       "<brighteningLightDebounceMillis>2000</brighteningLightDebounceMillis>\n" +                +       "<darkeningLightDebounceMillis>1000</darkeningLightDebounceMillis>\n" +                +   "</autoBrightness>\n"                  +   "<highBrightnessMode enabled=\"true\">\n"                  +       "<transitionPoint>0.62</transitionPoint>\n"                  +       "<minimumLux>10000</minimumLux>\n" diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java index e9171c0c3514..92c7871e611d 100644 --- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java @@ -336,6 +336,59 @@ public class SystemConfigTest {          assertThat(mSysConfig.getAllowedVendorApexes()).isEmpty();      } +    /** +     * Tests that readPermissions works correctly for the tag: {@code install-constraints-allowed}. +     */ +    @Test +    public void readPermissions_installConstraints_successful() throws IOException { +        final String contents = +                "<config>\n" +                        + "    <install-constraints-allowed package=\"com.android.apex1\" />\n" +                        + "</config>"; +        final File folder = createTempSubfolder("folder"); +        createTempFile(folder, "install-constraints-allowlist.xml", contents); + +        readPermissions(folder, /* Grant all permission flags */ ~0); + +        assertThat(mSysConfig.getInstallConstraintsAllowlist()) +                .containsExactly("com.android.apex1"); +    } + +    /** +     * Tests that readPermissions works correctly for the tag: {@code install-constraints-allowed}. +     */ +    @Test +    public void readPermissions_installConstraints_noPackage() throws IOException { +        final String contents = +                "<config>\n" +                        + "    <install-constraints-allowed/>\n" +                        + "</config>"; +        final File folder = createTempSubfolder("folder"); +        createTempFile(folder, "install-constraints-allowlist.xml", contents); + +        readPermissions(folder, /* Grant all permission flags */ ~0); + +        assertThat(mSysConfig.getInstallConstraintsAllowlist()).isEmpty(); +    } + +    /** +     * Tests that readPermissions works correctly for the tag {@code install-constraints-allowed} +     * without {@link SystemConfig#ALLOW_VENDOR_APEX}. +     */ +    @Test +    public void readPermissions_installConstraints_noAppConfigs() throws IOException { +        final String contents = +                "<config>\n" +                        + "    <install-constraints-allowed package=\"com.android.apex1\" />\n" +                        + "</config>"; +        final File folder = createTempSubfolder("folder"); +        createTempFile(folder, "install-constraints-allowlist.xml", contents); + +        readPermissions(folder,  /* Grant all but ALLOW_APP_CONFIGS flag */ ~0x08); + +        assertThat(mSysConfig.getInstallConstraintsAllowlist()).isEmpty(); +    } +      @Test      public void readApexPrivAppPermissions_addAllPermissions()              throws Exception { diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 21839aac4ec5..fde6e3cc0b4f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -102,7 +102,6 @@ import org.junit.Rule;  import org.junit.Test;  import org.junit.rules.TestRule;  import org.junit.runner.RunWith; -import org.mockito.Mockito;  /**   * Tests for Size Compatibility mode. @@ -2165,6 +2164,40 @@ public class SizeCompatTests extends WindowTestsBase {      }      @Test +    public void testLetterboxDetailsForStatusBar_letterboxNotOverlappingStatusBar() { +        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2800) +                .setNotch(100) +                .build(); +        setUpApp(display); +        TestWindowState statusBar = addStatusBar(mActivity.mDisplayContent); +        spyOn(statusBar); +        doReturn(new Rect(0, 0, statusBar.mRequestedWidth, statusBar.mRequestedHeight)) +                .when(statusBar).getFrame(); +        addWindowToActivity(mActivity); // Add a window to the activity so that we can get an +        // appearance inside letterboxDetails +        // Prepare unresizable activity with max aspect ratio +        prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED); +        // Refresh the letterbox +        mActivity.mRootWindowContainer.performSurfacePlacement(); + +        Rect mBounds = new Rect(mActivity.getWindowConfiguration().getBounds()); +        assertEquals(mBounds, new Rect(0, 750, 1000, 1950)); + +        DisplayPolicy displayPolicy = mActivity.getDisplayContent().getDisplayPolicy(); +        LetterboxDetails[] expectedLetterboxDetails = {new LetterboxDetails( +                mBounds, +                mActivity.getDisplayContent().getBounds(), +                mActivity.findMainWindow().mAttrs.insetsFlags.appearance +        )}; + +        // Check that letterboxDetails actually gets passed to SysUI +        StatusBarManagerInternal statusBarManager = displayPolicy.getStatusBarManagerInternal(); +        verify(statusBarManager).onSystemBarAttributesChanged(anyInt(), anyInt(), +                any(), anyBoolean(), anyInt(), +                any(InsetsVisibilities.class), isNull(), eq(expectedLetterboxDetails)); +    } + +    @Test      public void testSplitScreenLetterboxDetailsForStatusBar_twoLetterboxedApps() {          mAtm.mDevEnableNonResizableMultiWindow = true;          setUpDisplaySizeWithApp(2800, 1000); @@ -2785,7 +2818,7 @@ public class SizeCompatTests extends WindowTestsBase {          return w;      } -    private static void addStatusBar(DisplayContent displayContent) { +    private static TestWindowState addStatusBar(DisplayContent displayContent) {          final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();          doReturn(true).when(displayPolicy).hasStatusBar();          displayPolicy.onConfigurationChanged(); @@ -2806,6 +2839,7 @@ public class SizeCompatTests extends WindowTestsBase {          displayPolicy.addWindowLw(statusBar, attrs);          displayPolicy.layoutWindowLw(statusBar, null, displayContent.mDisplayFrames); +        return statusBar;      }      /** 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/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index 84c2c551de85..7d4e6fa53a64 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -578,6 +578,22 @@ public class WindowOrganizerTests extends WindowTestsBase {      }      @Test +    public void testContainerTranslucentChanges() { +        removeGlobalMinSizeRestriction(); +        final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true) +                .setWindowingMode(WINDOWING_MODE_FULLSCREEN).build(); +        final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(rootTask).build(); +        WindowContainerTransaction t = new WindowContainerTransaction(); +        assertFalse(rootTask.isTranslucent(activity)); +        t.setForceTranslucent(rootTask.mRemoteToken.toWindowContainerToken(), true); +        mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); +        assertTrue(rootTask.isTranslucent(activity)); +        t.setForceTranslucent(rootTask.mRemoteToken.toWindowContainerToken(), false); +        mWm.mAtmService.mWindowOrganizerController.applyTransaction(t); +        assertFalse(rootTask.isTranslucent(activity)); +    } + +    @Test      public void testSetIgnoreOrientationRequest_taskDisplayArea() {          removeGlobalMinSizeRestriction();          final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea(); 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;      }  } diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp index d432341a8cde..948b11b6795e 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -477,6 +477,7 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor,    manifest_action["compatible-screens"]["screen"];    manifest_action["supports-gl-texture"];    manifest_action["restrict-update"]; +  manifest_action["install-constraints"]["fingerprint-prefix"];    manifest_action["package-verifier"];    manifest_action["meta-data"] = meta_data_action;    manifest_action["uses-split"].Action(RequiredNameIsJavaPackage);  |