diff options
73 files changed, 2181 insertions, 443 deletions
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index f6d27ad08b00..37a90de8d600 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -317,7 +317,10 @@ public class NotificationManager { /** * Intent that is broadcast when the state of {@link #getEffectsSuppressor()} changes. - * This broadcast is only sent to registered receivers. + * + * <p>This broadcast is only sent to registered receivers and (starting from + * {@link Build.VERSION_CODES#Q}) receivers in packages that have been granted Do Not + * Disturb access (see {@link #isNotificationPolicyAccessGranted()}). * * @hide */ @@ -337,7 +340,10 @@ public class NotificationManager { /** * Intent that is broadcast when the state of getNotificationPolicy() changes. - * This broadcast is only sent to registered receivers. + * + * <p>This broadcast is only sent to registered receivers and (starting from + * {@link Build.VERSION_CODES#Q}) receivers in packages that have been granted Do Not + * Disturb access (see {@link #isNotificationPolicyAccessGranted()}). */ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_NOTIFICATION_POLICY_CHANGED @@ -345,7 +351,10 @@ public class NotificationManager { /** * Intent that is broadcast when the state of getCurrentInterruptionFilter() changes. - * This broadcast is only sent to registered receivers. + * + * <p>This broadcast is only sent to registered receivers and (starting from + * {@link Build.VERSION_CODES#Q}) receivers in packages that have been granted Do Not + * Disturb access (see {@link #isNotificationPolicyAccessGranted()}). */ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_INTERRUPTION_FILTER_CHANGED diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index e27af17ebc3d..d53ad17f07ef 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -61,6 +61,7 @@ import android.hardware.display.DisplayManager.DisplayListener; import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; @@ -180,6 +181,9 @@ public abstract class WallpaperService extends Service { private final ArrayList<Engine> mActiveEngines = new ArrayList<Engine>(); + private Handler mBackgroundHandler; + private HandlerThread mBackgroundThread; + static final class WallpaperCommand { String action; int x; @@ -198,14 +202,6 @@ public abstract class WallpaperService extends Service { */ public class Engine { IWallpaperEngineWrapper mIWallpaperEngine; - final ArraySet<RectF> mLocalColorAreas = new ArraySet<>(4); - final ArraySet<RectF> mLocalColorsToAdd = new ArraySet<>(4); - - // 2D matrix [x][y] to represent a page of a portion of a window - EngineWindowPage[] mWindowPages = new EngineWindowPage[0]; - Bitmap mLastScreenshot; - int mLastWindowPage = -1; - private boolean mResetWindowPages; // Copies from mIWallpaperEngine. HandlerCaller mCaller; @@ -267,11 +263,27 @@ public abstract class WallpaperService extends Service { final Object mLock = new Object(); boolean mOffsetMessageEnqueued; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) float mPendingXOffset; float mPendingYOffset; float mPendingXOffsetStep; float mPendingYOffsetStep; + + /** + * local color extraction related fields + * to be used by the background thread only (except the atomic boolean) + */ + final ArraySet<RectF> mLocalColorAreas = new ArraySet<>(4); + final ArraySet<RectF> mLocalColorsToAdd = new ArraySet<>(4); + private long mLastProcessLocalColorsTimestamp; + private AtomicBoolean mProcessLocalColorsPending = new AtomicBoolean(false); + private int mPixelCopyCount = 0; + // 2D matrix [x][y] to represent a page of a portion of a window + EngineWindowPage[] mWindowPages = new EngineWindowPage[0]; + Bitmap mLastScreenshot; + private boolean mResetWindowPages; + boolean mPendingSync; MotionEvent mPendingMove; boolean mIsInAmbientMode; @@ -280,12 +292,8 @@ public abstract class WallpaperService extends Service { private long mLastColorInvalidation; private final Runnable mNotifyColorsChanged = this::notifyColorsChanged; - // used to throttle processLocalColors - private long mLastProcessLocalColorsTimestamp; - private AtomicBoolean mProcessLocalColorsPending = new AtomicBoolean(false); private final Supplier<Long> mClockFunction; private final Handler mHandler; - private Display mDisplay; private Context mDisplayContext; private int mDisplayState; @@ -825,7 +833,7 @@ public abstract class WallpaperService extends Service { + "was not established."); } mResetWindowPages = true; - processLocalColors(mPendingXOffset, mPendingXOffsetStep); + processLocalColors(); } catch (RemoteException e) { Log.w(TAG, "Can't notify system because wallpaper connection was lost.", e); } @@ -1364,7 +1372,7 @@ public abstract class WallpaperService extends Service { resetWindowPages(); mSession.finishDrawing(mWindow, null /* postDrawTransaction */, Integer.MAX_VALUE); - processLocalColors(mPendingXOffset, mPendingXOffsetStep); + processLocalColors(); } reposition(); reportEngineShown(shouldWaitForEngineShown()); @@ -1509,7 +1517,7 @@ public abstract class WallpaperService extends Service { if (!mDestroyed) { mVisible = visible; reportVisibility(); - if (mReportedVisible) processLocalColors(mPendingXOffset, mPendingXOffsetStep); + if (mReportedVisible) processLocalColors(); } else { AnimationHandler.requestAnimatorsEnabled(visible, this); } @@ -1593,31 +1601,41 @@ public abstract class WallpaperService extends Service { } // setup local color extraction data - processLocalColors(xOffset, xOffsetStep); + processLocalColors(); } /** * Thread-safe util to call {@link #processLocalColorsInternal} with a minimum interval of * {@link #PROCESS_LOCAL_COLORS_INTERVAL_MS} between two calls. */ - private void processLocalColors(float xOffset, float xOffsetStep) { + private void processLocalColors() { if (mProcessLocalColorsPending.compareAndSet(false, true)) { final long now = mClockFunction.get(); final long timeSinceLastColorProcess = now - mLastProcessLocalColorsTimestamp; final long timeToWait = Math.max(0, PROCESS_LOCAL_COLORS_INTERVAL_MS - timeSinceLastColorProcess); - mHandler.postDelayed(() -> { + mBackgroundHandler.postDelayed(() -> { mLastProcessLocalColorsTimestamp = now + timeToWait; mProcessLocalColorsPending.set(false); - processLocalColorsInternal(xOffset, xOffsetStep); + processLocalColorsInternal(); }, timeToWait); } } - private void processLocalColorsInternal(float xOffset, float xOffsetStep) { + private void processLocalColorsInternal() { // implemented by the wallpaper if (supportsLocalColorExtraction()) return; + assertBackgroundThread(); + float xOffset; + float xOffsetStep; + float wallpaperDimAmount; + synchronized (mLock) { + xOffset = mPendingXOffset; + xOffsetStep = mPendingXOffsetStep; + wallpaperDimAmount = mWallpaperDimAmount; + } + if (DEBUG) { Log.d(TAG, "processLocalColors " + xOffset + " of step " + xOffsetStep); @@ -1680,7 +1698,7 @@ public abstract class WallpaperService extends Service { xPage = mWindowPages.length - 1; } current = mWindowPages[xPage]; - updatePage(current, xPage, xPages, finalXOffsetStep); + updatePage(current, xPage, xPages, wallpaperDimAmount); Trace.endSection(); } @@ -1700,16 +1718,23 @@ public abstract class WallpaperService extends Service { } } + /** + * Must be called with the surface lock held. + * Must not be called if the surface is not valid. + * Will unlock the surface when done using it. + */ void updatePage(EngineWindowPage currentPage, int pageIndx, int numPages, - float xOffsetStep) { + float wallpaperDimAmount) { + + assertBackgroundThread(); + // in case the clock is zero, we start with negative time long current = SystemClock.elapsedRealtime() - DEFAULT_UPDATE_SCREENSHOT_DURATION; long lapsed = current - currentPage.getLastUpdateTime(); // Always update the page when the last update time is <= 0 // This is important especially when the device first boots - if (lapsed < DEFAULT_UPDATE_SCREENSHOT_DURATION) { - return; - } + if (lapsed < DEFAULT_UPDATE_SCREENSHOT_DURATION) return; + Surface surface = mSurfaceHolder.getSurface(); if (!surface.isValid()) return; boolean widthIsLarger = mSurfaceSize.x > mSurfaceSize.y; @@ -1725,33 +1750,42 @@ public abstract class WallpaperService extends Service { Bitmap screenShot = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); final Bitmap finalScreenShot = screenShot; - Trace.beginSection("WallpaperService#pixelCopy"); - PixelCopy.request(surface, screenShot, (res) -> { - Trace.endSection(); - if (DEBUG) Log.d(TAG, "result of pixel copy is " + res); - if (res != PixelCopy.SUCCESS) { - Bitmap lastBitmap = currentPage.getBitmap(); - // assign the last bitmap taken for now - currentPage.setBitmap(mLastScreenshot); - Bitmap lastScreenshot = mLastScreenshot; - if (lastScreenshot != null && !lastScreenshot.isRecycled() - && !Objects.equals(lastBitmap, lastScreenshot)) { - updatePageColors(currentPage, pageIndx, numPages, xOffsetStep); + final String pixelCopySectionName = "WallpaperService#pixelCopy"; + final int pixelCopyCount = mPixelCopyCount++; + Trace.beginAsyncSection(pixelCopySectionName, pixelCopyCount); + try { + PixelCopy.request(surface, screenShot, (res) -> { + Trace.endAsyncSection(pixelCopySectionName, pixelCopyCount); + if (DEBUG) Log.d(TAG, "result of pixel copy is " + res); + if (res != PixelCopy.SUCCESS) { + Bitmap lastBitmap = currentPage.getBitmap(); + // assign the last bitmap taken for now + currentPage.setBitmap(mLastScreenshot); + Bitmap lastScreenshot = mLastScreenshot; + if (lastScreenshot != null && !lastScreenshot.isRecycled() + && !Objects.equals(lastBitmap, lastScreenshot)) { + updatePageColors(currentPage, pageIndx, numPages, wallpaperDimAmount); + } + } else { + mLastScreenshot = finalScreenShot; + // going to hold this lock for a while + currentPage.setBitmap(finalScreenShot); + currentPage.setLastUpdateTime(current); + updatePageColors(currentPage, pageIndx, numPages, wallpaperDimAmount); } - } else { - mLastScreenshot = finalScreenShot; - // going to hold this lock for a while - currentPage.setBitmap(finalScreenShot); - currentPage.setLastUpdateTime(current); - updatePageColors(currentPage, pageIndx, numPages, xOffsetStep); - } - }, mHandler); - + }, mBackgroundHandler); + } catch (IllegalArgumentException e) { + // this can potentially happen if the surface is invalidated right between the + // surface.isValid() check and the PixelCopy operation. + // in this case, stop: we'll compute colors on the next processLocalColors call. + Log.i(TAG, "Cancelling processLocalColors: exception caught during PixelCopy"); + } } // locked by the passed page - private void updatePageColors(EngineWindowPage page, int pageIndx, int numPages, - float xOffsetStep) { + private void updatePageColors( + EngineWindowPage page, int pageIndx, int numPages, float wallpaperDimAmount) { if (page.getBitmap() == null) return; + assertBackgroundThread(); Trace.beginSection("WallpaperService#updatePageColors"); if (DEBUG) { Log.d(TAG, "updatePageColorsLocked for page " + pageIndx + " with areas " @@ -1773,7 +1807,7 @@ public abstract class WallpaperService extends Service { Log.e(TAG, "Error creating page local color bitmap", e); continue; } - WallpaperColors color = WallpaperColors.fromBitmap(target, mWallpaperDimAmount); + WallpaperColors color = WallpaperColors.fromBitmap(target, wallpaperDimAmount); target.recycle(); WallpaperColors currentColor = page.getColors(area); @@ -1790,17 +1824,26 @@ public abstract class WallpaperService extends Service { + " local color callback for area" + area + " for page " + pageIndx + " of " + numPages); } - try { - mConnection.onLocalWallpaperColorsChanged(area, color, - mDisplayContext.getDisplayId()); - } catch (RemoteException e) { - Log.e(TAG, "Error calling Connection.onLocalWallpaperColorsChanged", e); - } + mHandler.post(() -> { + try { + mConnection.onLocalWallpaperColorsChanged(area, color, + mDisplayContext.getDisplayId()); + } catch (RemoteException e) { + Log.e(TAG, "Error calling Connection.onLocalWallpaperColorsChanged", e); + } + }); } } Trace.endSection(); } + private void assertBackgroundThread() { + if (!mBackgroundHandler.getLooper().isCurrentThread()) { + throw new IllegalStateException( + "ProcessLocalColors should be called from the background thread"); + } + } + private RectF generateSubRect(RectF in, int pageInx, int numPages) { float minLeft = (float) (pageInx) / (float) (numPages); float maxRight = (float) (pageInx + 1) / (float) (numPages); @@ -1825,7 +1868,6 @@ public abstract class WallpaperService extends Service { if (supportsLocalColorExtraction()) return; if (!mResetWindowPages) return; mResetWindowPages = false; - mLastWindowPage = -1; for (int i = 0; i < mWindowPages.length; i++) { mWindowPages[i].setLastUpdateTime(0L); } @@ -1851,12 +1893,10 @@ public abstract class WallpaperService extends Service { if (DEBUG) { Log.d(TAG, "addLocalColorsAreas adding local color areas " + regions); } - mHandler.post(() -> { + mBackgroundHandler.post(() -> { mLocalColorsToAdd.addAll(regions); - processLocalColors(mPendingXOffset, mPendingYOffset); + processLocalColors(); }); - - } /** @@ -1866,7 +1906,7 @@ public abstract class WallpaperService extends Service { */ public void removeLocalColorsAreas(@NonNull List<RectF> regions) { if (supportsLocalColorExtraction()) return; - mHandler.post(() -> { + mBackgroundHandler.post(() -> { float step = mPendingXOffsetStep; mLocalColorsToAdd.removeAll(regions); mLocalColorAreas.removeAll(regions); @@ -2497,6 +2537,9 @@ public abstract class WallpaperService extends Service { @Override public void onCreate() { Trace.beginSection("WPMS.onCreate"); + mBackgroundThread = new HandlerThread("DefaultWallpaperLocalColorExtractor"); + mBackgroundThread.start(); + mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); super.onCreate(); Trace.endSection(); } @@ -2509,6 +2552,7 @@ public abstract class WallpaperService extends Service { mActiveEngines.get(i).detach(); } mActiveEngines.clear(); + mBackgroundThread.quitSafely(); Trace.endSection(); } diff --git a/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java b/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java index d2b612a9e6f3..f1ed3bed5d89 100644 --- a/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java +++ b/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java @@ -56,6 +56,9 @@ public class GestureNavigationSettingsObserver extends ContentObserver { } }; + /** + * Registers the observer for all users. + */ public void register() { ContentResolver r = mContext.getContentResolver(); r.registerContentObserver( @@ -73,7 +76,10 @@ public class GestureNavigationSettingsObserver extends ContentObserver { mOnPropertiesChangedListener); } - public void registerForCurrentUser() { + /** + * Registers the observer for the calling user. + */ + public void registerForCallingUser() { ContentResolver r = mContext.getContentResolver(); r.registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT), @@ -103,12 +109,46 @@ public class GestureNavigationSettingsObserver extends ContentObserver { } } + /** + * Returns the left sensitivity for the current user. To be used in code that runs primarily + * in one user's process. + */ public int getLeftSensitivity(Resources userRes) { - return getSensitivity(userRes, Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT); + final float scale = Settings.Secure.getFloatForUser(mContext.getContentResolver(), + Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT, 1.0f, UserHandle.USER_CURRENT); + return (int) (getUnscaledInset(userRes) * scale); } + /** + * Returns the left sensitivity for the calling user. To be used in code that runs in a + * per-user process. + */ + @SuppressWarnings("NonUserGetterCalled") + public int getLeftSensitivityForCallingUser(Resources userRes) { + final float scale = Settings.Secure.getFloat(mContext.getContentResolver(), + Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT, 1.0f); + return (int) (getUnscaledInset(userRes) * scale); + } + + /** + * Returns the right sensitivity for the current user. To be used in code that runs primarily + * in one user's process. + */ public int getRightSensitivity(Resources userRes) { - return getSensitivity(userRes, Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT); + final float scale = Settings.Secure.getFloatForUser(mContext.getContentResolver(), + Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT, 1.0f, UserHandle.USER_CURRENT); + return (int) (getUnscaledInset(userRes) * scale); + } + + /** + * Returns the right sensitivity for the calling user. To be used in code that runs in a + * per-user process. + */ + @SuppressWarnings("NonUserGetterCalled") + public int getRightSensitivityForCallingUser(Resources userRes) { + final float scale = Settings.Secure.getFloat(mContext.getContentResolver(), + Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT, 1.0f); + return (int) (getUnscaledInset(userRes) * scale); } public boolean areNavigationButtonForcedVisible() { @@ -116,7 +156,7 @@ public class GestureNavigationSettingsObserver extends ContentObserver { Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) == 0; } - private int getSensitivity(Resources userRes, String side) { + private float getUnscaledInset(Resources userRes) { final DisplayMetrics dm = userRes.getDisplayMetrics(); final float defaultInset = userRes.getDimension( com.android.internal.R.dimen.config_backGestureInset) / dm.density; @@ -127,8 +167,6 @@ public class GestureNavigationSettingsObserver extends ContentObserver { : defaultInset; final float inset = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, backGestureInset, dm); - final float scale = Settings.Secure.getFloatForUser( - mContext.getContentResolver(), side, 1.0f, UserHandle.USER_CURRENT); - return (int) (inset * scale); + return inset; } } diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 6f31d0674246..1f9b6cf6c64f 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -331,30 +331,6 @@ --> <dimen name="overridable_minimal_size_pip_resizable_task">48dp</dimen> - <!-- The size of the drag handle / menu shown along with a floating task. --> - <dimen name="floating_task_menu_size">32dp</dimen> - - <!-- The size of menu items in the floating task menu. --> - <dimen name="floating_task_menu_item_size">24dp</dimen> - - <!-- The horizontal margin of menu items in the floating task menu. --> - <dimen name="floating_task_menu_item_padding">5dp</dimen> - - <!-- The width of visible floating view region when stashed. --> - <dimen name="floating_task_stash_offset">32dp</dimen> - - <!-- The amount of elevation for a floating task. --> - <dimen name="floating_task_elevation">8dp</dimen> - - <!-- The amount of padding around the bottom and top of the task. --> - <dimen name="floating_task_vertical_padding">8dp</dimen> - - <!-- The normal size of the dismiss target. --> - <dimen name="floating_task_dismiss_circle_size">150dp</dimen> - - <!-- The smaller size of the dismiss target (shrinks when something is in the target). --> - <dimen name="floating_dismiss_circle_small">120dp</dimen> - <!-- The thickness of shadows of a window that has focus in DIP. --> <dimen name="freeform_decor_shadow_focused_thickness">20dp</dimen> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java index 88525aabe53b..e2012b4e36dc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java @@ -16,6 +16,8 @@ package com.android.wm.shell; +import android.os.Build; + import com.android.wm.shell.protolog.ShellProtoLogImpl; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellInit; @@ -41,6 +43,9 @@ public class ProtoLogController implements ShellCommandHandler.ShellCommandActio void onInit() { mShellCommandHandler.addCommandCallback("protolog", this, this); + if (Build.IS_DEBUGGABLE) { + mShellProtoLog.startProtoLog(null /* PrintWriter */); + } } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java new file mode 100644 index 000000000000..22587f4c6456 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.common; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.content.Context; +import android.hardware.devicestate.DeviceStateManager; +import android.util.SparseIntArray; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; +import com.android.wm.shell.sysui.ShellInit; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; + +/** + * Wrapper class to track the device posture change on Fold-ables. + * See also <a + * href="https://developer.android.com/guide/topics/large-screens/learn-about-foldables + * #foldable_postures">Foldable states and postures</a> for reference. + * + * Note that most of the implementation here inherits from + * {@link com.android.systemui.statusbar.policy.DevicePostureController}. + */ +public class DevicePostureController { + @IntDef(prefix = {"DEVICE_POSTURE_"}, value = { + DEVICE_POSTURE_UNKNOWN, + DEVICE_POSTURE_CLOSED, + DEVICE_POSTURE_HALF_OPENED, + DEVICE_POSTURE_OPENED, + DEVICE_POSTURE_FLIPPED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DevicePostureInt {} + + // NOTE: These constants **must** match those defined for Jetpack Sidecar. This is because we + // use the Device State -> Jetpack Posture map to translate between the two. + public static final int DEVICE_POSTURE_UNKNOWN = 0; + public static final int DEVICE_POSTURE_CLOSED = 1; + public static final int DEVICE_POSTURE_HALF_OPENED = 2; + public static final int DEVICE_POSTURE_OPENED = 3; + public static final int DEVICE_POSTURE_FLIPPED = 4; + + private final Context mContext; + private final ShellExecutor mMainExecutor; + private final List<OnDevicePostureChangedListener> mListeners = new ArrayList<>(); + private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray(); + + private int mDevicePosture = DEVICE_POSTURE_UNKNOWN; + + public DevicePostureController( + Context context, ShellInit shellInit, ShellExecutor mainExecutor) { + mContext = context; + mMainExecutor = mainExecutor; + shellInit.addInitCallback(this::onInit, this); + } + + private void onInit() { + // Most of this is borrowed from WindowManager/Jetpack/DeviceStateManagerPostureProducer. + // Using the sidecar/extension libraries directly brings in a new dependency that it'd be + // good to avoid (along with the fact that sidecar is deprecated, and extensions isn't fully + // ready yet), and we'd have to make our own layer over the sidecar library anyway to easily + // allow the implementation to change, so it was easier to just interface with + // DeviceStateManager directly. + String[] deviceStatePosturePairs = mContext.getResources() + .getStringArray(R.array.config_device_state_postures); + for (String deviceStatePosturePair : deviceStatePosturePairs) { + String[] deviceStatePostureMapping = deviceStatePosturePair.split(":"); + if (deviceStatePostureMapping.length != 2) { + continue; + } + + int deviceState; + int posture; + try { + deviceState = Integer.parseInt(deviceStatePostureMapping[0]); + posture = Integer.parseInt(deviceStatePostureMapping[1]); + } catch (NumberFormatException e) { + continue; + } + + mDeviceStateToPostureMap.put(deviceState, posture); + } + + final DeviceStateManager deviceStateManager = mContext.getSystemService( + DeviceStateManager.class); + if (deviceStateManager != null) { + deviceStateManager.registerCallback(mMainExecutor, state -> onDevicePostureChanged( + mDeviceStateToPostureMap.get(state, DEVICE_POSTURE_UNKNOWN))); + } + } + + @VisibleForTesting + void onDevicePostureChanged(int devicePosture) { + if (devicePosture == mDevicePosture) return; + mDevicePosture = devicePosture; + mListeners.forEach(l -> l.onDevicePostureChanged(mDevicePosture)); + } + + /** + * Register {@link OnDevicePostureChangedListener} for device posture changes. + * The listener will receive callback with current device posture upon registration. + */ + public void registerOnDevicePostureChangedListener( + @NonNull OnDevicePostureChangedListener listener) { + if (mListeners.contains(listener)) return; + mListeners.add(listener); + listener.onDevicePostureChanged(mDevicePosture); + } + + /** + * Unregister {@link OnDevicePostureChangedListener} for device posture changes. + */ + public void unregisterOnDevicePostureChangedListener( + @NonNull OnDevicePostureChangedListener listener) { + mListeners.remove(listener); + } + + /** + * Listener interface for device posture change. + */ + public interface OnDevicePostureChangedListener { + /** + * Callback when device posture changes. + * See {@link DevicePostureInt} for callback values. + */ + void onDevicePostureChanged(@DevicePostureInt int posture); + } +} 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 25c430c27457..72dc771ee08c 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 @@ -41,6 +41,7 @@ import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.back.BackAnimationController; import com.android.wm.shell.bubbles.BubbleController; import com.android.wm.shell.bubbles.Bubbles; +import com.android.wm.shell.common.DevicePostureController; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayInsetsController; @@ -160,6 +161,16 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides + static DevicePostureController provideDevicePostureController( + Context context, + ShellInit shellInit, + @ShellMainThread ShellExecutor mainExecutor + ) { + return new DevicePostureController(context, shellInit, mainExecutor); + } + + @WMSingleton + @Provides static DragAndDropController provideDragAndDropController(Context context, ShellInit shellInit, ShellController shellController, 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 efc7d1ff0bb9..1239cdc5b606 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 @@ -558,16 +558,18 @@ public abstract class WMShellModule { static FullscreenUnfoldTaskAnimator provideFullscreenUnfoldTaskAnimator( Context context, UnfoldBackgroundController unfoldBackgroundController, + ShellController shellController, DisplayInsetsController displayInsetsController ) { return new FullscreenUnfoldTaskAnimator(context, unfoldBackgroundController, - displayInsetsController); + shellController, displayInsetsController); } @Provides static SplitTaskUnfoldAnimator provideSplitTaskUnfoldAnimatorBase( Context context, UnfoldBackgroundController backgroundController, + ShellController shellController, @ShellMainThread ShellExecutor executor, Lazy<Optional<SplitScreenController>> splitScreenOptional, DisplayInsetsController displayInsetsController @@ -577,7 +579,7 @@ public abstract class WMShellModule { // controller directly once we refactor ShellTaskOrganizer to not depend on the unfold // animation controller directly. return new SplitTaskUnfoldAnimator(context, executor, splitScreenOptional, - backgroundController, displayInsetsController); + shellController, backgroundController, displayInsetsController); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java index 3ade1ed9392f..bb6e3917ff3c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java @@ -369,7 +369,9 @@ public class DragLayout extends LinearLayout { // Start animating the drop UI out with the drag surface hide(event, dropCompleteCallback); - hideDragSurface(dragSurface); + if (handledDrop) { + hideDragSurface(dragSurface); + } return handledDrop; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java index d9ac76e833d8..23f73f614294 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java @@ -209,7 +209,7 @@ public class PipAnimationController { /** * Quietly cancel the animator by removing the listeners first. */ - static void quietCancel(@NonNull ValueAnimator animator) { + public static void quietCancel(@NonNull ValueAnimator animator) { animator.removeAllUpdateListeners(); animator.removeAllListeners(); animator.cancel(); 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 ec34f73126fb..fa3efeb51bd0 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 @@ -715,6 +715,12 @@ public class PipController implements PipTransitionController.PipTransitionCallb private void onDisplayChanged(DisplayLayout layout, boolean saveRestoreSnapFraction) { if (!mPipBoundsState.getDisplayLayout().isSameGeometry(layout)) { + PipAnimationController.PipTransitionAnimator animator = + mPipAnimationController.getCurrentAnimator(); + if (animator != null && animator.isRunning()) { + // cancel any running animator, as it is using stale display layout information + PipAnimationController.quietCancel(animator); + } onDisplayChangedUncheck(layout, saveRestoreSnapFraction); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java index 93ffb3dc8115..c59c42dadb9d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java @@ -31,8 +31,7 @@ import java.io.PrintWriter; */ public class ShellProtoLogImpl extends BaseProtoLogImpl { private static final String TAG = "ProtoLogImpl"; - private static final int BUFFER_CAPACITY = 1024 * 1024; - // TODO: find a proper location to save the protolog message file + private static final int BUFFER_CAPACITY = 128 * 1024; private static final String LOG_FILENAME = "/data/misc/wmtrace/shell_log.winscope"; private static final String VIEWER_CONFIG_FILENAME = "/system_ext/etc/wmshell.protolog.json.gz"; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java index eab82f00e962..e0f3fcd932c2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java @@ -26,8 +26,10 @@ import android.animation.TypeEvaluator; import android.annotation.NonNull; import android.app.TaskInfo; import android.content.Context; +import android.content.res.Configuration; import android.graphics.Matrix; import android.graphics.Rect; +import android.os.Trace; import android.util.SparseArray; import android.view.InsetsSource; import android.view.InsetsState; @@ -36,6 +38,8 @@ import android.view.SurfaceControl.Transaction; import com.android.internal.policy.ScreenDecorationsUtils; import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.sysui.ConfigurationChangeListener; +import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.unfold.UnfoldAnimationController; import com.android.wm.shell.unfold.UnfoldBackgroundController; @@ -51,7 +55,7 @@ import com.android.wm.shell.unfold.UnfoldBackgroundController; * instances of FullscreenUnfoldTaskAnimator. */ public class FullscreenUnfoldTaskAnimator implements UnfoldTaskAnimator, - DisplayInsetsController.OnInsetsChangedListener { + DisplayInsetsController.OnInsetsChangedListener, ConfigurationChangeListener { private static final float[] FLOAT_9 = new float[9]; private static final TypeEvaluator<Rect> RECT_EVALUATOR = new RectEvaluator(new Rect()); @@ -63,17 +67,21 @@ public class FullscreenUnfoldTaskAnimator implements UnfoldTaskAnimator, private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>(); private final int mExpandedTaskBarHeight; - private final float mWindowCornerRadiusPx; private final DisplayInsetsController mDisplayInsetsController; private final UnfoldBackgroundController mBackgroundController; + private final Context mContext; + private final ShellController mShellController; private InsetsSource mTaskbarInsetsSource; + private float mWindowCornerRadiusPx; public FullscreenUnfoldTaskAnimator(Context context, @NonNull UnfoldBackgroundController backgroundController, - DisplayInsetsController displayInsetsController) { + ShellController shellController, DisplayInsetsController displayInsetsController) { + mContext = context; mDisplayInsetsController = displayInsetsController; mBackgroundController = backgroundController; + mShellController = shellController; mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize( com.android.internal.R.dimen.taskbar_frame_height); mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context); @@ -81,6 +89,14 @@ public class FullscreenUnfoldTaskAnimator implements UnfoldTaskAnimator, public void init() { mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this); + mShellController.addConfigurationChangeListener(this); + } + + @Override + public void onConfigurationChanged(Configuration newConfiguration) { + Trace.beginSection("FullscreenUnfoldTaskAnimator#onConfigurationChanged"); + mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(mContext); + Trace.endSection(); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java index 6e10ebe94c5d..addd0a6012c4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java @@ -28,8 +28,10 @@ import android.animation.RectEvaluator; import android.animation.TypeEvaluator; import android.app.TaskInfo; import android.content.Context; +import android.content.res.Configuration; import android.graphics.Insets; import android.graphics.Rect; +import android.os.Trace; import android.util.SparseArray; import android.view.InsetsSource; import android.view.InsetsState; @@ -42,6 +44,8 @@ import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreen.SplitScreenListener; import com.android.wm.shell.splitscreen.SplitScreenController; +import com.android.wm.shell.sysui.ConfigurationChangeListener; +import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.unfold.UnfoldAnimationController; import com.android.wm.shell.unfold.UnfoldBackgroundController; @@ -62,16 +66,18 @@ import dagger.Lazy; * They use independent instances of SplitTaskUnfoldAnimator. */ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator, - DisplayInsetsController.OnInsetsChangedListener, SplitScreenListener { + DisplayInsetsController.OnInsetsChangedListener, SplitScreenListener, + ConfigurationChangeListener { private static final TypeEvaluator<Rect> RECT_EVALUATOR = new RectEvaluator(new Rect()); private static final float CROPPING_START_MARGIN_FRACTION = 0.05f; + private final Context mContext; private final Executor mExecutor; private final DisplayInsetsController mDisplayInsetsController; private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>(); private final int mExpandedTaskBarHeight; - private final float mWindowCornerRadiusPx; + private final ShellController mShellController; private final Lazy<Optional<SplitScreenController>> mSplitScreenController; private final UnfoldBackgroundController mUnfoldBackgroundController; @@ -79,6 +85,7 @@ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator, private final Rect mSideStageBounds = new Rect(); private final Rect mRootStageBounds = new Rect(); + private float mWindowCornerRadiusPx; private InsetsSource mTaskbarInsetsSource; @SplitPosition @@ -88,10 +95,12 @@ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator, public SplitTaskUnfoldAnimator(Context context, Executor executor, Lazy<Optional<SplitScreenController>> splitScreenController, - UnfoldBackgroundController unfoldBackgroundController, + ShellController shellController, UnfoldBackgroundController unfoldBackgroundController, DisplayInsetsController displayInsetsController) { mDisplayInsetsController = displayInsetsController; mExecutor = executor; + mContext = context; + mShellController = shellController; mUnfoldBackgroundController = unfoldBackgroundController; mSplitScreenController = splitScreenController; mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize( @@ -103,6 +112,14 @@ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator, @Override public void init() { mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this); + mShellController.addConfigurationChangeListener(this); + } + + @Override + public void onConfigurationChanged(Configuration newConfiguration) { + Trace.beginSection("SplitTaskUnfoldAnimator#onConfigurationChanged"); + mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(mContext); + Trace.endSection(); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index 245cc8d7ee87..0779f1d72551 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -213,6 +213,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin final View handle = caption.findViewById(R.id.caption_handle); handle.setOnTouchListener(mOnCaptionTouchListener); handle.setOnClickListener(mOnCaptionButtonClickListener); + if (DesktopModeStatus.isProto1Enabled()) { + final View back = caption.findViewById(R.id.back_button); + back.setOnClickListener(mOnCaptionButtonClickListener); + final View close = caption.findViewById(R.id.close_window); + close.setOnClickListener(mOnCaptionButtonClickListener); + } updateButtonVisibility(); } @@ -319,6 +325,14 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin final View handle = caption.findViewById(R.id.caption_handle); final Drawable handleBackground = handle.getBackground(); handleBackground.setTintList(buttonTintColor); + if (DesktopModeStatus.isProto1Enabled()) { + final View back = caption.findViewById(R.id.back_button); + final Drawable backBackground = back.getBackground(); + backBackground.setTintList(buttonTintColor); + final View close = caption.findViewById(R.id.close_window); + final Drawable closeBackground = close.getBackground(); + closeBackground.setTintList(buttonTintColor); + } } private void closeDragResizeListener() { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java new file mode 100644 index 000000000000..f8ee300e411c --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.common; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +import android.content.Context; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.sysui.ShellInit; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class DevicePostureControllerTest { + @Mock + private Context mContext; + + @Mock + private ShellInit mShellInit; + + @Mock + private ShellExecutor mMainExecutor; + + @Captor + private ArgumentCaptor<Integer> mDevicePostureCaptor; + + @Mock + private DevicePostureController.OnDevicePostureChangedListener mOnDevicePostureChangedListener; + + private DevicePostureController mDevicePostureController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mDevicePostureController = new DevicePostureController(mContext, mShellInit, mMainExecutor); + } + + @Test + public void instantiateController_addInitCallback() { + verify(mShellInit, times(1)).addInitCallback(any(), eq(mDevicePostureController)); + } + + @Test + public void registerOnDevicePostureChangedListener_callbackCurrentPosture() { + mDevicePostureController.registerOnDevicePostureChangedListener( + mOnDevicePostureChangedListener); + verify(mOnDevicePostureChangedListener, times(1)) + .onDevicePostureChanged(anyInt()); + } + + @Test + public void onDevicePostureChanged_differentPosture_callbackListener() { + mDevicePostureController.registerOnDevicePostureChangedListener( + mOnDevicePostureChangedListener); + verify(mOnDevicePostureChangedListener).onDevicePostureChanged( + mDevicePostureCaptor.capture()); + clearInvocations(mOnDevicePostureChangedListener); + + int differentDevicePosture = mDevicePostureCaptor.getValue() + 1; + mDevicePostureController.onDevicePostureChanged(differentDevicePosture); + + verify(mOnDevicePostureChangedListener, times(1)) + .onDevicePostureChanged(differentDevicePosture); + } + + @Test + public void onDevicePostureChanged_samePosture_doesNotCallbackListener() { + mDevicePostureController.registerOnDevicePostureChangedListener( + mOnDevicePostureChangedListener); + verify(mOnDevicePostureChangedListener).onDevicePostureChanged( + mDevicePostureCaptor.capture()); + clearInvocations(mOnDevicePostureChangedListener); + + int sameDevicePosture = mDevicePostureCaptor.getValue(); + mDevicePostureController.onDevicePostureChanged(sameDevicePosture); + + verifyZeroInteractions(mOnDevicePostureChangedListener); + } +} diff --git a/media/tests/MediaFrameworkTest/AndroidManifest.xml b/media/tests/MediaFrameworkTest/AndroidManifest.xml index 33872ed63b89..0da5de75548c 100644 --- a/media/tests/MediaFrameworkTest/AndroidManifest.xml +++ b/media/tests/MediaFrameworkTest/AndroidManifest.xml @@ -37,7 +37,6 @@ </activity> <activity android:label="Camera2CtsActivity" android:name="Camera2SurfaceViewActivity" - android:screenOrientation="landscape" android:configChanges="keyboardHidden|orientation|screenSize"> </activity> </application> diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt index 774255be4007..b98b92219c33 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt @@ -35,8 +35,7 @@ interface LaunchableView { * * Note that calls to [View.setTransitionVisibility] shouldn't be blocked. * - * @param block whether we should block/postpone all calls to `setVisibility` and - * `setTransitionVisibility`. + * @param block whether we should block/postpone all calls to `setVisibility`. */ fun setShouldBlockVisibilityChanges(block: Boolean) } diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt index 442c6fadb7c1..bd91c65ecc6e 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt @@ -68,7 +68,7 @@ class RippleAnimation(private val config: RippleAnimationConfig) { private fun applyConfigToShader() { with(rippleShader) { setCenter(config.centerX, config.centerY) - setMaxSize(config.maxWidth, config.maxHeight) + rippleSize.setMaxSize(config.maxWidth, config.maxHeight) pixelDensity = config.pixelDensity color = ColorUtils.setAlphaComponent(config.color, config.opacity) sparkleStrength = config.sparkleStrength diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt index 61ca90a297fe..0b842ad5331c 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt @@ -15,9 +15,10 @@ */ package com.android.systemui.surfaceeffects.ripple -import android.graphics.PointF import android.graphics.RuntimeShader +import android.util.Log import android.util.MathUtils +import androidx.annotation.VisibleForTesting import com.android.systemui.animation.Interpolators import com.android.systemui.surfaceeffects.shaderutil.SdfShaderLibrary import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary @@ -44,6 +45,8 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) : } // language=AGSL companion object { + private val TAG = RippleShader::class.simpleName + // Default fade in/ out values. The value range is [0,1]. const val DEFAULT_FADE_IN_START = 0f const val DEFAULT_FADE_OUT_END = 1f @@ -99,7 +102,7 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) : vec4 main(vec2 p) { float sparkleRing = soften(roundedBoxRing(p-in_center, in_size, in_cornerRadius, in_thickness), in_blur); - float inside = soften(sdRoundedBox(p-in_center, in_size * 1.2, in_cornerRadius), + float inside = soften(sdRoundedBox(p-in_center, in_size * 1.25, in_cornerRadius), in_blur); float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175) * (1.-sparkleRing) * in_fadeSparkle; @@ -184,12 +187,17 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) : setFloatUniform("in_center", x, y) } - /** Max width of the ripple. */ - private var maxSize: PointF = PointF() - fun setMaxSize(width: Float, height: Float) { - maxSize.x = width - maxSize.y = height - } + /** + * Blur multipliers for the ripple. + * + * <p>It interpolates from [blurStart] to [blurEnd] based on the [progress]. Increase number to + * add more blur. + */ + var blurStart: Float = 1.25f + var blurEnd: Float = 0.5f + + /** Size of the ripple. */ + val rippleSize = RippleSize() /** * Linear progress of the ripple. Float value between [0, 1]. @@ -209,15 +217,19 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) : /** Progress with Standard easing curve applied. */ private var progress: Float = 0.0f set(value) { - currentWidth = maxSize.x * value - currentHeight = maxSize.y * value - setFloatUniform("in_size", currentWidth, currentHeight) + field = value + + rippleSize.update(value) - setFloatUniform("in_thickness", maxSize.y * value * 0.5f) - // radius should not exceed width and height values. - setFloatUniform("in_cornerRadius", Math.min(maxSize.x, maxSize.y) * value) + setFloatUniform("in_size", rippleSize.currentWidth, rippleSize.currentHeight) + setFloatUniform("in_thickness", rippleSize.currentHeight * 0.5f) + // Corner radius is always max of the min between the current width and height. + setFloatUniform( + "in_cornerRadius", + Math.min(rippleSize.currentWidth, rippleSize.currentHeight) + ) - setFloatUniform("in_blur", MathUtils.lerp(1.25f, 0.5f, value)) + setFloatUniform("in_blur", MathUtils.lerp(blurStart, blurEnd, value)) } /** Play time since the start of the effect. */ @@ -264,12 +276,6 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) : setFloatUniform("in_pixelDensity", value) } - var currentWidth: Float = 0f - private set - - var currentHeight: Float = 0f - private set - /** Parameters that are used to fade in/ out of the sparkle ring. */ val sparkleRingFadeParams = FadeParams( @@ -342,4 +348,102 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) : /** The endpoint of the fade out, given that the animation goes from 0 to 1. */ var fadeOutEnd: Float = DEFAULT_FADE_OUT_END, ) + + /** + * Desired size of the ripple at a point t in [progress]. + * + * <p>Note that [progress] is curved and normalized. Below is an example usage: + * SizeAtProgress(t= 0f, width= 0f, height= 0f), SizeAtProgress(t= 0.2f, width= 500f, height= + * 700f), SizeAtProgress(t= 1f, width= 100f, height= 300f) + * + * <p>For simple ripple effects, you will want to use [setMaxSize] as it is translated into: + * SizeAtProgress(t= 0f, width= 0f, height= 0f), SizeAtProgress(t= 1f, width= maxWidth, height= + * maxHeight) + */ + data class SizeAtProgress( + /** Time t in [0,1] progress range. */ + var t: Float, + /** Target width size of the ripple at time [t]. */ + var width: Float, + /** Target height size of the ripple at time [t]. */ + var height: Float + ) + + /** Updates and stores the ripple size. */ + inner class RippleSize { + @VisibleForTesting var sizes = mutableListOf<SizeAtProgress>() + @VisibleForTesting var currentSizeIndex = 0 + @VisibleForTesting val initialSize = SizeAtProgress(0f, 0f, 0f) + + var currentWidth: Float = 0f + private set + var currentHeight: Float = 0f + private set + + /** + * Sets the max size of the ripple. + * + * <p>Use this if the ripple shape simply changes linearly. + */ + fun setMaxSize(width: Float, height: Float) { + setSizeAtProgresses(initialSize, SizeAtProgress(1f, width, height)) + } + + /** + * Sets the list of [sizes]. + * + * <p>Note that setting this clears the existing sizes. + */ + fun setSizeAtProgresses(vararg sizes: SizeAtProgress) { + // Reset everything. + this.sizes.clear() + currentSizeIndex = 0 + + this.sizes.addAll(sizes) + this.sizes.sortBy { it.t } + } + + /** + * Updates the current ripple size based on the progress. + * + * <p>Should be called when progress updates. + */ + fun update(progress: Float) { + val targetIndex = updateTargetIndex(progress) + val prevIndex = Math.max(targetIndex - 1, 0) + + val targetSize = sizes[targetIndex] + val prevSize = sizes[prevIndex] + + val subProgress = subProgress(prevSize.t, targetSize.t, progress) + + currentWidth = targetSize.width * subProgress + prevSize.width + currentHeight = targetSize.height * subProgress + prevSize.height + } + + private fun updateTargetIndex(progress: Float): Int { + if (sizes.isEmpty()) { + // It could be empty on init. + if (progress > 0f) { + Log.e( + TAG, + "Did you forget to set the ripple size? Use [setMaxSize] or " + + "[setSizeAtProgresses] before playing the animation." + ) + } + // If there's no size is set, we set everything to 0 and return early. + setSizeAtProgresses(initialSize) + return currentSizeIndex + } + + var candidate = sizes[currentSizeIndex] + + while (progress > candidate.t) { + currentSizeIndex = Math.min(currentSizeIndex + 1, sizes.size - 1) + candidate = sizes[currentSizeIndex] + } + + return currentSizeIndex + } + } } diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt index 3c9328c82b83..4c7c43548016 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt @@ -45,12 +45,8 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a var duration: Long = 1750 - private var maxWidth: Float = 0.0f - private var maxHeight: Float = 0.0f fun setMaxSize(maxWidth: Float, maxHeight: Float) { - this.maxWidth = maxWidth - this.maxHeight = maxHeight - rippleShader.setMaxSize(maxWidth, maxHeight) + rippleShader.rippleSize.setMaxSize(maxWidth, maxHeight) } private var centerX: Float = 0.0f @@ -84,6 +80,106 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a ripplePaint.shader = rippleShader } + /** + * Sets the fade parameters for the base ring. + * + * <p>Base ring indicates a blurred ring below the sparkle ring. See + * [RippleShader.baseRingFadeParams]. + */ + @JvmOverloads + fun setBaseRingFadeParams( + fadeInStart: Float = rippleShader.baseRingFadeParams.fadeInStart, + fadeInEnd: Float = rippleShader.baseRingFadeParams.fadeInEnd, + fadeOutStart: Float = rippleShader.baseRingFadeParams.fadeOutStart, + fadeOutEnd: Float = rippleShader.baseRingFadeParams.fadeOutEnd + ) { + setFadeParams( + rippleShader.baseRingFadeParams, + fadeInStart, + fadeInEnd, + fadeOutStart, + fadeOutEnd + ) + } + + /** + * Sets the fade parameters for the sparkle ring. + * + * <p>Sparkle ring refers to the ring that's drawn on top of the base ring. See + * [RippleShader.sparkleRingFadeParams]. + */ + @JvmOverloads + fun setSparkleRingFadeParams( + fadeInStart: Float = rippleShader.sparkleRingFadeParams.fadeInStart, + fadeInEnd: Float = rippleShader.sparkleRingFadeParams.fadeInEnd, + fadeOutStart: Float = rippleShader.sparkleRingFadeParams.fadeOutStart, + fadeOutEnd: Float = rippleShader.sparkleRingFadeParams.fadeOutEnd + ) { + setFadeParams( + rippleShader.sparkleRingFadeParams, + fadeInStart, + fadeInEnd, + fadeOutStart, + fadeOutEnd + ) + } + + /** + * Sets the fade parameters for the center fill. + * + * <p>One common use case is set all the params to 1, which completely removes the center fill. + * See [RippleShader.centerFillFadeParams]. + */ + @JvmOverloads + fun setCenterFillFadeParams( + fadeInStart: Float = rippleShader.centerFillFadeParams.fadeInStart, + fadeInEnd: Float = rippleShader.centerFillFadeParams.fadeInEnd, + fadeOutStart: Float = rippleShader.centerFillFadeParams.fadeOutStart, + fadeOutEnd: Float = rippleShader.centerFillFadeParams.fadeOutEnd + ) { + setFadeParams( + rippleShader.centerFillFadeParams, + fadeInStart, + fadeInEnd, + fadeOutStart, + fadeOutEnd + ) + } + + private fun setFadeParams( + fadeParams: RippleShader.FadeParams, + fadeInStart: Float, + fadeInEnd: Float, + fadeOutStart: Float, + fadeOutEnd: Float + ) { + with(fadeParams) { + this.fadeInStart = fadeInStart + this.fadeInEnd = fadeInEnd + this.fadeOutStart = fadeOutStart + this.fadeOutEnd = fadeOutEnd + } + } + + /** + * Sets blur multiplier at start and end of the progress. + * + * <p>It interpolates between [start] and [end]. No need to set it if using default blur. + */ + fun setBlur(start: Float, end: Float) { + rippleShader.blurStart = start + rippleShader.blurEnd = end + } + + /** + * Sets the list of [RippleShader.SizeAtProgress]. + * + * <p>Note that this clears the list before it sets with the new data. + */ + fun setSizeAtProgresses(vararg targetSizes: RippleShader.SizeAtProgress) { + rippleShader.rippleSize.setSizeAtProgresses(*targetSizes) + } + @JvmOverloads fun startRipple(onAnimationEnd: Runnable? = null) { if (animator.isRunning) { @@ -133,13 +229,13 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a } // To reduce overdraw, we mask the effect to a circle or a rectangle that's bigger than the // active effect area. Values here should be kept in sync with the animation implementation - // in the ripple shader. (Twice bigger) + // in the ripple shader. if (rippleShape == RippleShape.CIRCLE) { - val maskRadius = rippleShader.currentWidth + val maskRadius = rippleShader.rippleSize.currentWidth canvas.drawCircle(centerX, centerY, maskRadius, ripplePaint) - } else { - val maskWidth = rippleShader.currentWidth * 2 - val maskHeight = rippleShader.currentHeight * 2 + } else if (rippleShape == RippleShape.ELLIPSE) { + val maskWidth = rippleShader.rippleSize.currentWidth * 2 + val maskHeight = rippleShader.rippleSize.currentHeight * 2 canvas.drawRect( /* left= */ centerX - maskWidth, /* top= */ centerY - maskHeight, @@ -147,6 +243,10 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a /* bottom= */ centerY + maskHeight, ripplePaint ) + } else { // RippleShape.RoundedBox + // No masking for the rounded box, as it has more blur which requires larger bounds. + // Masking creates sharp bounds even when the masking is 4 times bigger. + canvas.drawPaint(ripplePaint) } } } diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt index 8b2f46648fef..78898932249b 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt @@ -50,9 +50,9 @@ class SdfShaderLibrary { float roundedBoxRing(vec2 p, vec2 size, float cornerRadius, float borderThickness) { - float outerRoundBox = sdRoundedBox(p, size, cornerRadius); - float innerRoundBox = sdRoundedBox(p, size - vec2(borderThickness), - cornerRadius - borderThickness); + float outerRoundBox = sdRoundedBox(p, size + vec2(borderThickness), + cornerRadius + borderThickness); + float innerRoundBox = sdRoundedBox(p, size, cornerRadius); return subtract(outerRoundBox, innerRoundBox); } """ @@ -69,10 +69,13 @@ class SdfShaderLibrary { vec2 u = wh*p, v = wh*wh; - float U1 = u.y/2.0; float U5 = 4.0*U1; - float U2 = v.y-v.x; float U6 = 6.0*U1; - float U3 = u.x-U2; float U7 = 3.0*U3; + float U1 = u.y/2.0; + float U2 = v.y-v.x; + float U3 = u.x-U2; float U4 = u.x+U2; + float U5 = 4.0*U1; + float U6 = 6.0*U1; + float U7 = 3.0*U3; float t = 0.5; for (int i = 0; i < 3; i ++) { diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt index 1c2f38beb867..ab4aca569bd4 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt @@ -113,7 +113,7 @@ interface ClockEvents { fun onColorPaletteChanged(resources: Resources) {} /** Call whenever the weather data should update */ - fun onWeatherDataChanged(data: Weather) {} + fun onWeatherDataChanged(data: WeatherData) {} } /** Methods which trigger various clock animations */ diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/Weather.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/Weather.kt deleted file mode 100644 index 302f17556bc7..000000000000 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/Weather.kt +++ /dev/null @@ -1,85 +0,0 @@ -package com.android.systemui.plugins - -import android.os.Bundle - -class Weather(val conditions: WeatherStateIcon, val temperature: Int, val isCelsius: Boolean) { - companion object { - private const val TAG = "Weather" - private const val WEATHER_STATE_ICON_KEY = "weather_state_icon_extra_key" - private const val TEMPERATURE_VALUE_KEY = "temperature_value_extra_key" - private const val TEMPERATURE_UNIT_KEY = "temperature_unit_extra_key" - private const val INVALID_TEMPERATURE = Int.MIN_VALUE - - fun fromBundle(extras: Bundle): Weather? { - val icon = - WeatherStateIcon.fromInt( - extras.getInt(WEATHER_STATE_ICON_KEY, WeatherStateIcon.UNKNOWN_ICON.id) - ) - if (icon == null || icon == WeatherStateIcon.UNKNOWN_ICON) { - return null - } - val temperature = extras.getInt(TEMPERATURE_VALUE_KEY, INVALID_TEMPERATURE) - if (temperature == INVALID_TEMPERATURE) { - return null - } - return Weather(icon, temperature, extras.getBoolean(TEMPERATURE_UNIT_KEY)) - } - } - - enum class WeatherStateIcon(val id: Int) { - UNKNOWN_ICON(0), - - // Clear, day & night. - SUNNY(1), - CLEAR_NIGHT(2), - - // Mostly clear, day & night. - MOSTLY_SUNNY(3), - MOSTLY_CLEAR_NIGHT(4), - - // Partly cloudy, day & night. - PARTLY_CLOUDY(5), - PARTLY_CLOUDY_NIGHT(6), - - // Mostly cloudy, day & night. - MOSTLY_CLOUDY_DAY(7), - MOSTLY_CLOUDY_NIGHT(8), - CLOUDY(9), - HAZE_FOG_DUST_SMOKE(10), - DRIZZLE(11), - HEAVY_RAIN(12), - SHOWERS_RAIN(13), - - // Scattered showers, day & night. - SCATTERED_SHOWERS_DAY(14), - SCATTERED_SHOWERS_NIGHT(15), - - // Isolated scattered thunderstorms, day & night. - ISOLATED_SCATTERED_TSTORMS_DAY(16), - ISOLATED_SCATTERED_TSTORMS_NIGHT(17), - STRONG_TSTORMS(18), - BLIZZARD(19), - BLOWING_SNOW(20), - FLURRIES(21), - HEAVY_SNOW(22), - - // Scattered snow showers, day & night. - SCATTERED_SNOW_SHOWERS_DAY(23), - SCATTERED_SNOW_SHOWERS_NIGHT(24), - SNOW_SHOWERS_SNOW(25), - MIXED_RAIN_HAIL_RAIN_SLEET(26), - SLEET_HAIL(27), - TORNADO(28), - TROPICAL_STORM_HURRICANE(29), - WINDY_BREEZY(30), - WINTRY_MIX_RAIN_SNOW(31); - - companion object { - fun fromInt(value: Int) = values().firstOrNull { it.id == value } - } - } - - override fun toString(): String { - return "$conditions $temperature${if (isCelsius) "C" else "F"}" - } -} diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt new file mode 100644 index 000000000000..52dfc55c105d --- /dev/null +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt @@ -0,0 +1,107 @@ +package com.android.systemui.plugins + +import android.os.Bundle +import androidx.annotation.VisibleForTesting + +class WeatherData +private constructor( + val description: String, + val state: WeatherStateIcon, + val useCelsius: Boolean, + val temperature: Int, +) { + companion object { + private const val TAG = "WeatherData" + @VisibleForTesting const val DESCRIPTION_KEY = "description" + @VisibleForTesting const val STATE_KEY = "state" + @VisibleForTesting const val USE_CELSIUS_KEY = "use_celsius" + @VisibleForTesting const val TEMPERATURE_KEY = "temperature" + private const val INVALID_WEATHER_ICON_STATE = -1 + + fun fromBundle(extras: Bundle): WeatherData? { + val description = extras.getString(DESCRIPTION_KEY) + val state = + WeatherStateIcon.fromInt(extras.getInt(STATE_KEY, INVALID_WEATHER_ICON_STATE)) + val temperature = readIntFromBundle(extras, TEMPERATURE_KEY) + return if ( + description == null || + state == null || + !extras.containsKey(USE_CELSIUS_KEY) || + temperature == null + ) + null + else + WeatherData( + description = description, + state = state, + useCelsius = extras.getBoolean(USE_CELSIUS_KEY), + temperature = temperature + ) + } + + private fun readIntFromBundle(extras: Bundle, key: String): Int? = + try { + extras.getString(key).toInt() + } catch (e: Exception) { + null + } + } + + enum class WeatherStateIcon(val id: Int) { + UNKNOWN_ICON(0), + + // Clear, day & night. + SUNNY(1), + CLEAR_NIGHT(2), + + // Mostly clear, day & night. + MOSTLY_SUNNY(3), + MOSTLY_CLEAR_NIGHT(4), + + // Partly cloudy, day & night. + PARTLY_CLOUDY(5), + PARTLY_CLOUDY_NIGHT(6), + + // Mostly cloudy, day & night. + MOSTLY_CLOUDY_DAY(7), + MOSTLY_CLOUDY_NIGHT(8), + CLOUDY(9), + HAZE_FOG_DUST_SMOKE(10), + DRIZZLE(11), + HEAVY_RAIN(12), + SHOWERS_RAIN(13), + + // Scattered showers, day & night. + SCATTERED_SHOWERS_DAY(14), + SCATTERED_SHOWERS_NIGHT(15), + + // Isolated scattered thunderstorms, day & night. + ISOLATED_SCATTERED_TSTORMS_DAY(16), + ISOLATED_SCATTERED_TSTORMS_NIGHT(17), + STRONG_TSTORMS(18), + BLIZZARD(19), + BLOWING_SNOW(20), + FLURRIES(21), + HEAVY_SNOW(22), + + // Scattered snow showers, day & night. + SCATTERED_SNOW_SHOWERS_DAY(23), + SCATTERED_SNOW_SHOWERS_NIGHT(24), + SNOW_SHOWERS_SNOW(25), + MIXED_RAIN_HAIL_RAIN_SLEET(26), + SLEET_HAIL(27), + TORNADO(28), + TROPICAL_STORM_HURRICANE(29), + WINDY_BREEZY(30), + WINTRY_MIX_RAIN_SNOW(31); + + companion object { + fun fromInt(value: Int) = values().firstOrNull { it.id == value } + } + } + + override fun toString(): String { + val unit = if (useCelsius) "C" else "F" + return "$state (\"$description\") $temperature°$unit" + } +} diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index e6ac59e6b106..464ce0333fd1 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -479,6 +479,8 @@ <string name="accessibility_desc_notification_shade">Notification shade.</string> <!-- Content description for the quick settings panel (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_desc_quick_settings">Quick settings.</string> + <!-- Content description for the split notification shade that also includes QS (not shown on the screen). [CHAR LIMIT=NONE] --> + <string name="accessibility_desc_qs_notification_shade">Quick settings and Notification shade.</string> <!-- Content description for the lock screen (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_desc_lock_screen">Lock screen.</string> <!-- Content description for the work profile lock screen. This prevents work profile apps from being used, but personal apps can be used as normal (not shown on the screen). [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt index c9a25b067b94..b6aae69ebad6 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt @@ -145,7 +145,7 @@ data class SysPropBooleanFlag constructor( override val namespace: String, override val default: Boolean = false, ) : SysPropFlag<Boolean> { - // TODO(b/223379190): Teamfood not supported for sysprop flags yet. + // TODO(b/268520433): Teamfood not supported for sysprop flags yet. override val teamfood: Boolean = false } diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 1254e1ee3311..92ee37310130 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -48,7 +48,7 @@ import com.android.systemui.plugins.ClockTickRate import com.android.systemui.plugins.log.LogBuffer import com.android.systemui.plugins.log.LogLevel.DEBUG import com.android.systemui.shared.regionsampling.RegionSampler -import com.android.systemui.plugins.Weather +import com.android.systemui.plugins.WeatherData import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback import com.android.systemui.statusbar.policy.ConfigurationController @@ -291,10 +291,8 @@ constructor( clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context)) } - override fun onWeatherDataChanged(data: Weather?) { - if (data != null) { - clock?.events?.onWeatherDataChanged(data) - } + override fun onWeatherDataChanged(data: WeatherData) { + clock?.events?.onWeatherDataChanged(data) } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index be9264dbfcf3..b2d4215b388a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -148,7 +148,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.dump.DumpsysTableLogger; import com.android.systemui.log.SessionTracker; -import com.android.systemui.plugins.Weather; +import com.android.systemui.plugins.WeatherData; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.system.TaskStackChangeListener; @@ -2765,7 +2765,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab boolean shouldListen = shouldListenKeyguardState && shouldListenUserState && shouldListenBouncerState && shouldListenUdfpsState - && shouldListenSideFpsState; + && shouldListenSideFpsState + && !isFingerprintLockedOut(); logListenerModelData( new KeyguardFingerprintListenModel( System.currentTimeMillis(), @@ -3285,12 +3286,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab /** * @param data the weather data (temp, conditions, unit) for weather clock to use */ - public void sendWeatherData(Weather data) { + public void sendWeatherData(WeatherData data) { mHandler.post(()-> { handleWeatherDataUpdate(data); }); } - private void handleWeatherDataUpdate(Weather data) { + private void handleWeatherDataUpdate(WeatherData data) { Assert.isMainThread(); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index 0da799e0964d..38f3e5065eec 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -23,7 +23,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.settingslib.fuelgauge.BatteryStatus; -import com.android.systemui.plugins.Weather; +import com.android.systemui.plugins.WeatherData; import com.android.systemui.statusbar.KeyguardIndicationController; import java.util.TimeZone; @@ -61,7 +61,7 @@ public class KeyguardUpdateMonitorCallback { /** * Called when receive new weather data. */ - public void onWeatherDataChanged(Weather data) { } + public void onWeatherDataChanged(WeatherData data) { } /** * Called when the carrier PLMN or SPN changes. diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt index 58b230f52e93..4b32759588e0 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt @@ -75,7 +75,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at } private var radius: Float = 0f set(value) { - rippleShader.setMaxSize(value * 2f, value * 2f) + rippleShader.rippleSize.setMaxSize(value * 2f, value * 2f) field = value } private var origin: Point = Point() @@ -364,7 +364,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at if (drawRipple) { canvas?.drawCircle(origin.x.toFloat(), origin.y.toFloat(), - rippleShader.currentWidth, ripplePaint) + rippleShader.rippleSize.currentWidth, ripplePaint) } } } diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java index 36103f8db8d8..cef415c8a490 100644 --- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java +++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java @@ -33,7 +33,9 @@ import android.widget.TextView; import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; +import com.android.systemui.shared.recents.utilities.Utilities; import com.android.systemui.surfaceeffects.ripple.RippleAnimationConfig; +import com.android.systemui.surfaceeffects.ripple.RippleShader; import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape; import com.android.systemui.surfaceeffects.ripple.RippleView; @@ -44,10 +46,12 @@ import java.text.NumberFormat; */ final class WirelessChargingLayout extends FrameLayout { private static final long CIRCLE_RIPPLE_ANIMATION_DURATION = 1500; - private static final long ROUNDED_BOX_RIPPLE_ANIMATION_DURATION = 1750; + private static final long ROUNDED_BOX_RIPPLE_ANIMATION_DURATION = 3000; private static final int SCRIM_COLOR = 0x4C000000; private static final int SCRIM_FADE_DURATION = 300; private RippleView mRippleView; + // This is only relevant to the rounded box ripple. + private RippleShader.SizeAtProgress[] mSizeAtProgressArray; WirelessChargingLayout(Context context, int transmittingBatteryLevel, int batteryLevel, boolean isDozing, RippleShape rippleShape) { @@ -125,20 +129,23 @@ final class WirelessChargingLayout extends FrameLayout { AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playTogether(textSizeAnimator, textOpacityAnimator, textFadeAnimator); - ValueAnimator scrimFadeInAnimator = ObjectAnimator.ofArgb(this, - "backgroundColor", Color.TRANSPARENT, SCRIM_COLOR); - scrimFadeInAnimator.setDuration(SCRIM_FADE_DURATION); - scrimFadeInAnimator.setInterpolator(Interpolators.LINEAR); - ValueAnimator scrimFadeOutAnimator = ObjectAnimator.ofArgb(this, - "backgroundColor", SCRIM_COLOR, Color.TRANSPARENT); - scrimFadeOutAnimator.setDuration(SCRIM_FADE_DURATION); - scrimFadeOutAnimator.setInterpolator(Interpolators.LINEAR); - scrimFadeOutAnimator.setStartDelay((rippleShape == RippleShape.CIRCLE - ? CIRCLE_RIPPLE_ANIMATION_DURATION : ROUNDED_BOX_RIPPLE_ANIMATION_DURATION) - - SCRIM_FADE_DURATION); - AnimatorSet animatorSetScrim = new AnimatorSet(); - animatorSetScrim.playTogether(scrimFadeInAnimator, scrimFadeOutAnimator); - animatorSetScrim.start(); + // For tablet docking animation, we don't play the background scrim. + if (!Utilities.isTablet(context)) { + ValueAnimator scrimFadeInAnimator = ObjectAnimator.ofArgb(this, + "backgroundColor", Color.TRANSPARENT, SCRIM_COLOR); + scrimFadeInAnimator.setDuration(SCRIM_FADE_DURATION); + scrimFadeInAnimator.setInterpolator(Interpolators.LINEAR); + ValueAnimator scrimFadeOutAnimator = ObjectAnimator.ofArgb(this, + "backgroundColor", SCRIM_COLOR, Color.TRANSPARENT); + scrimFadeOutAnimator.setDuration(SCRIM_FADE_DURATION); + scrimFadeOutAnimator.setInterpolator(Interpolators.LINEAR); + scrimFadeOutAnimator.setStartDelay((rippleShape == RippleShape.CIRCLE + ? CIRCLE_RIPPLE_ANIMATION_DURATION : ROUNDED_BOX_RIPPLE_ANIMATION_DURATION) + - SCRIM_FADE_DURATION); + AnimatorSet animatorSetScrim = new AnimatorSet(); + animatorSetScrim.playTogether(scrimFadeInAnimator, scrimFadeOutAnimator); + animatorSetScrim.start(); + } mRippleView = findViewById(R.id.wireless_charging_ripple); mRippleView.setupShader(rippleShape); @@ -147,7 +154,26 @@ final class WirelessChargingLayout extends FrameLayout { if (mRippleView.getRippleShape() == RippleShape.ROUNDED_BOX) { mRippleView.setDuration(ROUNDED_BOX_RIPPLE_ANIMATION_DURATION); mRippleView.setSparkleStrength(0.22f); - mRippleView.setColor(color, 28); + mRippleView.setColor(color, 102); // 40% of opacity. + mRippleView.setBaseRingFadeParams( + /* fadeInStart = */ 0f, + /* fadeInEnd = */ 0f, + /* fadeOutStart = */ 0.2f, + /* fadeOutEnd= */ 0.47f + ); + mRippleView.setSparkleRingFadeParams( + /* fadeInStart = */ 0f, + /* fadeInEnd = */ 0f, + /* fadeOutStart = */ 0.2f, + /* fadeOutEnd= */ 1f + ); + mRippleView.setCenterFillFadeParams( + /* fadeInStart = */ 0f, + /* fadeInEnd = */ 0f, + /* fadeOutStart = */ 0f, + /* fadeOutEnd= */ 0.2f + ); + mRippleView.setBlur(6.5f, 2.5f); } else { mRippleView.setDuration(CIRCLE_RIPPLE_ANIMATION_DURATION); mRippleView.setColor(color, RippleAnimationConfig.RIPPLE_DEFAULT_ALPHA); @@ -246,9 +272,7 @@ final class WirelessChargingLayout extends FrameLayout { int height = getMeasuredHeight(); mRippleView.setCenter(width * 0.5f, height * 0.5f); if (mRippleView.getRippleShape() == RippleShape.ROUNDED_BOX) { - // Those magic numbers are introduced for visual polish. This aspect ratio maps with - // the tablet's docking station. - mRippleView.setMaxSize(width * 1.36f, height * 1.46f); + updateRippleSizeAtProgressList(width, height); } else { float maxSize = Math.max(width, height); mRippleView.setMaxSize(maxSize, maxSize); @@ -257,4 +281,36 @@ final class WirelessChargingLayout extends FrameLayout { super.onLayout(changed, left, top, right, bottom); } + + private void updateRippleSizeAtProgressList(float width, float height) { + if (mSizeAtProgressArray == null) { + float maxSize = Math.max(width, height); + mSizeAtProgressArray = new RippleShader.SizeAtProgress[] { + // Those magic numbers are introduced for visual polish. It starts from a pill + // shape and expand to a full circle. + new RippleShader.SizeAtProgress(0f, 0f, 0f), + new RippleShader.SizeAtProgress(0.3f, width * 0.4f, height * 0.4f), + new RippleShader.SizeAtProgress(1f, maxSize, maxSize) + }; + } else { + // Same multipliers, just need to recompute with the new width and height. + RippleShader.SizeAtProgress first = mSizeAtProgressArray[0]; + first.setT(0f); + first.setWidth(0f); + first.setHeight(0f); + + RippleShader.SizeAtProgress second = mSizeAtProgressArray[1]; + second.setT(0.3f); + second.setWidth(width * 0.4f); + second.setHeight(height * 0.4f); + + float maxSize = Math.max(width, height); + RippleShader.SizeAtProgress last = mSizeAtProgressArray[2]; + last.setT(1f); + last.setWidth(maxSize); + last.setHeight(maxSize); + } + + mRippleView.setSizeAtProgresses(mSizeAtProgressArray); + } } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java index e9ac840cf4f4..f6b71336675f 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java @@ -402,7 +402,7 @@ public class BrightLineFalsingManager implements FalsingManager { || mAccessibilityManager.isTouchExplorationEnabled() || mDataProvider.isA11yAction() || (mFeatureFlags.isEnabled(Flags.FALSING_OFF_FOR_UNFOLDED) - && !mDataProvider.isFolded()); + && mDataProvider.isUnfolded()); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java index 5f347c158818..bc0f9950f865 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java @@ -380,8 +380,8 @@ public class FalsingDataProvider { return mBatteryController.isWirelessCharging() || mDockManager.isDocked(); } - public boolean isFolded() { - return Boolean.TRUE.equals(mFoldStateListener.getFolded()); + public boolean isUnfolded() { + return Boolean.FALSE.equals(mFoldStateListener.getFolded()); } /** Implement to be alerted abotu the beginning and ending of falsing tracking. */ diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java index 88c02b8aa790..13563dfd03f0 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java @@ -29,7 +29,7 @@ import com.android.systemui.dreams.DreamOverlayNotificationCountProvider; import com.android.systemui.dreams.DreamOverlayService; import com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule; import com.android.systemui.dreams.touch.scrim.dagger.ScrimModule; -import com.android.systemui.process.condition.UserProcessCondition; +import com.android.systemui.process.condition.SystemProcessCondition; import com.android.systemui.shared.condition.Condition; import com.android.systemui.shared.condition.Monitor; @@ -126,7 +126,7 @@ public interface DreamModule { @Binds @IntoSet @Named(DREAM_PRETEXT_CONDITIONS) - Condition bindsUserProcessCondition(UserProcessCondition condition); + Condition bindSystemProcessCondition(SystemProcessCondition condition); /** */ @Provides diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 9fe1739fab0f..6bc1eddf26f7 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -600,7 +600,8 @@ object Flags { @JvmField val UDFPS_ELLIPSE_DETECTION = unreleasedFlag(2202, "udfps_ellipse_detection") // 2300 - stylus - @JvmField val TRACK_STYLUS_EVER_USED = releasedFlag(2300, "track_stylus_ever_used") + @JvmField + val TRACK_STYLUS_EVER_USED = unreleasedFlag(2300, "track_stylus_ever_used", teamfood = true) @JvmField val ENABLE_STYLUS_CHARGING_UI = unreleasedFlag(2301, "enable_stylus_charging_ui") @JvmField val ENABLE_USI_BATTERY_NOTIFICATIONS = unreleasedFlag(2302, "enable_usi_battery_notifications") diff --git a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt index 7acd3f3447dd..9b748d0a0eb2 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt @@ -57,6 +57,7 @@ class ServerFlagReaderImpl @Inject constructor( override fun onPropertiesChanged(properties: DeviceConfig.Properties) { if (isTestHarness) { Log.w(TAG, "Ignore server flag changes in Test Harness mode.") + return } if (properties.namespace != namespace) { return diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt index dc7a4f18adbc..0b57175defe7 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt @@ -88,6 +88,8 @@ data class SmartspaceMediaData( } } +/** Key to indicate whether this card should be used to re-show recent media */ +const val EXTRA_KEY_TRIGGER_RESUME = "SHOULD_TRIGGER_RESUME" /** Key for extras [SmartspaceMediaData.cardAction] indicating why the card was sent */ const val EXTRA_KEY_TRIGGER_SOURCE = "MEDIA_RECOMMENDATION_TRIGGER_SOURCE" /** Value for [EXTRA_KEY_TRIGGER_SOURCE] when the card is sent on headphone connection */ diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt index 27f7b9736807..97717a64ce26 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt @@ -23,6 +23,7 @@ import com.android.internal.annotations.VisibleForTesting import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.controls.models.player.MediaData +import com.android.systemui.media.controls.models.recommendation.EXTRA_KEY_TRIGGER_RESUME import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.media.controls.util.MediaUiEventLogger @@ -138,14 +139,23 @@ constructor( val sorted = userEntries.toSortedMap(compareBy { userEntries.get(it)?.lastActive ?: -1 }) val timeSinceActive = timeSinceActiveForMostRecentMedia(sorted) var smartspaceMaxAgeMillis = SMARTSPACE_MAX_AGE - data.cardAction?.let { - val smartspaceMaxAgeSeconds = it.extras.getLong(RESUMABLE_MEDIA_MAX_AGE_SECONDS_KEY, 0) + data.cardAction?.extras?.let { + val smartspaceMaxAgeSeconds = it.getLong(RESUMABLE_MEDIA_MAX_AGE_SECONDS_KEY, 0) if (smartspaceMaxAgeSeconds > 0) { smartspaceMaxAgeMillis = TimeUnit.SECONDS.toMillis(smartspaceMaxAgeSeconds) } } - val shouldReactivate = !hasActiveMedia() && hasAnyMedia() && data.isActive + // Check if smartspace has explicitly specified whether to re-activate resumable media. + // The default behavior is to trigger if the smartspace data is active. + val shouldTriggerResume = + if (data.cardAction?.extras?.containsKey(EXTRA_KEY_TRIGGER_RESUME) == true) { + data.cardAction.extras.getBoolean(EXTRA_KEY_TRIGGER_RESUME, true) + } else { + true + } + val shouldReactivate = + shouldTriggerResume && !hasActiveMedia() && hasAnyMedia() && data.isActive if (timeSinceActive < smartspaceMaxAgeMillis) { // It could happen there are existing active media resume cards, then we don't need to diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt index 4ff082ad6e06..0b0535df6228 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt @@ -98,7 +98,7 @@ class ReceiverChipRippleView(context: Context?, attrs: AttributeSet?) : RippleVi // Calculates the actual starting percentage according to ripple shader progress set method. // Check calculations in [RippleShader.progress] fun calculateStartingPercentage(newHeight: Float): Float { - val ratio = rippleShader.currentHeight / newHeight + val ratio = rippleShader.rippleSize.currentHeight / newHeight val remainingPercentage = (1 - ratio).toDouble().pow(1 / 3.toDouble()).toFloat() return 1 - remainingPercentage } diff --git a/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java index 245cf89a8337..27510720ae2f 100644 --- a/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java @@ -26,7 +26,10 @@ public class ProcessWrapper { @Inject public ProcessWrapper() {} - public int getUserHandleIdentifier() { - return android.os.Process.myUserHandle().getIdentifier(); + /** + * Returns {@code true} if System User is running the current process. + */ + public boolean isSystemUser() { + return android.os.Process.myUserHandle().isSystem(); } } diff --git a/packages/SystemUI/src/com/android/systemui/process/condition/UserProcessCondition.java b/packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java index 5a21ea075ea3..80fbf9115065 100644 --- a/packages/SystemUI/src/com/android/systemui/process/condition/UserProcessCondition.java +++ b/packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java @@ -17,29 +17,26 @@ package com.android.systemui.process.condition; import com.android.systemui.process.ProcessWrapper; -import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.condition.Condition; import javax.inject.Inject; /** - * {@link UserProcessCondition} provides a signal when the process handle belongs to the current - * user. + * {@link SystemProcessCondition} checks to make sure the current process is being ran by the + * System User. */ -public class UserProcessCondition extends Condition { +public class SystemProcessCondition extends Condition { private final ProcessWrapper mProcessWrapper; - private final UserTracker mUserTracker; @Inject - public UserProcessCondition(ProcessWrapper processWrapper, UserTracker userTracker) { + public SystemProcessCondition(ProcessWrapper processWrapper) { + super(); mProcessWrapper = processWrapper; - mUserTracker = userTracker; } @Override protected void start() { - updateCondition(mUserTracker.getUserId() - == mProcessWrapper.getUserHandleIdentifier()); + updateCondition(mProcessWrapper.isSystemUser()); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index 8ad102ece9b3..ae965d37557f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -51,9 +51,7 @@ import com.android.systemui.animation.Interpolators; import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.compose.ComposeFacade; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.media.controls.ui.MediaHost; -import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QS; import com.android.systemui.plugins.qs.QSContainerController; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -61,6 +59,7 @@ import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.dagger.QSFragmentComponent; import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder; import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel; +import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; @@ -88,14 +87,11 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca private final Rect mQsBounds = new Rect(); private final SysuiStatusBarStateController mStatusBarStateController; - private final FalsingManager mFalsingManager; private final KeyguardBypassController mBypassController; private boolean mQsExpanded; private boolean mHeaderAnimating; private boolean mStackScrollerOverscrolling; - private long mDelay; - private QSAnimator mQSAnimator; private HeightListener mPanelView; private QSSquishinessController mQSSquishinessController; @@ -116,8 +112,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca private final MediaHost mQqsMediaHost; private final QSFragmentComponent.Factory mQsComponentFactory; private final QSFragmentDisableFlagsLogger mQsFragmentDisableFlagsLogger; - private final QSTileHost mHost; - private final FeatureFlags mFeatureFlags; + private final QSLogger mLogger; private final FooterActionsController mFooterActionsController; private final FooterActionsViewModel.Factory mFooterActionsViewModelFactory; private final ListeningAndVisibilityLifecycleOwner mListeningAndVisibilityLifecycleOwner; @@ -150,11 +145,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca */ private boolean mTransitioningToFullShade; - /** - * Whether the next Quick settings - */ - private boolean mAnimateNextQsUpdate; - private final DumpManager mDumpManager; /** @@ -178,14 +168,13 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca @Inject public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler, - QSTileHost qsTileHost, SysuiStatusBarStateController statusBarStateController, CommandQueue commandQueue, @Named(QS_PANEL) MediaHost qsMediaHost, @Named(QUICK_QS_PANEL) MediaHost qqsMediaHost, KeyguardBypassController keyguardBypassController, QSFragmentComponent.Factory qsComponentFactory, QSFragmentDisableFlagsLogger qsFragmentDisableFlagsLogger, - FalsingManager falsingManager, DumpManager dumpManager, FeatureFlags featureFlags, + DumpManager dumpManager, QSLogger qsLogger, FooterActionsController footerActionsController, FooterActionsViewModel.Factory footerActionsViewModelFactory) { mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler; @@ -193,13 +182,11 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mQqsMediaHost = qqsMediaHost; mQsComponentFactory = qsComponentFactory; mQsFragmentDisableFlagsLogger = qsFragmentDisableFlagsLogger; + mLogger = qsLogger; commandQueue.observe(getLifecycle(), this); - mHost = qsTileHost; - mFalsingManager = falsingManager; mBypassController = keyguardBypassController; mStatusBarStateController = statusBarStateController; mDumpManager = dumpManager; - mFeatureFlags = featureFlags; mFooterActionsController = footerActionsController; mFooterActionsViewModelFactory = footerActionsViewModelFactory; mListeningAndVisibilityLifecycleOwner = new ListeningAndVisibilityLifecycleOwner(); @@ -716,8 +703,10 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca private void setAlphaAnimationProgress(float progress) { final View view = getView(); if (progress == 0 && view.getVisibility() != View.INVISIBLE) { + mLogger.logVisibility("QS fragment", View.INVISIBLE); view.setVisibility(View.INVISIBLE); } else if (progress > 0 && view.getVisibility() != View.VISIBLE) { + mLogger.logVisibility("QS fragment", View.VISIBLE); view.setVisibility((View.VISIBLE)); } view.setAlpha(interpolateAlphaAnimationProgress(progress)); @@ -914,7 +903,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca getView().getViewTreeObserver().removeOnPreDrawListener(this); getView().animate() .translationY(0f) - .setStartDelay(mDelay) .setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE) .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) .setListener(mAnimateHeaderSlidingInListener) 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 d32ef327e90e..23c41db6d5a6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt @@ -17,15 +17,14 @@ package com.android.systemui.qs.logging import android.service.quicksettings.Tile +import android.view.View import com.android.systemui.log.dagger.QSLog import com.android.systemui.plugins.log.ConstantStringsLogger import com.android.systemui.plugins.log.ConstantStringsLoggerImpl import com.android.systemui.plugins.log.LogBuffer -import com.android.systemui.plugins.log.LogLevel import com.android.systemui.plugins.log.LogLevel.DEBUG import com.android.systemui.plugins.log.LogLevel.ERROR import com.android.systemui.plugins.log.LogLevel.VERBOSE -import com.android.systemui.plugins.log.LogMessage import com.android.systemui.plugins.qs.QSTile import com.android.systemui.statusbar.StatusBarState import com.google.errorprone.annotations.CompileTimeConstant @@ -332,4 +331,25 @@ class QSLogger @Inject constructor(@QSLog private val buffer: LogBuffer) : else -> "wrong state" } } + + fun logVisibility(viewName: String, @View.Visibility visibility: Int) { + buffer.log( + TAG, + DEBUG, + { + str1 = viewName + str2 = toVisibilityString(visibility) + }, + { "$str1 visibility: $str2" } + ) + } + + private fun toVisibilityString(visibility: Int): String { + return when (visibility) { + View.VISIBLE -> "VISIBLE" + View.INVISIBLE -> "INVISIBLE" + View.GONE -> "GONE" + else -> "undefined" + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 1259f5eed87a..2175a3358396 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -2206,7 +2206,12 @@ public final class NotificationPanelViewController implements Dumpable { && mQsController.getFullyExpanded()) { // Upon initialisation when we are not layouted yet we don't want to announce that we // are fully expanded, hence the != 0.0f check. - return mResources.getString(R.string.accessibility_desc_quick_settings); + if (mSplitShadeEnabled) { + // In split shade, QS is expanded but it also shows notifications + return mResources.getString(R.string.accessibility_desc_qs_notification_shade); + } else { + return mResources.getString(R.string.accessibility_desc_quick_settings); + } } else if (mBarState == KEYGUARD) { return mResources.getString(R.string.accessibility_desc_lock_screen); } else { diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java index 1f0cbf9af51c..74a61a3efebe 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java @@ -16,7 +16,7 @@ package com.android.systemui.shade; -import static android.os.Trace.TRACE_TAG_ALWAYS; +import static android.os.Trace.TRACE_TAG_APP; import static android.view.WindowInsets.Type.systemBars; import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG; @@ -328,7 +328,7 @@ public class NotificationShadeWindowView extends FrameLayout { @Override public void requestLayout() { - Trace.instant(TRACE_TAG_ALWAYS, "NotificationShadeWindowView#requestLayout"); + Trace.instant(TRACE_TAG_APP, "NotificationShadeWindowView#requestLayout"); super.requestLayout(); } diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java index c0ef4c1a872a..d041212d24c7 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java @@ -835,6 +835,7 @@ public class QuickSettingsController { @VisibleForTesting void setExpandImmediate(boolean expandImmediate) { if (expandImmediate != mExpandImmediate) { + mShadeLog.logQsExpandImmediateChanged(expandImmediate); mExpandImmediate = expandImmediate; mShadeExpansionStateManager.notifyExpandImmediateChange(expandImmediate); } @@ -1377,6 +1378,9 @@ public class QuickSettingsController { } private void collapseOrExpandQs() { + if (mSplitShadeEnabled) { + return; // QS is always expanded in split shade + } onExpansionStarted(); if (getExpanded()) { flingQs(0, FLING_COLLAPSE, null, true); diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt index b28509e8fbf5..aa8c5b65e0fe 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt @@ -150,6 +150,17 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { ) } + fun logQsExpandImmediateChanged(newValue: Boolean) { + buffer.log( + TAG, + LogLevel.VERBOSE, + { + bool1 = newValue + }, + { "qsExpandImmediate=$bool1" } + ) + } + fun logQsExpansionChanged( message: String, qsExpanded: Boolean, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt index 6ef6165bcbb3..29510d00a8d4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt @@ -52,18 +52,19 @@ import com.android.systemui.plugins.BcSmartspaceDataPlugin import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView import com.android.systemui.plugins.FalsingManager +import com.android.systemui.plugins.WeatherData import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.settings.UserTracker import com.android.systemui.shared.regionsampling.RegionSampler import com.android.systemui.shared.regionsampling.UpdateColorCallback import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DATE_SMARTSPACE_DATA_PLUGIN import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.WEATHER_SMARTSPACE_DATA_PLUGIN -import com.android.systemui.plugins.Weather import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.util.concurrency.Execution import com.android.systemui.util.settings.SecureSettings +import com.android.systemui.util.time.SystemClock import java.io.PrintWriter import java.time.Instant import java.util.Optional @@ -81,6 +82,7 @@ constructor( private val smartspaceManager: SmartspaceManager, private val activityStarter: ActivityStarter, private val falsingManager: FalsingManager, + private val systemClock: SystemClock, private val secureSettings: SecureSettings, private val userTracker: UserTracker, private val contentResolver: ContentResolver, @@ -152,6 +154,18 @@ constructor( // The weather data plugin takes unfiltered targets and performs the filtering internally. weatherPlugin?.onTargetsAvailable(targets) + val now = Instant.ofEpochMilli(systemClock.currentTimeMillis()) + val weatherTarget = targets.find { t -> + t.featureType == SmartspaceTarget.FEATURE_WEATHER && + now.isAfter(Instant.ofEpochMilli(t.creationTimeMillis)) && + now.isBefore(Instant.ofEpochMilli(t.expiryTimeMillis)) + } + if (weatherTarget != null) { + val weatherData = WeatherData.fromBundle(weatherTarget.baseAction.extras) + if (weatherData != null) { + keyguardUpdateMonitor.sendWeatherData(weatherData) + } + } val filteredTargets = targets.filter(::filterSmartspaceTarget) plugin?.onTargetsAvailable(filteredTargets) @@ -173,17 +187,6 @@ constructor( } isContentUpdatedOnce = true } - - val now = Instant.now() - val weatherTarget = targets.find { t -> - t.featureType == SmartspaceTarget.FEATURE_WEATHER && - now.isAfter(Instant.ofEpochMilli(t.creationTimeMillis)) && - now.isBefore(Instant.ofEpochMilli(t.expiryTimeMillis)) - } - if (weatherTarget != null) { - val weatherData = Weather.fromBundle(weatherTarget.baseAction.extras) - keyguardUpdateMonitor.sendWeatherData(weatherData) - } } private val userTrackerCallback = object : UserTracker.Callback { 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 df35c9e6832a..aa9a6c2c4cc6 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 @@ -164,6 +164,11 @@ public class NotifCollection implements Dumpable, PipelineDumpable { private Queue<NotifEvent> mEventQueue = new ArrayDeque<>(); + private final Runnable mRebuildListRunnable = () -> { + if (mBuildListener != null) { + mBuildListener.onBuildList(mReadOnlyNotificationSet, "asynchronousUpdate"); + } + }; private boolean mAttached = false; private boolean mAmDispatchingToOtherCode; @@ -458,7 +463,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable { int modificationType) { Assert.isMainThread(); mEventQueue.add(new ChannelChangedEvent(pkgName, user, channel, modificationType)); - dispatchEventsAndRebuildList("onNotificationChannelModified"); + dispatchEventsAndAsynchronouslyRebuildList(); } private void onNotificationsInitialized() { @@ -613,15 +618,39 @@ public class NotifCollection implements Dumpable, PipelineDumpable { private void dispatchEventsAndRebuildList(String reason) { Trace.beginSection("NotifCollection.dispatchEventsAndRebuildList"); + if (mMainHandler.hasCallbacks(mRebuildListRunnable)) { + mMainHandler.removeCallbacks(mRebuildListRunnable); + } + + dispatchEvents(); + + if (mBuildListener != null) { + mBuildListener.onBuildList(mReadOnlyNotificationSet, reason); + } + Trace.endSection(); + } + + private void dispatchEventsAndAsynchronouslyRebuildList() { + Trace.beginSection("NotifCollection.dispatchEventsAndAsynchronouslyRebuildList"); + + dispatchEvents(); + + if (!mMainHandler.hasCallbacks(mRebuildListRunnable)) { + mMainHandler.postDelayed(mRebuildListRunnable, 1000L); + } + + Trace.endSection(); + } + + private void dispatchEvents() { + Trace.beginSection("NotifCollection.dispatchEvents"); + mAmDispatchingToOtherCode = true; while (!mEventQueue.isEmpty()) { mEventQueue.remove().dispatchTo(mNotifCollectionListeners); } mAmDispatchingToOtherCode = false; - if (mBuildListener != null) { - mBuildListener.onBuildList(mReadOnlyNotificationSet, reason); - } Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt index cc1103de8ec0..abe067039cd9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt @@ -20,6 +20,7 @@ package com.android.systemui.statusbar.notification.logging import android.app.StatsManager import android.util.Log import android.util.StatsEvent +import androidx.annotation.VisibleForTesting import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -143,67 +144,70 @@ constructor( runBlocking(mainDispatcher) { traceSection("NML#getNotifications") { notificationPipeline.allNotifs } } +} - /** Aggregates memory usage data by package and style, returning sums. */ - private fun aggregateMemoryUsageData( - notificationMemoryUse: List<NotificationMemoryUsage> - ): Map<Pair<String, Int>, NotificationMemoryUseAtomBuilder> { - return notificationMemoryUse - .groupingBy { Pair(it.packageName, it.objectUsage.style) } - .aggregate { - _, - accumulator: NotificationMemoryUseAtomBuilder?, - element: NotificationMemoryUsage, - first -> - val use = - if (first) { - NotificationMemoryUseAtomBuilder(element.uid, element.objectUsage.style) - } else { - accumulator!! - } - - use.count++ - // If the views of the notification weren't inflated, the list of memory usage - // parameters will be empty. - if (element.viewUsage.isNotEmpty()) { - use.countWithInflatedViews++ +/** Aggregates memory usage data by package and style, returning sums. */ +@VisibleForTesting +internal fun aggregateMemoryUsageData( + notificationMemoryUse: List<NotificationMemoryUsage> +): Map<Pair<String, Int>, NotificationMemoryLogger.NotificationMemoryUseAtomBuilder> { + return notificationMemoryUse + .groupingBy { Pair(it.packageName, it.objectUsage.style) } + .aggregate { + _, + accumulator: NotificationMemoryLogger.NotificationMemoryUseAtomBuilder?, + element: NotificationMemoryUsage, + first -> + val use = + if (first) { + NotificationMemoryLogger.NotificationMemoryUseAtomBuilder( + element.uid, + element.objectUsage.style + ) + } else { + accumulator!! } - use.smallIconObject += element.objectUsage.smallIcon - if (element.objectUsage.smallIcon > 0) { - use.smallIconBitmapCount++ - } + use.count++ + // If the views of the notification weren't inflated, the list of memory usage + // parameters will be empty. + if (element.viewUsage.isNotEmpty()) { + use.countWithInflatedViews++ + } - use.largeIconObject += element.objectUsage.largeIcon - if (element.objectUsage.largeIcon > 0) { - use.largeIconBitmapCount++ - } + use.smallIconObject += element.objectUsage.smallIcon + if (element.objectUsage.smallIcon > 0) { + use.smallIconBitmapCount++ + } - use.bigPictureObject += element.objectUsage.bigPicture - if (element.objectUsage.bigPicture > 0) { - use.bigPictureBitmapCount++ - } + use.largeIconObject += element.objectUsage.largeIcon + if (element.objectUsage.largeIcon > 0) { + use.largeIconBitmapCount++ + } - use.extras += element.objectUsage.extras - use.extenders += element.objectUsage.extender - - // Use totals count which are more accurate when aggregated - // in this manner. - element.viewUsage - .firstOrNull { vu -> vu.viewType == ViewType.TOTAL } - ?.let { - use.smallIconViews += it.smallIcon - use.largeIconViews += it.largeIcon - use.systemIconViews += it.systemIcons - use.styleViews += it.style - use.customViews += it.style - use.softwareBitmaps += it.softwareBitmapsPenalty - } - - return@aggregate use + use.bigPictureObject += element.objectUsage.bigPicture + if (element.objectUsage.bigPicture > 0) { + use.bigPictureBitmapCount++ } - } - /** Rounds the passed value to the nearest KB - e.g. 700B rounds to 1KB. */ - private fun toKb(value: Int): Int = (value.toFloat() / 1024f).roundToInt() + use.extras += element.objectUsage.extras + use.extenders += element.objectUsage.extender + + // Use totals count which are more accurate when aggregated + // in this manner. + element.viewUsage + .firstOrNull { vu -> vu.viewType == ViewType.TOTAL } + ?.let { + use.smallIconViews += it.smallIcon + use.largeIconViews += it.largeIcon + use.systemIconViews += it.systemIcons + use.styleViews += it.style + use.customViews += it.customViews + use.softwareBitmaps += it.softwareBitmapsPenalty + } + + return@aggregate use + } } +/** Rounds the passed value to the nearest KB - e.g. 700B rounds to 1KB. */ +private fun toKb(value: Int): Int = (value.toFloat() / 1024f).roundToInt() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 1fb7eb5106e6..d2087ba6ca1c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -16,7 +16,7 @@ package com.android.systemui.statusbar.notification.stack; -import static android.os.Trace.TRACE_TAG_ALWAYS; +import static android.os.Trace.TRACE_TAG_APP; import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING; import static com.android.internal.jank.InteractionJankMonitor.CUJ_SHADE_CLEAR_ALL; @@ -1121,7 +1121,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @Override public void requestLayout() { - Trace.instant(TRACE_TAG_ALWAYS, "NotificationStackScrollLayout#requestLayout"); + Trace.instant(TRACE_TAG_APP, "NotificationStackScrollLayout#requestLayout"); super.requestLayout(); } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 52eef421eb8c..b847d67b448e 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -1398,6 +1398,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, @Override public void onAnimationCancel(@NonNull Animator animation) { mInteractionJankMonitor.cancel(CUJ_VOLUME_CONTROL); + Log.d(TAG, "onAnimationCancel"); } @Override @@ -1471,6 +1472,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mHandler.removeMessages(H.DISMISS); mHandler.removeMessages(H.SHOW); if (mIsAnimatingDismiss) { + Log.d(TAG, "dismissH: isAnimatingDismiss"); return; } mIsAnimatingDismiss = true; diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 6b80494a0c30..dc90e2d0a656 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -28,6 +28,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED; import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_AVAILABLE; +import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING; import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING_RESTARTING; import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT; import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT; @@ -1166,10 +1167,11 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { assertThat(mKeyguardUpdateMonitor.isFingerprintLockedOut()).isEqualTo(fpLocked); assertThat(mKeyguardUpdateMonitor.isFaceLockedOut()).isEqualTo(faceLocked); - // Fingerprint should be restarted once its cancelled bc on lockout, the device - // can still detectFingerprint (and if it's not locked out, fingerprint can listen) + // Fingerprint should be cancelled on lockout if going to lockout state, else + // restarted if it's not assertThat(mKeyguardUpdateMonitor.mFingerprintRunningState) - .isEqualTo(BIOMETRIC_STATE_CANCELLING_RESTARTING); + .isEqualTo(fpLocked + ? BIOMETRIC_STATE_CANCELLING : BIOMETRIC_STATE_CANCELLING_RESTARTING); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java index e4df754ec96a..8cb91304808d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java @@ -106,7 +106,7 @@ public class BrightLineClassifierTest extends SysuiTestCase { mClassifiers.add(mClassifierB); when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList); when(mKeyguardStateController.isShowing()).thenReturn(true); - when(mFalsingDataProvider.isFolded()).thenReturn(true); + when(mFalsingDataProvider.isUnfolded()).thenReturn(false); mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider, mMetricsLogger, mClassifiers, mSingleTapClassfier, mLongTapClassifier, mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java index ae38eb67c431..315774aad71a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java @@ -89,7 +89,7 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { mClassifiers.add(mClassifierA); when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList); when(mKeyguardStateController.isShowing()).thenReturn(true); - when(mFalsingDataProvider.isFolded()).thenReturn(true); + when(mFalsingDataProvider.isUnfolded()).thenReturn(false); mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider, mMetricsLogger, mClassifiers, mSingleTapClassifier, mLongTapClassifier, mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController, @@ -185,7 +185,7 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { @Test public void testSkipUnfolded() { assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue(); - when(mFalsingDataProvider.isFolded()).thenReturn(false); + when(mFalsingDataProvider.isUnfolded()).thenReturn(true); assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java index c451a1e754c9..2edc3d361316 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java @@ -324,12 +324,18 @@ public class FalsingDataProviderTest extends ClassifierTest { @Test public void test_FoldedState_Folded() { when(mFoldStateListener.getFolded()).thenReturn(true); - assertThat(mDataProvider.isFolded()).isTrue(); + assertThat(mDataProvider.isUnfolded()).isFalse(); } @Test public void test_FoldedState_Unfolded() { when(mFoldStateListener.getFolded()).thenReturn(false); - assertThat(mDataProvider.isFolded()).isFalse(); + assertThat(mDataProvider.isUnfolded()).isTrue(); + } + + @Test + public void test_FoldedState_NotFoldable() { + when(mFoldStateListener.getFolded()).thenReturn(null); + assertThat(mDataProvider.isUnfolded()).isFalse(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt index a12315b63fa7..2e9800606edf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt @@ -26,6 +26,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -58,4 +59,16 @@ class ServerFlagReaderImplTest : SysuiTestCase() { verify(changeListener).onChange(flag) } + + @Test + fun testChange_ignoresListenersDuringTest() { + val serverFlagReader = ServerFlagReaderImpl(NAMESPACE, deviceConfig, executor, true) + val flag = ReleasedFlag(1, "1", "test") + serverFlagReader.listenForChanges(listOf(flag), changeListener) + + deviceConfig.setProperty(NAMESPACE, "flag_override_1", "1", false) + executor.runAllReady() + + verify(changeListener, never()).onChange(flag) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt index eb6235ca8a6a..8532ffed85fb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.media.controls.pipeline import android.app.smartspace.SmartspaceAction +import android.os.Bundle import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest @@ -25,6 +26,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.media.controls.MediaTestUtils import com.android.systemui.media.controls.models.player.MediaData +import com.android.systemui.media.controls.models.recommendation.EXTRA_KEY_TRIGGER_RESUME import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData import com.android.systemui.media.controls.ui.MediaPlayerData import com.android.systemui.media.controls.util.MediaFlags @@ -75,6 +77,7 @@ class MediaDataFilterTest : SysuiTestCase() { @Mock private lateinit var smartspaceMediaRecommendationItem: SmartspaceAction @Mock private lateinit var logger: MediaUiEventLogger @Mock private lateinit var mediaFlags: MediaFlags + @Mock private lateinit var cardAction: SmartspaceAction private lateinit var mediaDataFilter: MediaDataFilter private lateinit var dataMain: MediaData @@ -122,6 +125,7 @@ class MediaDataFilterTest : SysuiTestCase() { whenever(smartspaceData.headphoneConnectionTimeMillis) .thenReturn(clock.currentTimeMillis() - 100) whenever(smartspaceData.instanceId).thenReturn(SMARTSPACE_INSTANCE_ID) + whenever(smartspaceData.cardAction).thenReturn(cardAction) } private fun setUser(id: Int) { @@ -574,4 +578,55 @@ class MediaDataFilterTest : SysuiTestCase() { verify(mediaDataManager, never()) .dismissSmartspaceRecommendation(eq(SMARTSPACE_KEY), anyLong()) } + + @Test + fun testSmartspaceLoaded_shouldTriggerResume_doesTrigger() { + // WHEN we have media that was recently played, but not currently active + val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) + mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) + verify(listener) + .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) + + // AND we get a smartspace signal with extra to trigger resume + val extras = Bundle().apply { putBoolean(EXTRA_KEY_TRIGGER_RESUME, true) } + whenever(cardAction.extras).thenReturn(extras) + mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) + + // THEN we should tell listeners to treat the media as active instead + val dataCurrentAndActive = dataCurrent.copy(active = true) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(KEY), + eq(dataCurrentAndActive), + eq(true), + eq(100), + eq(true) + ) + assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue() + // And send the smartspace data, but not prioritized + verify(listener) + .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false)) + } + + @Test + fun testSmartspaceLoaded_notShouldTriggerResume_doesNotTrigger() { + // WHEN we have media that was recently played, but not currently active + val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) + mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) + verify(listener) + .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) + + // AND we get a smartspace signal with extra to not trigger resume + val extras = Bundle().apply { putBoolean(EXTRA_KEY_TRIGGER_RESUME, false) } + whenever(cardAction.extras).thenReturn(extras) + mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) + + // THEN listeners are not updated to show media + verify(listener, never()) + .onMediaDataLoaded(eq(KEY), eq(KEY), any(), eq(true), eq(100), eq(true)) + // But the smartspace update is still propagated + verify(listener) + .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false)) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/process/condition/UserProcessConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java index 2293fc577029..fb7197706ddc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/process/condition/UserProcessConditionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java @@ -26,7 +26,6 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.process.ProcessWrapper; -import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.condition.Condition; import com.android.systemui.shared.condition.Monitor; import com.android.systemui.util.concurrency.FakeExecutor; @@ -41,10 +40,7 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @SmallTest -public class UserProcessConditionTest extends SysuiTestCase { - @Mock - UserTracker mUserTracker; - +public class SystemProcessConditionTest extends SysuiTestCase { @Mock ProcessWrapper mProcessWrapper; @@ -59,15 +55,14 @@ public class UserProcessConditionTest extends SysuiTestCase { } /** - * Verifies condition reports false when tracker reports a different user id than the - * identifier from the process handle. + * Verifies condition reports false when tracker reports the process is being ran by the + * system user. */ @Test - public void testConditionFailsWithDifferentIds() { + public void testConditionFailsWithNonSystemProcess() { - final Condition condition = new UserProcessCondition(mProcessWrapper, mUserTracker); - when(mProcessWrapper.getUserHandleIdentifier()).thenReturn(0); - when(mUserTracker.getUserId()).thenReturn(1); + final Condition condition = new SystemProcessCondition(mProcessWrapper); + when(mProcessWrapper.isSystemUser()).thenReturn(false); final Monitor monitor = new Monitor(mExecutor); @@ -81,15 +76,14 @@ public class UserProcessConditionTest extends SysuiTestCase { } /** - * Verifies condition reports false when tracker reports a different user id than the - * identifier from the process handle. + * Verifies condition reports true when tracker reports the process is being ran by the + * system user. */ @Test - public void testConditionSucceedsWithSameIds() { + public void testConditionSucceedsWithSystemProcess() { - final Condition condition = new UserProcessCondition(mProcessWrapper, mUserTracker); - when(mProcessWrapper.getUserHandleIdentifier()).thenReturn(0); - when(mUserTracker.getUserId()).thenReturn(0); + final Condition condition = new SystemProcessCondition(mProcessWrapper); + when(mProcessWrapper.isSystemUser()).thenReturn(true); final Monitor monitor = new Monitor(mExecutor); @@ -101,5 +95,4 @@ public class UserProcessConditionTest extends SysuiTestCase { verify(mCallback).onConditionsChanged(true); } - } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java index 4caa50fa847d..89606bf6be3d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java @@ -52,14 +52,13 @@ import com.android.systemui.R; import com.android.systemui.SysuiBaseFragmentTest; import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.media.controls.ui.MediaHost; -import com.android.systemui.plugins.FalsingManager; import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.dagger.QSFragmentComponent; import com.android.systemui.qs.external.TileServiceRequestController; import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder; import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel; +import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.StatusBarState; @@ -86,7 +85,6 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { @Mock private MediaHost mQSMediaHost; @Mock private MediaHost mQQSMediaHost; @Mock private KeyguardBypassController mBypassController; - @Mock private FalsingManager mFalsingManager; @Mock private TileServiceRequestController.Builder mTileServiceRequestControllerBuilder; @Mock private TileServiceRequestController mTileServiceRequestController; @Mock private QSCustomizerController mQsCustomizerController; @@ -503,11 +501,9 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { setUpMedia(); setUpOther(); - FakeFeatureFlags featureFlags = new FakeFeatureFlags(); return new QSFragment( new RemoteInputQuickSettingsDisabler( context, commandQueue, mock(ConfigurationController.class)), - mock(QSTileHost.class), mStatusBarStateController, commandQueue, mQSMediaHost, @@ -515,9 +511,8 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { mBypassController, mQsComponentFactory, mock(QSFragmentDisableFlagsLogger.class), - mFalsingManager, mock(DumpManager.class), - featureFlags, + mock(QSLogger.class), mock(FooterActionsController.class), mFooterActionsViewModelFactory); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java index 8601d6c0a357..04b372c4a361 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java @@ -45,6 +45,7 @@ import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.settings.UserTracker; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -53,6 +54,7 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest +@Ignore("b/269171747") public class ReduceBrightColorsTileTest extends SysuiTestCase { @Mock private QSTileHost mHost; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt index 7fdcfb210804..2de57051d4f2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.lockscreen +import android.app.smartspace.SmartspaceAction import android.app.smartspace.SmartspaceManager import android.app.smartspace.SmartspaceSession import android.app.smartspace.SmartspaceSession.OnTargetsAvailableListener @@ -26,6 +27,7 @@ import android.content.pm.UserInfo import android.database.ContentObserver import android.graphics.drawable.Drawable import android.net.Uri +import android.os.Bundle import android.os.Handler import android.os.UserHandle import android.provider.Settings @@ -43,6 +45,7 @@ import com.android.systemui.plugins.BcSmartspaceDataPlugin import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView import com.android.systemui.plugins.FalsingManager +import com.android.systemui.plugins.WeatherData import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener import com.android.systemui.settings.UserTracker @@ -54,6 +57,7 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceP import com.android.systemui.util.concurrency.FakeExecution import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argThat import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq import com.android.systemui.util.settings.SecureSettings @@ -69,6 +73,7 @@ import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.spy +import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import java.util.Optional @@ -76,6 +81,13 @@ import java.util.concurrent.Executor @SmallTest class LockscreenSmartspaceControllerTest : SysuiTestCase() { + companion object { + const val SMARTSPACE_TIME_TOO_EARLY = 1000L + const val SMARTSPACE_TIME_JUST_RIGHT = 4000L + const val SMARTSPACE_TIME_TOO_LATE = 9000L + const val SMARTSPACE_CREATION_TIME = 1234L + const val SMARTSPACE_EXPIRY_TIME = 5678L + } @Mock private lateinit var featureFlags: FeatureFlags @Mock @@ -224,6 +236,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { smartspaceManager, activityStarter, falsingManager, + clock, secureSettings, userTracker, contentResolver, @@ -529,6 +542,190 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { } @Test + fun testSessionListener_ifWeatherExtraMissing_thenWeatherDataNotSent() { + connectSession() + clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT) + // WHEN we receive a list of targets + val targets = listOf( + makeTarget(1, userHandlePrimary, isSensitive = true), + makeTarget(2, userHandlePrimary, featureType = SmartspaceTarget.FEATURE_WEATHER) + + ) + sessionListener.onTargetsAvailable(targets) + verify(keyguardUpdateMonitor, times(0)).sendWeatherData(any()) + } + + @Test + fun testSessionListener_ifWeatherExtraIsMissingValues_thenWeatherDataNotSent() { + connectSession() + + clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT) + // WHEN we receive a list of targets + val targets = listOf( + makeTarget(1, userHandlePrimary, isSensitive = true), + makeWeatherTargetWithExtras( + id = 2, + userHandle = userHandlePrimary, + description = null, + state = WeatherData.WeatherStateIcon.SUNNY.id, + temperature = "32", + useCelsius = null) + + ) + + sessionListener.onTargetsAvailable(targets) + + verify(keyguardUpdateMonitor, times(0)).sendWeatherData(any()) + } + + @Test + fun testSessionListener_ifTooEarly_thenWeatherDataNotSent() { + connectSession() + + clock.setCurrentTimeMillis(SMARTSPACE_TIME_TOO_EARLY) + // WHEN we receive a list of targets + val targets = listOf( + makeWeatherTargetWithExtras( + id = 1, + userHandle = userHandleManaged, + description = "Sunny", + state = WeatherData.WeatherStateIcon.SUNNY.id, + temperature = "32", + useCelsius = false) + ) + sessionListener.onTargetsAvailable(targets) + verify(keyguardUpdateMonitor, times(0)).sendWeatherData(any()) + } + + @Test + fun testSessionListener_ifOnTime_thenWeatherDataSent() { + connectSession() + + clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT) + // WHEN we receive a list of targets + val targets = listOf( + makeWeatherTargetWithExtras( + id = 1, + userHandle = userHandleManaged, + description = "Snow Showers", + state = WeatherData.WeatherStateIcon.SNOW_SHOWERS_SNOW.id, + temperature = "-1", + useCelsius = false) + ) + sessionListener.onTargetsAvailable(targets) + verify(keyguardUpdateMonitor).sendWeatherData(argThat { w -> + w.description == "Snow Showers" && + w.state == WeatherData.WeatherStateIcon.SNOW_SHOWERS_SNOW && + w.temperature == -1 && !w.useCelsius + }) + } + + @Test + fun testSessionListener_ifTooLate_thenWeatherDataNotSent() { + connectSession() + + clock.setCurrentTimeMillis(SMARTSPACE_TIME_TOO_LATE) + // WHEN we receive a list of targets + val targets = listOf( + makeWeatherTargetWithExtras( + id = 1, + userHandle = userHandleManaged, + description = "Sunny", + state = WeatherData.WeatherStateIcon.SUNNY.id, + temperature = "72", + useCelsius = false) + ) + sessionListener.onTargetsAvailable(targets) + verify(keyguardUpdateMonitor, times(0)).sendWeatherData(any()) + } + + @Test + fun testSessionListener_onlyFirstWeatherDataSent() { + connectSession() + + clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT) + // WHEN we receive a list of targets + val targets = listOf( + makeWeatherTargetWithExtras( + id = 1, + userHandle = userHandleManaged, + description = "Sunny", + state = WeatherData.WeatherStateIcon.SUNNY.id, + temperature = "72", + useCelsius = false), + makeWeatherTargetWithExtras( + id = 2, + userHandle = userHandleManaged, + description = "Showers", + state = WeatherData.WeatherStateIcon.SHOWERS_RAIN.id, + temperature = "62", + useCelsius = true) + ) + sessionListener.onTargetsAvailable(targets) + verify(keyguardUpdateMonitor).sendWeatherData(argThat { w -> + w.description == "Sunny" && + w.state == WeatherData.WeatherStateIcon.SUNNY && + w.temperature == 72 && !w.useCelsius + }) + } + + @Test + fun testSessionListener_ifDecouplingEnabled_weatherDataUpdates() { + `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true) + connectSession() + + clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT) + // WHEN we receive a list of targets + val targets = listOf( + makeTarget(1, userHandlePrimary, isSensitive = true), + makeTarget(2, userHandlePrimary), + makeTarget(3, userHandleManaged), + makeWeatherTargetWithExtras( + id = 4, + userHandle = userHandlePrimary, + description = "Flurries", + state = WeatherData.WeatherStateIcon.FLURRIES.id, + temperature = "0", + useCelsius = true) + ) + + sessionListener.onTargetsAvailable(targets) + + verify(keyguardUpdateMonitor).sendWeatherData(argThat { w -> + w.description == "Flurries" && + w.state == WeatherData.WeatherStateIcon.FLURRIES && + w.temperature == 0 && w.useCelsius + }) + } + + @Test + fun testSessionListener_ifDecouplingDisabled_weatherDataUpdates() { + `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(false) + connectSession() + + clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT) + // WHEN we receive a list of targets + val targets = listOf( + makeWeatherTargetWithExtras( + id = 1, + userHandle = userHandlePrimary, + description = "Sunny", + state = WeatherData.WeatherStateIcon.SUNNY.id, + temperature = "32", + useCelsius = false), + makeTarget(2, userHandlePrimary, isSensitive = true) + ) + + sessionListener.onTargetsAvailable(targets) + + verify(keyguardUpdateMonitor).sendWeatherData(argThat { w -> + w.description == "Sunny" && + w.state == WeatherData.WeatherStateIcon.SUNNY && + w.temperature == 32 && !w.useCelsius + }) + } + + @Test fun testSettingsAreReloaded() { // GIVEN a connected session where the privacy settings later flip to false connectSession() @@ -740,7 +937,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { return userInfo } - fun makeTarget( + private fun makeTarget( id: Int, userHandle: UserHandle, isSensitive: Boolean = false, @@ -755,6 +952,38 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { .build() } + private fun makeWeatherTargetWithExtras( + id: Int, + userHandle: UserHandle, + description: String?, + state: Int?, + temperature: String?, + useCelsius: Boolean? + ): SmartspaceTarget { + val mockWeatherBundle = mock(Bundle::class.java).apply { + `when`(getString(WeatherData.DESCRIPTION_KEY)).thenReturn(description) + if (state != null) + `when`(getInt(eq(WeatherData.STATE_KEY), any())).thenReturn(state) + `when`(getString(WeatherData.TEMPERATURE_KEY)).thenReturn(temperature) + `when`(containsKey(WeatherData.USE_CELSIUS_KEY)).thenReturn(useCelsius != null) + if (useCelsius != null) + `when`(getBoolean(WeatherData.USE_CELSIUS_KEY)).thenReturn(useCelsius) + } + + val mockBaseAction = mock(SmartspaceAction::class.java) + `when`(mockBaseAction.extras).thenReturn(mockWeatherBundle) + return SmartspaceTarget.Builder( + "targetWithWeatherExtras$id", + ComponentName("testpackage", "testclass$id"), + userHandle) + .setSensitive(false) + .setFeatureType(SmartspaceTarget.FEATURE_WEATHER) + .setBaseAction(mockBaseAction) + .setExpiryTimeMillis(SMARTSPACE_EXPIRY_TIME) + .setCreationTimeMillis(SMARTSPACE_CREATION_TIME) + .build() + } + private fun setAllowPrivateNotifications(user: UserHandle, value: Boolean) { `when`(secureSettings.getIntForUser( eq(PRIVATE_LOCKSCREEN_SETTING), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java index 94e3e6cb5f8e..edb2965eabfa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java @@ -105,6 +105,7 @@ import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.Spy; +import org.mockito.stubbing.Answer; import java.util.Arrays; import java.util.Collection; @@ -376,6 +377,90 @@ public class NotifCollectionTest extends SysuiTestCase { } @Test + public void testScheduleBuildNotificationListWhenChannelChanged() { + // GIVEN + final NotificationEntryBuilder neb = buildNotif(TEST_PACKAGE, 48); + final NotificationChannel channel = new NotificationChannel( + "channelId", + "channelName", + NotificationManager.IMPORTANCE_DEFAULT); + neb.setChannel(channel); + + final NotifEvent notif = mNoMan.postNotif(neb); + final NotificationEntry entry = mCollectionListener.getEntry(notif.key); + + when(mMainHandler.hasCallbacks(any())).thenReturn(false); + + clearInvocations(mBuildListener); + + // WHEN + mNotifHandler.onNotificationChannelModified(TEST_PACKAGE, + entry.getSbn().getUser(), channel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED); + + // THEN + verify(mMainHandler).postDelayed(any(), eq(1000L)); + } + + @Test + public void testCancelScheduledBuildNotificationListEventWhenNotifUpdatedSynchronously() { + // GIVEN + final NotificationEntry entry1 = buildNotif(TEST_PACKAGE, 1) + .setGroup(mContext, "group_1") + .build(); + final NotificationEntry entry2 = buildNotif(TEST_PACKAGE, 2) + .setGroup(mContext, "group_1") + .setContentTitle(mContext, "New version") + .build(); + final NotificationEntry entry3 = buildNotif(TEST_PACKAGE, 3) + .setGroup(mContext, "group_1") + .build(); + + final List<CoalescedEvent> entriesToBePosted = Arrays.asList( + new CoalescedEvent(entry1.getKey(), 0, entry1.getSbn(), entry1.getRanking(), null), + new CoalescedEvent(entry2.getKey(), 1, entry2.getSbn(), entry2.getRanking(), null), + new CoalescedEvent(entry3.getKey(), 2, entry3.getSbn(), entry3.getRanking(), null) + ); + + when(mMainHandler.hasCallbacks(any())).thenReturn(true); + + // WHEN + mNotifHandler.onNotificationBatchPosted(entriesToBePosted); + + // THEN + verify(mMainHandler).removeCallbacks(any()); + } + + @Test + public void testBuildNotificationListWhenChannelChanged() { + // GIVEN + final NotificationEntryBuilder neb = buildNotif(TEST_PACKAGE, 48); + final NotificationChannel channel = new NotificationChannel( + "channelId", + "channelName", + NotificationManager.IMPORTANCE_DEFAULT); + neb.setChannel(channel); + + final NotifEvent notif = mNoMan.postNotif(neb); + final NotificationEntry entry = mCollectionListener.getEntry(notif.key); + + when(mMainHandler.hasCallbacks(any())).thenReturn(false); + when(mMainHandler.postDelayed(any(), eq(1000L))).thenAnswer((Answer) invocation -> { + final Runnable runnable = invocation.getArgument(0); + runnable.run(); + return null; + }); + + clearInvocations(mBuildListener); + + // WHEN + mNotifHandler.onNotificationChannelModified(TEST_PACKAGE, + entry.getSbn().getUser(), channel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED); + + // THEN + verifyBuiltList(List.of(entry)); + } + + @Test public void testRankingsAreUpdatedForOtherNotifs() { // GIVEN a collection with one notif NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt index bd039031cecc..33a838ed5183 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt @@ -20,6 +20,7 @@ import android.app.Notification import android.app.StatsManager import android.graphics.Bitmap import android.graphics.drawable.Icon +import android.stats.sysui.NotificationEnums import android.testing.AndroidTestingRunner import android.util.StatsEvent import androidx.test.filters.SmallTest @@ -31,10 +32,12 @@ import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Expect import com.google.common.truth.Truth.assertThat import java.lang.RuntimeException import kotlinx.coroutines.Dispatchers import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock @@ -45,6 +48,8 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) class NotificationMemoryLoggerTest : SysuiTestCase() { + @Rule @JvmField val expect = Expect.create() + private val bgExecutor = FakeExecutor(FakeSystemClock()) private val immediate = Dispatchers.Main.immediate @@ -132,6 +137,123 @@ class NotificationMemoryLoggerTest : SysuiTestCase() { .isEqualTo(StatsManager.PULL_SKIP) } + @Test + fun aggregateMemoryUsageData_returnsCorrectlyAggregatedSamePackageData() { + val usage = getPresetMemoryUsages() + val aggregateUsage = aggregateMemoryUsageData(usage) + + assertThat(aggregateUsage).hasSize(3) + assertThat(aggregateUsage) + .containsKey(Pair("package 1", NotificationEnums.STYLE_BIG_PICTURE)) + + // Aggregated fields + val aggregatedData = + aggregateUsage[Pair("package 1", NotificationEnums.STYLE_BIG_PICTURE)]!! + val presetUsage1 = usage[0] + val presetUsage2 = usage[1] + assertAggregatedData( + aggregatedData, + 2, + 2, + smallIconObject = + presetUsage1.objectUsage.smallIcon + presetUsage2.objectUsage.smallIcon, + smallIconBitmapCount = 2, + largeIconObject = + presetUsage1.objectUsage.largeIcon + presetUsage2.objectUsage.largeIcon, + largeIconBitmapCount = 2, + bigPictureObject = + presetUsage1.objectUsage.bigPicture + presetUsage2.objectUsage.bigPicture, + bigPictureBitmapCount = 2, + extras = presetUsage1.objectUsage.extras + presetUsage2.objectUsage.extras, + extenders = presetUsage1.objectUsage.extender + presetUsage2.objectUsage.extender, + // Only totals need to be summarized. + smallIconViews = + presetUsage1.viewUsage[0].smallIcon + presetUsage2.viewUsage[0].smallIcon, + largeIconViews = + presetUsage1.viewUsage[0].largeIcon + presetUsage2.viewUsage[0].largeIcon, + systemIconViews = + presetUsage1.viewUsage[0].systemIcons + presetUsage2.viewUsage[0].systemIcons, + styleViews = presetUsage1.viewUsage[0].style + presetUsage2.viewUsage[0].style, + customViews = + presetUsage1.viewUsage[0].customViews + presetUsage2.viewUsage[0].customViews, + softwareBitmaps = + presetUsage1.viewUsage[0].softwareBitmapsPenalty + + presetUsage2.viewUsage[0].softwareBitmapsPenalty, + seenCount = 0 + ) + } + + @Test + fun aggregateMemoryUsageData_correctlySeparatesDifferentStyles() { + val usage = getPresetMemoryUsages() + val aggregateUsage = aggregateMemoryUsageData(usage) + + assertThat(aggregateUsage).hasSize(3) + assertThat(aggregateUsage) + .containsKey(Pair("package 1", NotificationEnums.STYLE_BIG_PICTURE)) + assertThat(aggregateUsage).containsKey(Pair("package 1", NotificationEnums.STYLE_BIG_TEXT)) + + // Different style should be separate + val separateStyleData = + aggregateUsage[Pair("package 1", NotificationEnums.STYLE_BIG_TEXT)]!! + val presetUsage = usage[2] + assertAggregatedData( + separateStyleData, + 1, + 1, + presetUsage.objectUsage.smallIcon, + 1, + presetUsage.objectUsage.largeIcon, + 1, + presetUsage.objectUsage.bigPicture, + 1, + presetUsage.objectUsage.extras, + presetUsage.objectUsage.extender, + presetUsage.viewUsage[0].smallIcon, + presetUsage.viewUsage[0].largeIcon, + presetUsage.viewUsage[0].systemIcons, + presetUsage.viewUsage[0].style, + presetUsage.viewUsage[0].customViews, + presetUsage.viewUsage[0].softwareBitmapsPenalty, + 0 + ) + } + + @Test + fun aggregateMemoryUsageData_correctlySeparatesDifferentProcess() { + val usage = getPresetMemoryUsages() + val aggregateUsage = aggregateMemoryUsageData(usage) + + assertThat(aggregateUsage).hasSize(3) + assertThat(aggregateUsage) + .containsKey(Pair("package 2", NotificationEnums.STYLE_BIG_PICTURE)) + + // Different UID/package should also be separate + val separatePackageData = + aggregateUsage[Pair("package 2", NotificationEnums.STYLE_BIG_PICTURE)]!! + val presetUsage = usage[3] + assertAggregatedData( + separatePackageData, + 1, + 1, + presetUsage.objectUsage.smallIcon, + 1, + presetUsage.objectUsage.largeIcon, + 1, + presetUsage.objectUsage.bigPicture, + 1, + presetUsage.objectUsage.extras, + presetUsage.objectUsage.extender, + presetUsage.viewUsage[0].smallIcon, + presetUsage.viewUsage[0].largeIcon, + presetUsage.viewUsage[0].systemIcons, + presetUsage.viewUsage[0].style, + presetUsage.viewUsage[0].customViews, + presetUsage.viewUsage[0].softwareBitmapsPenalty, + 0 + ) + } + private fun createLoggerWithNotifications( notifications: List<Notification> ): NotificationMemoryLogger { @@ -143,4 +265,182 @@ class NotificationMemoryLoggerTest : SysuiTestCase() { whenever(pipeline.allNotifs).thenReturn(notifications) return NotificationMemoryLogger(pipeline, statsManager, immediate, bgExecutor) } + + /** + * Short hand for making sure the passed NotificationMemoryUseAtomBuilder object contains + * expected values. + */ + private fun assertAggregatedData( + value: NotificationMemoryLogger.NotificationMemoryUseAtomBuilder, + count: Int, + countWithInflatedViews: Int, + smallIconObject: Int, + smallIconBitmapCount: Int, + largeIconObject: Int, + largeIconBitmapCount: Int, + bigPictureObject: Int, + bigPictureBitmapCount: Int, + extras: Int, + extenders: Int, + smallIconViews: Int, + largeIconViews: Int, + systemIconViews: Int, + styleViews: Int, + customViews: Int, + softwareBitmaps: Int, + seenCount: Int + ) { + expect.withMessage("count").that(value.count).isEqualTo(count) + expect + .withMessage("countWithInflatedViews") + .that(value.countWithInflatedViews) + .isEqualTo(countWithInflatedViews) + expect.withMessage("smallIconObject").that(value.smallIconObject).isEqualTo(smallIconObject) + expect + .withMessage("smallIconBitmapCount") + .that(value.smallIconBitmapCount) + .isEqualTo(smallIconBitmapCount) + expect.withMessage("largeIconObject").that(value.largeIconObject).isEqualTo(largeIconObject) + expect + .withMessage("largeIconBitmapCount") + .that(value.largeIconBitmapCount) + .isEqualTo(largeIconBitmapCount) + expect + .withMessage("bigPictureObject") + .that(value.bigPictureObject) + .isEqualTo(bigPictureObject) + expect + .withMessage("bigPictureBitmapCount") + .that(value.bigPictureBitmapCount) + .isEqualTo(bigPictureBitmapCount) + expect.withMessage("extras").that(value.extras).isEqualTo(extras) + expect.withMessage("extenders").that(value.extenders).isEqualTo(extenders) + expect.withMessage("smallIconViews").that(value.smallIconViews).isEqualTo(smallIconViews) + expect.withMessage("largeIconViews").that(value.largeIconViews).isEqualTo(largeIconViews) + expect.withMessage("systemIconViews").that(value.systemIconViews).isEqualTo(systemIconViews) + expect.withMessage("styleViews").that(value.styleViews).isEqualTo(styleViews) + expect.withMessage("customViews").that(value.customViews).isEqualTo(customViews) + expect.withMessage("softwareBitmaps").that(value.softwareBitmaps).isEqualTo(softwareBitmaps) + expect.withMessage("seenCount").that(value.seenCount).isEqualTo(seenCount) + } + + /** Generates a static set of [NotificationMemoryUsage] objects. */ + private fun getPresetMemoryUsages() = + listOf( + // A pair of notifications that have to be aggregated, same UID and style + NotificationMemoryUsage( + "package 1", + 384, + "key1", + Notification.Builder(context).setStyle(Notification.BigPictureStyle()).build(), + NotificationObjectUsage( + 23, + 45, + 67, + NotificationEnums.STYLE_BIG_PICTURE, + 12, + 483, + 4382, + true + ), + listOf( + NotificationViewUsage(ViewType.TOTAL, 493, 584, 4833, 584, 4888, 5843), + NotificationViewUsage( + ViewType.PRIVATE_CONTRACTED_VIEW, + 100, + 250, + 300, + 594, + 6000, + 5843 + ) + ) + ), + NotificationMemoryUsage( + "package 1", + 384, + "key2", + Notification.Builder(context).setStyle(Notification.BigPictureStyle()).build(), + NotificationObjectUsage( + 77, + 54, + 34, + NotificationEnums.STYLE_BIG_PICTURE, + 77, + 432, + 2342, + true + ), + listOf( + NotificationViewUsage(ViewType.TOTAL, 3245, 1234, 7653, 543, 765, 7655), + NotificationViewUsage( + ViewType.PRIVATE_CONTRACTED_VIEW, + 160, + 350, + 300, + 5544, + 66500, + 5433 + ) + ) + ), + // Different style is different aggregation + NotificationMemoryUsage( + "package 1", + 384, + "key2", + Notification.Builder(context).setStyle(Notification.BigTextStyle()).build(), + NotificationObjectUsage( + 77, + 54, + 34, + NotificationEnums.STYLE_BIG_TEXT, + 77, + 432, + 2342, + true + ), + listOf( + NotificationViewUsage(ViewType.TOTAL, 3245, 1234, 7653, 543, 765, 7655), + NotificationViewUsage( + ViewType.PRIVATE_CONTRACTED_VIEW, + 160, + 350, + 300, + 5544, + 66500, + 5433 + ) + ) + ), + // Different package is also different aggregation + NotificationMemoryUsage( + "package 2", + 684, + "key2", + Notification.Builder(context).setStyle(Notification.BigPictureStyle()).build(), + NotificationObjectUsage( + 32, + 654, + 234, + NotificationEnums.STYLE_BIG_PICTURE, + 211, + 776, + 435, + true + ), + listOf( + NotificationViewUsage(ViewType.TOTAL, 4355, 6543, 4322, 5435, 6546, 65485), + NotificationViewUsage( + ViewType.PRIVATE_CONTRACTED_VIEW, + 6546, + 7657, + 4353, + 6546, + 76575, + 54654 + ) + ) + ) + ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleShaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleShaderTest.kt new file mode 100644 index 000000000000..89cc18cc5d67 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleShaderTest.kt @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.surfaceeffects.ripple + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +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 + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class RippleShaderTest : SysuiTestCase() { + + private lateinit var rippleShader: RippleShader + + @Before + fun setup() { + rippleShader = RippleShader() + } + + @Test + fun setMaxSize_hasCorrectSizes() { + val expectedMaxWidth = 300f + val expectedMaxHeight = 500f + + rippleShader.rippleSize.setMaxSize(expectedMaxWidth, expectedMaxHeight) + + assertThat(rippleShader.rippleSize.sizes.size).isEqualTo(2) + assertThat(rippleShader.rippleSize.sizes[0]).isEqualTo(rippleShader.rippleSize.initialSize) + val maxSize = rippleShader.rippleSize.sizes[1] + assertThat(maxSize.t).isEqualTo(1f) + assertThat(maxSize.width).isEqualTo(expectedMaxWidth) + assertThat(maxSize.height).isEqualTo(expectedMaxHeight) + } + + @Test + fun setSizeAtProgresses_hasCorrectSizes() { + val expectedSize0 = RippleShader.SizeAtProgress(t = 0f, width = 100f, height = 100f) + val expectedSize1 = RippleShader.SizeAtProgress(t = 0.2f, width = 1500f, height = 1200f) + val expectedSize2 = RippleShader.SizeAtProgress(t = 0.4f, width = 200f, height = 70f) + + rippleShader.rippleSize.setSizeAtProgresses(expectedSize0, expectedSize1, expectedSize2) + + assertThat(rippleShader.rippleSize.sizes.size).isEqualTo(3) + assertThat(rippleShader.rippleSize.sizes[0]).isEqualTo(expectedSize0) + assertThat(rippleShader.rippleSize.sizes[1]).isEqualTo(expectedSize1) + assertThat(rippleShader.rippleSize.sizes[2]).isEqualTo(expectedSize2) + } + + @Test + fun setSizeAtProgresses_sizeListIsSortedByT() { + val expectedSize0 = RippleShader.SizeAtProgress(t = 0f, width = 100f, height = 100f) + val expectedSize1 = RippleShader.SizeAtProgress(t = 0.2f, width = 1500f, height = 1200f) + val expectedSize2 = RippleShader.SizeAtProgress(t = 0.4f, width = 200f, height = 70f) + val expectedSize3 = RippleShader.SizeAtProgress(t = 0.8f, width = 300f, height = 900f) + val expectedSize4 = RippleShader.SizeAtProgress(t = 1f, width = 500f, height = 300f) + + // Add them in unsorted order + rippleShader.rippleSize.setSizeAtProgresses( + expectedSize0, + expectedSize3, + expectedSize2, + expectedSize4, + expectedSize1 + ) + + assertThat(rippleShader.rippleSize.sizes.size).isEqualTo(5) + assertThat(rippleShader.rippleSize.sizes[0]).isEqualTo(expectedSize0) + assertThat(rippleShader.rippleSize.sizes[1]).isEqualTo(expectedSize1) + assertThat(rippleShader.rippleSize.sizes[2]).isEqualTo(expectedSize2) + assertThat(rippleShader.rippleSize.sizes[3]).isEqualTo(expectedSize3) + assertThat(rippleShader.rippleSize.sizes[4]).isEqualTo(expectedSize4) + } + + @Test + fun update_getsCorrectNextTargetSize() { + val expectedSize0 = RippleShader.SizeAtProgress(t = 0f, width = 100f, height = 100f) + val expectedSize1 = RippleShader.SizeAtProgress(t = 0.2f, width = 1500f, height = 1200f) + val expectedSize2 = RippleShader.SizeAtProgress(t = 0.4f, width = 200f, height = 70f) + val expectedSize3 = RippleShader.SizeAtProgress(t = 0.8f, width = 300f, height = 900f) + val expectedSize4 = RippleShader.SizeAtProgress(t = 1f, width = 500f, height = 300f) + + rippleShader.rippleSize.setSizeAtProgresses( + expectedSize0, + expectedSize1, + expectedSize2, + expectedSize3, + expectedSize4 + ) + + rippleShader.rippleSize.update(0.5f) + // Progress is between 0.4 and 0.8 (expectedSize3 and 4), so the index should be 3. + assertThat(rippleShader.rippleSize.currentSizeIndex).isEqualTo(3) + } + + @Test + fun update_sizeListIsEmpty_setsInitialSize() { + assertThat(rippleShader.rippleSize.sizes).isEmpty() + + rippleShader.rippleSize.update(0.3f) + + assertThat(rippleShader.rippleSize.sizes.size).isEqualTo(1) + assertThat(rippleShader.rippleSize.sizes[0]).isEqualTo(rippleShader.rippleSize.initialSize) + } +} diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 0589cfc0967b..6410142278b5 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -1218,6 +1218,10 @@ import java.util.concurrent.atomic.AtomicBoolean; sendILMsg(MSG_IL_BTA2DP_TIMEOUT, SENDMSG_QUEUE, a2dpCodec, address, delayMs); } + /*package*/ void setLeAudioTimeout(String address, int device, int delayMs) { + sendILMsg(MSG_IL_BTLEAUDIO_TIMEOUT, SENDMSG_QUEUE, device, address, delayMs); + } + /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) { synchronized (mDeviceStateLock) { mBtHelper.setAvrcpAbsoluteVolumeSupported(supported); @@ -1422,6 +1426,13 @@ import java.util.concurrent.atomic.AtomicBoolean; mDeviceInventory.onMakeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1); } break; + case MSG_IL_BTLEAUDIO_TIMEOUT: + // msg.obj == address of LE Audio device + synchronized (mDeviceStateLock) { + mDeviceInventory.onMakeLeAudioDeviceUnavailableNow( + (String) msg.obj, msg.arg1); + } + break; case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: final BluetoothDevice btDevice = (BluetoothDevice) msg.obj; synchronized (mDeviceStateLock) { @@ -1512,6 +1523,7 @@ import java.util.concurrent.atomic.AtomicBoolean; case MSG_I_BT_SERVICE_DISCONNECTED_PROFILE: if (msg.arg1 != BluetoothProfile.HEADSET) { synchronized (mDeviceStateLock) { + mBtHelper.onBtProfileDisconnected(msg.arg1); mDeviceInventory.onBtProfileDisconnected(msg.arg1); } } else { @@ -1648,11 +1660,14 @@ import java.util.concurrent.atomic.AtomicBoolean; // process set volume for Le Audio, obj is BleVolumeInfo private static final int MSG_II_SET_LE_AUDIO_OUT_VOLUME = 46; + private static final int MSG_IL_BTLEAUDIO_TIMEOUT = 49; + private static boolean isMessageHandledUnderWakelock(int msgId) { switch(msgId) { case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: case MSG_L_SET_BT_ACTIVE_DEVICE: case MSG_IL_BTA2DP_TIMEOUT: + case MSG_IL_BTLEAUDIO_TIMEOUT: case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: case MSG_TOGGLE_HDMI: case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT: @@ -1743,6 +1758,7 @@ import java.util.concurrent.atomic.AtomicBoolean; case MSG_L_SET_BT_ACTIVE_DEVICE: case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: case MSG_IL_BTA2DP_TIMEOUT: + case MSG_IL_BTLEAUDIO_TIMEOUT: case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: if (sLastDeviceConnectMsgTime >= time) { // add a little delay to make sure messages are ordered as expected diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index 35da73ef58c0..a74f4154eb85 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -374,7 +374,7 @@ public class AudioDeviceInventory { case BluetoothProfile.LE_AUDIO: case BluetoothProfile.LE_AUDIO_BROADCAST: if (switchToUnavailable) { - makeLeAudioDeviceUnavailable(address, btInfo.mAudioSystemDevice); + makeLeAudioDeviceUnavailableNow(address, btInfo.mAudioSystemDevice); } else if (switchToAvailable) { makeLeAudioDeviceAvailable(address, BtHelper.getName(btInfo.mDevice), streamType, btInfo.mVolume == -1 ? -1 : btInfo.mVolume * 10, @@ -486,6 +486,12 @@ public class AudioDeviceInventory { } } + /*package*/ void onMakeLeAudioDeviceUnavailableNow(String address, int device) { + synchronized (mDevicesLock) { + makeLeAudioDeviceUnavailableNow(address, device); + } + } + /*package*/ void onReportNewRoutes() { int n = mRoutesObservers.beginBroadcast(); if (n > 0) { @@ -883,10 +889,11 @@ public class AudioDeviceInventory { new MediaMetrics.Item(mMetricsId + "disconnectLeAudio") .record(); if (toRemove.size() > 0) { - final int delay = checkSendBecomingNoisyIntentInt(device, 0, + final int delay = checkSendBecomingNoisyIntentInt(device, + AudioService.CONNECTION_STATE_DISCONNECTED, AudioSystem.DEVICE_NONE); toRemove.stream().forEach(deviceAddress -> - makeLeAudioDeviceUnavailable(deviceAddress, device) + makeLeAudioDeviceUnavailableLater(deviceAddress, device, delay) ); } } @@ -1187,9 +1194,21 @@ public class AudioDeviceInventory { */ mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/, eventSource); - AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(device, address, name), + final int res = AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( + device, address, name), AudioSystem.DEVICE_STATE_AVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); + if (res != AudioSystem.AUDIO_STATUS_OK) { + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( + "APM failed to make available LE Audio device addr=" + address + + " error=" + res).printLog(TAG)); + // TODO: connection failed, stop here + // TODO: return; + } else { + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( + "LE Audio device addr=" + address + " now available").printLog(TAG)); + } + mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address), new DeviceInfo(device, name, address, AudioSystem.AUDIO_FORMAT_DEFAULT)); mDeviceBroker.postAccessoryPlugMediaUnmute(device); @@ -1210,11 +1229,23 @@ public class AudioDeviceInventory { } @GuardedBy("mDevicesLock") - private void makeLeAudioDeviceUnavailable(String address, int device) { + private void makeLeAudioDeviceUnavailableNow(String address, int device) { if (device != AudioSystem.DEVICE_NONE) { - AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(device, address), + final int res = AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( + device, address), AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); + + if (res != AudioSystem.AUDIO_STATUS_OK) { + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( + "APM failed to make unavailable LE Audio device addr=" + address + + " error=" + res).printLog(TAG)); + // TODO: failed to disconnect, stop here + // TODO: return; + } else { + AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent( + "LE Audio device addr=" + address + " made unavailable")).printLog(TAG)); + } mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address)); } @@ -1222,6 +1253,14 @@ public class AudioDeviceInventory { } @GuardedBy("mDevicesLock") + private void makeLeAudioDeviceUnavailableLater(String address, int device, int delayMs) { + // the device will be made unavailable later, so consider it disconnected right away + mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address)); + // send the delayed message to make the device unavailable later + mDeviceBroker.setLeAudioTimeout(address, device, delayMs); + } + + @GuardedBy("mDevicesLock") private void setCurrentAudioRouteNameIfPossible(String name, boolean fromA2dp) { synchronized (mCurAudioRoutes) { if (TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) { diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index 6cd42f87aede..691ce93b3f7b 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -279,7 +279,11 @@ public class BtHelper { } AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent( AudioServiceEvents.VolumeEvent.VOL_SET_AVRCP_VOL, index)); - mA2dp.setAvrcpAbsoluteVolume(index); + try { + mA2dp.setAvrcpAbsoluteVolume(index); + } catch (Exception e) { + Log.e(TAG, "Exception while changing abs volume", e); + } } /*package*/ synchronized @AudioSystem.AudioFormatNativeEnumForBtCodec int getA2dpCodec( @@ -287,7 +291,12 @@ public class BtHelper { if (mA2dp == null) { return AudioSystem.AUDIO_FORMAT_DEFAULT; } - final BluetoothCodecStatus btCodecStatus = mA2dp.getCodecStatus(device); + BluetoothCodecStatus btCodecStatus = null; + try { + btCodecStatus = mA2dp.getCodecStatus(device); + } catch (Exception e) { + Log.e(TAG, "Exception while getting status of " + device, e); + } if (btCodecStatus == null) { return AudioSystem.AUDIO_FORMAT_DEFAULT; } @@ -421,7 +430,11 @@ public class BtHelper { } AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent( AudioServiceEvents.VolumeEvent.VOL_SET_LE_AUDIO_VOL, index, maxIndex)); - mLeAudio.setVolume(volume); + try { + mLeAudio.setVolume(volume); + } catch (Exception e) { + Log.e(TAG, "Exception while setting LE volume", e); + } } /*package*/ synchronized void setHearingAidVolume(int index, int streamType, @@ -447,7 +460,11 @@ public class BtHelper { AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent( AudioServiceEvents.VolumeEvent.VOL_SET_HEARING_AID_VOL, index, gainDB)); } - mHearingAid.setVolume(gainDB); + try { + mHearingAid.setVolume(gainDB); + } catch (Exception e) { + Log.i(TAG, "Exception while setting hearing aid volume", e); + } } /*package*/ synchronized void onBroadcastScoConnectionState(int state) { @@ -472,7 +489,7 @@ public class BtHelper { } // @GuardedBy("AudioDeviceBroker.mSetModeLock") - @GuardedBy("AudioDeviceBroker.mDeviceStateLock") + //@GuardedBy("AudioDeviceBroker.mDeviceStateLock") /*package*/ synchronized void resetBluetoothSco() { mScoAudioState = SCO_STATE_INACTIVE; broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); @@ -487,6 +504,35 @@ public class BtHelper { mBluetoothHeadset = null; } + //@GuardedBy("AudioDeviceBroker.mDeviceStateLock") + /*package*/ synchronized void onBtProfileDisconnected(int profile) { + switch (profile) { + case BluetoothProfile.A2DP: + mA2dp = null; + break; + case BluetoothProfile.HEARING_AID: + mHearingAid = null; + break; + case BluetoothProfile.LE_AUDIO: + mLeAudio = null; + break; + + case BluetoothProfile.A2DP_SINK: + case BluetoothProfile.LE_AUDIO_BROADCAST: + // shouldn't be received here as profile doesn't involve BtHelper + Log.e(TAG, "onBtProfileDisconnected: Not a profile handled by BtHelper " + + BluetoothProfile.getProfileName(profile)); + break; + + default: + // Not a valid profile to disconnect + Log.e(TAG, "onBtProfileDisconnected: Not a valid profile to disconnect " + + BluetoothProfile.getProfileName(profile)); + break; + } + } + + @GuardedBy("AudioDeviceBroker.mDeviceStateLock") /*package*/ synchronized void onBtProfileConnected(int profile, BluetoothProfile proxy) { if (profile == BluetoothProfile.HEADSET) { onHeadsetProfileConnected((BluetoothHeadset) proxy); @@ -518,7 +564,7 @@ public class BtHelper { } // @GuardedBy("AudioDeviceBroker.mSetModeLock") - @GuardedBy("AudioDeviceBroker.mDeviceStateLock") + //@GuardedBy("AudioDeviceBroker.mDeviceStateLock") /*package*/ synchronized void onHeadsetProfileConnected(BluetoothHeadset headset) { // Discard timeout message mDeviceBroker.handleCancelFailureToConnectToBtHeadsetService(); @@ -672,7 +718,6 @@ public class BtHelper { public void onServiceConnected(int profile, BluetoothProfile proxy) { switch(profile) { case BluetoothProfile.A2DP: - case BluetoothProfile.A2DP_SINK: case BluetoothProfile.HEADSET: case BluetoothProfile.HEARING_AID: case BluetoothProfile.LE_AUDIO: @@ -682,6 +727,10 @@ public class BtHelper { mDeviceBroker.postBtProfileConnected(profile, proxy); break; + case BluetoothProfile.A2DP_SINK: + // no A2DP sink functionality handled by BtHelper + case BluetoothProfile.LE_AUDIO_BROADCAST: + // no broadcast functionality handled by BtHelper default: break; } @@ -690,14 +739,19 @@ public class BtHelper { switch (profile) { case BluetoothProfile.A2DP: - case BluetoothProfile.A2DP_SINK: case BluetoothProfile.HEADSET: case BluetoothProfile.HEARING_AID: case BluetoothProfile.LE_AUDIO: - case BluetoothProfile.LE_AUDIO_BROADCAST: + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( + "BT profile service: disconnecting " + + BluetoothProfile.getProfileName(profile) + " profile")); mDeviceBroker.postBtProfileDisconnected(profile); break; + case BluetoothProfile.A2DP_SINK: + // no A2DP sink functionality handled by BtHelper + case BluetoothProfile.LE_AUDIO_BROADCAST: + // no broadcast functionality handled by BtHelper default: break; } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java index 598e2b990ea5..94b67cedf86c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java @@ -452,6 +452,13 @@ public class FingerprintService extends SystemService { return -1; } + if (!Utils.isUserEncryptedOrLockdown(mLockPatternUtils, userId)) { + // If this happens, something in KeyguardUpdateMonitor is wrong. This should only + // ever be invoked when the user is encrypted or lockdown. + Slog.e(TAG, "detectFingerprint invoked when user is not encrypted or lockdown"); + return -1; + } + final Pair<Integer, ServiceProvider> provider = getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for detectFingerprint"); diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 4341634fb890..909c531c5e92 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -434,6 +434,8 @@ public final class DisplayManagerService extends SystemService { private boolean mIsDocked; private boolean mIsDreaming; + private boolean mBootCompleted = false; + private final BroadcastReceiver mIdleModeReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -573,6 +575,12 @@ public final class DisplayManagerService extends SystemService { } } } else if (phase == PHASE_BOOT_COMPLETED) { + synchronized (mSyncRoot) { + mBootCompleted = true; + for (int i = 0; i < mDisplayPowerControllers.size(); i++) { + mDisplayPowerControllers.valueAt(i).onBootCompleted(); + } + } mDisplayModeDirector.onBootCompleted(); mLogicalDisplayMapper.onBootCompleted(); } @@ -2680,7 +2688,7 @@ public final class DisplayManagerService extends SystemService { final DisplayPowerController displayPowerController = new DisplayPowerController( mContext, mDisplayPowerCallbacks, mPowerHandler, mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting, - () -> handleBrightnessChange(display), hbmMetadata); + () -> handleBrightnessChange(display), hbmMetadata, mBootCompleted); mDisplayPowerControllers.append(display.getDisplayIdLocked(), displayPowerController); } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 8025fa60ca2b..5ecc7f2b7bb8 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -133,6 +133,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private static final int MSG_BRIGHTNESS_RAMP_DONE = 12; private static final int MSG_STATSD_HBM_BRIGHTNESS = 13; private static final int MSG_SWITCH_USER = 14; + private static final int MSG_BOOT_COMPLETED = 15; private static final int PROXIMITY_UNKNOWN = -1; private static final int PROXIMITY_NEGATIVE = 0; @@ -506,6 +507,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private boolean mIsEnabled; private boolean mIsInTransition; + private boolean mBootCompleted; + /** * Creates the display power controller. */ @@ -513,7 +516,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call DisplayPowerCallbacks callbacks, Handler handler, SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay, BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting, - Runnable onBrightnessChangeRunnable, HighBrightnessModeMetadata hbmMetadata) { + Runnable onBrightnessChangeRunnable, HighBrightnessModeMetadata hbmMetadata, + boolean bootCompleted) { mLogicalDisplay = logicalDisplay; mDisplayId = mLogicalDisplay.getDisplayIdLocked(); final String displayIdStr = "[" + mDisplayId + "]"; @@ -655,6 +659,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mTemporaryAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT; mPendingAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT; + mBootCompleted = bootCompleted; } private void applyReduceBrightColorsSplineAdjustment() { @@ -1312,7 +1317,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call if (mScreenOffBrightnessSensorController != null) { mScreenOffBrightnessSensorController.setLightSensorEnabled(mUseAutoBrightness - && (state == Display.STATE_OFF || (state == Display.STATE_DOZE + && mIsEnabled && (state == Display.STATE_OFF || (state == Display.STATE_DOZE && !mAllowAutoBrightnessWhileDozingConfig))); } @@ -1370,7 +1375,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // Initialize things the first time the power state is changed. if (mustInitialize) { - initialize(state); + initialize(readyToUpdateDisplayState() ? state : Display.STATE_UNKNOWN); } // Animate the screen state change unless already animating. @@ -2050,7 +2055,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } } - if (!reportOnly && mPowerState.getScreenState() != state) { + if (!reportOnly && mPowerState.getScreenState() != state + && readyToUpdateDisplayState()) { Trace.traceCounter(Trace.TRACE_TAG_POWER, "ScreenState", state); // TODO(b/153319140) remove when we can get this from the above trace invocation SystemProperties.set("debug.tracing.screen_state", String.valueOf(state)); @@ -2497,6 +2503,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mBrightnessSetting.setBrightness(brightnessValue); } + void onBootCompleted() { + mHandler.obtainMessage(MSG_BOOT_COMPLETED).sendToTarget(); + } + private void updateScreenBrightnessSetting(float brightnessValue) { if (!isValidBrightnessValue(brightnessValue) || brightnessValue == mCurrentScreenBrightnessSetting) { @@ -2642,6 +2652,17 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } }; + /** + * Indicates whether the display state is ready to update. If this is the default display, we + * want to update it right away so that we can draw the boot animation on it. If it is not + * the default display, drawing the boot animation on it would look incorrect, so we need + * to wait until boot is completed. + * @return True if the display state is ready to update + */ + private boolean readyToUpdateDisplayState() { + return mDisplayId == Display.DEFAULT_DISPLAY || mBootCompleted; + } + public void dump(final PrintWriter pw) { synchronized (mLock) { pw.println(); @@ -3177,6 +3198,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call case MSG_SWITCH_USER: handleOnSwitchUser(msg.arg1); break; + + case MSG_BOOT_COMPLETED: + mBootCompleted = true; + updatePowerState(); + break; } } } diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index 6ea416b54811..468bf1719d89 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -510,7 +510,6 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser private boolean mUsbAccessoryConnected; private boolean mSourcePower; private boolean mSinkPower; - private boolean mConfigured; private boolean mAudioAccessoryConnected; private boolean mAudioAccessorySupported; @@ -543,7 +542,12 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser private final UsbPermissionManager mPermissionManager; private NotificationManager mNotificationManager; + /** + * Do not debounce for the first disconnect after resetUsbGadget. + */ + protected boolean mResetUsbGadgetDisableDebounce; protected boolean mConnected; + protected boolean mConfigured; protected long mScreenUnlockedFunctions; protected boolean mBootCompleted; protected boolean mCurrentFunctionsApplied; @@ -650,15 +654,29 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser Slog.e(TAG, "unknown state " + state); return; } - if (configured == 0) removeMessages(MSG_UPDATE_STATE); if (connected == 1) removeMessages(MSG_FUNCTION_SWITCH_TIMEOUT); Message msg = Message.obtain(this, MSG_UPDATE_STATE); msg.arg1 = connected; msg.arg2 = configured; - // debounce disconnects to avoid problems bringing up USB tethering - sendMessageDelayed(msg, + if (DEBUG) { + Slog.i(TAG, "mResetUsbGadgetDisableDebounce:" + mResetUsbGadgetDisableDebounce + + " connected:" + connected + "configured:" + configured); + } + if (mResetUsbGadgetDisableDebounce) { + // Do not debounce disconnect after resetUsbGadget. + sendMessage(msg); + if (connected == 1) mResetUsbGadgetDisableDebounce = false; + } else { + if (configured == 0) { + removeMessages(MSG_UPDATE_STATE); + if (DEBUG) Slog.i(TAG, "removeMessages MSG_UPDATE_STATE"); + } + if (connected == 1) removeMessages(MSG_FUNCTION_SWITCH_TIMEOUT); + // debounce disconnects to avoid problems bringing up USB tethering. + sendMessageDelayed(msg, (connected == 0) ? (mScreenLocked ? DEVICE_STATE_UPDATE_DELAY : DEVICE_STATE_UPDATE_DELAY_EXT) : 0); + } } public void updateHostState(UsbPort port, UsbPortStatus status) { @@ -904,7 +922,10 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser case MSG_UPDATE_STATE: mConnected = (msg.arg1 == 1); mConfigured = (msg.arg2 == 1); - + if (DEBUG) { + Slog.i(TAG, "handleMessage MSG_UPDATE_STATE " + "mConnected:" + mConnected + + " mConfigured:" + mConfigured); + } updateUsbNotification(false); updateAdbNotification(false); if (mBootCompleted) { @@ -2010,12 +2031,19 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } try { + // MSG_ACCESSORY_MODE_ENTER_TIMEOUT has to be removed to allow exiting + // AOAP mode during resetUsbGadget. + removeMessages(MSG_ACCESSORY_MODE_ENTER_TIMEOUT); + if (mConfigured) { + mResetUsbGadgetDisableDebounce = true; + } android.hardware.usb.gadget.V1_1.IUsbGadget gadgetProxy = android.hardware.usb.gadget.V1_1.IUsbGadget .castFrom(mGadgetProxy); gadgetProxy.reset(); } catch (RemoteException e) { Slog.e(TAG, "reset Usb Gadget failed", e); + mResetUsbGadgetDisableDebounce = false; } } break; |