diff options
106 files changed, 3330 insertions, 853 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index d37f80671a6b..31b0f057d4ea 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -7673,7 +7673,7 @@ package android.app.admin { field public static final String DELEGATION_PACKAGE_ACCESS = "delegation-package-access"; field public static final String DELEGATION_PERMISSION_GRANT = "delegation-permission-grant"; field public static final String DELEGATION_SECURITY_LOGGING = "delegation-security-logging"; - field public static final int ENCRYPTION_STATUS_ACTIVATING = 2; // 0x2 + field @Deprecated public static final int ENCRYPTION_STATUS_ACTIVATING = 2; // 0x2 field public static final int ENCRYPTION_STATUS_ACTIVE = 3; // 0x3 field public static final int ENCRYPTION_STATUS_ACTIVE_DEFAULT_KEY = 4; // 0x4 field public static final int ENCRYPTION_STATUS_ACTIVE_PER_USER = 5; // 0x5 diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 2c605151ee63..2fff53791f52 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -3367,7 +3367,7 @@ package android.window { method @NonNull public android.window.WindowContainerTransaction reparentTasks(@Nullable android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken, @Nullable int[], @Nullable int[], boolean); method @NonNull public android.window.WindowContainerTransaction scheduleFinishEnterPip(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect); method @NonNull public android.window.WindowContainerTransaction setActivityWindowingMode(@NonNull android.window.WindowContainerToken, int); - method @NonNull public android.window.WindowContainerTransaction setAdjacentRoots(@NonNull android.window.WindowContainerToken, @NonNull android.window.WindowContainerToken, boolean); + method @NonNull public android.window.WindowContainerTransaction setAdjacentRoots(@NonNull android.window.WindowContainerToken, @NonNull android.window.WindowContainerToken); method @NonNull public android.window.WindowContainerTransaction setAdjacentTaskFragments(@NonNull android.os.IBinder, @Nullable android.os.IBinder, @Nullable android.window.WindowContainerTransaction.TaskFragmentAdjacentParams); method @NonNull public android.window.WindowContainerTransaction setAppBounds(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect); method @NonNull public android.window.WindowContainerTransaction setBounds(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 69868ab76c3c..57bfc64adaff 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -207,7 +207,6 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.Preconditions; import com.android.internal.util.function.pooled.PooledLambda; -import com.android.org.conscrypt.OpenSSLSocketImpl; import com.android.org.conscrypt.TrustedCertificateStore; import com.android.server.am.MemInfoDumpProto; @@ -1404,7 +1403,6 @@ public final class ActivityThread extends ClientTransactionHandler ContextImpl.class, Activity.class, WebView.class, - OpenSSLSocketImpl.class, View.class, ViewRootImpl.class }; @@ -1412,9 +1410,8 @@ public final class ActivityThread extends ClientTransactionHandler long appContextInstanceCount = instanceCounts[0]; long activityInstanceCount = instanceCounts[1]; long webviewInstanceCount = instanceCounts[2]; - long openSslSocketCount = instanceCounts[3]; - long viewInstanceCount = instanceCounts[4]; - long viewRootInstanceCount = instanceCounts[5]; + long viewInstanceCount = instanceCounts[3]; + long viewRootInstanceCount = instanceCounts[4]; int globalAssetCount = AssetManager.getGlobalAssetCount(); int globalAssetManagerCount = AssetManager.getGlobalAssetManagerCount(); @@ -1447,7 +1444,6 @@ public final class ActivityThread extends ClientTransactionHandler pw.print(binderProxyObjectCount); pw.print(','); pw.print(binderDeathObjectCount); pw.print(','); - pw.print(openSslSocketCount); pw.print(','); // SQL pw.print(stats.memoryUsed / 1024); pw.print(','); @@ -1484,8 +1480,7 @@ public final class ActivityThread extends ClientTransactionHandler printRow(pw, TWO_COUNT_COLUMNS, "Parcel memory:", parcelSize/1024, "Parcel count:", parcelCount); printRow(pw, TWO_COUNT_COLUMNS, "Death Recipients:", binderDeathObjectCount, - "OpenSSL Sockets:", openSslSocketCount); - printRow(pw, ONE_COUNT_COLUMN, "WebViews:", webviewInstanceCount); + "WebViews:", webviewInstanceCount); // SQLite mem info pw.println(" "); @@ -1558,7 +1553,6 @@ public final class ActivityThread extends ClientTransactionHandler ContextImpl.class, Activity.class, WebView.class, - OpenSSLSocketImpl.class, View.class, ViewRootImpl.class }; @@ -1566,9 +1560,8 @@ public final class ActivityThread extends ClientTransactionHandler long appContextInstanceCount = instanceCounts[0]; long activityInstanceCount = instanceCounts[1]; long webviewInstanceCount = instanceCounts[2]; - long openSslSocketCount = instanceCounts[3]; - long viewInstanceCount = instanceCounts[4]; - long viewRootInstanceCount = instanceCounts[5]; + long viewInstanceCount = instanceCounts[3]; + long viewRootInstanceCount = instanceCounts[4]; int globalAssetCount = AssetManager.getGlobalAssetCount(); int globalAssetManagerCount = AssetManager.getGlobalAssetManagerCount(); @@ -1610,8 +1603,6 @@ public final class ActivityThread extends ClientTransactionHandler proto.write(MemInfoDumpProto.AppData.ObjectStats.PARCEL_COUNT, parcelCount); proto.write(MemInfoDumpProto.AppData.ObjectStats.BINDER_OBJECT_DEATH_COUNT, binderDeathObjectCount); - proto.write(MemInfoDumpProto.AppData.ObjectStats.OPEN_SSL_SOCKET_COUNT, - openSslSocketCount); proto.write(MemInfoDumpProto.AppData.ObjectStats.WEBVIEW_INSTANCE_COUNT, webviewInstanceCount); proto.end(oToken); diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 578e29090d13..939763ac811d 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -6490,8 +6490,10 @@ public class DevicePolicyManager { * Result code for {@link #getStorageEncryptionStatus}: indicating that encryption is not * currently active, but is currently being activated. * <p> - * This result code has never actually been used. + * @deprecated This result code has never actually been used, so there is no reason for apps to + * check for it. */ + @Deprecated public static final int ENCRYPTION_STATUS_ACTIVATING = 2; /** diff --git a/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl b/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl index a61bb3304c12..34d016adbc06 100644 --- a/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl +++ b/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl @@ -15,8 +15,8 @@ */ package android.hardware.camera2.extension; -import android.hardware.camera2.extension.Size; import android.hardware.camera2.extension.OutputConfigId; +import android.hardware.camera2.extension.Size; import android.view.Surface; /** @hide */ @@ -35,5 +35,5 @@ parcelable CameraOutputConfig OutputConfigId outputId; int surfaceGroupId; String physicalCameraId; - List<OutputConfigId> surfaceSharingOutputConfigs; + List<CameraOutputConfig> sharedSurfaceConfigs; } diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java index 5503e2834d98..c8dc2d0b0b91 100644 --- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java @@ -217,60 +217,30 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes CameraSessionConfig sessionConfig = mSessionProcessor.initSession(mCameraDevice.getId(), previewSurface, captureSurface); List<CameraOutputConfig> outputConfigs = sessionConfig.outputConfigs; - // map camera output ids to output configurations - HashMap<Integer, OutputConfiguration> cameraOutputs = new HashMap<>(); - for (CameraOutputConfig output : outputConfigs) { - OutputConfiguration cameraOutput = null; - switch(output.type) { - case CameraOutputConfig.TYPE_SURFACE: - if (output.surface == null) { - Log.w(TAG, "Unsupported client output id: " + output.outputId.id + - ", skipping!"); - continue; - } - cameraOutput = new OutputConfiguration(output.surfaceGroupId, - output.surface); - break; - case CameraOutputConfig.TYPE_IMAGEREADER: - if ((output.imageFormat == ImageFormat.UNKNOWN) || (output.size.width <= 0) || - (output.size.height <= 0)) { - Log.w(TAG, "Unsupported client output id: " + output.outputId.id + - ", skipping!"); - continue; - } - ImageReader reader = ImageReader.newInstance(output.size.width, - output.size.height, output.imageFormat, output.capacity); - mReaderMap.put(output.outputId.id, reader); - cameraOutput = new OutputConfiguration(output.surfaceGroupId, - reader.getSurface()); - break; - case CameraOutputConfig.TYPE_MULTIRES_IMAGEREADER: - // Support for multi-resolution outputs to be added in future releases - default: - throw new IllegalArgumentException("Unsupported output config type: " + - output.type); - } - cameraOutput.setPhysicalCameraId(output.physicalCameraId); - cameraOutputs.put(output.outputId.id, cameraOutput); - } - ArrayList<OutputConfiguration> outputList = new ArrayList<>(); for (CameraOutputConfig output : outputConfigs) { - if (!cameraOutputs.containsKey(output.outputId.id)) { - // Shared surface already removed by a previous iteration + Surface outputSurface = initializeSurfrace(output); + if (outputSurface == null) { continue; } - OutputConfiguration outConfig = cameraOutputs.get(output.outputId.id); - if ((output.surfaceSharingOutputConfigs != null) && - !output.surfaceSharingOutputConfigs.isEmpty()) { - outConfig.enableSurfaceSharing(); - for (OutputConfigId outputId : output.surfaceSharingOutputConfigs) { - outConfig.addSurface(cameraOutputs.get(outputId.id).getSurface()); - cameraOutputs.remove(outputId.id); + OutputConfiguration cameraOutput = new OutputConfiguration(output.surfaceGroupId, + outputSurface); + + if ((output.sharedSurfaceConfigs != null) && !output.sharedSurfaceConfigs.isEmpty()) { + cameraOutput.enableSurfaceSharing(); + for (CameraOutputConfig sharedOutputConfig : output.sharedSurfaceConfigs) { + Surface sharedSurface = initializeSurfrace(sharedOutputConfig); + if (sharedSurface == null) { + continue; + } + cameraOutput.addSurface(sharedSurface); + mCameraConfigMap.put(sharedSurface, sharedOutputConfig); } } - outputList.add(outConfig); - mCameraConfigMap.put(outConfig.getSurface(), output); + + cameraOutput.setPhysicalCameraId(output.physicalCameraId); + outputList.add(cameraOutput); + mCameraConfigMap.put(cameraOutput.getSurface(), output); } SessionConfiguration sessionConfiguration = new SessionConfiguration( @@ -995,4 +965,32 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes CameraMetadataNative.update(ret.getNativeMetadata(), request.parameters); return ret; } + + private Surface initializeSurfrace(CameraOutputConfig output) { + switch(output.type) { + case CameraOutputConfig.TYPE_SURFACE: + if (output.surface == null) { + Log.w(TAG, "Unsupported client output id: " + output.outputId.id + + ", skipping!"); + return null; + } + return output.surface; + case CameraOutputConfig.TYPE_IMAGEREADER: + if ((output.imageFormat == ImageFormat.UNKNOWN) || (output.size.width <= 0) || + (output.size.height <= 0)) { + Log.w(TAG, "Unsupported client output id: " + output.outputId.id + + ", skipping!"); + return null; + } + ImageReader reader = ImageReader.newInstance(output.size.width, + output.size.height, output.imageFormat, output.capacity); + mReaderMap.put(output.outputId.id, reader); + return reader.getSurface(); + case CameraOutputConfig.TYPE_MULTIRES_IMAGEREADER: + // Support for multi-resolution outputs to be added in future releases + default: + throw new IllegalArgumentException("Unsupported output config type: " + + output.type); + } + } } diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 731ea9207283..eadcac91dcd7 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -108,6 +108,17 @@ public final class DisplayManager { public static final String DISPLAY_CATEGORY_PRESENTATION = "android.hardware.display.category.PRESENTATION"; + /** + * Display category: All displays, including disabled displays. + * <p> + * This returns all displays, including currently disabled and inaccessible displays. + * + * @see #getDisplays(String) + * @hide + */ + public static final String DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED = + "android.hardware.display.category.ALL_INCLUDING_DISABLED"; + /** @hide **/ @IntDef(prefix = "VIRTUAL_DISPLAY_FLAG_", flag = true, value = { VIRTUAL_DISPLAY_FLAG_PUBLIC, @@ -552,7 +563,8 @@ public final class DisplayManager { final int[] displayIds = mGlobal.getDisplayIds(); synchronized (mLock) { try { - if (category == null) { + if (category == null + || DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED.equals(category)) { addAllDisplaysLocked(mTempDisplays, displayIds); } else if (category.equals(DISPLAY_CATEGORY_PRESENTATION)) { addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_WIFI); diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java index ac2156e9e46e..4965057a7fdb 100644 --- a/core/java/android/os/Trace.java +++ b/core/java/android/os/Trace.java @@ -140,7 +140,7 @@ public final class Trace { String trackName, String name, int cookie); @FastNative private static native void nativeAsyncTraceForTrackEnd(long tag, - String trackName, String name, int cookie); + String trackName, int cookie); @FastNative private static native void nativeInstant(long tag, String name); @FastNative @@ -281,8 +281,10 @@ public final class Trace { /** * Writes a trace message to indicate that a given section of code has * begun. Must be followed by a call to {@link #asyncTraceForTrackEnd} using the same - * tag. This function operates exactly like {@link #asyncTraceBegin(long, String, int)}, + * track name and cookie. + * This function operates exactly like {@link #asyncTraceBegin(long, String, int)}, * except with the inclusion of a track name argument for where this method should appear. + * The cookie must be unique on the trackName level, not the methodName level * * @param traceTag The trace tag. * @param trackName The track where the event should appear in the trace. @@ -302,19 +304,31 @@ public final class Trace { * Writes a trace message to indicate that the current method has ended. * Must be called exactly once for each call to * {@link #asyncTraceForTrackBegin(long, String, String, int)} - * using the same tag, track name, name and cookie. + * using the same tag, track name, and cookie. * * @param traceTag The trace tag. * @param trackName The track where the event should appear in the trace. - * @param methodName The method name to appear in the trace. * @param cookie Unique identifier for distinguishing simultaneous events * * @hide */ public static void asyncTraceForTrackEnd(long traceTag, + @NonNull String trackName, int cookie) { + if (isTagEnabled(traceTag)) { + nativeAsyncTraceForTrackEnd(traceTag, trackName, cookie); + } + } + + /** + * @deprecated use asyncTraceForTrackEnd without methodName argument + * + * @hide + */ + @Deprecated + public static void asyncTraceForTrackEnd(long traceTag, @NonNull String trackName, @NonNull String methodName, int cookie) { if (isTagEnabled(traceTag)) { - nativeAsyncTraceForTrackEnd(traceTag, trackName, methodName, cookie); + nativeAsyncTraceForTrackEnd(traceTag, trackName, cookie); } } diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java index 3d5abb3b8a2f..d831ecb5e816 100644 --- a/core/java/android/os/UserHandle.java +++ b/core/java/android/os/UserHandle.java @@ -368,7 +368,7 @@ public final class UserHandle implements Parcelable { @UnsupportedAppUsage @TestApi public static int getUid(@UserIdInt int userId, @AppIdInt int appId) { - if (MU_ENABLED) { + if (MU_ENABLED && appId >= 0) { return userId * PER_USER_RANGE + (appId % PER_USER_RANGE); } else { return appId; diff --git a/core/java/android/os/storage/OWNERS b/core/java/android/os/storage/OWNERS index ff126e12cf61..1f686e5c449c 100644 --- a/core/java/android/os/storage/OWNERS +++ b/core/java/android/os/storage/OWNERS @@ -8,3 +8,4 @@ sahanas@google.com abkaur@google.com chiangi@google.com narayan@google.com +dipankarb@google.com diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index cbfd805d2363..154fcab8827d 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -931,9 +931,10 @@ public final class Telephony { * Set as a "result" extra in the {@link #SMS_REJECTED_ACTION} intent to indicate an sms * was received while the phone was in encrypted state. * <p> - * This result is never used on devices that launched with Android 10 (API level 29) or - * higher, since Android's storage encryption implementation has changed and it no - * longer can cause the rejection of incoming SMS messages. + * This result code is only used on devices that use Full Disk Encryption. Support for + * Full Disk Encryption was entirely removed in API level 33, having been replaced by + * File Based Encryption. Devices that use File Based Encryption never reject incoming + * SMS messages due to the encryption state. */ public static final int RESULT_SMS_RECEIVED_WHILE_ENCRYPTED = 9; diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index acefebc232ae..65f0824a9b78 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -258,7 +258,10 @@ public abstract class NotificationListenerService extends Service { public static final int REASON_CLEAR_DATA = 21; /** Notification was canceled due to an assistant adjustment update. */ public static final int REASON_ASSISTANT_CANCEL = 22; - /** Notification was canceled when lockdown mode is enabled. */ + /** + * Notification was canceled when entering lockdown mode, which turns off + * Smart Lock, fingerprint unlocking, and notifications on the lock screen. + */ public static final int REASON_LOCKDOWN = 23; /** diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 1e22856c1bde..9679a6ab3acb 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -1539,8 +1539,9 @@ public abstract class WallpaperService extends Service { // may have been destroyed so now we need to make // sure it is re-created. doOffsetsChanged(false); - // force relayout to get new surface - updateSurface(true, false, false); + // It will check mSurfaceCreated so no need to force relayout. + updateSurface(false /* forceRelayout */, false /* forceReport */, + false /* redrawNeeded */); } onVisibilityChanged(visible); if (mReportedVisible && mFrozenRequested) { diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index f246cd9fa107..acdff4fb52fd 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -543,6 +543,21 @@ interface IWindowManager boolean isWindowTraceEnabled(); /** + * Starts a transition trace. + */ + void startTransitionTrace(); + + /** + * Stops a transition trace. + */ + void stopTransitionTrace(); + + /** + * Returns true if transition trace is enabled. + */ + boolean isTransitionTraceEnabled(); + + /** * Gets the windowing mode of the display. * * @param displayId The id of the display. diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java index 4d07171d3086..43d427db2c75 100644 --- a/core/java/android/view/WindowManagerPolicyConstants.java +++ b/core/java/android/view/WindowManagerPolicyConstants.java @@ -237,10 +237,6 @@ public interface WindowManagerPolicyConstants { */ int LAYER_OFFSET_THUMBNAIL = WINDOW_LAYER_MULTIPLIER - 1; - // TODO(b/207185041): Remove this divider workaround after we full remove leagacy split and - // make app pair split only have single root then we can just attach the - // divider to the single root task in shell. - int SPLIT_DIVIDER_LAYER = TYPE_LAYER_MULTIPLIER * 3; int WATERMARK_LAYER = TYPE_LAYER_MULTIPLIER * 100; int STRICT_MODE_LAYER = TYPE_LAYER_MULTIPLIER * 101; int WINDOW_FREEZE_LAYER = TYPE_LAYER_MULTIPLIER * 200; diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 62e0a521c735..c18d089f6375 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -521,6 +521,14 @@ public final class AutofillManager { public static final String DEVICE_CONFIG_AUTOFILL_DIALOG_HINTS = "autofill_dialog_hints"; + /** + * Sets a value of delay time to show up the inline tooltip view. + * + * @hide + */ + public static final String DEVICE_CONFIG_AUTOFILL_TOOLTIP_SHOW_UP_DELAY = + "autofill_inline_tooltip_first_show_delay"; + private static final String DIALOG_HINTS_DELIMITER = ":"; /** @hide */ diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java index 7dc039d44f95..633d87937049 100644 --- a/core/java/android/window/WindowContainerTransaction.java +++ b/core/java/android/window/WindowContainerTransaction.java @@ -384,12 +384,10 @@ public final class WindowContainerTransaction implements Parcelable { */ @NonNull public WindowContainerTransaction setAdjacentRoots( - @NonNull WindowContainerToken root1, @NonNull WindowContainerToken root2, - boolean moveTogether) { + @NonNull WindowContainerToken root1, @NonNull WindowContainerToken root2) { mHierarchyOps.add(HierarchyOp.createForAdjacentRoots( root1.asBinder(), - root2.asBinder(), - moveTogether)); + root2.asBinder())); return this; } @@ -1106,9 +1104,6 @@ public final class WindowContainerTransaction implements Parcelable { private boolean mReparentTopOnly; - // TODO(b/207185041): Remove this once having a single-top root for split screen. - private boolean mMoveAdjacentTogether; - @Nullable private int[] mWindowingModes; @@ -1171,12 +1166,10 @@ public final class WindowContainerTransaction implements Parcelable { } /** Create a hierarchy op for setting adjacent root tasks. */ - public static HierarchyOp createForAdjacentRoots(IBinder root1, IBinder root2, - boolean moveTogether) { + public static HierarchyOp createForAdjacentRoots(IBinder root1, IBinder root2) { return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS) .setContainer(root1) .setReparentContainer(root2) - .setMoveAdjacentTogether(moveTogether) .build(); } @@ -1223,7 +1216,6 @@ public final class WindowContainerTransaction implements Parcelable { mInsetsProviderFrame = copy.mInsetsProviderFrame; mToTop = copy.mToTop; mReparentTopOnly = copy.mReparentTopOnly; - mMoveAdjacentTogether = copy.mMoveAdjacentTogether; mWindowingModes = copy.mWindowingModes; mActivityTypes = copy.mActivityTypes; mLaunchOptions = copy.mLaunchOptions; @@ -1245,7 +1237,6 @@ public final class WindowContainerTransaction implements Parcelable { } mToTop = in.readBoolean(); mReparentTopOnly = in.readBoolean(); - mMoveAdjacentTogether = in.readBoolean(); mWindowingModes = in.createIntArray(); mActivityTypes = in.createIntArray(); mLaunchOptions = in.readBundle(); @@ -1300,10 +1291,6 @@ public final class WindowContainerTransaction implements Parcelable { return mReparentTopOnly; } - public boolean getMoveAdjacentTogether() { - return mMoveAdjacentTogether; - } - public int[] getWindowingModes() { return mWindowingModes; } @@ -1356,8 +1343,7 @@ public final class WindowContainerTransaction implements Parcelable { return "{reorder: " + mContainer + " to " + (mToTop ? "top" : "bottom") + "}"; case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS: return "{SetAdjacentRoot: container=" + mContainer - + " adjacentRoot=" + mReparent + " mMoveAdjacentTogether=" - + mMoveAdjacentTogether + "}"; + + " adjacentRoot=" + mReparent + "}"; case HIERARCHY_OP_TYPE_LAUNCH_TASK: return "{LaunchTask: " + mLaunchOptions + "}"; case HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT: @@ -1413,7 +1399,6 @@ public final class WindowContainerTransaction implements Parcelable { } dest.writeBoolean(mToTop); dest.writeBoolean(mReparentTopOnly); - dest.writeBoolean(mMoveAdjacentTogether); dest.writeIntArray(mWindowingModes); dest.writeIntArray(mActivityTypes); dest.writeBundle(mLaunchOptions); @@ -1458,8 +1443,6 @@ public final class WindowContainerTransaction implements Parcelable { private boolean mReparentTopOnly; - private boolean mMoveAdjacentTogether; - @Nullable private int[] mWindowingModes; @@ -1515,11 +1498,6 @@ public final class WindowContainerTransaction implements Parcelable { return this; } - Builder setMoveAdjacentTogether(boolean moveAdjacentTogether) { - mMoveAdjacentTogether = moveAdjacentTogether; - return this; - } - Builder setWindowingModes(@Nullable int[] windowingModes) { mWindowingModes = windowingModes; return this; @@ -1570,7 +1548,6 @@ public final class WindowContainerTransaction implements Parcelable { hierarchyOp.mInsetsProviderFrame = mInsetsProviderFrame; hierarchyOp.mToTop = mToTop; hierarchyOp.mReparentTopOnly = mReparentTopOnly; - hierarchyOp.mMoveAdjacentTogether = mMoveAdjacentTogether; hierarchyOp.mLaunchOptions = mLaunchOptions; hierarchyOp.mActivityIntent = mActivityIntent; hierarchyOp.mPendingIntent = mPendingIntent; diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 9eec2128fd9d..33325d382e94 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -1002,6 +1002,7 @@ public class ChooserActivity extends ResolverActivity implements mMaxTargetsPerRow = getResources().getInteger(R.integer.config_chooser_max_targets_per_row); adjustPreviewWidth(newConfig.orientation, null); updateStickyContentPreview(); + updateTabPadding(); } private boolean shouldDisplayLandscape(int orientation) { @@ -1024,6 +1025,20 @@ public class ChooserActivity extends ResolverActivity implements updateLayoutWidth(R.id.content_preview_file_layout, width, parent); } + private void updateTabPadding() { + if (shouldShowTabs()) { + View tabs = findViewById(R.id.tabs); + float iconSize = getResources().getDimension(R.dimen.chooser_icon_size); + // The entire width consists of icons or padding. Divide the item padding in half to get + // paddingHorizontal. + float padding = (tabs.getWidth() - mMaxTargetsPerRow * iconSize) + / mMaxTargetsPerRow / 2; + // Subtract the margin the buttons already have. + padding -= getResources().getDimension(R.dimen.resolver_profile_tab_margin); + tabs.setPadding((int) padding, 0, (int) padding, 0); + } + } + private void updateLayoutWidth(int layoutResourceId, int width, View parent) { View view = parent.findViewById(layoutResourceId); if (view != null && view.getLayoutParams() != null) { @@ -2493,6 +2508,8 @@ public class ChooserActivity extends ResolverActivity implements recyclerView.setAdapter(gridAdapter); ((GridLayoutManager) recyclerView.getLayoutManager()).setSpanCount( mMaxTargetsPerRow); + + updateTabPadding(); } UserHandle currentUserHandle = mChooserMultiProfilePagerAdapter.getCurrentUserHandle(); diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java index d35d5ca6fca7..e6cc62472f5c 100644 --- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java @@ -86,7 +86,11 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd final LayoutInflater inflater = LayoutInflater.from(getContext()); final ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.chooser_list_per_profile, null, false); - return new ChooserProfileDescriptor(rootView, adapter); + ChooserProfileDescriptor profileDescriptor = + new ChooserProfileDescriptor(rootView, adapter); + profileDescriptor.recyclerView.setAccessibilityDelegateCompat( + new ChooserRecyclerViewAccessibilityDelegate(profileDescriptor.recyclerView)); + return profileDescriptor; } RecyclerView getListViewForIndex(int index) { diff --git a/core/java/com/android/internal/app/ChooserRecyclerViewAccessibilityDelegate.java b/core/java/com/android/internal/app/ChooserRecyclerViewAccessibilityDelegate.java new file mode 100644 index 000000000000..66c9838bf71b --- /dev/null +++ b/core/java/com/android/internal/app/ChooserRecyclerViewAccessibilityDelegate.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.app; + +import android.annotation.NonNull; +import android.graphics.Rect; +import android.view.View; +import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; + +import com.android.internal.widget.RecyclerView; +import com.android.internal.widget.RecyclerViewAccessibilityDelegate; + +class ChooserRecyclerViewAccessibilityDelegate extends RecyclerViewAccessibilityDelegate { + private final Rect mTempRect = new Rect(); + private final int[] mConsumed = new int[2]; + + ChooserRecyclerViewAccessibilityDelegate(RecyclerView recyclerView) { + super(recyclerView); + } + + @Override + public boolean onRequestSendAccessibilityEvent( + @NonNull ViewGroup host, + @NonNull View view, + @NonNull AccessibilityEvent event) { + boolean result = super.onRequestSendAccessibilityEvent(host, view, event); + if (result && event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) { + ensureViewOnScreenVisibility((RecyclerView) host, view); + } + return result; + } + + /** + * Bring the view that received accessibility focus on the screen. + * The method's logic is based on a model where RecyclerView is a child of another scrollable + * component (ResolverDrawerLayout) and can be partially scrolled off the screen. In that case, + * RecyclerView's children that are positioned fully within RecyclerView bounds but scrolled + * out of the screen by the outer component, when selected by the accessibility navigation will + * remain off the screen (as neither components detect such specific case). + * If the view that receiving accessibility focus is scrolled of the screen, perform the nested + * scrolling to make in visible. + */ + private void ensureViewOnScreenVisibility(RecyclerView recyclerView, View view) { + View child = recyclerView.findContainingItemView(view); + if (child == null) { + return; + } + recyclerView.getBoundsOnScreen(mTempRect, true); + int recyclerOnScreenTop = mTempRect.top; + int recyclerOnScreenBottom = mTempRect.bottom; + child.getBoundsOnScreen(mTempRect); + int dy = 0; + // if needed, do the page-length scroll instead of just a row-length scroll as + // ResolverDrawerLayout snaps to the compact view and the row-length scroll can be snapped + // back right away. + if (mTempRect.top < recyclerOnScreenTop) { + // snap to the bottom + dy = mTempRect.bottom - recyclerOnScreenBottom; + } else if (mTempRect.bottom > recyclerOnScreenBottom) { + // snap to the top + dy = mTempRect.top - recyclerOnScreenTop; + } + nestedVerticalScrollBy(recyclerView, dy); + } + + private void nestedVerticalScrollBy(RecyclerView recyclerView, int dy) { + if (dy == 0) { + return; + } + recyclerView.startNestedScroll(View.SCROLL_AXIS_VERTICAL); + if (recyclerView.dispatchNestedPreScroll(0, dy, mConsumed, null)) { + dy -= mConsumed[1]; + } + recyclerView.scrollBy(0, dy); + recyclerView.stopNestedScroll(); + } +} diff --git a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java index 5fe111148c91..ff188dc6f2b9 100644 --- a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java +++ b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java @@ -27,6 +27,7 @@ import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.Filter; import android.widget.Filterable; +import android.widget.LinearLayout; import android.widget.TextView; import com.android.internal.R; @@ -104,6 +105,13 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable { @Override public int getItemViewType(int position) { if (!showHeaders()) { + LocaleStore.LocaleInfo item = (LocaleStore.LocaleInfo) getItem(position); + if (item.isSystemLocale()) { + return TYPE_SYSTEM_LANGUAGE_FOR_APP_LANGUAGE_PICKER; + } + if (item.isAppCurrentLocale()) { + return TYPE_CURRENT_LOCALE; + } return TYPE_LOCALE; } else { if (position == 0) { @@ -193,15 +201,11 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable { } int itemType = getItemViewType(position); + View itemView = getNewViewIfNeeded(convertView, parent, itemType, position); switch (itemType) { case TYPE_HEADER_SUGGESTED: // intentional fallthrough case TYPE_HEADER_ALL_OTHERS: - // Covers both null, and "reusing" a wrong kind of view - if (!(convertView instanceof TextView)) { - convertView = mInflater.inflate(R.layout.language_picker_section_header, - parent, false); - } - TextView textView = (TextView) convertView; + TextView textView = (TextView) itemView; if (itemType == TYPE_HEADER_SUGGESTED) { setTextTo(textView, R.string.language_picker_section_suggested); } else { @@ -215,38 +219,77 @@ public class SuggestedLocaleAdapter extends BaseAdapter implements Filterable { mDisplayLocale != null ? mDisplayLocale : Locale.getDefault()); break; case TYPE_SYSTEM_LANGUAGE_FOR_APP_LANGUAGE_PICKER: - if (!(convertView instanceof ViewGroup)) { - TextView title; - if (((LocaleStore.LocaleInfo)getItem(position)).isAppCurrentLocale()) { - convertView = mInflater.inflate( - R.layout.app_language_picker_current_locale_item, parent, false); - title = convertView.findViewById(R.id.language_picker_item); - addStateDescriptionIntoCurrentLocaleItem(convertView); - } else { - convertView = mInflater.inflate( + TextView title; + if (((LocaleStore.LocaleInfo)getItem(position)).isAppCurrentLocale()) { + title = itemView.findViewById(R.id.language_picker_item); + } else { + title = itemView.findViewById(R.id.locale); + } + title.setText(R.string.system_locale_title); + break; + case TYPE_CURRENT_LOCALE: + updateTextView(itemView, + itemView.findViewById(R.id.language_picker_item), position); + break; + default: + updateTextView(itemView, itemView.findViewById(R.id.locale), position); + break; + } + return itemView; + } + + /** Check if the old view can be reused, otherwise create a new one. */ + private View getNewViewIfNeeded( + View convertView, ViewGroup parent, int itemType, int position) { + View updatedView = convertView; + boolean shouldReuseView; + switch (itemType) { + case TYPE_HEADER_SUGGESTED: // intentional fallthrough + case TYPE_HEADER_ALL_OTHERS: + shouldReuseView = convertView instanceof TextView + && convertView.findViewById(R.id.language_picker_header) != null; + if (!shouldReuseView) { + updatedView = mInflater.inflate( + R.layout.language_picker_section_header, parent, false); + } + break; + case TYPE_SYSTEM_LANGUAGE_FOR_APP_LANGUAGE_PICKER: + if (((LocaleStore.LocaleInfo) getItem(position)).isAppCurrentLocale()) { + shouldReuseView = convertView instanceof LinearLayout + && convertView.findViewById(R.id.language_picker_item) != null; + if (!shouldReuseView) { + updatedView = mInflater.inflate( + R.layout.app_language_picker_current_locale_item, + parent, false); + addStateDescriptionIntoCurrentLocaleItem(updatedView); + } + } else { + shouldReuseView = convertView instanceof TextView + && convertView.findViewById(R.id.locale) != null; + if (!shouldReuseView) { + updatedView = mInflater.inflate( R.layout.language_picker_item, parent, false); - title = convertView.findViewById(R.id.locale); } - title.setText(R.string.system_locale_title); } break; case TYPE_CURRENT_LOCALE: - if (!(convertView instanceof ViewGroup)) { - convertView = mInflater.inflate( + shouldReuseView = convertView instanceof LinearLayout + && convertView.findViewById(R.id.language_picker_item) != null; + if (!shouldReuseView) { + updatedView = mInflater.inflate( R.layout.app_language_picker_current_locale_item, parent, false); - addStateDescriptionIntoCurrentLocaleItem(convertView); + addStateDescriptionIntoCurrentLocaleItem(updatedView); } - updateTextView( - convertView, convertView.findViewById(R.id.language_picker_item), position); break; default: - // Covers both null, and "reusing" a wrong kind of view - if (!(convertView instanceof ViewGroup)) { - convertView = mInflater.inflate(R.layout.language_picker_item, parent, false); + shouldReuseView = convertView instanceof TextView + && convertView.findViewById(R.id.locale) != null; + if (!shouldReuseView) { + updatedView = mInflater.inflate(R.layout.language_picker_item, parent, false); } - updateTextView(convertView, convertView.findViewById(R.id.locale), position); + break; } - return convertView; + return updatedView; } private boolean showHeaders() { diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java index 1fd04109ae45..6eae34bf1999 100644 --- a/core/java/com/android/internal/util/ArrayUtils.java +++ b/core/java/com/android/internal/util/ArrayUtils.java @@ -331,6 +331,16 @@ public class ArrayUtils { return array; } + @NonNull + public static int[] convertToIntArray(@NonNull ArraySet<Integer> set) { + final int size = set.size(); + int[] array = new int[size]; + for (int i = 0; i < size; i++) { + array[i] = set.valueAt(i); + } + return array; + } + public static @Nullable long[] convertToLongArray(@Nullable int[] intArray) { if (intArray == null) return null; long[] array = new long[intArray.length]; diff --git a/core/java/com/android/internal/view/inline/InlineTooltipUi.java b/core/java/com/android/internal/view/inline/InlineTooltipUi.java index 25fa678d0507..3eae89e350a0 100644 --- a/core/java/com/android/internal/view/inline/InlineTooltipUi.java +++ b/core/java/com/android/internal/view/inline/InlineTooltipUi.java @@ -15,24 +15,30 @@ */ package com.android.internal.view.inline; +import static android.view.autofill.AutofillManager.DEVICE_CONFIG_AUTOFILL_TOOLTIP_SHOW_UP_DELAY; import static android.view.autofill.Helper.sVerbose; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.ContextWrapper; +import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.provider.DeviceConfig; +import android.provider.Settings; import android.transition.Transition; import android.util.Slog; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; +import android.view.ViewParent; import android.view.WindowManager; import android.widget.LinearLayout; import android.widget.PopupWindow; import android.widget.inline.InlineContentView; import java.io.PrintWriter; +import java.lang.ref.WeakReference; /** * UI container for the inline suggestion tooltip. @@ -40,6 +46,8 @@ import java.io.PrintWriter; public final class InlineTooltipUi extends PopupWindow implements AutoCloseable { private static final String TAG = "InlineTooltipUi"; + private static final int FIRST_TIME_SHOW_DEFAULT_DELAY_MS = 250; + private final WindowManager mWm; private final ViewGroup mContentContainer; @@ -47,6 +55,16 @@ public final class InlineTooltipUi extends PopupWindow implements AutoCloseable private WindowManager.LayoutParams mWindowLayoutParams; + private DelayShowRunnable mDelayShowTooltip; + + private boolean mHasEverDetached; + + private boolean mDelayShowAtStart = true; + private boolean mDelaying = false; + private int mShowDelayConfigMs; + + private final Rect mTmpRect = new Rect(); + private final View.OnAttachStateChangeListener mAnchorOnAttachStateChangeListener = new View.OnAttachStateChangeListener() { @Override @@ -56,6 +74,7 @@ public final class InlineTooltipUi extends PopupWindow implements AutoCloseable @Override public void onViewDetachedFromWindow(View v) { + mHasEverDetached = true; dismiss(); } }; @@ -66,6 +85,13 @@ public final class InlineTooltipUi extends PopupWindow implements AutoCloseable @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { + if (mHasEverDetached) { + // If the tooltip is ever detached, skip adjusting the position, + // because it only accepts to attach once and does not show again + // after detaching. + return; + } + if (mHeight != bottom - top) { mHeight = bottom - top; adjustPosition(); @@ -77,6 +103,13 @@ public final class InlineTooltipUi extends PopupWindow implements AutoCloseable mContentContainer = new LinearLayout(new ContextWrapper(context)); mWm = context.getSystemService(WindowManager.class); + // That's a default delay time, and it will scale via the value of + // Settings.Global.ANIMATOR_DURATION_SCALE + mShowDelayConfigMs = DeviceConfig.getInt( + DeviceConfig.NAMESPACE_AUTOFILL, + DEVICE_CONFIG_AUTOFILL_TOOLTIP_SHOW_UP_DELAY, + FIRST_TIME_SHOW_DEFAULT_DELAY_MS); + setTouchModal(false); setOutsideTouchable(true); setInputMethodMode(INPUT_METHOD_NOT_NEEDED); @@ -95,7 +128,7 @@ public final class InlineTooltipUi extends PopupWindow implements AutoCloseable @Override public void close() { - hide(); + dismiss(); } @Override @@ -117,14 +150,57 @@ public final class InlineTooltipUi extends PopupWindow implements AutoCloseable * The effective {@code update} method that should be called by its clients. */ public void update(View anchor) { + if (anchor == null) { + final View oldAnchor = getAnchor(); + if (oldAnchor != null) { + removeDelayShowTooltip(oldAnchor); + } + return; + } + + if (mDelayShowAtStart) { + // To avoid showing when the anchor is doing the fade in animation. That will + // cause the tooltip to show in the wrong position and jump at the start. + mDelayShowAtStart = false; + mDelaying = true; + + if (mDelayShowTooltip == null) { + mDelayShowTooltip = new DelayShowRunnable(anchor); + } + + int delayTimeMs = mShowDelayConfigMs; + try { + final float scale = Settings.Global.getFloat( + anchor.getContext().getContentResolver(), + Settings.Global.ANIMATOR_DURATION_SCALE); + delayTimeMs *= scale; + } catch (Settings.SettingNotFoundException e) { + // do nothing + } + anchor.postDelayed(mDelayShowTooltip, delayTimeMs); + } else if (!mDelaying) { + // Note: If we are going to reuse the tooltip, we need to take care the delay in + // the case that update for the new anchor. + updateInner(anchor); + } + } + + private void removeDelayShowTooltip(View anchor) { + if (mDelayShowTooltip != null) { + anchor.removeCallbacks(mDelayShowTooltip); + mDelayShowTooltip = null; + } + } + + private void updateInner(View anchor) { + if (mHasEverDetached) { + return; + } // set to the application type with the highest z-order setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL); - // The first time to show up, the height of tooltip is zero, - // so set the offset Y to 2 * anchor height. - final int achoredHeight = mContentContainer.getHeight(); - final int offsetY = (achoredHeight == 0) - ? -anchor.getHeight() << 1 : -anchor.getHeight() - achoredHeight; + final int offsetY = -anchor.getHeight() - getPreferHeight(anchor); + if (!isShowing()) { setWidth(WindowManager.LayoutParams.WRAP_CONTENT); setHeight(WindowManager.LayoutParams.WRAP_CONTENT); @@ -135,6 +211,34 @@ public final class InlineTooltipUi extends PopupWindow implements AutoCloseable } } + private int getPreferHeight(View anchor) { + // The first time to show up, the height of tooltip is zero, so make its height + // the same as anchor. + final int achoredHeight = mContentContainer.getHeight(); + return (achoredHeight == 0) ? anchor.getHeight() : achoredHeight; + } + + @Override + protected boolean findDropDownPosition(View anchor, WindowManager.LayoutParams outParams, + int xOffset, int yOffset, int width, int height, int gravity, boolean allowScroll) { + boolean isAbove = super.findDropDownPosition(anchor, outParams, xOffset, yOffset, width, + height, gravity, allowScroll); + // Make the tooltips y fo position is above or under the parent of the anchor, + // otherwise suggestions doesn't clickable. + ViewParent parent = anchor.getParent(); + if (parent instanceof View) { + final Rect r = mTmpRect; + ((View) parent).getGlobalVisibleRect(r); + if (isAbove) { + outParams.y = r.top - getPreferHeight(anchor); + } else { + outParams.y = r.bottom + 1; + } + } + + return isAbove; + } + @Override protected void update(View anchor, WindowManager.LayoutParams params) { // update content view for the anchor is scrolling @@ -175,7 +279,9 @@ public final class InlineTooltipUi extends PopupWindow implements AutoCloseable final View anchor = getAnchor(); if (anchor != null) { anchor.removeOnAttachStateChangeListener(mAnchorOnAttachStateChangeListener); + removeDelayShowTooltip(anchor); } + mHasEverDetached = true; super.detachFromAnchor(); } @@ -185,7 +291,6 @@ public final class InlineTooltipUi extends PopupWindow implements AutoCloseable return; } - setShowing(false); setTransitioningToDismiss(true); hide(); @@ -193,6 +298,7 @@ public final class InlineTooltipUi extends PopupWindow implements AutoCloseable if (getOnDismissListener() != null) { getOnDismissListener().onDismiss(); } + super.dismiss(); } private void adjustPosition() { @@ -202,15 +308,15 @@ public final class InlineTooltipUi extends PopupWindow implements AutoCloseable } private void show(WindowManager.LayoutParams params) { - if (sVerbose) { - Slog.v(TAG, "show()"); - } mWindowLayoutParams = params; try { params.packageName = "android"; params.setTitle("Autofill Inline Tooltip"); // Title is set for debugging purposes if (!mShowing) { + if (sVerbose) { + Slog.v(TAG, "show()"); + } params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; params.privateFlags |= @@ -232,11 +338,11 @@ public final class InlineTooltipUi extends PopupWindow implements AutoCloseable } private void hide() { - if (sVerbose) { - Slog.v(TAG, "hide()"); - } try { if (mShowing) { + if (sVerbose) { + Slog.v(TAG, "hide()"); + } mContentContainer.removeOnLayoutChangeListener(mAnchoredOnLayoutChangeListener); mWm.removeView(mContentContainer); mShowing = false; @@ -336,4 +442,26 @@ public final class InlineTooltipUi extends PopupWindow implements AutoCloseable } } } + + private class DelayShowRunnable implements Runnable { + WeakReference<View> mAnchor; + + DelayShowRunnable(View anchor) { + mAnchor = new WeakReference<>(anchor); + } + + @Override + public void run() { + mDelaying = false; + final View anchor = mAnchor.get(); + if (anchor != null) { + updateInner(anchor); + } + } + + public void setAnchor(View anchor) { + mAnchor.clear(); + mAnchor = new WeakReference<>(anchor); + } + } } diff --git a/core/java/com/android/internal/widget/MessagingImageMessage.java b/core/java/com/android/internal/widget/MessagingImageMessage.java index f7955c3f72da..8e7fe18b222b 100644 --- a/core/java/com/android/internal/widget/MessagingImageMessage.java +++ b/core/java/com/android/internal/widget/MessagingImageMessage.java @@ -226,6 +226,13 @@ public class MessagingImageMessage extends ImageView implements MessagingMessage @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + if (mDrawable == null) { + Log.e(TAG, "onMeasure() after recycle()!"); + setMeasuredDimension(0, 0); + return; + } + if (mIsIsolated) { // When isolated we have a fixed size, let's use that sizing. setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), diff --git a/core/jni/android_os_Trace.cpp b/core/jni/android_os_Trace.cpp index 734b6ca47660..1c61c7be4414 100644 --- a/core/jni/android_os_Trace.cpp +++ b/core/jni/android_os_Trace.cpp @@ -82,9 +82,8 @@ static void android_os_Trace_nativeAsyncTraceEnd(JNIEnv* env, jclass, }); } -static void android_os_Trace_nativeAsyncTraceForTrackBegin(JNIEnv* env, jclass, jlong tag, - jstring trackStr, jstring nameStr, - jint cookie) { +static void android_os_Trace_nativeAsyncTraceForTrackBegin(JNIEnv* env, jclass, + jlong tag, jstring trackStr, jstring nameStr, jint cookie) { withString(env, trackStr, [env, tag, nameStr, cookie](char* track) { withString(env, nameStr, [tag, track, cookie](char* name) { atrace_async_for_track_begin(tag, track, name, cookie); @@ -92,13 +91,10 @@ static void android_os_Trace_nativeAsyncTraceForTrackBegin(JNIEnv* env, jclass, }); } -static void android_os_Trace_nativeAsyncTraceForTrackEnd(JNIEnv* env, jclass, jlong tag, - jstring trackStr, jstring nameStr, - jint cookie) { - withString(env, trackStr, [env, tag, nameStr, cookie](char* track) { - withString(env, nameStr, [tag, track, cookie](char* name) { - atrace_async_for_track_end(tag, track, name, cookie); - }); +static void android_os_Trace_nativeAsyncTraceForTrackEnd(JNIEnv* env, jclass, + jlong tag, jstring trackStr, jint cookie) { + withString(env, trackStr, [tag, cookie](char* track) { + atrace_async_for_track_end(tag, track, cookie); }); } @@ -156,7 +152,7 @@ static const JNINativeMethod gTraceMethods[] = { "(JLjava/lang/String;Ljava/lang/String;I)V", (void*)android_os_Trace_nativeAsyncTraceForTrackBegin }, { "nativeAsyncTraceForTrackEnd", - "(JLjava/lang/String;Ljava/lang/String;I)V", + "(JLjava/lang/String;I)V", (void*)android_os_Trace_nativeAsyncTraceForTrackEnd }, { "nativeInstant", "(JLjava/lang/String;)V", diff --git a/core/proto/android/server/windowmanagertransitiontrace.proto b/core/proto/android/server/windowmanagertransitiontrace.proto new file mode 100644 index 000000000000..9429127b2f6e --- /dev/null +++ b/core/proto/android/server/windowmanagertransitiontrace.proto @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto3"; + +package com.android.server.wm.shell; + +import "frameworks/base/core/proto/android/server/windowmanagerservice.proto"; + +option java_multiple_files = true; + +/* Represents a file full of transition entries. + Encoded, it should start with 0x9 0x57 0x49 0x4e 0x54 0x52 0x41 0x43 0x45 (.TRNTRACE), such + that it can be easily identified. */ +message TransitionTraceProto { + + /* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 | MagicNumber.MAGIC_NUMBER_L + (this is needed because enums have to be 32 bits and there's no nice way to put 64bit + constants into .proto files. */ + enum MagicNumber { + INVALID = 0; + MAGIC_NUMBER_L = 0x544e5254; /* TRNT (little-endian ASCII) */ + MAGIC_NUMBER_H = 0x45434152; /* RACE (little-endian ASCII) */ + } + + fixed64 magic_number = 1; /* Must be the first field, set to value in MagicNumber */ + int64 timestamp = 2; /* The timestamp of when the trace was started. */ + repeated Transition transition = 3; +} + +message Transition { + + enum State { + COLLECTING = 0; + PENDING = -1; + STARTED = 1; + PLAYING = 2; + ABORT = 3; + FINISHED = 4; + } + + int32 id = 1; + int32 transition_type = 2; + int64 timestamp = 3; + State state = 5; + int32 flags = 6; + repeated ChangeInfo change = 7; +} + +message ChangeInfo { + com.android.server.wm.IdentifierProto window_identifier = 1; + int32 transit_mode = 2; + bool has_changed = 3; + int32 change_flags = 4; +} diff --git a/core/res/res/layout/chooser_grid.xml b/core/res/res/layout/chooser_grid.xml index 6e3a11af27a7..b102b4bb2124 100644 --- a/core/res/res/layout/chooser_grid.xml +++ b/core/res/res/layout/chooser_grid.xml @@ -41,7 +41,7 @@ android:src="@drawable/ic_drag_handle" android:layout_marginTop="@dimen/chooser_edge_margin_thin" android:layout_marginBottom="@dimen/chooser_edge_margin_thin" - android:tint="@color/lighter_gray" + android:tint="?attr/colorSurfaceVariant" android:layout_centerHorizontal="true" android:layout_alignParentTop="true" /> diff --git a/core/res/res/layout/language_picker_section_header.xml b/core/res/res/layout/language_picker_section_header.xml index 4fa4d9b043c6..58042f9a42f8 100644 --- a/core/res/res/layout/language_picker_section_header.xml +++ b/core/res/res/layout/language_picker_section_header.xml @@ -24,4 +24,5 @@ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" android:textColor="?android:attr/colorAccent" android:textStyle="bold" + android:id="@+id/language_picker_header" tools:text="@string/language_picker_section_all"/> diff --git a/core/res/res/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml index 8480ec37a79e..6a200d05c2d7 100644 --- a/core/res/res/layout/resolver_list.xml +++ b/core/res/res/layout/resolver_list.xml @@ -90,11 +90,13 @@ android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content"> + <!-- horizontal padding = 8dp content padding - 4dp margin that tab buttons have. --> <TabWidget android:id="@android:id/tabs" android:layout_width="match_parent" android:layout_height="wrap_content" android:tabStripEnabled="false" + android:paddingHorizontal="4dp" android:visibility="gone" /> <FrameLayout android:id="@android:id/tabcontent" diff --git a/core/res/res/layout/resolver_profile_tab_button.xml b/core/res/res/layout/resolver_profile_tab_button.xml index 936c8e23b87a..fd168e6414f1 100644 --- a/core/res/res/layout/resolver_profile_tab_button.xml +++ b/core/res/res/layout/resolver_profile_tab_button.xml @@ -21,7 +21,7 @@ android:layout_height="36dp" android:layout_weight="1" android:layout_marginVertical="6dp" - android:layout_marginHorizontal="4dp" + android:layout_marginHorizontal="@dimen/resolver_profile_tab_margin" android:background="@drawable/resolver_profile_tab_bg" android:textColor="@color/resolver_profile_tab_text" android:textSize="@dimen/resolver_tab_text_size" diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index ab6798282cb4..ddaf2e84ad5d 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3004,6 +3004,10 @@ </string-array> + <!-- Whether to show a notification informing users about notification permission settings + upon upgrade to T from a pre-T version --> + <bool name="config_notificationReviewPermissions">false</bool> + <!-- Default Gravity setting for the system Toast view. Equivalent to: Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM --> <integer name="config_toastDefaultGravity">0x00000051</integer> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index ff8477a62665..09571213e81f 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -962,6 +962,7 @@ <dimen name="resolver_title_padding_bottom">0dp</dimen> <dimen name="resolver_empty_state_container_padding_top">48dp</dimen> <dimen name="resolver_empty_state_container_padding_bottom">8dp</dimen> + <dimen name="resolver_profile_tab_margin">4dp</dimen> <dimen name="chooser_action_button_icon_size">18dp</dimen> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 5c851eb35ca7..900dcb9da54c 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2268,6 +2268,7 @@ <java-symbol type="integer" name="config_screenshotChordKeyTimeout" /> <java-symbol type="integer" name="config_maxResolverActivityColumns" /> <java-symbol type="array" name="config_notificationSignalExtractors" /> + <java-symbol type="bool" name="config_notificationReviewPermissions" /> <java-symbol type="layout" name="notification_material_action" /> <java-symbol type="layout" name="notification_material_action_list" /> @@ -4323,6 +4324,7 @@ <java-symbol type="dimen" name="resolver_title_padding_bottom" /> <java-symbol type="dimen" name="resolver_empty_state_container_padding_top" /> <java-symbol type="dimen" name="resolver_empty_state_container_padding_bottom" /> + <java-symbol type="dimen" name="resolver_profile_tab_margin" /> <java-symbol type="string" name="config_deviceSpecificDisplayAreaPolicyProvider" /> @@ -4800,6 +4802,7 @@ <java-symbol type="layout" name="app_language_picker_current_locale_item" /> <java-symbol type="id" name="system_locale_subtitle" /> <java-symbol type="id" name="language_picker_item" /> + <java-symbol type="id" name="language_picker_header" /> <java-symbol type="dimen" name="status_bar_height_default" /> </resources> diff --git a/data/etc/platform.xml b/data/etc/platform.xml index 61bb1ee2561f..75e188ff37bb 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -120,6 +120,10 @@ <group gid="reserved_disk" /> </permission> + <permission name="android.permission.WRITE_SECURITY_LOG"> + <group gid="security_log" /> + </permission> + <!-- These are permissions that were mapped to gids but we need to keep them here until an upgrade from L to the current version is to be supported. These permissions are built-in diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 23cf1d9654c4..3380a23f3c09 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -47,6 +47,7 @@ import android.util.SparseArray; import android.window.TaskFragmentInfo; import android.window.WindowContainerTransaction; +import androidx.annotation.GuardedBy; import androidx.window.common.EmptyLifecycleCallbacksAdapter; import com.android.internal.annotations.VisibleForTesting; @@ -65,9 +66,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen private static final String TAG = "SplitController"; @VisibleForTesting + @GuardedBy("mLock") final SplitPresenter mPresenter; // Currently applied split configuration. + @GuardedBy("mLock") private final List<EmbeddingRule> mSplitRules = new ArrayList<>(); /** * Map from Task id to {@link TaskContainer} which contains all TaskFragment and split pair info @@ -76,6 +79,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * organizer. */ @VisibleForTesting + @GuardedBy("mLock") final SparseArray<TaskContainer> mTaskContainers = new SparseArray<>(); // Callback to Jetpack to notify about changes to split states. @@ -83,6 +87,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen private Consumer<List<SplitInfo>> mEmbeddingCallback; private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>(); private final Handler mHandler; + private final Object mLock = new Object(); public SplitController() { final MainThreadExecutor executor = new MainThreadExecutor(); @@ -100,180 +105,183 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** Updates the embedding rules applied to future activity launches. */ @Override public void setEmbeddingRules(@NonNull Set<EmbeddingRule> rules) { - mSplitRules.clear(); - mSplitRules.addAll(rules); - for (int i = mTaskContainers.size() - 1; i >= 0; i--) { - updateAnimationOverride(mTaskContainers.valueAt(i)); + synchronized (mLock) { + mSplitRules.clear(); + mSplitRules.addAll(rules); + for (int i = mTaskContainers.size() - 1; i >= 0; i--) { + updateAnimationOverride(mTaskContainers.valueAt(i)); + } } } @NonNull - public List<EmbeddingRule> getSplitRules() { + List<EmbeddingRule> getSplitRules() { return mSplitRules; } /** - * Starts an activity to side of the launchingActivity with the provided split config. - */ - public void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent intent, - @Nullable Bundle options, @NonNull SplitRule sideRule, - @Nullable Consumer<Exception> failureCallback, boolean isPlaceholder) { - try { - mPresenter.startActivityToSide(launchingActivity, intent, options, sideRule, - isPlaceholder); - } catch (Exception e) { - if (failureCallback != null) { - failureCallback.accept(e); - } - } - } - - /** * Registers the split organizer callback to notify about changes to active splits. */ @Override public void setSplitInfoCallback(@NonNull Consumer<List<SplitInfo>> callback) { - mEmbeddingCallback = callback; - updateCallbackIfNecessary(); + synchronized (mLock) { + mEmbeddingCallback = callback; + updateCallbackIfNecessary(); + } } @Override public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) { - TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); - if (container == null) { - return; - } + synchronized (mLock) { + TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); + if (container == null) { + return; + } - container.setInfo(taskFragmentInfo); - if (container.isFinished()) { - mPresenter.cleanupContainer(container, false /* shouldFinishDependent */); + container.setInfo(taskFragmentInfo); + if (container.isFinished()) { + mPresenter.cleanupContainer(container, false /* shouldFinishDependent */); + } + updateCallbackIfNecessary(); } - updateCallbackIfNecessary(); } @Override public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) { - TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); - if (container == null) { - return; - } + synchronized (mLock) { + TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); + if (container == null) { + return; + } - final WindowContainerTransaction wct = new WindowContainerTransaction(); - final boolean wasInPip = isInPictureInPicture(container); - container.setInfo(taskFragmentInfo); - final boolean isInPip = isInPictureInPicture(container); - // Check if there are no running activities - consider the container empty if there are no - // non-finishing activities left. - if (!taskFragmentInfo.hasRunningActivity()) { - if (taskFragmentInfo.isTaskFragmentClearedForPip()) { - // Do not finish the dependents if the last activity is reparented to PiP. - // Instead, the original split should be cleanup, and the dependent may be expanded - // to fullscreen. + final WindowContainerTransaction wct = new WindowContainerTransaction(); + final boolean wasInPip = isInPictureInPicture(container); + container.setInfo(taskFragmentInfo); + final boolean isInPip = isInPictureInPicture(container); + // Check if there are no running activities - consider the container empty if there are + // no non-finishing activities left. + if (!taskFragmentInfo.hasRunningActivity()) { + if (taskFragmentInfo.isTaskFragmentClearedForPip()) { + // Do not finish the dependents if the last activity is reparented to PiP. + // Instead, the original split should be cleanup, and the dependent may be + // expanded to fullscreen. + cleanupForEnterPip(wct, container); + mPresenter.cleanupContainer(container, false /* shouldFinishDependent */, wct); + } else if (taskFragmentInfo.isTaskClearedForReuse()) { + // Do not finish the dependents if this TaskFragment was cleared due to + // launching activity in the Task. + mPresenter.cleanupContainer(container, false /* shouldFinishDependent */, wct); + } else if (!container.isWaitingActivityAppear()) { + // Do not finish the container before the expected activity appear until + // timeout. + mPresenter.cleanupContainer(container, true /* shouldFinishDependent */, wct); + } + } else if (wasInPip && isInPip) { + // No update until exit PIP. + return; + } else if (isInPip) { + // Enter PIP. + // All overrides will be cleanup. + container.setLastRequestedBounds(null /* bounds */); + container.setLastRequestedWindowingMode(WINDOWING_MODE_UNDEFINED); cleanupForEnterPip(wct, container); - mPresenter.cleanupContainer(container, false /* shouldFinishDependent */, wct); - } else if (taskFragmentInfo.isTaskClearedForReuse()) { - // Do not finish the dependents if this TaskFragment was cleared due to launching - // activity in the Task. - mPresenter.cleanupContainer(container, false /* shouldFinishDependent */, wct); - } else if (!container.isWaitingActivityAppear()) { - // Do not finish the container before the expected activity appear until timeout. - mPresenter.cleanupContainer(container, true /* shouldFinishDependent */, wct); + } else if (wasInPip) { + // Exit PIP. + // Updates the presentation of the container. Expand or launch placeholder if + // needed. + updateContainer(wct, container); } - } else if (wasInPip && isInPip) { - // No update until exit PIP. - return; - } else if (isInPip) { - // Enter PIP. - // All overrides will be cleanup. - container.setLastRequestedBounds(null /* bounds */); - container.setLastRequestedWindowingMode(WINDOWING_MODE_UNDEFINED); - cleanupForEnterPip(wct, container); - } else if (wasInPip) { - // Exit PIP. - // Updates the presentation of the container. Expand or launch placeholder if needed. - updateContainer(wct, container); + mPresenter.applyTransaction(wct); + updateCallbackIfNecessary(); } - mPresenter.applyTransaction(wct); - updateCallbackIfNecessary(); } @Override public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) { - final TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); - if (container != null) { - // Cleanup if the TaskFragment vanished is not requested by the organizer. - removeContainer(container); - // Make sure the top container is updated. - final TaskFragmentContainer newTopContainer = getTopActiveContainer( - container.getTaskId()); - if (newTopContainer != null) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - updateContainer(wct, newTopContainer); - mPresenter.applyTransaction(wct); + synchronized (mLock) { + final TaskFragmentContainer container = getContainer( + taskFragmentInfo.getFragmentToken()); + if (container != null) { + // Cleanup if the TaskFragment vanished is not requested by the organizer. + removeContainer(container); + // Make sure the top container is updated. + final TaskFragmentContainer newTopContainer = getTopActiveContainer( + container.getTaskId()); + if (newTopContainer != null) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + updateContainer(wct, newTopContainer); + mPresenter.applyTransaction(wct); + } + updateCallbackIfNecessary(); } - updateCallbackIfNecessary(); + cleanupTaskFragment(taskFragmentInfo.getFragmentToken()); } - cleanupTaskFragment(taskFragmentInfo.getFragmentToken()); } @Override public void onTaskFragmentParentInfoChanged(@NonNull IBinder fragmentToken, @NonNull Configuration parentConfig) { - final TaskFragmentContainer container = getContainer(fragmentToken); - if (container != null) { - onTaskConfigurationChanged(container.getTaskId(), parentConfig); - if (isInPictureInPicture(parentConfig)) { - // No need to update presentation in PIP until the Task exit PIP. - return; + synchronized (mLock) { + final TaskFragmentContainer container = getContainer(fragmentToken); + if (container != null) { + onTaskConfigurationChanged(container.getTaskId(), parentConfig); + if (isInPictureInPicture(parentConfig)) { + // No need to update presentation in PIP until the Task exit PIP. + return; + } + mPresenter.updateContainer(container); + updateCallbackIfNecessary(); } - mPresenter.updateContainer(container); - updateCallbackIfNecessary(); } } @Override public void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken) { - // If the activity belongs to the current app process, we treat it as a new activity launch. - final Activity activity = getActivity(activityToken); - if (activity != null) { - // We don't allow split as primary for new launch because we currently only support - // launching to top. We allow split as primary for activity reparent because the - // activity may be split as primary before it is reparented out. In that case, we want - // to show it as primary again when it is reparented back. - if (!resolveActivityToContainer(activity, true /* isOnReparent */)) { - // When there is no embedding rule matched, try to place it in the top container - // like a normal launch. - placeActivityInTopContainer(activity); + synchronized (mLock) { + // If the activity belongs to the current app process, we treat it as a new activity + // launch. + final Activity activity = getActivity(activityToken); + if (activity != null) { + // We don't allow split as primary for new launch because we currently only support + // launching to top. We allow split as primary for activity reparent because the + // activity may be split as primary before it is reparented out. In that case, we + // want to show it as primary again when it is reparented back. + if (!resolveActivityToContainer(activity, true /* isOnReparent */)) { + // When there is no embedding rule matched, try to place it in the top container + // like a normal launch. + placeActivityInTopContainer(activity); + } + updateCallbackIfNecessary(); + return; } - updateCallbackIfNecessary(); - return; - } - final TaskContainer taskContainer = getTaskContainer(taskId); - if (taskContainer == null || taskContainer.isInPictureInPicture()) { - // We don't embed activity when it is in PIP. - return; - } + final TaskContainer taskContainer = getTaskContainer(taskId); + if (taskContainer == null || taskContainer.isInPictureInPicture()) { + // We don't embed activity when it is in PIP. + return; + } - // If the activity belongs to a different app process, we treat it as starting new intent, - // since both actions might result in a new activity that should appear in an organized - // TaskFragment. - final WindowContainerTransaction wct = new WindowContainerTransaction(); - TaskFragmentContainer targetContainer = resolveStartActivityIntent(wct, taskId, - activityIntent, null /* launchingActivity */); - if (targetContainer == null) { - // When there is no embedding rule matched, try to place it in the top container like a - // normal launch. - targetContainer = taskContainer.getTopTaskFragmentContainer(); - } - if (targetContainer == null) { - return; + // If the activity belongs to a different app process, we treat it as starting new + // intent, since both actions might result in a new activity that should appear in an + // organized TaskFragment. + final WindowContainerTransaction wct = new WindowContainerTransaction(); + TaskFragmentContainer targetContainer = resolveStartActivityIntent(wct, taskId, + activityIntent, null /* launchingActivity */); + if (targetContainer == null) { + // When there is no embedding rule matched, try to place it in the top container + // like a normal launch. + targetContainer = taskContainer.getTopTaskFragmentContainer(); + } + if (targetContainer == null) { + return; + } + wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(), + activityToken); + mPresenter.applyTransaction(wct); + // Because the activity does not belong to the organizer process, we wait until + // onTaskFragmentAppeared to trigger updateCallbackIfNecessary(). } - wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(), activityToken); - mPresenter.applyTransaction(wct); - // Because the activity does not belong to the organizer process, we wait until - // onTaskFragmentAppeared to trigger updateCallbackIfNecessary(). } /** Called on receiving {@link #onTaskFragmentVanished(TaskFragmentInfo)} for cleanup. */ @@ -481,6 +489,22 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } /** + * Starts an activity to side of the launchingActivity with the provided split config. + */ + private void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent intent, + @Nullable Bundle options, @NonNull SplitRule sideRule, + @Nullable Consumer<Exception> failureCallback, boolean isPlaceholder) { + try { + mPresenter.startActivityToSide(launchingActivity, intent, options, sideRule, + isPlaceholder); + } catch (Exception e) { + if (failureCallback != null) { + failureCallback.accept(e); + } + } + } + + /** * Expands the given activity by either expanding the TaskFragment it is currently in or putting * it into a new expanded TaskFragment. */ @@ -1382,25 +1406,28 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @Override public void onActivityPreCreated(Activity activity, Bundle savedInstanceState) { - final IBinder activityToken = activity.getActivityToken(); - final IBinder initialTaskFragmentToken = getInitialTaskFragmentToken(activity); - // If the activity is not embedded, then it will not have an initial task fragment token - // so no further action is needed. - if (initialTaskFragmentToken == null) { - return; - } - for (int i = mTaskContainers.size() - 1; i >= 0; i--) { - final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i) - .mContainers; - for (int j = containers.size() - 1; j >= 0; j--) { - final TaskFragmentContainer container = containers.get(j); - if (!container.hasActivity(activityToken) - && container.getTaskFragmentToken().equals(initialTaskFragmentToken)) { - // The onTaskFragmentInfoChanged callback containing this activity has not - // reached the client yet, so add the activity to the pending appeared - // activities. - container.addPendingAppearedActivity(activity); - return; + synchronized (mLock) { + final IBinder activityToken = activity.getActivityToken(); + final IBinder initialTaskFragmentToken = getInitialTaskFragmentToken(activity); + // If the activity is not embedded, then it will not have an initial task fragment + // token so no further action is needed. + if (initialTaskFragmentToken == null) { + return; + } + for (int i = mTaskContainers.size() - 1; i >= 0; i--) { + final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i) + .mContainers; + for (int j = containers.size() - 1; j >= 0; j--) { + final TaskFragmentContainer container = containers.get(j); + if (!container.hasActivity(activityToken) + && container.getTaskFragmentToken() + .equals(initialTaskFragmentToken)) { + // The onTaskFragmentInfoChanged callback containing this activity has + // not reached the client yet, so add the activity to the pending + // appeared activities. + container.addPendingAppearedActivity(activity); + return; + } } } } @@ -1412,17 +1439,23 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // first. In case of a configured placeholder activity we want to make sure // that we don't launch it if an activity itself already requested something to be // launched to side. - SplitController.this.onActivityCreated(activity); + synchronized (mLock) { + SplitController.this.onActivityCreated(activity); + } } @Override public void onActivityConfigurationChanged(Activity activity) { - SplitController.this.onActivityConfigurationChanged(activity); + synchronized (mLock) { + SplitController.this.onActivityConfigurationChanged(activity); + } } @Override public void onActivityPostDestroyed(Activity activity) { - SplitController.this.onActivityDestroyed(activity); + synchronized (mLock) { + SplitController.this.onActivityDestroyed(activity); + } } } @@ -1457,16 +1490,18 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return super.onStartActivity(who, intent, options); } - final int taskId = getTaskId(launchingActivity); - final WindowContainerTransaction wct = new WindowContainerTransaction(); - final TaskFragmentContainer launchedInTaskFragment = resolveStartActivityIntent(wct, - taskId, intent, launchingActivity); - if (launchedInTaskFragment != null) { - mPresenter.applyTransaction(wct); - // Amend the request to let the WM know that the activity should be placed in the - // dedicated container. - options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN, - launchedInTaskFragment.getTaskFragmentToken()); + synchronized (mLock) { + final int taskId = getTaskId(launchingActivity); + final WindowContainerTransaction wct = new WindowContainerTransaction(); + final TaskFragmentContainer launchedInTaskFragment = resolveStartActivityIntent(wct, + taskId, intent, launchingActivity); + if (launchedInTaskFragment != null) { + mPresenter.applyTransaction(wct); + // Amend the request to let the WM know that the activity should be placed in + // the dedicated container. + options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN, + launchedInTaskFragment.getTaskFragmentToken()); + } } return super.onStartActivity(who, intent, options); @@ -1479,7 +1514,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen */ @Override public boolean isActivityEmbedded(@NonNull Activity activity) { - return mPresenter.isActivityEmbedded(activity.getActivityToken()); + synchronized (mLock) { + return mPresenter.isActivityEmbedded(activity.getActivityToken()); + } } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index eb7aa1f7ff50..6f990081de9c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -434,7 +434,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } } - final Rect destinationBounds = mPipBoundsState.getDisplayBounds(); + final Rect destinationBounds = getExitDestinationBounds(); final int direction = syncWithSplitScreenBounds(destinationBounds, requestEnterSplit) ? TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN : TRANSITION_DIRECTION_LEAVE_PIP; @@ -489,6 +489,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, }); } + /** Returns the bounds to restore to when exiting PIP mode. */ + public Rect getExitDestinationBounds() { + return mPipBoundsState.getDisplayBounds(); + } + private void exitLaunchIntoPipTask(WindowContainerTransaction wct) { wct.startTask(mTaskInfo.launchIntoPipHostTaskId, null /* ActivityOptions */); mTaskOrganizer.applyTransaction(wct); @@ -974,6 +979,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, if (!isInPip()) { return; } + if (mLeash == null || !mLeash.isValid()) { + ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Invalid leash on setPipVisibility: %s", TAG, mLeash); + return; + } final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); mSurfaceTransactionHelper.alpha(tx, mLeash, visible ? 1f : 0f); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index f969caac3196..3cf8a45310ef 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -1025,8 +1025,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, wct.reparent(mMainStage.mRootTaskInfo.token, mRootTaskInfo.token, true); wct.reparent(mSideStage.mRootTaskInfo.token, mRootTaskInfo.token, true); // Make the stages adjacent to each other so they occlude what's behind them. - wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token, - true /* moveTogether */); + wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token); wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token); mTaskOrganizer.applyTransaction(wct); } diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 7c99ce5a8c4d..64c080fd938a 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1134,6 +1134,8 @@ <string name="battery_info_status_charging_slow">Charging slowly</string> <!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when charging wirelessly. --> <string name="battery_info_status_charging_wireless">Charging wirelessly</string> + <!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when the device is dock charging. --> + <string name="battery_info_status_charging_dock">Charging Dock</string> <!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed --> <string name="battery_info_status_discharging">Not charging</string> <!-- Battery Info screen. Value for a status item. A state which device is connected with any charger(e.g. USB, Adapter or Wireless) but not charging yet. Used for diagnostic info screens, precise translation isn't needed --> diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index feb4212035bc..b9c4030d9d0e 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -239,6 +239,8 @@ public class Utils { statusString = res.getString(R.string.battery_info_status_charging); break; } + } else if (batteryStatus.isPluggedInDock()) { + statusString = res.getString(R.string.battery_info_status_charging_dock); } else { statusString = res.getString(R.string.battery_info_status_charging_wireless); } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java index 09b2a2e73c5b..336cdd3f259f 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java @@ -365,6 +365,17 @@ public class UtilsTest { } @Test + public void getBatteryStatus_chargingDock_returnDockChargingString() { + final Intent intent = new Intent(); + intent.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_CHARGING); + intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_DOCK); + final Resources resources = mContext.getResources(); + + assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo( + resources.getString(R.string.battery_info_status_charging_dock)); + } + + @Test public void getBatteryStatus_chargingWireless_returnWirelessChargingString() { final Intent intent = new Intent(); intent.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_CHARGING); diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 92812cf64db7..bfd76c5e29f9 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -48,7 +48,50 @@ <!-- The minimum display position of the arrow on the screen --> <dimen name="navigation_edge_arrow_min_y">64dp</dimen> <!-- The amount by which the arrow is shifted to avoid the finger--> - <dimen name="navigation_edge_finger_offset">48dp</dimen> + <dimen name="navigation_edge_finger_offset">64dp</dimen> + + <!-- The thickness of the arrow --> + <dimen name="navigation_edge_arrow_thickness">4dp</dimen> + <!-- The minimum delta needed to change direction / stop triggering back --> + <dimen name="navigation_edge_minimum_x_delta_for_switch">32dp</dimen> + + <!-- entry state --> + <dimen name="navigation_edge_entry_margin">4dp</dimen> + <dimen name="navigation_edge_entry_background_width">8dp</dimen> + <dimen name="navigation_edge_entry_background_height">60dp</dimen> + <dimen name="navigation_edge_entry_edge_corners">6dp</dimen> + <dimen name="navigation_edge_entry_far_corners">6dp</dimen> + <dimen name="navigation_edge_entry_arrow_length">10dp</dimen> + <dimen name="navigation_edge_entry_arrow_height">7dp</dimen> + + <!-- pre-threshold --> + <dimen name="navigation_edge_pre_threshold_margin">4dp</dimen> + <dimen name="navigation_edge_pre_threshold_background_width">64dp</dimen> + <dimen name="navigation_edge_pre_threshold_background_height">60dp</dimen> + <dimen name="navigation_edge_pre_threshold_edge_corners">22dp</dimen> + <dimen name="navigation_edge_pre_threshold_far_corners">26dp</dimen> + + <!-- post-threshold / active --> + <dimen name="navigation_edge_active_margin">14dp</dimen> + <dimen name="navigation_edge_active_background_width">60dp</dimen> + <dimen name="navigation_edge_active_background_height">60dp</dimen> + <dimen name="navigation_edge_active_edge_corners">30dp</dimen> + <dimen name="navigation_edge_active_far_corners">30dp</dimen> + <dimen name="navigation_edge_active_arrow_length">8dp</dimen> + <dimen name="navigation_edge_active_arrow_height">9dp</dimen> + + <!-- stretch @412 dp --> + <dimen name="navigation_edge_stretch_threshold">412dp</dimen> + <dimen name="navigation_edge_stretch_margin">18dp</dimen> + <dimen name="navigation_edge_stretch_background_width">74dp</dimen> + <dimen name="navigation_edge_stretch_background_height">60dp</dimen> + <dimen name="navigation_edge_stretch_left_corners">30dp</dimen> + <dimen name="navigation_edge_stretch_right_corners">30dp</dimen> + <dimen name="navigation_edge_stretched_arrow_length">7dp</dimen> + <dimen name="navigation_edge_stretched_arrow_height">10dp</dimen> + + <dimen name="navigation_edge_cancelled_arrow_length">12dp</dimen> + <dimen name="navigation_edge_cancelled_arrow_height">0dp</dimen> <!-- Height of notification icons in the status bar --> <dimen name="status_bar_icon_size">@*android:dimen/status_bar_icon_size</dimen> diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt new file mode 100644 index 000000000000..56ad19ae89ca --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt @@ -0,0 +1,340 @@ +package com.android.systemui.navigationbar.gestural + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Path +import android.graphics.RectF +import android.view.View +import androidx.dynamicanimation.animation.FloatPropertyCompat +import androidx.dynamicanimation.animation.SpringAnimation +import androidx.dynamicanimation.animation.SpringForce +import com.android.internal.util.LatencyTracker +import com.android.settingslib.Utils +import com.android.systemui.navigationbar.gestural.BackPanelController.DelayedOnAnimationEndListener + +private const val TAG = "BackPanel" +private const val DEBUG = false + +class BackPanel(context: Context, private val latencyTracker: LatencyTracker) : View(context) { + + var arrowsPointLeft = false + set(value) { + if (field != value) { + invalidate() + field = value + } + } + + // Arrow color and shape + private val arrowPath = Path() + private val arrowPaint = Paint() + + // Arrow background color and shape + private var arrowBackgroundRect = RectF() + private var arrowBackgroundPaint = Paint() + + // True if the panel is currently on the left of the screen + var isLeftPanel = false + + /** + * Used to track back arrow latency from [android.view.MotionEvent.ACTION_DOWN] to [onDraw] + */ + private var trackingBackArrowLatency = false + + /** + * The length of the arrow measured horizontally. Used for animating [arrowPath] + */ + private var arrowLength = AnimatedFloat("arrowLength", SpringForce()) + + /** + * The height of the arrow measured vertically from its center to its top (i.e. half the total + * height). Used for animating [arrowPath] + */ + private var arrowHeight = AnimatedFloat("arrowHeight", SpringForce()) + + private val backgroundWidth = AnimatedFloat( + name = "backgroundWidth", + SpringForce().apply { + stiffness = 600f + dampingRatio = 0.65f + }) + + private val backgroundHeight = AnimatedFloat( + name = "backgroundHeight", + SpringForce().apply { + stiffness = 600f + dampingRatio = 0.65f + }) + + /** + * Corners of the background closer to the edge of the screen (where the arrow appeared from). + * Used for animating [arrowBackgroundRect] + */ + private val backgroundEdgeCornerRadius = AnimatedFloat( + name = "backgroundEdgeCornerRadius", + SpringForce().apply { + stiffness = 400f + dampingRatio = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY + }) + + /** + * Corners of the background further from the edge of the screens (toward the direction the + * arrow is being dragged). Used for animating [arrowBackgroundRect] + */ + private val backgroundDragCornerRadius = AnimatedFloat( + name = "backgroundDragCornerRadius", + SpringForce().apply { + stiffness = 2200f + dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY + }) + + /** + * Left/right position of the background relative to the canvas. Also corresponds with the + * background's margin relative to the screen edge. The arrow will be centered within the + * background. + */ + private var horizontalTranslation = AnimatedFloat("horizontalTranslation", SpringForce()) + + /** + * Canvas vertical translation. How far up/down the arrow and background appear relative to the + * canvas. + */ + private var verticalTranslation: AnimatedFloat = + AnimatedFloat("verticalTranslation", SpringForce().apply { + stiffness = SpringForce.STIFFNESS_MEDIUM + }) + + /** + * Use for drawing debug info. Can only be set if [DEBUG]=true + */ + var drawDebugInfo: ((canvas: Canvas) -> Unit)? = null + set(value) { + if (DEBUG) field = value + } + + internal fun updateArrowPaint(arrowThickness: Float) { + // Arrow constants + arrowPaint.strokeWidth = arrowThickness + + arrowPaint.color = + Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary) + arrowBackgroundPaint.color = Utils.getColorAccentDefaultColor(context) + } + + private inner class AnimatedFloat(name: String, springForce: SpringForce) { + // The resting position when not stretched by a touch drag + private var restingPosition = 0f + + // The current position as updated by the SpringAnimation + var pos = 0f + set(v) { + if (field != v) { + field = v + invalidate() + } + } + + val animation: SpringAnimation + + init { + val floatProp = object : FloatPropertyCompat<AnimatedFloat>(name) { + override fun setValue(animatedFloat: AnimatedFloat, value: Float) { + animatedFloat.pos = value + } + + override fun getValue(animatedFloat: AnimatedFloat): Float = animatedFloat.pos + } + animation = SpringAnimation(this, floatProp) + animation.spring = springForce + } + + fun snapTo(newPosition: Float) { + animation.cancel() + restingPosition = newPosition + animation.spring.finalPosition = newPosition + pos = newPosition + } + + fun stretchTo(stretchAmount: Float) { + animation.animateToFinalPosition(restingPosition + stretchAmount) + } + + fun updateRestingPosition(pos: Float, animated: Boolean) { + restingPosition = pos + if (animated) + animation.animateToFinalPosition(restingPosition) + else + snapTo(restingPosition) + } + } + + init { + visibility = GONE + arrowPaint.apply { + style = Paint.Style.STROKE + strokeCap = Paint.Cap.SQUARE + } + arrowBackgroundPaint.apply { + style = Paint.Style.FILL + strokeJoin = Paint.Join.ROUND + strokeCap = Paint.Cap.ROUND + } + } + + private fun calculateArrowPath(dx: Float, dy: Float): Path { + arrowPath.reset() + arrowPath.moveTo(dx, -dy) + arrowPath.lineTo(0f, 0f) + arrowPath.lineTo(dx, dy) + arrowPath.moveTo(dx, -dy) + return arrowPath + } + + fun addEndListener(endListener: DelayedOnAnimationEndListener): Boolean { + return if (horizontalTranslation.animation.isRunning) { + horizontalTranslation.animation.addEndListener(endListener) + true + } else { + endListener.runNow() + false + } + } + + fun setStretch( + arrowLengthStretch: Float, + arrowHeightStretch: Float, + backgroundWidthStretch: Float, + backgroundHeightStretch: Float, + backgroundEdgeCornerRadiusStretch: Float, + backgroundDragCornerRadiusStretch: Float, + horizontalTranslationStretch: Float + ) { + arrowLength.stretchTo(arrowLengthStretch) + arrowHeight.stretchTo(arrowHeightStretch) + backgroundWidth.stretchTo(backgroundWidthStretch) + backgroundHeight.stretchTo(backgroundHeightStretch) + backgroundEdgeCornerRadius.stretchTo(backgroundEdgeCornerRadiusStretch) + backgroundDragCornerRadius.stretchTo(backgroundDragCornerRadiusStretch) + horizontalTranslation.stretchTo(horizontalTranslationStretch) + } + + fun resetStretch() { + setStretch(0f, 0f, 0f, 0f, 0f, 0f, 0f) + } + + /** + * Updates resting arrow and background size not accounting for stretch + */ + internal fun updateRestingArrowDimens( + backgroundWidth: Float, + backgroundHeight: Float, + backgroundEdgeCornerRadius: Float, + backgroundDragCornerRadius: Float, + arrowLength: Float, + arrowHeight: Float, + horizontalTranslation: Float, + animate: Boolean + ) { + this.arrowLength.updateRestingPosition(arrowLength, animate) + this.arrowHeight.updateRestingPosition(arrowHeight, animate) + this.backgroundWidth.updateRestingPosition(backgroundWidth, animate) + this.backgroundHeight.updateRestingPosition(backgroundHeight, animate) + this.backgroundEdgeCornerRadius.updateRestingPosition(backgroundEdgeCornerRadius, animate) + this.backgroundDragCornerRadius.updateRestingPosition(backgroundDragCornerRadius, animate) + this.horizontalTranslation.updateRestingPosition(horizontalTranslation, animate) + } + + fun animateVertically(yPos: Float) = verticalTranslation.stretchTo(yPos) + + fun setArrowStiffness(arrowStiffness: Float, arrowDampingRatio: Float) { + arrowLength.animation.spring.apply { + stiffness = arrowStiffness + dampingRatio = arrowDampingRatio + } + arrowHeight.animation.spring.apply { + stiffness = arrowStiffness + dampingRatio = arrowDampingRatio + } + } + + override fun hasOverlappingRendering() = false + + override fun onDraw(canvas: Canvas) { + var edgeCorner = backgroundEdgeCornerRadius.pos + val farCorner = backgroundDragCornerRadius.pos + val halfHeight = backgroundHeight.pos / 2 + + canvas.save() + + if (!isLeftPanel) canvas.scale(-1f, 1f, width / 2.0f, 0f) + + canvas.translate( + horizontalTranslation.pos, + height * 0.5f + verticalTranslation.pos + ) + + val arrowBackground = arrowBackgroundRect.apply { + left = 0f + top = -halfHeight + right = backgroundWidth.pos + bottom = halfHeight + }.toPathWithRoundCorners( + topLeft = edgeCorner, + bottomLeft = edgeCorner, + topRight = farCorner, + bottomRight = farCorner + ) + canvas.drawPath(arrowBackground, arrowBackgroundPaint) + + val dx = arrowLength.pos + val dy = arrowHeight.pos + + // How far the arrow bounding box should be from the edge of the screen. Measured from + // either the tip or the back of the arrow, whichever is closer + var arrowOffset = (backgroundWidth.pos - dx) / 2 + canvas.translate( + /* dx= */ arrowOffset, + /* dy= */ 0f /* pass 0 for the y position since the canvas was already translated */ + ) + + val arrowPointsAwayFromEdge = !arrowsPointLeft.xor(isLeftPanel) + if (arrowPointsAwayFromEdge) { + canvas.apply { + scale(-1f, 1f, 0f, 0f) + translate(-dx, 0f) + } + } + + val arrowPath = calculateArrowPath(dx = dx, dy = dy) + canvas.drawPath(arrowPath, arrowPaint) + canvas.restore() + + if (trackingBackArrowLatency) { + latencyTracker.onActionEnd(LatencyTracker.ACTION_SHOW_BACK_ARROW) + trackingBackArrowLatency = false + } + + if (DEBUG) drawDebugInfo?.invoke(canvas) + } + + fun startTrackingShowBackArrowLatency() { + latencyTracker.onActionStart(LatencyTracker.ACTION_SHOW_BACK_ARROW) + trackingBackArrowLatency = true + } + + private fun RectF.toPathWithRoundCorners( + topLeft: Float = 0f, + topRight: Float = 0f, + bottomRight: Float = 0f, + bottomLeft: Float = 0f + ): Path = Path().apply { + val corners = floatArrayOf( + topLeft, topLeft, + topRight, topRight, + bottomRight, bottomRight, + bottomLeft, bottomLeft + ) + addRoundRect(this@toPathWithRoundCorners, corners, Path.Direction.CW) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt new file mode 100644 index 000000000000..100411b1cb93 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt @@ -0,0 +1,735 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.navigationbar.gestural + +import android.content.Context +import android.content.res.Configuration +import android.graphics.Color +import android.graphics.Paint +import android.graphics.Point +import android.os.Handler +import android.os.SystemClock +import android.os.VibrationEffect +import android.util.Log +import android.util.MathUtils.constrain +import android.util.MathUtils.saturate +import android.view.Gravity +import android.view.MotionEvent +import android.view.VelocityTracker +import android.view.View +import android.view.WindowManager +import android.view.animation.AccelerateInterpolator +import android.view.animation.PathInterpolator +import android.window.BackEvent +import androidx.dynamicanimation.animation.DynamicAnimation +import androidx.dynamicanimation.animation.SpringForce +import com.android.internal.util.LatencyTracker +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.plugins.NavigationEdgeBackPlugin +import com.android.systemui.statusbar.VibratorHelper +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.util.ViewController +import com.android.wm.shell.back.BackAnimation +import java.io.PrintWriter +import javax.inject.Inject +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min +import kotlin.math.sign + +private const val TAG = "BackPanelController" +private const val DEBUG = false + +private const val ENABLE_FAILSAFE = true + +private const val FAILSAFE_DELAY_MS: Long = 350 + +/** + * The time required between the arrow-appears vibration effect and the back-committed vibration + * effect. If the arrow is flung quickly, the phone only vibrates once. However, if the arrow is + * held on the screen for a long time, it will vibrate a second time when the back gesture is + * committed. + */ +private const val GESTURE_DURATION_FOR_CLICK_MS = 400 + +/** + * The min duration arrow remains on screen during a fling event. + */ +private const val FLING_PAUSE_DURATION_MS = 50L + +/** + * The min duration arrow remains on screen during a fling event. + */ +private const val MIN_FLING_VELOCITY = 3000 + +/** + * The amount of rubber banding we do for the vertical translation + */ +private const val RUBBER_BAND_AMOUNT = 15 + +private const val ARROW_APPEAR_STIFFNESS = 600f +private const val ARROW_APPEAR_DAMPING_RATIO = 0.4f +private const val ARROW_DISAPPEAR_STIFFNESS = 1200f +private const val ARROW_DISAPPEAR_DAMPING_RATIO = SpringForce.DAMPING_RATIO_NO_BOUNCY + +/** + * The interpolator used to rubber band + */ +private val RUBBER_BAND_INTERPOLATOR = PathInterpolator(1.0f / 5.0f, 1.0f, 1.0f, 1.0f) + +private val ACCELERATE_INTERPOLATOR = AccelerateInterpolator(0.7f) + +class BackPanelController private constructor( + context: Context, + private var backAnimation: BackAnimation?, + private val windowManager: WindowManager, + @Main private val mainHandler: Handler, + private val vibratorHelper: VibratorHelper, + private val configurationController: ConfigurationController, + latencyTracker: LatencyTracker +) : ViewController<BackPanel>(BackPanel(context, latencyTracker)), NavigationEdgeBackPlugin { + + /** + * Injectable instance to create a new BackPanelController. + * + * Necessary because EdgeBackGestureHandler sometimes needs to create new instances of + * BackPanelController, and we need to match EdgeBackGestureHandler's context. + */ + class Factory @Inject constructor( + private val windowManager: WindowManager, + @Main private val mainHandler: Handler, + private val vibratorHelper: VibratorHelper, + private val configurationController: ConfigurationController, + private val latencyTracker: LatencyTracker + ) { + /** Construct a [BackPanelController]. */ + fun create(context: Context, backAnimation: BackAnimation?): BackPanelController { + val backPanelController = BackPanelController( + context, + backAnimation, + windowManager, + mainHandler, + vibratorHelper, + configurationController, + latencyTracker + ) + backPanelController.init() + return backPanelController + } + } + + private var params: EdgePanelParams = EdgePanelParams(resources) + private var currentState: GestureState = GestureState.GONE + private var previousState: GestureState = GestureState.GONE + + // Phone should only vibrate the first time the arrow is activated + private var hasHapticPlayed = false + + // Screen attributes + private lateinit var layoutParams: WindowManager.LayoutParams + private val displaySize = Point() + + private lateinit var backCallback: NavigationEdgeBackPlugin.BackCallback + + private var previousXTranslation = 0f + private var totalTouchDelta = 0f + private var velocityTracker: VelocityTracker? = null + set(value) { + if (field != value) field?.recycle() + field = value + } + get() { + if (field == null) field = VelocityTracker.obtain() + return field + } + + // The x,y position of the first touch event + private var startX = 0f + private var startY = 0f + + private val failsafeRunnable = Runnable { onFailsafe() } + + private enum class GestureState { + /* Arrow is off the screen and invisible */ + GONE, + + /* Arrow is animating in */ + ENTRY, + + /* could be entry, neutral, or stretched, releasing will commit back */ + ACTIVE, + + /* releasing will cancel back */ + INACTIVE, + + /* like committed, but animation takes longer */ + FLUNG, + + /* back action currently occurring, arrow soon to be GONE */ + COMMITTED, + + /* back action currently cancelling, arrow soon to be GONE */ + CANCELLED + } + + /** + * Wrapper around OnAnimationEndListener which runs the given runnable after a delay. The + * runnable is not called if the animation is cancelled + */ + class DelayedOnAnimationEndListener( + private val handler: Handler, + private val runnable: Runnable, + private val delay: Long + ) : DynamicAnimation.OnAnimationEndListener { + + override fun onAnimationEnd( + animation: DynamicAnimation<*>, + canceled: Boolean, + value: Float, + velocity: Float + ) { + animation.removeEndListener(this) + if (!canceled) { + handler.postDelayed(runnable, delay) + } + } + + fun runNow() { + runnable.run() + } + } + + private val setCommittedEndListener = + DelayedOnAnimationEndListener( + mainHandler, + { updateArrowState(GestureState.COMMITTED) }, + delay = FLING_PAUSE_DURATION_MS + ) + + private val setGoneEndListener = + DelayedOnAnimationEndListener( + mainHandler, + { + cancelFailsafe() + updateArrowState(GestureState.GONE) + }, + delay = 0 + ) + + // Vibration + private var vibrationTime: Long = 0 + + // Minimum size of the screen's width or height + private var screenSize = 0 + + /** + * Used for initialization and configuration changes + */ + private fun updateConfiguration() { + params.update(resources) + initializeBackAnimation() + mView.updateArrowPaint(params.arrowThickness) + } + + private val configurationListener = object : ConfigurationController.ConfigurationListener { + override fun onConfigChanged(newConfig: Configuration?) { + updateConfiguration() + } + + override fun onLayoutDirectionChanged(isLayoutRtl: Boolean) { + updateArrowDirection(isLayoutRtl) + } + } + + override fun onViewAttached() { + updateConfiguration() + updateArrowDirection(configurationController.isLayoutRtl) + updateArrowState(GestureState.GONE, force = true) + updateRestingArrowDimens(animated = false, currentState) + configurationController.addCallback(configurationListener) + } + + /** Update the arrow direction. The arrow should point the same way for both panels. */ + private fun updateArrowDirection(isLayoutRtl: Boolean) { + mView.arrowsPointLeft = isLayoutRtl + } + + override fun onViewDetached() { + configurationController.removeCallback(configurationListener) + } + + override fun onMotionEvent(event: MotionEvent) { + backAnimation?.onBackMotion( + event, + event.actionMasked, + if (mView.isLeftPanel) BackEvent.EDGE_LEFT else BackEvent.EDGE_RIGHT + ) + + velocityTracker!!.addMovement(event) + when (event.actionMasked) { + MotionEvent.ACTION_DOWN -> { + resetOnDown() + startX = event.x + startY = event.y + + // Reset the arrow to the side + updateArrowState(GestureState.ENTRY) + + windowManager.updateViewLayout(mView, layoutParams) + mView.startTrackingShowBackArrowLatency() + } + MotionEvent.ACTION_MOVE -> handleMoveEvent(event) + MotionEvent.ACTION_UP -> { + if (currentState == GestureState.ACTIVE) { + updateArrowState(if (isFlung()) GestureState.FLUNG else GestureState.COMMITTED) + } else { + updateArrowState(GestureState.CANCELLED) + } + velocityTracker = null + } + MotionEvent.ACTION_CANCEL -> { + updateArrowState(GestureState.CANCELLED) + velocityTracker = null + } + } + } + + private fun updateArrowStateOnMove(yTranslation: Float, xTranslation: Float) { + when (currentState) { + GestureState.GONE, GestureState.FLUNG, GestureState.COMMITTED, GestureState.CANCELLED -> + return + } + + updateArrowState( + when { + // Check if we should transition from ENTRY to ACTIVE + currentState == GestureState.ENTRY && xTranslation > params.swipeTriggerThreshold -> + GestureState.ACTIVE + + // Abort if we had continuous motion toward the edge for a while, OR the direction + // in Y is bigger than X * 2 + currentState == GestureState.ACTIVE && + ((totalTouchDelta < 0 && -totalTouchDelta > params.minDeltaForSwitch) || + (yTranslation > xTranslation * 2)) -> + GestureState.INACTIVE + + // Re-activate if we had continuous motion away from the edge for a while + currentState == GestureState.INACTIVE && + (totalTouchDelta > 0 && totalTouchDelta > params.minDeltaForSwitch) -> + GestureState.ACTIVE + + // By default assume the current direction is kept + else -> currentState + } + ) + } + + private fun handleMoveEvent(event: MotionEvent) { + when (currentState) { + GestureState.GONE, GestureState.FLUNG, GestureState.COMMITTED, + GestureState.CANCELLED -> return + } + + val x = event.x + val y = event.y + + val yOffset = y - startY + + // How far in the y direction we are from the original touch + val yTranslation = abs(yOffset) + + // How far in the x direction we are from the original touch ignoring motion that + // occurs between the screen edge and the touch start. + val xTranslation = max(0f, if (mView.isLeftPanel) x - startX else startX - x) + + // Compared to last time, how far we moved in the x direction. If <0, we are moving closer + // to the edge. If >0, we are moving further from the edge + val xDelta = xTranslation - previousXTranslation + previousXTranslation = xTranslation + + if (abs(xDelta) > 0) { + if (sign(xDelta) == sign(totalTouchDelta)) { + // Direction has NOT changed, so keep counting the delta + totalTouchDelta += xDelta + } else { + // Direction has changed, so reset the delta + totalTouchDelta = xDelta + } + } + + updateArrowStateOnMove(yTranslation, xTranslation) + when (currentState) { + GestureState.ACTIVE -> setActiveStretch(fullScreenStretchProgress(xTranslation)) + GestureState.ENTRY -> + setEntryStretch(preThresholdStretchProgress(xTranslation)) + GestureState.INACTIVE -> mView.resetStretch() + } + + // set y translation + setVerticalTranslation(yOffset) + } + + fun setVerticalTranslation(yOffset: Float) { + val yTranslation = abs(yOffset) + val maxYOffset = (mView.height / 2) - (params.entryBackgroundHeight / 2) + val yProgress = saturate(yTranslation / (maxYOffset * RUBBER_BAND_AMOUNT)) + mView.animateVertically( + RUBBER_BAND_INTERPOLATOR.getInterpolation(yProgress) * maxYOffset * sign( + yOffset + ) + ) + } + + /** + * @return the relative position of the drag from the time after the arrow is activated until + * the arrow is fully stretched (between 0.0 - 1.0f) + */ + fun fullScreenStretchProgress(xTranslation: Float): Float { + return saturate( + (xTranslation - params.swipeTriggerThreshold) / + (min( + params.fullyStretchedThreshold, + screenSize.toFloat() + ) - params.swipeTriggerThreshold) + ) + } + + /** + * Tracks the relative position of the drag from the entry until the threshold where the arrow + * activates (between 0.0 - 1.0f) + */ + fun preThresholdStretchProgress(xTranslation: Float): Float { + return saturate(xTranslation / params.swipeTriggerThreshold) + } + + fun setActiveStretch(progress: Float) { + val stretch = RUBBER_BAND_INTERPOLATOR.getInterpolation(progress) + mView.setStretch( + arrowLengthStretch = stretch * (params.stretchedArrowLength - params.activeArrowLength), + arrowHeightStretch = stretch * (params.stretchedArrowHeight - params.activeArrowHeight), + backgroundWidthStretch = + stretch * (params.stretchBackgroundWidth - params.activeBackgroundWidth), + backgroundHeightStretch = + stretch * (params.stretchBackgroundHeight - params.activeBackgroundHeight), + backgroundEdgeCornerRadiusStretch = + stretch * (params.stretchEdgeCorners - params.activeEdgeCorners), + backgroundDragCornerRadiusStretch = + stretch * (params.stretchFarCorners - params.activeFarCorners), + horizontalTranslationStretch = stretch * (params.stretchMargin - params.activeMargin) + ) + } + + fun setEntryStretch(progress: Float) { + val bgStretch = ACCELERATE_INTERPOLATOR.getInterpolation(progress) + val arrowStretch = RUBBER_BAND_INTERPOLATOR.getInterpolation(progress) + mView.setStretch( + arrowLengthStretch = + arrowStretch * (params.activeArrowLength - params.entryArrowLength), + arrowHeightStretch = + arrowStretch * (params.activeArrowHeight - params.entryArrowHeight), + backgroundWidthStretch = + bgStretch * (params.preThresholdBackgroundWidth - params.entryBackgroundWidth), + backgroundHeightStretch = + bgStretch * (params.preThresholdBackgroundHeight - params.entryBackgroundHeight), + backgroundEdgeCornerRadiusStretch = + bgStretch * (params.preThresholdEdgeCorners - params.entryEdgeCorners), + backgroundDragCornerRadiusStretch = + bgStretch * (params.preThresholdFarCorners - params.entryFarCorners), + horizontalTranslationStretch = + bgStretch * (params.preThresholdMargin - params.entryMargin) + ) + } + + fun setBackAnimation(backAnimation: BackAnimation?) { + this.backAnimation = backAnimation + initializeBackAnimation() + } + + private fun initializeBackAnimation() { + backAnimation?.setSwipeThresholds( + params.swipeTriggerThreshold, + params.swipeProgressThreshold + ) + } + + override fun onDestroy() { + cancelFailsafe() + windowManager.removeView(mView) + } + + override fun setIsLeftPanel(isLeftPanel: Boolean) { + mView.isLeftPanel = isLeftPanel + layoutParams.gravity = if (isLeftPanel) { + Gravity.LEFT or Gravity.TOP + } else { + Gravity.RIGHT or Gravity.TOP + } + } + + override fun setInsets(insetLeft: Int, insetRight: Int) { + } + + override fun setBackCallback(callback: NavigationEdgeBackPlugin.BackCallback) { + backCallback = callback + } + + override fun setLayoutParams(layoutParams: WindowManager.LayoutParams) { + this.layoutParams = layoutParams + windowManager.addView(mView, layoutParams) + } + + private fun isFlung() = velocityTracker!!.run { + computeCurrentVelocity(1000) + abs(xVelocity) > MIN_FLING_VELOCITY + } + + private fun playFlingBackAnimation() { + playAnimation(setCommittedEndListener) + } + + private fun playCommitBackAnimation() { + // Check if we should vibrate again + if (previousState != GestureState.FLUNG) { + backCallback.triggerBack() + velocityTracker!!.computeCurrentVelocity(1000) + val isSlow = abs(velocityTracker!!.xVelocity) < 500 + val hasNotVibratedRecently = + SystemClock.uptimeMillis() - vibrationTime >= GESTURE_DURATION_FOR_CLICK_MS + if (isSlow || hasNotVibratedRecently) { + vibratorHelper.vibrate(VibrationEffect.EFFECT_CLICK) + } + } + playAnimation(setGoneEndListener) + } + + private fun playCancelBackAnimation() { + backCallback.cancelBack() + playAnimation(setGoneEndListener) + } + + /** + * @return true if the animation is running, false otherwise. Some transitions don't animate + */ + private fun playAnimation(endListener: DelayedOnAnimationEndListener) { + updateRestingArrowDimens(animated = true, currentState) + + if (!mView.addEndListener(endListener)) { + scheduleFailsafe() + } + } + + private fun resetOnDown() { + hasHapticPlayed = false + totalTouchDelta = 0f + vibrationTime = 0 + cancelFailsafe() + backAnimation?.setTriggerBack(false) + } + + private fun updateYPosition(touchY: Float) { + var yPosition = touchY - params.fingerOffset + yPosition = Math.max(yPosition, params.minArrowYPosition.toFloat()) + yPosition -= layoutParams.height / 2.0f + layoutParams.y = constrain(yPosition.toInt(), 0, displaySize.y) + } + + override fun setDisplaySize(displaySize: Point) { + this.displaySize.set(displaySize.x, displaySize.y) + screenSize = Math.min(displaySize.x, displaySize.y) + } + + /** + * Updates resting arrow and background size not accounting for stretch + */ + private fun updateRestingArrowDimens(animated: Boolean, currentState: GestureState) { + mView.updateRestingArrowDimens( + backgroundWidth = + when (currentState) { + GestureState.GONE, GestureState.ENTRY -> params.entryBackgroundWidth + else -> params.activeBackgroundWidth + }, + backgroundHeight = + when (currentState) { + GestureState.GONE, GestureState.ENTRY -> params.entryBackgroundHeight + else -> params.activeBackgroundHeight + }, + backgroundEdgeCornerRadius = + when (currentState) { + GestureState.GONE, GestureState.ENTRY, GestureState.INACTIVE -> + params.entryEdgeCorners + else -> + params.activeEdgeCorners + }, + backgroundDragCornerRadius = + when (currentState) { + GestureState.GONE, GestureState.ENTRY -> params.entryFarCorners + else -> params.activeFarCorners + }, + arrowLength = + when (currentState) { + GestureState.ACTIVE, GestureState.INACTIVE, GestureState.COMMITTED, + GestureState.FLUNG -> params.activeArrowLength + GestureState.CANCELLED -> params.cancelledArrowLength + GestureState.GONE, GestureState.ENTRY -> params.entryArrowLength + }, + arrowHeight = + when (currentState) { + GestureState.ACTIVE, GestureState.INACTIVE, GestureState.COMMITTED, + GestureState.FLUNG -> params.activeArrowHeight + GestureState.CANCELLED -> params.cancelledArrowHeight + GestureState.GONE, GestureState.ENTRY -> params.entryArrowHeight + }, + horizontalTranslation = + when (currentState) { + GestureState.GONE -> -params.activeBackgroundWidth + // Position the cancelled/committed arrow slightly further off the screen so we + // do not see part of it bouncing + GestureState.CANCELLED, GestureState.COMMITTED -> + -params.activeBackgroundWidth * 1.5f + GestureState.FLUNG -> params.stretchMargin + GestureState.ACTIVE -> params.activeMargin + GestureState.ENTRY, GestureState.INACTIVE -> params.entryMargin + }, + animate = animated + ) + if (animated) { + when (currentState) { + GestureState.ENTRY, GestureState.ACTIVE, GestureState.FLUNG -> + mView.setArrowStiffness(ARROW_APPEAR_STIFFNESS, ARROW_APPEAR_DAMPING_RATIO) + else -> + mView.setArrowStiffness( + ARROW_DISAPPEAR_STIFFNESS, ARROW_DISAPPEAR_DAMPING_RATIO) + } + } + } + + /** + * Update arrow state. If state has not changed, this is a no-op. + * + * Transitioning to active/inactive will indicate whether or not releasing touch will trigger + * the back action. + */ + private fun updateArrowState(newState: GestureState, force: Boolean = false) { + if (!force && currentState == newState) return + + if (DEBUG) Log.d(TAG, "updateArrowState $currentState -> $newState") + previousState = currentState + currentState = newState + mView.visibility = if (currentState == GestureState.GONE) View.GONE else View.VISIBLE + + when (currentState) { + // Transitioning to GONE never animates since the arrow is (presumably) already off the + // screen + GestureState.GONE -> updateRestingArrowDimens(animated = false, currentState) + GestureState.ENTRY -> { + updateYPosition(startY) + updateRestingArrowDimens(animated = true, currentState) + } + GestureState.ACTIVE -> { + backAnimation?.setTriggerBack(true) + updateRestingArrowDimens(animated = true, currentState) + // Vibrate the first time we transition to ACTIVE + if (!hasHapticPlayed) { + hasHapticPlayed = true + vibrationTime = SystemClock.uptimeMillis() + vibratorHelper.vibrate(VibrationEffect.EFFECT_TICK) + } + } + GestureState.INACTIVE -> { + backAnimation?.setTriggerBack(false) + updateRestingArrowDimens(animated = true, currentState) + } + GestureState.FLUNG -> playFlingBackAnimation() + GestureState.COMMITTED -> playCommitBackAnimation() + GestureState.CANCELLED -> playCancelBackAnimation() + } + } + + private fun scheduleFailsafe() { + if (!ENABLE_FAILSAFE) return + cancelFailsafe() + if (DEBUG) Log.d(TAG, "scheduleFailsafe") + mainHandler.postDelayed(failsafeRunnable, FAILSAFE_DELAY_MS) + } + + private fun cancelFailsafe() { + if (DEBUG) Log.d(TAG, "cancelFailsafe") + mainHandler.removeCallbacks(failsafeRunnable) + } + + private fun onFailsafe() { + if (DEBUG) Log.d(TAG, "onFailsafe") + updateArrowState(GestureState.GONE, force = true) + } + + override fun dump(pw: PrintWriter) { + pw.println("$TAG:") + pw.println(" currentState=$currentState") + pw.println(" isLeftPanel=$mView.isLeftPanel") + } + + init { + if (DEBUG) mView.drawDebugInfo = { canvas -> + val debugStrings = listOf( + "$currentState", + "startX=$startX", + "startY=$startY", + "xDelta=${"%.1f".format(totalTouchDelta)}", + "xTranslation=${"%.1f".format(previousXTranslation)}", + "pre=${"%.0f".format(preThresholdStretchProgress(previousXTranslation) * 100)}%", + "post=${"%.0f".format(fullScreenStretchProgress(previousXTranslation) * 100)}%" + ) + val debugPaint = Paint().apply { + color = Color.WHITE + } + val debugInfoBottom = debugStrings.size * 32f + 4f + canvas.drawRect( + 4f, + 4f, + canvas.width.toFloat(), + debugStrings.size * 32f + 4f, + debugPaint + ) + debugPaint.apply { + color = Color.BLACK + textSize = 32f + } + var offset = 32f + for (debugText in debugStrings) { + canvas.drawText(debugText, 10f, offset, debugPaint) + offset += 32f + } + debugPaint.apply { + color = Color.RED + style = Paint.Style.STROKE + strokeWidth = 4f + } + val canvasWidth = canvas.width.toFloat() + val canvasHeight = canvas.height.toFloat() + canvas.drawRect(0f, 0f, canvasWidth, canvasHeight, debugPaint) + + fun drawVerticalLine(x: Float, color: Int) { + debugPaint.color = color + val x = if (mView.isLeftPanel) x else canvasWidth - x + canvas.drawLine(x, debugInfoBottom, x, canvas.height.toFloat(), debugPaint) + } + + drawVerticalLine(x = params.swipeTriggerThreshold, color = Color.BLUE) + drawVerticalLine(x = startX, color = Color.GREEN) + drawVerticalLine(x = previousXTranslation, color = Color.DKGRAY) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index ea41fe74f798..d41837b7cf4d 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -62,6 +62,8 @@ import com.android.systemui.R; import com.android.systemui.SystemUIFactory; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.NavigationBarView; import com.android.systemui.navigationbar.NavigationModeController; @@ -182,6 +184,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker private final PluginManager mPluginManager; private final ProtoTracer mProtoTracer; private final NavigationModeController mNavigationModeController; + private final BackPanelController.Factory mBackPanelControllerFactory; private final ViewConfiguration mViewConfiguration; private final WindowManager mWindowManager; private final IWindowManager mWindowManagerService; @@ -199,6 +202,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker private final Region mExcludeRegion = new Region(); private final Region mUnrestrictedExcludeRegion = new Region(); private final LatencyTracker mLatencyTracker; + private final FeatureFlags mFeatureFlags; // The left side edge width where touch down is allowed private int mEdgeWidthLeft; @@ -230,6 +234,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker private boolean mIsBackGestureAllowed; private boolean mGestureBlockingActivityRunning; private boolean mIsInPipMode; + private boolean mIsPredictiveBackAnimEnabled; private InputMonitor mInputMonitor; private InputChannelCompat.InputEventReceiver mInputEventReceiver; @@ -298,12 +303,22 @@ public class EdgeBackGestureHandler extends CurrentUserTracker }; - EdgeBackGestureHandler(Context context, OverviewProxyService overviewProxyService, - SysUiState sysUiState, PluginManager pluginManager, @Main Executor executor, - BroadcastDispatcher broadcastDispatcher, ProtoTracer protoTracer, - NavigationModeController navigationModeController, ViewConfiguration viewConfiguration, - WindowManager windowManager, IWindowManager windowManagerService, - FalsingManager falsingManager, LatencyTracker latencyTracker) { + EdgeBackGestureHandler( + Context context, + OverviewProxyService overviewProxyService, + SysUiState sysUiState, + PluginManager pluginManager, + @Main Executor executor, + BroadcastDispatcher broadcastDispatcher, + ProtoTracer protoTracer, + NavigationModeController navigationModeController, + BackPanelController.Factory backPanelControllerFactory, + ViewConfiguration viewConfiguration, + WindowManager windowManager, + IWindowManager windowManagerService, + FalsingManager falsingManager, + LatencyTracker latencyTracker, + FeatureFlags featureFlags) { super(broadcastDispatcher); mContext = context; mDisplayId = context.getDisplayId(); @@ -313,11 +328,13 @@ public class EdgeBackGestureHandler extends CurrentUserTracker mPluginManager = pluginManager; mProtoTracer = protoTracer; mNavigationModeController = navigationModeController; + mBackPanelControllerFactory = backPanelControllerFactory; mViewConfiguration = viewConfiguration; mWindowManager = windowManager; mWindowManagerService = windowManagerService; mFalsingManager = falsingManager; mLatencyTracker = latencyTracker; + mFeatureFlags = featureFlags; ComponentName recentsComponentName = ComponentName.unflattenFromString( context.getString(com.android.internal.R.string.config_recentsComponentName)); if (recentsComponentName != null) { @@ -507,8 +524,9 @@ public class EdgeBackGestureHandler extends CurrentUserTracker Choreographer.getInstance(), this::onInputEvent); // Add a nav bar panel window - setEdgeBackPlugin( - new NavigationBarEdgePanel(mContext, mBackAnimation, mLatencyTracker)); + mIsPredictiveBackAnimEnabled = + mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_ANIM); + resetEdgeBackPlugin(); mPluginManager.addPluginListener( this, NavigationEdgeBackPlugin.class, /*allowMultiple=*/ false); } @@ -523,7 +541,17 @@ public class EdgeBackGestureHandler extends CurrentUserTracker @Override public void onPluginDisconnected(NavigationEdgeBackPlugin plugin) { - setEdgeBackPlugin(new NavigationBarEdgePanel(mContext, mBackAnimation, mLatencyTracker)); + resetEdgeBackPlugin(); + } + + private void resetEdgeBackPlugin() { + if (mIsPredictiveBackAnimEnabled) { + setEdgeBackPlugin( + mBackPanelControllerFactory.create(mContext, mBackAnimation)); + } else { + setEdgeBackPlugin( + new NavigationBarEdgePanel(mContext, mBackAnimation, mLatencyTracker)); + } } private void setEdgeBackPlugin(NavigationEdgeBackPlugin edgeBackPlugin) { @@ -948,8 +976,12 @@ public class EdgeBackGestureHandler extends CurrentUserTracker public void setBackAnimation(BackAnimation backAnimation) { mBackAnimation = backAnimation; - if (mEdgeBackPlugin != null && mEdgeBackPlugin instanceof NavigationBarEdgePanel) { - ((NavigationBarEdgePanel) mEdgeBackPlugin).setBackAnimation(backAnimation); + if (mEdgeBackPlugin != null) { + if (mEdgeBackPlugin instanceof NavigationBarEdgePanel) { + ((NavigationBarEdgePanel) mEdgeBackPlugin).setBackAnimation(backAnimation); + } else if (mEdgeBackPlugin instanceof BackPanelController) { + ((BackPanelController) mEdgeBackPlugin).setBackAnimation(backAnimation); + } } } @@ -967,20 +999,29 @@ public class EdgeBackGestureHandler extends CurrentUserTracker private final BroadcastDispatcher mBroadcastDispatcher; private final ProtoTracer mProtoTracer; private final NavigationModeController mNavigationModeController; + private final BackPanelController.Factory mBackPanelControllerFactory; private final ViewConfiguration mViewConfiguration; private final WindowManager mWindowManager; private final IWindowManager mWindowManagerService; private final FalsingManager mFalsingManager; private final LatencyTracker mLatencyTracker; + private final FeatureFlags mFeatureFlags; @Inject public Factory(OverviewProxyService overviewProxyService, - SysUiState sysUiState, PluginManager pluginManager, @Main Executor executor, - BroadcastDispatcher broadcastDispatcher, ProtoTracer protoTracer, - NavigationModeController navigationModeController, - ViewConfiguration viewConfiguration, WindowManager windowManager, - IWindowManager windowManagerService, FalsingManager falsingManager, - LatencyTracker latencyTracker) { + SysUiState sysUiState, + PluginManager pluginManager, + @Main Executor executor, + BroadcastDispatcher broadcastDispatcher, + ProtoTracer protoTracer, + NavigationModeController navigationModeController, + BackPanelController.Factory backPanelControllerFactory, + ViewConfiguration viewConfiguration, + WindowManager windowManager, + IWindowManager windowManagerService, + FalsingManager falsingManager, + LatencyTracker latencyTracker, + FeatureFlags featureFlags) { mOverviewProxyService = overviewProxyService; mSysUiState = sysUiState; mPluginManager = pluginManager; @@ -988,19 +1029,33 @@ public class EdgeBackGestureHandler extends CurrentUserTracker mBroadcastDispatcher = broadcastDispatcher; mProtoTracer = protoTracer; mNavigationModeController = navigationModeController; + mBackPanelControllerFactory = backPanelControllerFactory; mViewConfiguration = viewConfiguration; mWindowManager = windowManager; mWindowManagerService = windowManagerService; mFalsingManager = falsingManager; mLatencyTracker = latencyTracker; + mFeatureFlags = featureFlags; } /** Construct a {@link EdgeBackGestureHandler}. */ public EdgeBackGestureHandler create(Context context) { - return new EdgeBackGestureHandler(context, mOverviewProxyService, mSysUiState, - mPluginManager, mExecutor, mBroadcastDispatcher, mProtoTracer, - mNavigationModeController, mViewConfiguration, mWindowManager, - mWindowManagerService, mFalsingManager, mLatencyTracker); + return new EdgeBackGestureHandler( + context, + mOverviewProxyService, + mSysUiState, + mPluginManager, + mExecutor, + mBroadcastDispatcher, + mProtoTracer, + mNavigationModeController, + mBackPanelControllerFactory, + mViewConfiguration, + mWindowManager, + mWindowManagerService, + mFalsingManager, + mLatencyTracker, + mFeatureFlags); } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt new file mode 100644 index 000000000000..51566b0f8393 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt @@ -0,0 +1,139 @@ +package com.android.systemui.navigationbar.gestural + +import android.content.res.Resources +import com.android.systemui.R + +data class EdgePanelParams(private var resources: Resources) { + var arrowThickness: Float = 0f + private set + var entryArrowLength: Float = 0f + private set + var entryArrowHeight: Float = 0f + private set + var activeArrowLength: Float = 0f + private set + var activeArrowHeight: Float = 0f + private set + var stretchedArrowLength: Float = 0f + private set + var stretchedArrowHeight: Float = 0f + private set + var cancelledArrowLength: Float = 0f + private set + var cancelledArrowHeight: Float = 0f + private set + var entryMargin: Float = 0f + private set + var entryBackgroundWidth: Float = 0f + private set + var entryBackgroundHeight: Float = 0f + private set + var entryEdgeCorners: Float = 0f + private set + var entryFarCorners: Float = 0f + private set + var preThresholdMargin: Float = 0f + private set + var preThresholdBackgroundWidth: Float = 0f + private set + var preThresholdBackgroundHeight: Float = 0f + private set + var preThresholdEdgeCorners: Float = 0f + private set + var preThresholdFarCorners: Float = 0f + private set + var activeMargin: Float = 0f + private set + var activeBackgroundWidth: Float = 0f + private set + var activeBackgroundHeight: Float = 0f + private set + var activeEdgeCorners: Float = 0f + private set + var activeFarCorners: Float = 0f + private set + var fullyStretchedThreshold: Float = 0f + private set + var stretchMargin: Float = 0f + private set + var stretchBackgroundWidth: Float = 0f + private set + var stretchBackgroundHeight: Float = 0f + private set + var stretchEdgeCorners: Float = 0f + private set + var stretchFarCorners: Float = 0f + private set + + // navigation bar edge constants + var arrowPaddingEnd: Int = 0 + private set + + // The closest to y + var minArrowYPosition: Int = 0 + private set + var fingerOffset: Int = 0 + private set + var swipeTriggerThreshold: Float = 0f + private set + var swipeProgressThreshold: Float = 0f + private set + + // The minimum delta needed to change direction / stop triggering back + var minDeltaForSwitch: Int = 0 + private set + + init { + update(resources) + } + + private fun getDimen(id: Int): Float { + return resources.getDimension(id) + } + + private fun getPx(id: Int): Int { + return resources.getDimensionPixelSize(id) + } + + fun update(resources: Resources) { + this.resources = resources + arrowThickness = getDimen(R.dimen.navigation_edge_arrow_thickness) + entryArrowLength = getDimen(R.dimen.navigation_edge_entry_arrow_length) + entryArrowHeight = getDimen(R.dimen.navigation_edge_entry_arrow_height) + activeArrowLength = getDimen(R.dimen.navigation_edge_active_arrow_length) + activeArrowHeight = getDimen(R.dimen.navigation_edge_active_arrow_height) + stretchedArrowLength = getDimen(R.dimen.navigation_edge_stretched_arrow_length) + stretchedArrowHeight = getDimen(R.dimen.navigation_edge_stretched_arrow_height) + cancelledArrowLength = getDimen(R.dimen.navigation_edge_cancelled_arrow_length) + cancelledArrowHeight = getDimen(R.dimen.navigation_edge_cancelled_arrow_height) + entryMargin = getDimen(R.dimen.navigation_edge_entry_margin) + entryBackgroundWidth = getDimen(R.dimen.navigation_edge_entry_background_width) + entryBackgroundHeight = getDimen(R.dimen.navigation_edge_entry_background_height) + entryEdgeCorners = getDimen(R.dimen.navigation_edge_entry_edge_corners) + entryFarCorners = getDimen(R.dimen.navigation_edge_entry_far_corners) + preThresholdMargin = getDimen(R.dimen.navigation_edge_pre_threshold_margin) + preThresholdBackgroundWidth = + getDimen(R.dimen.navigation_edge_pre_threshold_background_width) + preThresholdBackgroundHeight = + getDimen(R.dimen.navigation_edge_pre_threshold_background_height) + preThresholdEdgeCorners = getDimen(R.dimen.navigation_edge_pre_threshold_edge_corners) + preThresholdFarCorners = getDimen(R.dimen.navigation_edge_pre_threshold_far_corners) + activeMargin = getDimen(R.dimen.navigation_edge_active_margin) + activeBackgroundWidth = getDimen(R.dimen.navigation_edge_active_background_width) + activeBackgroundHeight = getDimen(R.dimen.navigation_edge_active_background_height) + activeEdgeCorners = getDimen(R.dimen.navigation_edge_active_edge_corners) + activeFarCorners = getDimen(R.dimen.navigation_edge_active_far_corners) + fullyStretchedThreshold = getDimen(R.dimen.navigation_edge_stretch_threshold) + stretchMargin = getDimen(R.dimen.navigation_edge_stretch_margin) + stretchBackgroundWidth = getDimen(R.dimen.navigation_edge_stretch_background_width) + stretchBackgroundHeight = getDimen(R.dimen.navigation_edge_stretch_background_height) + stretchEdgeCorners = getDimen(R.dimen.navigation_edge_stretch_left_corners) + stretchFarCorners = getDimen(R.dimen.navigation_edge_stretch_right_corners) + arrowPaddingEnd = getPx(R.dimen.navigation_edge_panel_padding) + minArrowYPosition = getPx(R.dimen.navigation_edge_arrow_min_y) + fingerOffset = getPx(R.dimen.navigation_edge_finger_offset) + swipeTriggerThreshold = getDimen(R.dimen.navigation_edge_action_drag_threshold) + swipeProgressThreshold = getDimen(R.dimen.navigation_edge_action_progress_threshold) + minDeltaForSwitch = getPx(R.dimen.navigation_edge_minimum_x_delta_for_switch) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt index d05c3385e982..6287857e7be9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt @@ -37,6 +37,7 @@ import kotlin.properties.Delegates.notNull private const val TAG = "NotifStackSizeCalc" private val DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG) +private val SPEW = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE) /** Calculates number of notifications to display and the height of the notification stack. */ @SysUISingleton @@ -87,9 +88,10 @@ constructor( // Could be < 0 if the space available is less than the shelf size. Returns 0 in this case. maxNotifications = max(0, maxNotifications) log { + val sequence = if (SPEW) " stackHeightSequence=${stackHeightSequence.toList()}" else "" "computeMaxKeyguardNotifications(" + "availableSpace=$totalAvailableSpace" + - " shelfHeight=$shelfIntrinsicHeight) -> $maxNotifications" + " shelfHeight=$shelfIntrinsicHeight) -> $maxNotifications$sequence" } return maxNotifications } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index f55e76b3734a..6ff7dd4a240f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -3500,11 +3500,6 @@ public class CentralSurfacesImpl extends CoreStartable implements @Override public void onTrackingStopped(boolean expand) { - if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) { - if (!expand && !mKeyguardStateController.canDismissLockScreen()) { - mStatusBarKeyguardViewManager.showBouncer(false /* scrimmed */); - } - } } // TODO: Figure out way to remove these. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 4a912572b7ed..2977ab49af9b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -192,6 +192,7 @@ import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.unfold.SysUIUnfoldComponent; +import com.android.systemui.util.Compile; import com.android.systemui.util.LargeScreenUtils; import com.android.systemui.util.ListenerSet; import com.android.systemui.util.Utils; @@ -216,7 +217,9 @@ import javax.inject.Provider; @CentralSurfacesComponent.CentralSurfacesScope public class NotificationPanelViewController extends PanelViewController { - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private static final boolean DEBUG_LOGCAT = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG); + private static final boolean SPEW_LOGCAT = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE); + private static final boolean DEBUG_DRAWABLE = false; /** * The parallax amount of the quick settings translation when dragging down the panel @@ -816,7 +819,7 @@ public class NotificationPanelViewController extends PanelViewController { mSettingsChangeObserver = new SettingsChangeObserver(handler); mSplitShadeEnabled = LargeScreenUtils.shouldUseSplitNotificationShade(mResources); - mView.setWillNotDraw(!DEBUG); + mView.setWillNotDraw(!DEBUG_DRAWABLE); mLargeScreenShadeHeaderController = largeScreenShadeHeaderController; mLayoutInflater = layoutInflater; mFeatureFlags = featureFlags; @@ -890,7 +893,7 @@ public class NotificationPanelViewController extends PanelViewController { mView.setOnApplyWindowInsetsListener(new OnApplyWindowInsetsListener()); - if (DEBUG) { + if (DEBUG_DRAWABLE) { mView.getOverlay().add(new DebugDrawable()); } @@ -1189,7 +1192,7 @@ public class NotificationPanelViewController extends PanelViewController { } private void reInflateViews() { - if (DEBUG) Log.d(TAG, "reInflateViews"); + if (DEBUG_LOGCAT) Log.d(TAG, "reInflateViews"); // Re-inflate the status view group. KeyguardStatusView keyguardStatusView = mNotificationContainerParent.findViewById(R.id.keyguard_status_view); @@ -1293,6 +1296,8 @@ public class NotificationPanelViewController extends PanelViewController { private void updateMaxDisplayedNotifications(boolean recompute) { if (recompute) { mMaxAllowedKeyguardNotifications = Math.max(computeMaxKeyguardNotifications(), 1); + } else { + if (SPEW_LOGCAT) Log.d(TAG, "Skipping computeMaxKeyguardNotifications() by request"); } if (mKeyguardShowing && !mKeyguardBypassController.getBypassEnabled()) { @@ -1545,6 +1550,19 @@ public class NotificationPanelViewController extends PanelViewController { mNotificationStackScrollLayoutController.getHeight() - staticTopPadding - bottomPadding; + + if (SPEW_LOGCAT) { + Log.d(TAG, "getSpaceForLockscreenNotifications()" + + " availableSpace=" + availableSpace + + " NSSL.height=" + mNotificationStackScrollLayoutController.getHeight() + + " NSSL.top=" + mNotificationStackScrollLayoutController.getTop() + + " staticTopPadding=" + staticTopPadding + + " bottomPadding=" + bottomPadding + + " lockIconPadding=" + lockIconPadding + + " mIndicationBottomPadding=" + mIndicationBottomPadding + + " mAmbientIndicationBottomPadding=" + mAmbientIndicationBottomPadding + ); + } return availableSpace; } @@ -1553,7 +1571,12 @@ public class NotificationPanelViewController extends PanelViewController { */ @VisibleForTesting int computeMaxKeyguardNotifications() { - if (mAmbientState.getFractionToShade() > 0 || mAmbientState.getDozeAmount() > 0) { + if (mAmbientState.getFractionToShade() > 0) { + if (SPEW_LOGCAT) { + Log.v(TAG, "Internally skipping computeMaxKeyguardNotifications()" + + " fractionToShade=" + mAmbientState.getFractionToShade() + ); + } return mMaxAllowedKeyguardNotifications; } @@ -1743,7 +1766,7 @@ public class NotificationPanelViewController extends PanelViewController { } private boolean onQsIntercept(MotionEvent event) { - if (DEBUG) Log.d(TAG, "onQsIntercept"); + if (DEBUG_LOGCAT) Log.d(TAG, "onQsIntercept"); int pointerIndex = event.findPointerIndex(mTrackingPointer); if (pointerIndex < 0) { pointerIndex = 0; @@ -1798,7 +1821,7 @@ public class NotificationPanelViewController extends PanelViewController { if ((h > getTouchSlop(event) || (h < -getTouchSlop(event) && mQsExpanded)) && Math.abs(h) > Math.abs(x - mInitialTouchX) && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) { - if (DEBUG) Log.d(TAG, "onQsIntercept - start tracking expansion"); + if (DEBUG_LOGCAT) Log.d(TAG, "onQsIntercept - start tracking expansion"); mView.getParent().requestDisallowInterceptTouchEvent(true); mQsTracking = true; traceQsJank(true /* startTracing */, false /* wasCancelled */); @@ -2075,7 +2098,7 @@ public class NotificationPanelViewController extends PanelViewController { private void handleQsDown(MotionEvent event) { if (event.getActionMasked() == MotionEvent.ACTION_DOWN && shouldQuickSettingsIntercept( event.getX(), event.getY(), -1)) { - if (DEBUG) Log.d(TAG, "handleQsDown"); + if (DEBUG_LOGCAT) Log.d(TAG, "handleQsDown"); mFalsingCollector.onQsDown(); mQsTracking = true; onQsExpansionStarted(); @@ -2189,7 +2212,7 @@ public class NotificationPanelViewController extends PanelViewController { break; case MotionEvent.ACTION_MOVE: - if (DEBUG) Log.d(TAG, "onQSTouch move"); + if (DEBUG_LOGCAT) Log.d(TAG, "onQSTouch move"); setQsExpansion(h + mInitialHeightOnTouch); if (h >= getFalsingThreshold()) { mQsTouchAboveFalsingThreshold = true; @@ -2341,7 +2364,7 @@ public class NotificationPanelViewController extends PanelViewController { mCentralSurfaces.executeRunnableDismissingKeyguard(null, null /* cancelAction */, false /* dismissShade */, true /* afterKeyguardGone */, false /* deferred */); } - if (DEBUG) { + if (DEBUG_DRAWABLE) { mView.invalidate(); } } @@ -2998,7 +3021,7 @@ public class NotificationPanelViewController extends PanelViewController { // This is a circular dependency and should be avoided, otherwise we'll have // a stack overflow. if (mStackScrollerMeasuringPass > 2) { - if (DEBUG) Log.d(TAG, "Unstable notification panel height. Aborting."); + if (DEBUG_LOGCAT) Log.d(TAG, "Unstable notification panel height. Aborting."); } else { positionClockAndNotifications(); } @@ -3032,7 +3055,7 @@ public class NotificationPanelViewController extends PanelViewController { updateNotificationTranslucency(); updatePanelExpanded(); updateGestureExclusionRect(); - if (DEBUG) { + if (DEBUG_DRAWABLE) { mView.invalidate(); } } @@ -3675,7 +3698,7 @@ public class NotificationPanelViewController extends PanelViewController { public void onQsPanelScrollChanged(int scrollY) { mLargeScreenShadeHeaderController.setQsScrollY(scrollY); if (scrollY > 0 && !mQsFullyExpanded) { - if (DEBUG) Log.d(TAG, "Scrolling while not expanded. Forcing expand"); + if (DEBUG_LOGCAT) Log.d(TAG, "Scrolling while not expanded. Forcing expand"); // If we are scrolling QS, we should be fully expanded. expandWithQs(); } @@ -4070,7 +4093,7 @@ public class NotificationPanelViewController extends PanelViewController { } public void setHeaderDebugInfo(String text) { - if (DEBUG) mHeaderDebugInfo = text; + if (DEBUG_DRAWABLE) mHeaderDebugInfo = text; } public void onThemeChanged() { @@ -4112,7 +4135,7 @@ public class NotificationPanelViewController extends PanelViewController { } if (!isFullyCollapsed() && onQsIntercept(event)) { - if (DEBUG) Log.d(TAG, "onQsIntercept true"); + if (DEBUG_LOGCAT) Log.d(TAG, "onQsIntercept true"); return true; } return super.onInterceptTouchEvent(event); @@ -4183,7 +4206,7 @@ public class NotificationPanelViewController extends PanelViewController { handled |= mHeadsUpTouchHelper.onTouchEvent(event); if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) { - if (DEBUG) Log.d(TAG, "handleQsTouch true"); + if (DEBUG_LOGCAT) Log.d(TAG, "handleQsTouch true"); return true; } if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) { @@ -4623,7 +4646,7 @@ public class NotificationPanelViewController extends PanelViewController { private class ConfigurationListener implements ConfigurationController.ConfigurationListener { @Override public void onThemeChanged() { - if (DEBUG) Log.d(TAG, "onThemeChanged"); + if (DEBUG_LOGCAT) Log.d(TAG, "onThemeChanged"); mThemeResId = mView.getContext().getThemeResId(); reInflateViews(); } @@ -4631,7 +4654,7 @@ public class NotificationPanelViewController extends PanelViewController { @Override public void onSmallestScreenWidthChanged() { Trace.beginSection("onSmallestScreenWidthChanged"); - if (DEBUG) Log.d(TAG, "onSmallestScreenWidthChanged"); + if (DEBUG_LOGCAT) Log.d(TAG, "onSmallestScreenWidthChanged"); // Can affect multi-user switcher visibility as it depends on screen size by default: // it is enabled only for devices with large screens (see config_keyguardUserSwitcher) @@ -4648,7 +4671,7 @@ public class NotificationPanelViewController extends PanelViewController { @Override public void onDensityOrFontScaleChanged() { - if (DEBUG) Log.d(TAG, "onDensityOrFontScaleChanged"); + if (DEBUG_LOGCAT) Log.d(TAG, "onDensityOrFontScaleChanged"); reInflateViews(); } } @@ -4661,7 +4684,7 @@ public class NotificationPanelViewController extends PanelViewController { @Override public void onChange(boolean selfChange) { - if (DEBUG) Log.d(TAG, "onSettingsChanged"); + if (DEBUG_LOGCAT) Log.d(TAG, "onSettingsChanged"); // Can affect multi-user switcher visibility reInflateViews(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 48f57774218b..e3c070e3686e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -483,12 +483,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb if (mBouncer == null) { return; } + mBouncer.hide(destroyView); if (mShowing) { // If we were showing the bouncer and then aborting, we need to also clear out any // potential actions unless we actually unlocked. cancelPostAuthActions(); } - mBouncer.hide(destroyView); cancelPendingWakeupAction(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java index fa9161a19cb1..f599e3b12c57 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java @@ -578,19 +578,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { } @Test - public void computeMaxKeyguardNotifications_dozeAmountNotZero_returnsExistingMax() { - when(mAmbientState.getDozeAmount()).thenReturn(0.5f); - mNotificationPanelViewController.setMaxDisplayedNotifications(-1); - - // computeMaxKeyguardNotifications sets maxAllowed to 0 at minimum if it updates the value - assertThat(mNotificationPanelViewController.computeMaxKeyguardNotifications()) - .isEqualTo(-1); - } - - @Test public void computeMaxKeyguardNotifications_noTransition_updatesMax() { when(mAmbientState.getFractionToShade()).thenReturn(0f); - when(mAmbientState.getDozeAmount()).thenReturn(0f); mNotificationPanelViewController.setMaxDisplayedNotifications(-1); // computeMaxKeyguardNotifications sets maxAllowed to 0 at minimum if it updates the value diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index aaa5a6bdbe83..0c1d04253bf5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -320,6 +320,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mStatusBarKeyguardViewManager.dismissWithAction( action, cancelAction, true /* afterKeyguardGone */); + when(mBouncer.isShowing()).thenReturn(false); mStatusBarKeyguardViewManager.hideBouncer(true); mStatusBarKeyguardViewManager.hide(0, 30); verify(action, never()).onDismiss(); @@ -327,6 +328,20 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test + public void testHidingBouncer_cancelsGoneRunnable() { + OnDismissAction action = mock(OnDismissAction.class); + Runnable cancelAction = mock(Runnable.class); + mStatusBarKeyguardViewManager.dismissWithAction( + action, cancelAction, true /* afterKeyguardGone */); + + when(mBouncer.isShowing()).thenReturn(false); + mStatusBarKeyguardViewManager.hideBouncer(true); + + verify(action, never()).onDismiss(); + verify(cancelAction).run(); + } + + @Test public void testHiding_doesntCancelWhenShowing() { OnDismissAction action = mock(OnDismissAction.class); Runnable cancelAction = mock(Runnable.class); diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java index e27b7a659ae6..09a05bb6b40e 100644 --- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java +++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java @@ -1135,41 +1135,13 @@ public class CameraExtensionsProxyService extends Service { CameraSessionConfig ret = new CameraSessionConfig(); ret.outputConfigs = new ArrayList<>(); for (Camera2OutputConfigImpl output : outputConfigs) { - CameraOutputConfig entry = new CameraOutputConfig(); - entry.outputId = new OutputConfigId(); - entry.outputId.id = output.getId(); - entry.physicalCameraId = output.getPhysicalCameraId(); - entry.surfaceGroupId = output.getSurfaceGroupId(); - if (output instanceof SurfaceOutputConfigImpl) { - SurfaceOutputConfigImpl surfaceConfig = (SurfaceOutputConfigImpl) output; - entry.type = CameraOutputConfig.TYPE_SURFACE; - entry.surface = surfaceConfig.getSurface(); - } else if (output instanceof ImageReaderOutputConfigImpl) { - ImageReaderOutputConfigImpl imageReaderOutputConfig = - (ImageReaderOutputConfigImpl) output; - entry.type = CameraOutputConfig.TYPE_IMAGEREADER; - entry.size = new android.hardware.camera2.extension.Size(); - entry.size.width = imageReaderOutputConfig.getSize().getWidth(); - entry.size.height = imageReaderOutputConfig.getSize().getHeight(); - entry.imageFormat = imageReaderOutputConfig.getImageFormat(); - entry.capacity = imageReaderOutputConfig.getMaxImages(); - } else if (output instanceof MultiResolutionImageReaderOutputConfigImpl) { - MultiResolutionImageReaderOutputConfigImpl multiResReaderConfig = - (MultiResolutionImageReaderOutputConfigImpl) output; - entry.type = CameraOutputConfig.TYPE_MULTIRES_IMAGEREADER; - entry.imageFormat = multiResReaderConfig.getImageFormat(); - entry.capacity = multiResReaderConfig.getMaxImages(); - } else { - throw new IllegalStateException("Unknown output config type!"); - } + CameraOutputConfig entry = getCameraOutputConfig(output); List<Camera2OutputConfigImpl> sharedOutputs = output.getSurfaceSharingOutputConfigs(); if ((sharedOutputs != null) && (!sharedOutputs.isEmpty())) { - entry.surfaceSharingOutputConfigs = new ArrayList<>(); + entry.sharedSurfaceConfigs = new ArrayList<>(); for (Camera2OutputConfigImpl sharedOutput : sharedOutputs) { - OutputConfigId outputId = new OutputConfigId(); - outputId.id = sharedOutput.getId(); - entry.surfaceSharingOutputConfigs.add(outputId); + entry.sharedSurfaceConfigs.add(getCameraOutputConfig(sharedOutput)); } } ret.outputConfigs.add(entry); @@ -1854,4 +1826,36 @@ public class CameraExtensionsProxyService extends Service { } } } + + private static CameraOutputConfig getCameraOutputConfig(Camera2OutputConfigImpl output) { + CameraOutputConfig ret = new CameraOutputConfig(); + ret.outputId = new OutputConfigId(); + ret.outputId.id = output.getId(); + ret.physicalCameraId = output.getPhysicalCameraId(); + ret.surfaceGroupId = output.getSurfaceGroupId(); + if (output instanceof SurfaceOutputConfigImpl) { + SurfaceOutputConfigImpl surfaceConfig = (SurfaceOutputConfigImpl) output; + ret.type = CameraOutputConfig.TYPE_SURFACE; + ret.surface = surfaceConfig.getSurface(); + } else if (output instanceof ImageReaderOutputConfigImpl) { + ImageReaderOutputConfigImpl imageReaderOutputConfig = + (ImageReaderOutputConfigImpl) output; + ret.type = CameraOutputConfig.TYPE_IMAGEREADER; + ret.size = new android.hardware.camera2.extension.Size(); + ret.size.width = imageReaderOutputConfig.getSize().getWidth(); + ret.size.height = imageReaderOutputConfig.getSize().getHeight(); + ret.imageFormat = imageReaderOutputConfig.getImageFormat(); + ret.capacity = imageReaderOutputConfig.getMaxImages(); + } else if (output instanceof MultiResolutionImageReaderOutputConfigImpl) { + MultiResolutionImageReaderOutputConfigImpl multiResReaderConfig = + (MultiResolutionImageReaderOutputConfigImpl) output; + ret.type = CameraOutputConfig.TYPE_MULTIRES_IMAGEREADER; + ret.imageFormat = multiResReaderConfig.getImageFormat(); + ret.capacity = multiResReaderConfig.getMaxImages(); + } else { + throw new IllegalStateException("Unknown output config type!"); + } + + return ret; + } } diff --git a/services/autofill/java/com/android/server/autofill/ui/CustomScrollView.java b/services/autofill/java/com/android/server/autofill/ui/CustomScrollView.java index 9e8b2228195e..26a295ef9253 100644 --- a/services/autofill/java/com/android/server/autofill/ui/CustomScrollView.java +++ b/services/autofill/java/com/android/server/autofill/ui/CustomScrollView.java @@ -56,39 +56,41 @@ public class CustomScrollView extends ScrollView { private int mWidth = -1; private int mHeight = -1; - private int mMaxPortraitBodyHeightPercent = 20; - private int mMaxLandscapeBodyHeightPercent = 20; + private int mMaxPortraitBodyHeightPercent; + private int mMaxLandscapeBodyHeightPercent; + private int mAttrBasedMaxHeightPercent; public CustomScrollView(Context context) { super(context); - setMaxBodyHeightPercent(); + setMaxBodyHeightPercent(context); } public CustomScrollView(Context context, AttributeSet attrs) { super(context, attrs); - setMaxBodyHeightPercent(); + setMaxBodyHeightPercent(context); } public CustomScrollView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - setMaxBodyHeightPercent(); + setMaxBodyHeightPercent(context); } public CustomScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - setMaxBodyHeightPercent(); + setMaxBodyHeightPercent(context); } - private void setMaxBodyHeightPercent() { + private void setMaxBodyHeightPercent(Context context) { + mAttrBasedMaxHeightPercent = getAttrBasedMaxHeightPercent(context); mMaxPortraitBodyHeightPercent = DeviceConfig.getInt( DeviceConfig.NAMESPACE_AUTOFILL, DEVICE_CONFIG_SAVE_DIALOG_PORTRAIT_BODY_HEIGHT_MAX_PERCENT, - mMaxPortraitBodyHeightPercent); + mAttrBasedMaxHeightPercent); mMaxLandscapeBodyHeightPercent = DeviceConfig.getInt( DeviceConfig.NAMESPACE_AUTOFILL, DEVICE_CONFIG_SAVE_DIALOG_LANDSCAPE_BODY_HEIGHT_MAX_PERCENT, - mMaxLandscapeBodyHeightPercent); + mAttrBasedMaxHeightPercent); } @Override @@ -117,29 +119,27 @@ public class CustomScrollView extends ScrollView { final int contentHeight = content.getMeasuredHeight(); int displayHeight = point.y; - int configBasedMaxHeight = (getResources().getConfiguration().orientation + int maxHeight = (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) ? (int) (mMaxLandscapeBodyHeightPercent * displayHeight / 100) : (int) (mMaxPortraitBodyHeightPercent * displayHeight / 100); - mHeight = configBasedMaxHeight > 0 - ? Math.min(contentHeight, configBasedMaxHeight) - : Math.min(contentHeight, getAttrBasedMaxHeight(context, displayHeight)); + mHeight = Math.min(contentHeight, maxHeight); if (sDebug) { Slog.d(TAG, "calculateDimensions():" + " mMaxPortraitBodyHeightPercent=" + mMaxPortraitBodyHeightPercent + ", mMaxLandscapeBodyHeightPercent=" + mMaxLandscapeBodyHeightPercent - + ", configBasedMaxHeight=" + configBasedMaxHeight - + ", attrBasedMaxHeight=" + getAttrBasedMaxHeight(context, displayHeight) + + ", mAttrBasedMaxHeightPercent=" + mAttrBasedMaxHeightPercent + + ", maxHeight=" + maxHeight + ", contentHeight=" + contentHeight + ", w=" + mWidth + ", h=" + mHeight); } } - private int getAttrBasedMaxHeight(Context context, int displayHeight) { + private int getAttrBasedMaxHeightPercent(Context context) { final TypedValue maxHeightAttrTypedValue = new TypedValue(); context.getTheme().resolveAttribute(R.attr.autofillSaveCustomSubtitleMaxHeight, maxHeightAttrTypedValue, true); - return (int) maxHeightAttrTypedValue.getFraction(displayHeight, displayHeight); + return (int) maxHeightAttrTypedValue.getFraction(100, 100); } } diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java index 1d7d44327022..2ab1aa80176a 100644 --- a/services/companion/java/com/android/server/companion/CompanionApplicationController.java +++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java @@ -108,10 +108,10 @@ public class CompanionApplicationController { * CDM binds to the companion app. */ public void bindCompanionApplication(@UserIdInt int userId, @NonNull String packageName, - boolean bindImportant) { + boolean isSelfManaged) { if (DEBUG) { Log.i(TAG, "bind() u" + userId + "/" + packageName - + " important=" + bindImportant); + + " isSelfManaged=" + isSelfManaged); } final List<ComponentName> companionServices = @@ -134,7 +134,7 @@ public class CompanionApplicationController { serviceConnectors = CollectionUtils.map(companionServices, componentName -> CompanionDeviceServiceConnector.newInstance(mContext, userId, - componentName, bindImportant)); + componentName, isSelfManaged)); mBoundCompanionApplications.setValueForPackage(userId, packageName, serviceConnectors); } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java index b9b29ffb4e86..cb945381dfa5 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java @@ -17,7 +17,7 @@ package com.android.server.companion; import static android.content.Context.BIND_ALMOST_PERCEPTIBLE; -import static android.content.Context.BIND_IMPORTANT; +import static android.content.Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE; import static android.os.Process.THREAD_PRIORITY_DEFAULT; import android.annotation.NonNull; @@ -61,21 +61,22 @@ class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDe /** * Create a CompanionDeviceServiceConnector instance. * - * When bindImportant is false, the binding flag will be BIND_ALMOST_PERCEPTIBLE + * For self-managed apps, the binding flag will be BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE + * (oom_score_adj = VISIBLE_APP_ADJ = 100). + * + * For non self-managed apps, the binding flag will be BIND_ALMOST_PERCEPTIBLE * (oom_score_adj = PERCEPTIBLE_MEDIUM_APP = 225). The target service will be treated * as important as a perceptible app (IMPORTANCE_VISIBLE = 200), and will be unbound when * the app is removed from task manager. - * When bindImportant is true, the binding flag will be BIND_IMPORTANT - * (oom_score_adj = PERCEPTIBLE_MEDIUM_APP = -700). The target service will - * have the highest priority to avoid being killed (IMPORTANCE_FOREGROUND = 100). * * One time permission's importance level to keep session alive is * IMPORTANCE_FOREGROUND_SERVICE = 125. In order to kill the one time permission session, the * service importance level should be higher than 125. */ static CompanionDeviceServiceConnector newInstance(@NonNull Context context, - @UserIdInt int userId, @NonNull ComponentName componentName, boolean bindImportant) { - final int bindingFlags = bindImportant ? BIND_IMPORTANT : BIND_ALMOST_PERCEPTIBLE; + @UserIdInt int userId, @NonNull ComponentName componentName, boolean isSelfManaged) { + final int bindingFlags = isSelfManaged ? BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE + : BIND_ALMOST_PERCEPTIBLE; return new CompanionDeviceServiceConnector(context, userId, componentName, bindingFlags); } diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index aa30c0897d60..a25ac210f9c8 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -152,6 +152,9 @@ import javax.xml.datatype.DatatypeConfigurationException; * <screenBrightnessRampSlowDecrease>0.03</screenBrightnessRampSlowDecrease> * <screenBrightnessRampSlowIncrease>0.04</screenBrightnessRampSlowIncrease> * + * <screenBrightnessRampIncreaseMaxMillis>2000</screenBrightnessRampIncreaseMaxMillis> + * <screenBrightnessRampDecreaseMaxMillis>3000</screenBrightnessRampDecreaseMaxMillis> + * * <lightSensor> * <type>android.sensor.light</type> * <name>1234 Ambient Light Sensor</name> @@ -259,6 +262,8 @@ public class DisplayDeviceConfig { private float mBrightnessRampFastIncrease = Float.NaN; private float mBrightnessRampSlowDecrease = Float.NaN; private float mBrightnessRampSlowIncrease = Float.NaN; + private long mBrightnessRampDecreaseMaxMillis = 0; + private long mBrightnessRampIncreaseMaxMillis = 0; private int mAmbientHorizonLong = AMBIENT_LIGHT_LONG_HORIZON_MILLIS; private int mAmbientHorizonShort = AMBIENT_LIGHT_SHORT_HORIZON_MILLIS; private float mScreenBrighteningMinThreshold = 0.0f; // Retain behaviour as though there is @@ -534,6 +539,14 @@ public class DisplayDeviceConfig { return mBrightnessRampSlowIncrease; } + public long getBrightnessRampDecreaseMaxMillis() { + return mBrightnessRampDecreaseMaxMillis; + } + + public long getBrightnessRampIncreaseMaxMillis() { + return mBrightnessRampIncreaseMaxMillis; + } + public int getAmbientHorizonLong() { return mAmbientHorizonLong; } @@ -628,6 +641,8 @@ public class DisplayDeviceConfig { + ", mBrightnessRampFastIncrease=" + mBrightnessRampFastIncrease + ", mBrightnessRampSlowDecrease=" + mBrightnessRampSlowDecrease + ", mBrightnessRampSlowIncrease=" + mBrightnessRampSlowIncrease + + ", mBrightnessRampDecreaseMaxMillis=" + mBrightnessRampDecreaseMaxMillis + + ", mBrightnessRampIncreaseMaxMillis=" + mBrightnessRampIncreaseMaxMillis + ", mAmbientHorizonLong=" + mAmbientHorizonLong + ", mAmbientHorizonShort=" + mAmbientHorizonShort + ", mScreenDarkeningMinThreshold=" + mScreenDarkeningMinThreshold @@ -725,6 +740,8 @@ public class DisplayDeviceConfig { mBrightnessRampFastIncrease = PowerManager.BRIGHTNESS_MAX; mBrightnessRampSlowDecrease = PowerManager.BRIGHTNESS_MAX; mBrightnessRampSlowIncrease = PowerManager.BRIGHTNESS_MAX; + mBrightnessRampDecreaseMaxMillis = 0; + mBrightnessRampIncreaseMaxMillis = 0; setSimpleMappingStrategyValues(); loadAmbientLightSensorFromConfigXml(); setProxSensorUnspecified(); @@ -1115,6 +1132,15 @@ public class DisplayDeviceConfig { } loadBrightnessRampsFromConfigXml(); } + + final BigInteger increaseMax = config.getScreenBrightnessRampIncreaseMaxMillis(); + if (increaseMax != null) { + mBrightnessRampIncreaseMaxMillis = increaseMax.intValue(); + } + final BigInteger decreaseMax = config.getScreenBrightnessRampDecreaseMaxMillis(); + if (decreaseMax != null) { + mBrightnessRampDecreaseMaxMillis = decreaseMax.intValue(); + } } private void loadBrightnessRampsFromConfigXml() { diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java index 7dce2380407e..a4f49549c7eb 100644 --- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java +++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java @@ -76,6 +76,8 @@ class DisplayManagerShellCommand extends ShellCommand { return setUserDisabledHdrTypes(); case "get-user-disabled-hdr-types": return getUserDisabledHdrTypes(); + case "get-displays": + return getDisplays(); case "dock": return setDockedAndIdle(); case "undock": @@ -133,6 +135,9 @@ class DisplayManagerShellCommand extends ShellCommand { pw.println(" Sets the user disabled HDR types as TYPES"); pw.println(" get-user-disabled-hdr-types"); pw.println(" Returns the user disabled HDR types"); + pw.println(" get-displays [CATEGORY]"); + pw.println(" Returns the current displays. Can specify string category among"); + pw.println(" DisplayManager.DISPLAY_CATEGORY_*; must use the actual string value."); pw.println(" dock"); pw.println(" Sets brightness to docked + idle screen brightness mode"); pw.println(" undock"); @@ -141,6 +146,18 @@ class DisplayManagerShellCommand extends ShellCommand { Intent.printIntentArgsHelp(pw , ""); } + private int getDisplays() { + String category = getNextArg(); + DisplayManager dm = mService.getContext().getSystemService(DisplayManager.class); + Display[] displays = dm.getDisplays(category); + PrintWriter out = getOutPrintWriter(); + out.println("Displays:"); + for (int i = 0; i < displays.length; i++) { + out.println(" " + displays[i]); + } + return 0; + } + private int setBrightness() { String brightnessText = getNextArg(); if (brightnessText == null) { diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 80ff8349a153..d13a9a3ec27e 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -255,6 +255,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // to reach the final state. private final boolean mBrightnessBucketsInDozeConfig; + // Maximum time a ramp animation can take. + private long mBrightnessRampIncreaseMaxTimeMillis; + private long mBrightnessRampDecreaseMaxTimeMillis; + // The pending power request. // Initially null until the first call to requestPowerState. @GuardedBy("mLock") @@ -507,7 +511,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call final Resources resources = context.getResources(); - // DOZE AND DIM SETTINGS mScreenBrightnessDozeConfig = clampAbsoluteBrightness( pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE)); @@ -641,7 +644,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mIsRbcActive = mCdsi.isReduceBrightColorsActivated(); mAutomaticBrightnessController.recalculateSplines(mIsRbcActive, adjustedNits); - // If rbc is turned on, off or there is a change in strength, we want to reset the short // term model. Since the nits range at which brightness now operates has changed due to // RBC/strength change, any short term model based on the previous range should be @@ -837,6 +839,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call loadNitsRange(mContext.getResources()); setUpAutoBrightness(mContext.getResources(), mHandler); reloadReduceBrightColours(); + if (mScreenBrightnessRampAnimator != null) { + mScreenBrightnessRampAnimator.setAnimationTimeLimits( + mBrightnessRampIncreaseMaxTimeMillis, + mBrightnessRampDecreaseMaxTimeMillis); + } mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId, mDisplayDeviceConfig.getHighBrightnessModeData(), new HighBrightnessModeController.HdrBrightnessDeviceConfig() { @@ -883,6 +890,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mScreenBrightnessRampAnimator = new DualRampAnimator<>(mPowerState, DisplayPowerState.SCREEN_BRIGHTNESS_FLOAT, DisplayPowerState.SCREEN_SDR_BRIGHTNESS_FLOAT); + mScreenBrightnessRampAnimator.setAnimationTimeLimits( + mBrightnessRampIncreaseMaxTimeMillis, + mBrightnessRampDecreaseMaxTimeMillis); mScreenBrightnessRampAnimator.setListener(mRampAnimatorListener); noteScreenState(mPowerState.getScreenState()); @@ -1007,6 +1017,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mBrightnessRampRateFastIncrease = mDisplayDeviceConfig.getBrightnessRampFastIncrease(); mBrightnessRampRateSlowDecrease = mDisplayDeviceConfig.getBrightnessRampSlowDecrease(); mBrightnessRampRateSlowIncrease = mDisplayDeviceConfig.getBrightnessRampSlowIncrease(); + mBrightnessRampDecreaseMaxTimeMillis = + mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis(); + mBrightnessRampIncreaseMaxTimeMillis = + mDisplayDeviceConfig.getBrightnessRampIncreaseMaxMillis(); } private void loadNitsRange(Resources resources) { diff --git a/services/core/java/com/android/server/display/RampAnimator.java b/services/core/java/com/android/server/display/RampAnimator.java index 2567e4306293..690ec3fb5aaf 100644 --- a/services/core/java/com/android/server/display/RampAnimator.java +++ b/services/core/java/com/android/server/display/RampAnimator.java @@ -34,6 +34,8 @@ class RampAnimator<T> { private float mCurrentValue; private float mTargetValue; private float mRate; + private float mAnimationIncreaseMaxTimeSecs; + private float mAnimationDecreaseMaxTimeSecs; private boolean mAnimating; private float mAnimatedValue; // higher precision copy of mCurrentValue @@ -43,13 +45,24 @@ class RampAnimator<T> { private Listener mListener; - public RampAnimator(T object, FloatProperty<T> property) { + RampAnimator(T object, FloatProperty<T> property) { mObject = object; mProperty = property; mChoreographer = Choreographer.getInstance(); } /** + * Sets the maximum time that a brightness animation can take. + */ + public void setAnimationTimeLimits(long animationRampIncreaseMaxTimeMillis, + long animationRampDecreaseMaxTimeMillis) { + mAnimationIncreaseMaxTimeSecs = (animationRampIncreaseMaxTimeMillis > 0) + ? (animationRampIncreaseMaxTimeMillis / 1000.0f) : 0.0f; + mAnimationDecreaseMaxTimeSecs = (animationRampDecreaseMaxTimeMillis > 0) + ? (animationRampDecreaseMaxTimeMillis / 1000.0f) : 0.0f; + } + + /** * Starts animating towards the specified value. * * If this is the first time the property is being set or if the rate is 0, @@ -83,6 +96,15 @@ class RampAnimator<T> { return false; } + // Adjust the rate so that we do not exceed our maximum animation time. + if (target > mCurrentValue && mAnimationIncreaseMaxTimeSecs > 0.0f + && ((target - mCurrentValue) / rate) > mAnimationIncreaseMaxTimeSecs) { + rate = (target - mCurrentValue) / mAnimationIncreaseMaxTimeSecs; + } else if (target < mCurrentValue && mAnimationDecreaseMaxTimeSecs > 0.0f + && ((mCurrentValue - target) / rate) > mAnimationDecreaseMaxTimeSecs) { + rate = (mCurrentValue - target) / mAnimationDecreaseMaxTimeSecs; + } + // Adjust the rate based on the closest target. // If a faster rate is specified, then use the new rate so that we converge // more rapidly based on the new request. @@ -209,6 +231,17 @@ class RampAnimator<T> { } /** + * Sets the maximum time that a brightness animation can take. + */ + public void setAnimationTimeLimits(long animationRampIncreaseMaxTimeMillis, + long animationRampDecreaseMaxTimeMillis) { + mFirst.setAnimationTimeLimits(animationRampIncreaseMaxTimeMillis, + animationRampDecreaseMaxTimeMillis); + mSecond.setAnimationTimeLimits(animationRampIncreaseMaxTimeMillis, + animationRampDecreaseMaxTimeMillis); + } + + /** * Starts animating towards the specified values. * * If this is the first time the property is being set or if the rate is 0, diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 9e5da450c8a5..d536a463f7b4 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -40,6 +40,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; import android.media.AudioManager; import android.media.AudioPlaybackConfiguration; import android.media.AudioSystem; @@ -85,6 +86,7 @@ import android.view.ViewConfiguration; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.server.LocalManagerRegistry; +import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.Watchdog; import com.android.server.Watchdog.Monitor; @@ -540,14 +542,19 @@ public class MediaSessionService extends SystemService implements Monitor { if (TextUtils.isEmpty(packageName)) { throw new IllegalArgumentException("packageName may not be empty"); } - String[] packages = mContext.getPackageManager().getPackagesForUid(uid); - final int packageCount = packages.length; - for (int i = 0; i < packageCount; i++) { - if (packageName.equals(packages[i])) { - return; - } + if (uid == Process.ROOT_UID || uid == Process.SHELL_UID) { + // If the caller is shell, then trust the packageName given and allow it + // to proceed. + return; + } + final PackageManagerInternal packageManagerInternal = + LocalServices.getService(PackageManagerInternal.class); + final int actualUid = packageManagerInternal.getPackageUid( + packageName, 0 /* flags */, UserHandle.getUserId(uid)); + if (!UserHandle.isSameApp(uid, actualUid)) { + throw new IllegalArgumentException("packageName does not belong to the calling uid; " + + "pkg=" + packageName + ", uid=" + uid); } - throw new IllegalArgumentException("packageName is not owned by the calling process"); } void tempAllowlistTargetPkgIfPossible(int targetUid, String targetPackage, diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 1bcb41b784d9..d3580a4e3317 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -658,6 +658,9 @@ public class NotificationManagerService extends SystemService { private int mWarnRemoteViewsSizeBytes; private int mStripRemoteViewsSizeBytes; + @VisibleForTesting + protected boolean mShowReviewPermissionsNotification; + private MetricsLogger mMetricsLogger; private NotificationChannelLogger mNotificationChannelLogger; private TriPredicate<String, Integer, String> mAllowedManagedServicePackages; @@ -2281,7 +2284,8 @@ public class NotificationManagerService extends SystemService { mPermissionHelper, mNotificationChannelLogger, mAppOps, - new SysUiStatsEvent.BuilderFactory()); + new SysUiStatsEvent.BuilderFactory(), + mShowReviewPermissionsNotification); mPreferencesHelper.updateFixedImportance(mUm.getUsers()); mRankingHelper = new RankingHelper(getContext(), mRankingHandler, @@ -2470,6 +2474,9 @@ public class NotificationManagerService extends SystemService { WorkerHandler handler = new WorkerHandler(Looper.myLooper()); + mShowReviewPermissionsNotification = getContext().getResources().getBoolean( + R.bool.config_notificationReviewPermissions); + init(handler, new RankingHandlerWorker(mRankingThread.getLooper()), AppGlobals.getPackageManager(), getContext().getPackageManager(), getLocalService(LightsManager.class), @@ -6321,6 +6328,11 @@ public class NotificationManagerService extends SystemService { @Override public void sendReviewPermissionsNotification() { + if (!mShowReviewPermissionsNotification) { + // don't show if this notification is turned off + return; + } + // This method is meant to be called from the JobService upon running the job for this // notification having been rescheduled; so without checking any other state, it will // send the notification. @@ -6937,9 +6949,14 @@ public class NotificationManagerService extends SystemService { try { if (mPackageManagerClient.hasSystemFeature(FEATURE_TELECOM) && mTelecomManager != null) { - return mTelecomManager.isInManagedCall() - || mTelecomManager.isInSelfManagedCall( - pkg, UserHandle.getUserHandleForUid(uid)); + try { + return mTelecomManager.isInManagedCall() + || mTelecomManager.isInSelfManagedCall( + pkg, UserHandle.getUserHandleForUid(uid)); + } catch (IllegalStateException ise) { + // Telecom is not ready (this is likely early boot), so there are no calls. + return false; + } } return false; } finally { @@ -11654,6 +11671,11 @@ public class NotificationManagerService extends SystemService { } protected void maybeShowInitialReviewPermissionsNotification() { + if (!mShowReviewPermissionsNotification) { + // if this notification is disabled by settings do not ever show it + return; + } + int currentState = Settings.Global.getInt(getContext().getContentResolver(), Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, REVIEW_NOTIF_STATE_UNKNOWN); diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 97133a56779d..477b8da61e0f 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -86,7 +86,6 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -191,6 +190,7 @@ public class PreferencesHelper implements RankingConfig { private boolean mIsMediaNotificationFilteringEnabled = DEFAULT_MEDIA_NOTIFICATION_FILTERING; private boolean mAreChannelsBypassingDnd; private boolean mHideSilentStatusBarIcons = DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS; + private boolean mShowReviewPermissionsNotification; private boolean mAllowInvalidShortcuts = false; @@ -198,7 +198,8 @@ public class PreferencesHelper implements RankingConfig { ZenModeHelper zenHelper, PermissionHelper permHelper, NotificationChannelLogger notificationChannelLogger, AppOpsManager appOpsManager, - SysUiStatsEvent.BuilderFactory statsEventBuilderFactory) { + SysUiStatsEvent.BuilderFactory statsEventBuilderFactory, + boolean showReviewPermissionsNotification) { mContext = context; mZenModeHelper = zenHelper; mRankingHandler = rankingHandler; @@ -207,6 +208,7 @@ public class PreferencesHelper implements RankingConfig { mNotificationChannelLogger = notificationChannelLogger; mAppOps = appOpsManager; mStatsEventBuilderFactory = statsEventBuilderFactory; + mShowReviewPermissionsNotification = showReviewPermissionsNotification; XML_VERSION = 4; @@ -226,7 +228,8 @@ public class PreferencesHelper implements RankingConfig { final int xmlVersion = parser.getAttributeInt(null, ATT_VERSION, -1); boolean upgradeForBubbles = xmlVersion == XML_VERSION_BUBBLES_UPGRADE; boolean migrateToPermission = (xmlVersion < XML_VERSION_NOTIF_PERMISSION); - if (xmlVersion < XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION) { + if (mShowReviewPermissionsNotification + && (xmlVersion < XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION)) { // make a note that we should show the notification at some point. // it shouldn't be possible for the user to already have seen it, as the XML version // would be newer then. diff --git a/services/core/java/com/android/server/pm/ApexPackageInfo.java b/services/core/java/com/android/server/pm/ApexPackageInfo.java index 07f2fd3e1d1f..f959a52f0374 100644 --- a/services/core/java/com/android/server/pm/ApexPackageInfo.java +++ b/services/core/java/com/android/server/pm/ApexPackageInfo.java @@ -27,7 +27,6 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.util.ArrayMap; import android.util.PrintWriterPrinter; -import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.IndentingPrintWriter; @@ -271,11 +270,11 @@ class ApexPackageInfo { } } ipw.println("Active APEX packages:"); - dumpFromPackagesCache(getActivePackages(), packageName, ipw); + dumpPackages(getActivePackages(), packageName, ipw); ipw.println("Inactive APEX packages:"); - dumpFromPackagesCache(getInactivePackages(), packageName, ipw); + dumpPackages(getInactivePackages(), packageName, ipw); ipw.println("Factory APEX packages:"); - dumpFromPackagesCache(getFactoryPackages(), packageName, ipw); + dumpPackages(getFactoryPackages(), packageName, ipw); } @GuardedBy("mLock") @@ -370,7 +369,7 @@ class ApexPackageInfo { * only information about that specific package will be dumped. * @param ipw the {@link IndentingPrintWriter} object to send information to. */ - private static void dumpFromPackagesCache(List<PackageInfo> packagesCache, + static void dumpPackages(List<PackageInfo> packagesCache, @Nullable String packageName, IndentingPrintWriter ipw) { ipw.println(); ipw.increaseIndent(); diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 45a139dfb28f..a53e9be51b5f 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -984,9 +984,15 @@ public class ComputerEngine implements Computer { TAG, "getApplicationInfo " + packageName + ": " + p); } + final boolean matchApex = (flags & MATCH_APEX) != 0; if (p != null) { PackageStateInternal ps = mSettings.getPackage(packageName); if (ps == null) return null; + if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) { + if (!matchApex && p.isApex()) { + return null; + } + } if (filterSharedLibPackage(ps, filterCallingUid, userId, flags)) { return null; } @@ -1001,20 +1007,22 @@ public class ComputerEngine implements Computer { } return ai; } - if ((flags & PackageManager.MATCH_APEX) != 0) { - // For APKs, PackageInfo.applicationInfo is not exactly the same as ApplicationInfo - // returned from getApplicationInfo, but for APEX packages difference shouldn't be - // very big. - // TODO(b/155328545): generate proper application info for APEXes as well. - int apexFlags = ApexManager.MATCH_ACTIVE_PACKAGE; - if ((flags & PackageManager.MATCH_SYSTEM_ONLY) != 0) { - apexFlags = ApexManager.MATCH_FACTORY_PACKAGE; - } - final PackageInfo pi = mApexPackageInfo.getPackageInfo(packageName, apexFlags); - if (pi == null) { - return null; + if (!ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) { + if (matchApex) { + // For APKs, PackageInfo.applicationInfo is not exactly the same as ApplicationInfo + // returned from getApplicationInfo, but for APEX packages difference shouldn't be + // very big. + // TODO(b/155328545): generate proper application info for APEXes as well. + int apexFlags = ApexManager.MATCH_ACTIVE_PACKAGE; + if ((flags & PackageManager.MATCH_SYSTEM_ONLY) != 0) { + apexFlags = ApexManager.MATCH_FACTORY_PACKAGE; + } + final PackageInfo pi = mApexPackageInfo.getPackageInfo(packageName, apexFlags); + if (pi == null) { + return null; + } + return pi.applicationInfo; } - return pi.applicationInfo; } if ("android".equals(packageName) || "system".equals(packageName)) { return androidApplication(); @@ -1704,14 +1712,22 @@ public class ComputerEngine implements Computer { packageName = resolveInternalPackageName(packageName, versionCode); final boolean matchFactoryOnly = (flags & MATCH_FACTORY_ONLY) != 0; + final boolean matchApex = (flags & MATCH_APEX) != 0; if (matchFactoryOnly) { // Instant app filtering for APEX modules is ignored - if ((flags & MATCH_APEX) != 0) { - return mApexPackageInfo.getPackageInfo(packageName, - ApexManager.MATCH_FACTORY_PACKAGE); + if (!ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) { + if (matchApex) { + return mApexPackageInfo.getPackageInfo(packageName, + ApexManager.MATCH_FACTORY_PACKAGE); + } } final PackageStateInternal ps = mSettings.getDisabledSystemPkg(packageName); if (ps != null) { + if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) { + if (!matchApex && ps.getPkg() != null && ps.getPkg().isApex()) { + return null; + } + } if (filterSharedLibPackage(ps, filterCallingUid, userId, flags)) { return null; } @@ -1731,6 +1747,11 @@ public class ComputerEngine implements Computer { } if (p != null) { final PackageStateInternal ps = getPackageStateInternal(p.getPackageName()); + if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) { + if (!matchApex && p.isApex()) { + return null; + } + } if (filterSharedLibPackage(ps, filterCallingUid, userId, flags)) { return null; } @@ -1751,8 +1772,11 @@ public class ComputerEngine implements Computer { } return generatePackageInfo(ps, flags, userId); } - if ((flags & MATCH_APEX) != 0) { - return mApexPackageInfo.getPackageInfo(packageName, ApexManager.MATCH_ACTIVE_PACKAGE); + if (!ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) { + if (matchApex) { + return mApexPackageInfo.getPackageInfo(packageName, + ApexManager.MATCH_ACTIVE_PACKAGE); + } } return null; } @@ -1809,6 +1833,11 @@ public class ComputerEngine implements Computer { ps = psDisabled; } } + if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) { + if (!listApex && ps.getPkg() != null && ps.getPkg().isApex()) { + continue; + } + } if (filterSharedLibPackage(ps, callingUid, userId, flags)) { continue; } @@ -1834,6 +1863,11 @@ public class ComputerEngine implements Computer { ps = psDisabled; } } + if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) { + if (!listApex && p.isApex()) { + continue; + } + } if (filterSharedLibPackage(ps, callingUid, userId, flags)) { continue; } @@ -1846,11 +1880,13 @@ public class ComputerEngine implements Computer { } } } - if (listApex) { - if (listFactory) { - list.addAll(mApexPackageInfo.getFactoryPackages()); - } else { - list.addAll(mApexPackageInfo.getActivePackages()); + if (!ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) { + if (listApex) { + if (listFactory) { + list.addAll(mApexPackageInfo.getFactoryPackages()); + } else { + list.addAll(mApexPackageInfo.getActivePackages()); + } } } return new ParceledListSlice<>(list); @@ -3338,13 +3374,58 @@ public class ComputerEngine implements Computer { case DumpState.DUMP_APEX: { if (packageName == null || isApexPackage(packageName)) { mApexManager.dump(pw); - mApexPackageInfo.dump(pw, packageName); + dumpApex(pw, packageName); } break; } } // switch } + private void generateApexPackageInfo(List<PackageInfo> activePackages, + List<PackageInfo> inactivePackages, List<PackageInfo> factoryPackages) { + for (AndroidPackage p : mPackages.values()) { + final String packageName = p.getPackageName(); + PackageStateInternal ps = mSettings.getPackage(packageName); + if (!p.isApex() || ps == null) { + continue; + } + PackageInfo pi = generatePackageInfo(ps, 0, 0); + if (pi == null) { + continue; + } + pi.isActiveApex = true; + activePackages.add(pi); + if (!ps.isUpdatedSystemApp()) { + factoryPackages.add(pi); + } else { + PackageStateInternal psDisabled = mSettings.getDisabledSystemPkg(packageName); + pi = generatePackageInfo(psDisabled, 0, 0); + if (pi != null) { + factoryPackages.add(pi); + inactivePackages.add(pi); + } + } + } + } + + private void dumpApex(PrintWriter pw, String packageName) { + if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) { + final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120); + List<PackageInfo> activePackages = new ArrayList<>(); + List<PackageInfo> inactivePackages = new ArrayList<>(); + List<PackageInfo> factoryPackages = new ArrayList<>(); + generateApexPackageInfo(activePackages, inactivePackages, factoryPackages); + ipw.println("Active APEX packages:"); + ApexPackageInfo.dumpPackages(activePackages, packageName, ipw); + ipw.println("Inactive APEX packages:"); + ApexPackageInfo.dumpPackages(inactivePackages, packageName, ipw); + ipw.println("Factory APEX packages:"); + ApexPackageInfo.dumpPackages(factoryPackages, packageName, ipw); + } else { + mApexPackageInfo.dump(pw, packageName); + } + } + // The body of findPreferredActivity. protected PackageManagerService.FindPreferredActivityBodyResult findPreferredActivityBody( Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, @@ -3721,7 +3802,12 @@ public class ComputerEngine implements Computer { @Override public boolean isApexPackage(String packageName) { - return mApexPackageInfo.isApexPackage(packageName); + if (!ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) { + return mApexPackageInfo.isApexPackage(packageName); + } else { + final AndroidPackage pkg = mPackages.get(packageName); + return pkg != null && pkg.isApex(); + } } @Override @@ -4710,6 +4796,7 @@ public class ComputerEngine implements Computer { if (!mUserManager.exists(userId)) return Collections.emptyList(); flags = updateFlagsForApplication(flags, userId); final boolean listUninstalled = (flags & MATCH_KNOWN_PACKAGES) != 0; + final boolean listApex = (flags & MATCH_APEX) != 0; enforceCrossUserPermission( callingUid, @@ -4730,6 +4817,11 @@ public class ComputerEngine implements Computer { effectiveFlags |= PackageManager.MATCH_ANY_USER; } if (ps.getPkg() != null) { + if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) { + if (!listApex && ps.getPkg().isApex()) { + continue; + } + } if (filterSharedLibPackage(ps, callingUid, userId, flags)) { continue; } @@ -4758,6 +4850,11 @@ public class ComputerEngine implements Computer { if (pkg == null) { continue; } + if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) { + if (!listApex && pkg.isApex()) { + continue; + } + } if (filterSharedLibPackage(packageState, Binder.getCallingUid(), userId, flags)) { continue; } @@ -5109,7 +5206,7 @@ public class ComputerEngine implements Computer { final PackageStateInternal ps = mSettings.getPackage(packageName); // Installer info for Apex is not stored in PackageManager - if (ps == null && mApexPackageInfo.isApexPackage(packageName)) { + if (isApexPackage(packageName)) { return InstallSource.EMPTY; } diff --git a/services/core/java/com/android/server/pm/InitAppsHelper.java b/services/core/java/com/android/server/pm/InitAppsHelper.java index 4019c15ee550..b142ba6822d9 100644 --- a/services/core/java/com/android/server/pm/InitAppsHelper.java +++ b/services/core/java/com/android/server/pm/InitAppsHelper.java @@ -194,7 +194,6 @@ final class InitAppsHelper { apexScanResults = mInstallPackageHelper.scanApexPackages( mApexManager.getAllApexInfos(), mSystemParseFlags, mSystemScanFlags, packageParser, mExecutorService); - mApexPackageInfo.notifyScanResult(apexScanResults); } else { apexScanResults = mApexPackageInfo.scanApexPackages( mApexManager.getAllApexInfos(), packageParser, mExecutorService); diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 7dfa7299ae32..22189ff73bb3 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -343,7 +343,10 @@ final class InstallPackageHelper { mPm.mTransferredPackages.add(pkg.getPackageName()); } - if (reconciledPkg.mCollectedSharedLibraryInfos != null) { + if (reconciledPkg.mCollectedSharedLibraryInfos != null + || (oldPkgSetting != null && oldPkgSetting.getUsesLibraryInfos() != null)) { + // Reconcile if the new package or the old package uses shared libraries. + // It is possible that the old package uses shared libraries but the new one doesn't. mSharedLibraries.executeSharedLibrariesUpdateLPw(pkg, pkgSetting, null, null, reconciledPkg.mCollectedSharedLibraryInfos, allUsers); } @@ -826,19 +829,24 @@ final class InstallPackageHelper { } return; } + + processApkInstallRequests(success, installRequests); + } + + private void processApkInstallRequests(boolean success, List<InstallRequest> installRequests) { if (success) { - for (InstallRequest request : apkInstallRequests) { + for (InstallRequest request : installRequests) { request.mArgs.doPreInstall(request.mInstallResult.mReturnCode); } synchronized (mPm.mInstallLock) { - installPackagesTracedLI(apkInstallRequests); + installPackagesTracedLI(installRequests); } - for (InstallRequest request : apkInstallRequests) { + for (InstallRequest request : installRequests) { request.mArgs.doPostInstall( request.mInstallResult.mReturnCode, request.mInstallResult.mUid); } } - for (InstallRequest request : apkInstallRequests) { + for (InstallRequest request : installRequests) { restoreAndPostInstall(request.mArgs.mUser.getIdentifier(), request.mInstallResult, new PostInstallData(request.mArgs, @@ -880,11 +888,15 @@ final class InstallPackageHelper { try (PackageParser2 packageParser = mPm.mInjector.getScanningPackageParser()) { ApexInfo apexInfo = mApexManager.installPackage(apexes[0]); if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) { - ParsedPackage parsedPackage = packageParser.parsePackage( - new File(apexInfo.modulePath), 0, /* useCaches= */ false); - scanSystemPackageLI(parsedPackage, 0, SCAN_AS_APEX, null); - mPm.mApexPackageInfo.notifyPackageInstalled( - apexInfo, parsedPackage.hideAsFinal()); + // APEX has been handled successfully by apexd. Let's continue the install flow + // so it will be scanned and registered with the system. + // TODO(b/225756739): Improve atomicity of rebootless APEX install. + // The newly installed APEX will not be reverted even if + // processApkInstallRequests() fails. Need a way to keep info stored in apexd + // and PMS in sync in the face of install failures. + request.mInstallResult.mApexInfo = apexInfo; + mPm.mHandler.post(() -> processApkInstallRequests(true, requests)); + return; } else { mPm.mApexPackageInfo.notifyPackageInstalled(apexInfo, packageParser); } @@ -985,7 +997,12 @@ final class InstallPackageHelper { + PackageManager.PROPERTY_NO_APP_DATA_STORAGE); return; } - createdAppId.put(packageName, optimisticallyRegisterAppId(result)); + final boolean isApex = (result.mRequest.mScanFlags & SCAN_AS_APEX) != 0; + if (!isApex) { + createdAppId.put(packageName, optimisticallyRegisterAppId(result)); + } else { + result.mPkgSetting.setAppId(Process.INVALID_UID); + } versionInfos.put(result.mPkgSetting.getPkg().getPackageName(), mPm.getSettingsVersionForPackage(result.mPkgSetting.getPkg())); } catch (PackageManagerException e) { @@ -1088,12 +1105,12 @@ final class InstallPackageHelper { private PrepareResult preparePackageLI(InstallArgs args, PackageInstalledInfo res) throws PrepareFailure { final int installFlags = args.mInstallFlags; - final File tmpPackageFile = new File(args.getCodePath()); final boolean onExternal = args.mVolumeUuid != null; final boolean instantApp = ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0); final boolean fullApp = ((installFlags & PackageManager.INSTALL_FULL_APP) != 0); final boolean virtualPreload = ((installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0); + final boolean isApex = ((installFlags & PackageManager.INSTALL_APEX) != 0); final boolean isRollback = args.mInstallReason == PackageManager.INSTALL_REASON_ROLLBACK; @PackageManagerService.ScanFlags int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE; if (args.mMoveInfo != null) { @@ -1112,7 +1129,12 @@ final class InstallPackageHelper { if (virtualPreload) { scanFlags |= SCAN_AS_VIRTUAL_PRELOAD; } + if (isApex) { + scanFlags |= SCAN_AS_APEX; + } + final File tmpPackageFile = new File( + isApex ? res.mApexInfo.modulePath : args.getCodePath()); if (DEBUG_INSTALL) Slog.d(TAG, "installPackageLI: path=" + tmpPackageFile); // Validity check @@ -1518,16 +1540,22 @@ final class InstallPackageHelper { } } - if (!args.doRename(res.mReturnCode, parsedPackage)) { - throw new PrepareFailure(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Failed rename"); - } + if (!isApex) { + if (!args.doRename(res.mReturnCode, parsedPackage)) { + throw new PrepareFailure(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Failed rename"); + } - try { - setUpFsVerityIfPossible(parsedPackage); - } catch (Installer.InstallerException | IOException | DigestException - | NoSuchAlgorithmException e) { - throw new PrepareFailure(INSTALL_FAILED_INTERNAL_ERROR, - "Failed to set up verity: " + e); + try { + setUpFsVerityIfPossible(parsedPackage); + } catch (Installer.InstallerException | IOException | DigestException + | NoSuchAlgorithmException e) { + throw new PrepareFailure(INSTALL_FAILED_INTERNAL_ERROR, + "Failed to set up verity: " + e); + } + } else { + // Use the path returned by apexd + parsedPackage.setPath(res.mApexInfo.modulePath); + parsedPackage.setBaseApkPath(res.mApexInfo.modulePath); } final PackageFreezer freezer = @@ -1934,6 +1962,8 @@ final class InstallPackageHelper { // Set the update and install times PackageStateInternal deletedPkgSetting = mPm.snapshotComputer() .getPackageStateInternal(oldPackage.getPackageName()); + // TODO(b/225756739): For rebootless APEX, consider using lastUpdateMillis provided + // by apexd to be more accurate. reconciledPkg.mPkgSetting .setFirstInstallTimeFromReplaced(deletedPkgSetting, request.mAllUsers) .setLastUpdateTime(System.currentTimeMillis()); @@ -2236,6 +2266,8 @@ final class InstallPackageHelper { for (ReconciledPackage reconciledPkg : commitRequest.mReconciledPackages.values()) { final boolean instantApp = ((reconciledPkg.mScanResult.mRequest.mScanFlags & SCAN_AS_INSTANT_APP) != 0); + final boolean isApex = ((reconciledPkg.mScanResult.mRequest.mScanFlags + & SCAN_AS_APEX) != 0); final AndroidPackage pkg = reconciledPkg.mPkgSetting.getPkg(); final String packageName = pkg.getPackageName(); final String codePath = pkg.getPath(); @@ -2323,7 +2355,8 @@ final class InstallPackageHelper { android.provider.Settings.Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0) && !pkg.isDebuggable() && (!onIncremental) - && dexoptOptions.isCompilationEnabled(); + && dexoptOptions.isCompilationEnabled() + && !isApex; if (performDexopt) { // Compile the layout resources. @@ -3292,6 +3325,10 @@ final class InstallPackageHelper { final PackageSetting disabledPs = mPm.mSettings.getDisabledSystemPkgLPr(packageName); if (scannedPkg != null) { + if (scannedPkg.isApex()) { + // APEX on /data has been scanned. No need to expect better. + continue; + } /* * If the system app is both scanned and in the * disabled packages list, then it must have been @@ -3391,6 +3428,18 @@ final class InstallPackageHelper { } } + /** + * Scans APEX packages and registers them with the system. + * + * apexd has its own policy to decide which APEX to activate and which not. The policy might + * conflicts that of PMS. The APEX package info stored in PMS is a mirror of that managed by + * apexd. To keep things simple and keep activation status in sync for both apexd and PMS, we + * don't persist APEX in settings and always scan APEX from scratch during boot. However, some + * data like lastUpdateTime will be lost if PackageSetting is not persisted for APEX. + * + * TODO(b/225756739): Read lastUpdateTime from ApexInfoList to populate PackageSetting correctly + */ + @GuardedBy({"mPm.mInstallLock", "mPm.mLock"}) public List<ApexManager.ScanResult> scanApexPackages(ApexInfo[] allPackages, int parseFlags, int scanFlags, PackageParser2 packageParser, ExecutorService executorService) { if (allPackages == null) { @@ -3408,18 +3457,39 @@ final class InstallPackageHelper { parsingApexInfo.put(apexFile, ai); } - // Process results one by one - List<ApexManager.ScanResult> results = new ArrayList<>(parsingApexInfo.size()); + List<ParallelPackageParser.ParseResult> parseResults = + new ArrayList<>(parsingApexInfo.size()); for (int i = 0; i < parsingApexInfo.size(); i++) { ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take(); + parseResults.add(parseResult); + } + // Sort the list to ensure we always process factory packages first + Collections.sort(parseResults, (a, b) -> { + ApexInfo ai = parsingApexInfo.get(a.scanFile); + return ai.isFactory ? -1 : 1; + }); + + + // Process results one by one + List<ApexManager.ScanResult> results = new ArrayList<>(parsingApexInfo.size()); + for (int i = 0; i < parseResults.size(); i++) { + ParallelPackageParser.ParseResult parseResult = parseResults.get(i); Throwable throwable = parseResult.throwable; ApexInfo ai = parsingApexInfo.get(parseResult.scanFile); + int newParseFlags = parseFlags; int newScanFlags = scanFlags | SCAN_AS_APEX; + if (!ai.isFactory) { + newParseFlags &= ~ParsingPackageUtils.PARSE_IS_SYSTEM_DIR; + newScanFlags |= SCAN_NEW_INSTALL; + } if (throwable == null) { try { - scanSystemPackageLI(parseResult.parsedPackage, parseFlags, newScanFlags, null); - AndroidPackage pkg = parseResult.parsedPackage.hideAsFinal(); + AndroidPackage pkg = addForInitLI( + parseResult.parsedPackage, newParseFlags, newScanFlags, null); + if (ai.isFactory && !ai.isActive) { + disableSystemPackageLPw(pkg); + } results.add(new ApexManager.ScanResult(ai, pkg, pkg.getPackageName())); } catch (PackageManagerException e) { throw new IllegalStateException("Failed to scan: " + ai.modulePath, e); @@ -3638,7 +3708,11 @@ final class InstallPackageHelper { ReconcilePackageUtils.reconcilePackages(reconcileRequest, mSharedLibraries, mPm.mSettings.getKeySetManagerService(), mPm.mSettings); - appIdCreated = optimisticallyRegisterAppId(scanResult); + if ((scanFlags & SCAN_AS_APEX) == 0) { + appIdCreated = optimisticallyRegisterAppId(scanResult); + } else { + scanResult.mPkgSetting.setAppId(Process.INVALID_UID); + } commitReconciledScanResultLocked(reconcileResult.get(pkgName), mPm.mUserManager.getUserIds()); } catch (PackageManagerException e) { diff --git a/services/core/java/com/android/server/pm/PackageInstalledInfo.java b/services/core/java/com/android/server/pm/PackageInstalledInfo.java index d0ca9d845560..1c25dbbfa313 100644 --- a/services/core/java/com/android/server/pm/PackageInstalledInfo.java +++ b/services/core/java/com/android/server/pm/PackageInstalledInfo.java @@ -18,6 +18,7 @@ package com.android.server.pm; import static com.android.server.pm.PackageManagerService.TAG; +import android.apex.ApexInfo; import android.util.ExceptionUtils; import android.util.Slog; @@ -45,6 +46,9 @@ final class PackageInstalledInfo { String mOrigPackage; String mOrigPermission; + // The ApexInfo returned by ApexManager#installPackage, used by rebootless APEX install + ApexInfo mApexInfo; + PackageInstalledInfo(int currentStatus) { mReturnCode = currentStatus; mUid = -1; diff --git a/services/core/java/com/android/server/pm/PreferredActivityHelper.java b/services/core/java/com/android/server/pm/PreferredActivityHelper.java index 9befd6e09eb4..0ca5febd0d99 100644 --- a/services/core/java/com/android/server/pm/PreferredActivityHelper.java +++ b/services/core/java/com/android/server/pm/PreferredActivityHelper.java @@ -48,7 +48,6 @@ import android.util.Xml; import com.android.internal.util.ArrayUtils; import com.android.server.net.NetworkPolicyManagerInternal; -import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import org.xmlpull.v1.XmlPullParser; @@ -595,11 +594,7 @@ final class PreferredActivityHelper { synchronized (mPm.mLock) { mPm.mSettings.applyDefaultPreferredAppsLPw(userId); mPm.mDomainVerificationManager.clearUser(userId); - final int numPackages = mPm.mPackages.size(); - for (int i = 0; i < numPackages; i++) { - final AndroidPackage pkg = mPm.mPackages.valueAt(i); - mPm.mPermissionManager.resetRuntimePermissions(pkg, userId); - } + mPm.mPermissionManager.resetRuntimePermissionsForUser(userId); } updateDefaultHomeNotLocked(mPm.snapshotComputer(), userId); resetNetworkPolicies(userId); diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index a4db628d7290..ad303311b1c5 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -2468,10 +2468,18 @@ public final class Settings implements Watchable, Snappable { serializer.endTag(null, "permissions"); for (final PackageSetting pkg : mPackages.values()) { + if (pkg.getPkg() != null && pkg.getPkg().isApex()) { + // Don't persist APEX which doesn't have a valid app id and will fail to load + continue; + } writePackageLPr(serializer, pkg); } for (final PackageSetting pkg : mDisabledSysPackages.values()) { + if (pkg.getPkg() != null && pkg.getPkg().isApex()) { + // Don't persist APEX which doesn't have a valid app id and will fail to load + continue; + } writeDisabledSysPackageLPr(serializer, pkg); } @@ -4968,6 +4976,10 @@ public final class Settings implements Watchable, Snappable { && !packageName.equals(ps.getPackageName())) { continue; } + if (ps.getPkg() != null && ps.getPkg().isApex()) { + // Filter APEX packages which will be dumped in the APEX section + continue; + } final LegacyPermissionState permissionsState = mPermissionDataProvider.getLegacyPermissionState(ps.getAppId()); if (permissionNames != null @@ -5020,6 +5032,10 @@ public final class Settings implements Watchable, Snappable { && !packageName.equals(ps.getPackageName())) { continue; } + if (ps.getPkg() != null && ps.getPkg().isApex()) { + // Filter APEX packages which will be dumped in the APEX section + continue; + } if (!checkin && !printedSomething) { if (dumpState.onTitlePrinted()) pw.println(); diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index edbfa1aa00e0..367beb50473f 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -780,6 +780,10 @@ public class PermissionManagerService extends IPermissionManager.Stub { public void resetRuntimePermissions(@NonNull AndroidPackage pkg, @UserIdInt int userId) { mPermissionManagerServiceImpl.resetRuntimePermissions(pkg, userId); } + @Override + public void resetRuntimePermissionsForUser(@UserIdInt int userId) { + mPermissionManagerServiceImpl.resetRuntimePermissionsForUser(userId); + } @Override public Permission getPermissionTEMP(String permName) { diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index bd3fc50473de..13ea9f377a39 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -103,7 +103,6 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.DebugUtils; import android.util.EventLog; -import android.util.IntArray; import android.util.Log; import android.util.Pair; import android.util.Slog; @@ -1661,30 +1660,16 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt /** * Reverts user permission state changes (permissions and flags). * - * @param pkg The package for which to reset. + * @param filterPkg The package for which to reset, or {@code null} for all packages. * @param userId The device user for which to do a reset. */ - private void resetRuntimePermissionsInternal(@NonNull AndroidPackage pkg, + private void resetRuntimePermissionsInternal(@Nullable AndroidPackage filterPkg, @UserIdInt int userId) { - final String packageName = pkg.getPackageName(); - - // These are flags that can change base on user actions. - final int userSettableMask = FLAG_PERMISSION_USER_SET - | FLAG_PERMISSION_USER_FIXED - | FLAG_PERMISSION_REVOKED_COMPAT - | FLAG_PERMISSION_REVIEW_REQUIRED - | FLAG_PERMISSION_ONE_TIME - | FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY; - - final int policyOrSystemFlags = FLAG_PERMISSION_SYSTEM_FIXED - | FLAG_PERMISSION_POLICY_FIXED; - // Delay and combine non-async permission callbacks - final int permissionCount = ArrayUtils.size(pkg.getRequestedPermissions()); final boolean[] permissionRemoved = new boolean[1]; final ArraySet<Long> revokedPermissions = new ArraySet<>(); - final IntArray syncUpdatedUsers = new IntArray(permissionCount); - final IntArray asyncUpdatedUsers = new IntArray(permissionCount); + final ArraySet<Integer> syncUpdatedUsers = new ArraySet<>(); + final ArraySet<Integer> asyncUpdatedUsers = new ArraySet<>(); PermissionCallback delayingPermCallback = new PermissionCallback() { public void onGidsChanged(int appId, int userId) { @@ -1746,6 +1731,55 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } }; + if (filterPkg != null) { + resetRuntimePermissionsInternal(filterPkg, userId, delayingPermCallback); + } else { + mPackageManagerInt.forEachPackage(pkg -> + resetRuntimePermissionsInternal(pkg, userId, delayingPermCallback)); + } + + // Execute delayed callbacks + if (permissionRemoved[0]) { + mDefaultPermissionCallback.onPermissionRemoved(); + } + + // Slight variation on the code in mPermissionCallback.onPermissionRevoked() as we cannot + // kill uid while holding mPackages-lock + if (!revokedPermissions.isEmpty()) { + int numRevokedPermissions = revokedPermissions.size(); + for (int i = 0; i < numRevokedPermissions; i++) { + int revocationUID = IntPair.first(revokedPermissions.valueAt(i)); + int revocationUserId = IntPair.second(revokedPermissions.valueAt(i)); + + mOnPermissionChangeListeners.onPermissionsChanged(revocationUID); + + // Kill app later as we are holding mPackages + mHandler.post(() -> killUid(UserHandle.getAppId(revocationUID), revocationUserId, + KILL_APP_REASON_PERMISSIONS_REVOKED)); + } + } + + mPackageManagerInt.writePermissionSettings(ArrayUtils.convertToIntArray(syncUpdatedUsers), + false); + mPackageManagerInt.writePermissionSettings(ArrayUtils.convertToIntArray(asyncUpdatedUsers), + true); + } + + private void resetRuntimePermissionsInternal(@NonNull AndroidPackage pkg, + @UserIdInt int userId, @NonNull PermissionCallback delayingPermCallback) { + // These are flags that can change base on user actions. + final int userSettableMask = FLAG_PERMISSION_USER_SET + | FLAG_PERMISSION_USER_FIXED + | FLAG_PERMISSION_REVOKED_COMPAT + | FLAG_PERMISSION_REVIEW_REQUIRED + | FLAG_PERMISSION_ONE_TIME + | FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY; + + final int policyOrSystemFlags = FLAG_PERMISSION_SYSTEM_FIXED + | FLAG_PERMISSION_POLICY_FIXED; + + final String packageName = pkg.getPackageName(); + final int permissionCount = ArrayUtils.size(pkg.getRequestedPermissions()); for (int i = 0; i < permissionCount; i++) { final String permName = pkg.getRequestedPermissions().get(i); @@ -1824,30 +1858,6 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt userId, null, delayingPermCallback); } } - - // Execute delayed callbacks - if (permissionRemoved[0]) { - mDefaultPermissionCallback.onPermissionRemoved(); - } - - // Slight variation on the code in mPermissionCallback.onPermissionRevoked() as we cannot - // kill uid while holding mPackages-lock - if (!revokedPermissions.isEmpty()) { - int numRevokedPermissions = revokedPermissions.size(); - for (int i = 0; i < numRevokedPermissions; i++) { - int revocationUID = IntPair.first(revokedPermissions.valueAt(i)); - int revocationUserId = IntPair.second(revokedPermissions.valueAt(i)); - - mOnPermissionChangeListeners.onPermissionsChanged(revocationUID); - - // Kill app later as we are holding mPackages - mHandler.post(() -> killUid(UserHandle.getAppId(revocationUID), revocationUserId, - KILL_APP_REASON_PERMISSIONS_REVOKED)); - } - } - - mPackageManagerInt.writePermissionSettings(syncUpdatedUsers.toArray(), false); - mPackageManagerInt.writePermissionSettings(asyncUpdatedUsers.toArray(), true); } /** @@ -5178,6 +5188,12 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } @Override + public void resetRuntimePermissionsForUser(@UserIdInt int userId) { + Preconditions.checkArgumentNonNegative(userId, "userId"); + resetRuntimePermissionsInternal(null, userId); + } + + @Override public Permission getPermissionTEMP(String permName) { synchronized (mLock) { return mRegistry.getPermission(permName); diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java index 3771f030aefa..02d184e2b1e0 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java @@ -428,6 +428,13 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte @UserIdInt int userId); /** + * Reset the runtime permission state changes for all packages in a user. + * + * @param userId the user ID + */ + void resetRuntimePermissionsForUser(@UserIdInt int userId); + + /** * Read legacy permission state from package settings. * * TODO(zhanghai): This is a temporary method because we should not expose diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java index d2c4ec4cc5a5..67d51813778c 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java @@ -102,6 +102,14 @@ public interface PermissionManagerServiceInternal extends PermissionManagerInter @UserIdInt int userId); /** + * Reset the runtime permission state changes for all packages in a user. + * + * @param userId the user ID + */ + //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) + void resetRuntimePermissionsForUser(@UserIdInt int userId); + + /** * Read legacy permission state from package settings. * * TODO(zhanghai): This is a temporary method because we should not expose diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index f5df738c8848..e700103bf0d2 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -4450,14 +4450,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A /** * @return Whether we are allowed to show non-starting windows at the moment. We disallow - * showing windows during transitions in case we have windows that have wide-color-gamut - * color mode set to avoid jank in the middle of the transition. + * showing windows while the transition animation is playing in case we have windows + * that have wide-color-gamut color mode set to avoid jank in the middle of the + * animation. */ boolean canShowWindows() { final boolean drawn = mTransitionController.isShellTransitionsEnabled() ? mSyncState != SYNC_STATE_WAITING_FOR_DRAW : allDrawn; final boolean animating = mTransitionController.isShellTransitionsEnabled() - ? mTransitionController.inTransition(this) + ? mTransitionController.inPlayingTransition(this) : isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION); return drawn && !(animating && hasNonDefaultColorWindow()); } diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index e5ec4c3dfcd2..a78d25f4e21a 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -1522,8 +1522,8 @@ public class DisplayRotation { } @Override - public boolean isKeyguardLocked() { - return mService.isKeyguardLocked(); + public boolean isKeyguardShowingAndNotOccluded() { + return mService.isKeyguardShowingAndNotOccluded(); } @Override diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 6b3981a0c1cc..61ed6d3d8d8f 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -4567,22 +4567,12 @@ class Task extends TaskFragment { moveToFront(reason, null); } - void moveToFront(String reason, Task task) { - if (mMoveAdjacentTogether && getAdjacentTaskFragment() != null) { - final Task adjacentTask = getAdjacentTaskFragment().asTask(); - if (adjacentTask != null) { - adjacentTask.moveToFrontInner(reason + " adjacentTaskToTop", null /* task */); - } - } - moveToFrontInner(reason, task); - } - /** * @param reason The reason for moving the root task to the front. * @param task If non-null, the task will be moved to the top of the root task. */ @VisibleForTesting - void moveToFrontInner(String reason, Task task) { + void moveToFront(String reason, Task task) { if (!isAttached()) { return; } diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index a9e80af04e1c..d9835722130f 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -29,7 +29,6 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; -import static android.view.WindowManagerPolicyConstants.SPLIT_DIVIDER_LAYER; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.server.wm.ActivityRecord.State.RESUMED; @@ -91,20 +90,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { */ private int mColorLayerCounter = 0; - /** - * Given that the split-screen divider does not have an AppWindowToken, it - * will have to live inside of a "NonAppWindowContainer". However, in visual Z order - * it will need to be interleaved with some of our children, appearing on top of - * both docked root tasks but underneath any assistant root tasks. - * - * To solve this problem we have this anchor control, which will always exist so - * we can always assign it the correct value in our {@link #assignChildLayers}. - * Likewise since it always exists, we can always - * assign the divider a layer relative to it. This way we prevent linking lifecycle - * events between tasks and the divider window. - */ - private SurfaceControl mSplitScreenDividerAnchor; - // Cached reference to some special tasks we tend to get a lot so we don't need to loop // through the list to find them. private Task mRootHomeTask; @@ -730,12 +715,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { // Place root home tasks to the bottom. layer = adjustRootTaskLayer(t, mTmpHomeChildren, layer); layer = adjustRootTaskLayer(t, mTmpNormalChildren, layer); - // TODO(b/207185041): Remove this divider workaround after we full remove leagacy split and - // make app pair split only have single root then we can just attach the - // divider to the single root task in shell. - layer = Math.max(layer, SPLIT_DIVIDER_LAYER + 1); adjustRootTaskLayer(t, mTmpAlwaysOnTopChildren, layer); - t.setLayer(mSplitScreenDividerAnchor, SPLIT_DIVIDER_LAYER); } /** @@ -763,19 +743,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { continue; } - final Task childTask = child.asTask(); - final boolean inAdjacentTask = childTask != null - && child.inMultiWindowMode() - && childTask.getRootTask().getAdjacentTaskFragment() != null; - - if (inAdjacentTask) { - hasAdjacentTask = true; - } else if (hasAdjacentTask && startLayer < SPLIT_DIVIDER_LAYER) { - // Task on top of adjacent tasks should be higher than split divider layer so - // set it as start. - startLayer = SPLIT_DIVIDER_LAYER + 1; - } - child.assignLayer(t, startLayer++); } @@ -802,31 +769,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { return activity != null ? activity.createRemoteAnimationTarget(record) : null; } - SurfaceControl getSplitScreenDividerAnchor() { - return mSplitScreenDividerAnchor; - } - - @Override - void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) { - if (getParent() != null) { - super.onParentChanged(newParent, oldParent, () -> { - mSplitScreenDividerAnchor = makeChildSurface(null) - .setName("splitScreenDividerAnchor") - .setCallsite("TaskDisplayArea.onParentChanged") - .build(); - - getSyncTransaction() - .show(mSplitScreenDividerAnchor); - }); - } else { - super.onParentChanged(newParent, oldParent); - mWmService.mTransactionFactory.get() - .remove(mSplitScreenDividerAnchor) - .apply(); - mSplitScreenDividerAnchor = null; - } - } - void setBackgroundColor(@ColorInt int colorInt) { setBackgroundColor(colorInt, false /* restore */); } @@ -872,12 +814,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { setBackgroundColor(mBackgroundColor, true /* restore */); } - if (mSplitScreenDividerAnchor == null) { - return; - } - - // As TaskDisplayArea is getting a new surface, reparent and reorder the child surfaces. - t.reparent(mSplitScreenDividerAnchor, mSurfaceControl); reassignLayer(t); scheduleAnimation(); } diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index cb090ca77fa8..470e835ddfc5 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -173,14 +173,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { private TaskFragment mAdjacentTaskFragment; /** - * Whether to move adjacent task fragment together when re-positioning. - * - * @see #mAdjacentTaskFragment - */ - // TODO(b/207185041): Remove this once having a single-top root for split screen. - boolean mMoveAdjacentTogether; - - /** * Prevents duplicate calls to onTaskAppeared. */ boolean mTaskFragmentAppearedSent; @@ -332,15 +324,14 @@ class TaskFragment extends WindowContainer<WindowContainer> { return service.mWindowOrganizerController.getTaskFragment(token); } - void setAdjacentTaskFragment(@Nullable TaskFragment taskFragment, boolean moveTogether) { + void setAdjacentTaskFragment(@Nullable TaskFragment taskFragment) { if (mAdjacentTaskFragment == taskFragment) { return; } resetAdjacentTaskFragment(); if (taskFragment != null) { mAdjacentTaskFragment = taskFragment; - mMoveAdjacentTogether = moveTogether; - taskFragment.setAdjacentTaskFragment(this, moveTogether); + taskFragment.setAdjacentTaskFragment(this); } } @@ -349,11 +340,9 @@ class TaskFragment extends WindowContainer<WindowContainer> { if (mAdjacentTaskFragment != null && mAdjacentTaskFragment.mAdjacentTaskFragment == this) { mAdjacentTaskFragment.mAdjacentTaskFragment = null; mAdjacentTaskFragment.mDelayLastActivityRemoval = false; - mAdjacentTaskFragment.mMoveAdjacentTogether = false; } mAdjacentTaskFragment = null; mDelayLastActivityRemoval = false; - mMoveAdjacentTogether = false; } void setTaskFragmentOrganizer(@NonNull TaskFragmentOrganizerToken organizer, int uid, diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 3af372076b78..93425800e25e 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -131,18 +131,27 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe */ private static final int STATE_ABORT = 3; + /** + * This transition has finished playing successfully. + */ + private static final int STATE_FINISHED = 4; + @IntDef(prefix = { "STATE_" }, value = { STATE_PENDING, STATE_COLLECTING, STATE_STARTED, STATE_PLAYING, - STATE_ABORT + STATE_ABORT, + STATE_FINISHED }) @Retention(RetentionPolicy.SOURCE) @interface TransitionState {} final @TransitionType int mType; private int mSyncId = -1; + // Used for tracking a Transition throughout a lifecycle (i.e. from STATE_COLLECTING to + // STATE_FINISHED or STATE_ABORT), and should only be used for testing and debugging. + private int mDebugId = -1; private @TransitionFlags int mFlags; private final TransitionController mController; private final BLASTSyncEngine mSyncEngine; @@ -202,6 +211,8 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe mFlags = flags; mController = controller; mSyncEngine = syncEngine; + + controller.mTransitionTracer.logState(this); } void addFlag(int flag) { @@ -242,11 +253,21 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe info.mFlags = info.mFlags | ChangeInfo.FLAG_SEAMLESS_ROTATION; } + @TransitionState + int getState() { + return mState; + } + @VisibleForTesting int getSyncId() { return mSyncId; } + @VisibleForTesting + int getDebugId() { + return mDebugId; + } + @TransitionFlags int getFlags() { return mFlags; @@ -259,6 +280,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe } mState = STATE_COLLECTING; mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG); + mDebugId = mSyncId; + + mController.mTransitionTracer.logState(this); } /** @@ -276,6 +300,8 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Starting Transition %d", mSyncId); applyReady(); + + mController.mTransitionTracer.logState(this); } /** @@ -437,14 +463,10 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe t.setPosition(targetLeash, tmpPos.x, tmpPos.y); final Rect clipRect; // No need to clip the display in case seeing the clipped content when during the - // display rotation. - if (target.asDisplayContent() != null) { + // display rotation. No need to clip activities because they rely on clipping on + // task layers. + if (target.asDisplayContent() != null || target.asActivityRecord() != null) { clipRect = null; - } else if (target.asActivityRecord() != null) { - // Always use parent bounds of activity because letterbox area (e.g. fixed - // aspect ratio or size compat mode) should be included. - clipRect = target.getParent().getRequestedOverrideBounds(); - clipRect.offset(-tmpPos.x, -tmpPos.y); } else { clipRect = target.getRequestedOverrideBounds(); clipRect.offset(-tmpPos.x, -tmpPos.y); @@ -648,6 +670,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe dc.removeImeSurfaceImmediately(); dc.handleCompleteDeferredRemoval(); } + + mState = STATE_FINISHED; + mController.mTransitionTracer.logState(this); } void abort() { @@ -677,6 +702,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe Slog.e(TAG, "Unexpected Sync ID " + syncId + ". Expected " + mSyncId); return; } + if (mTargetDisplays.isEmpty()) { mTargetDisplays.add(mController.mAtm.mRootWindowContainer.getDefaultDisplay()); } @@ -788,6 +814,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe } mStartTransaction = transaction; mFinishTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get(); + buildFinishTransaction(mFinishTransaction, info.getRootLeash()); if (mController.getTransitionPlayer() != null) { mController.dispatchLegacyAppTransitionStarting(info); diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 6491d6bbe26e..2f41d5b3582a 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -75,6 +75,7 @@ class TransitionController { private ITransitionPlayer mTransitionPlayer; final TransitionMetricsReporter mTransitionMetricsReporter = new TransitionMetricsReporter(); + final TransitionTracer mTransitionTracer; private IApplicationThread mTransitionPlayerThread; final ActivityTaskManagerService mAtm; @@ -100,10 +101,12 @@ class TransitionController { final StatusBarManagerInternal mStatusBar; TransitionController(ActivityTaskManagerService atm, - TaskSnapshotController taskSnapshotController) { + TaskSnapshotController taskSnapshotController, + TransitionTracer transitionTracer) { mAtm = atm; mStatusBar = LocalServices.getService(StatusBarManagerInternal.class); mTaskSnapshotController = taskSnapshotController; + mTransitionTracer = transitionTracer; mTransitionPlayerDeath = () -> { synchronized (mAtm.mGlobalLock) { // Clean-up/finish any playing transitions. @@ -207,6 +210,18 @@ class TransitionController { } /** + * @return {@code true} if transition is actively collecting changes and `wc` is one of them + * or a descendant of one of them. {@code false} once playing. + */ + boolean inCollectingTransition(@NonNull WindowContainer wc) { + if (!isCollecting()) return false; + for (WindowContainer p = wc; p != null; p = p.getParent()) { + if (mCollectingTransition.mParticipants.contains(p)) return true; + } + return false; + } + + /** * @return {@code true} if transition is actively playing. This is not necessarily {@code true} * during collection. */ @@ -214,6 +229,18 @@ class TransitionController { return !mPlayingTransitions.isEmpty(); } + /** + * @return {@code true} if one of the playing transitions contains `wc`. + */ + boolean inPlayingTransition(@NonNull WindowContainer wc) { + for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) { + for (WindowContainer p = wc; p != null; p = p.getParent()) { + if (mPlayingTransitions.get(i).mParticipants.contains(p)) return true; + } + } + return false; + } + /** @return {@code true} if a transition is running */ boolean inTransition() { // TODO(shell-transitions): eventually properly support multiple @@ -222,19 +249,7 @@ class TransitionController { /** @return {@code true} if a transition is running in a participant subtree of wc */ boolean inTransition(@NonNull WindowContainer wc) { - if (isCollecting()) { - for (WindowContainer p = wc; p != null; p = p.getParent()) { - if (isCollecting(p)) return true; - } - } - for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) { - for (WindowContainer p = wc; p != null; p = p.getParent()) { - if (mPlayingTransitions.get(i).mParticipants.contains(p)) { - return true; - } - } - } - return false; + return inCollectingTransition(wc) || inPlayingTransition(wc); } boolean inRecentsTransition(@NonNull WindowContainer wc) { @@ -502,6 +517,7 @@ class TransitionController { setAnimationRunning(true /* running */); } mPlayingTransitions.add(transition); + mTransitionTracer.logState(transition); } private void setAnimationRunning(boolean running) { @@ -520,6 +536,7 @@ class TransitionController { } transition.abort(); mCollectingTransition = null; + mTransitionTracer.logState(transition); } /** diff --git a/services/core/java/com/android/server/wm/TransitionTracer.java b/services/core/java/com/android/server/wm/TransitionTracer.java new file mode 100644 index 000000000000..192b9abc62a7 --- /dev/null +++ b/services/core/java/com/android/server/wm/TransitionTracer.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.os.Build.IS_USER; + +import static com.android.server.wm.shell.ChangeInfo.CHANGE_FLAGS; +import static com.android.server.wm.shell.ChangeInfo.HAS_CHANGED; +import static com.android.server.wm.shell.ChangeInfo.TRANSIT_MODE; +import static com.android.server.wm.shell.ChangeInfo.WINDOW_IDENTIFIER; +import static com.android.server.wm.shell.Transition.CHANGE; +import static com.android.server.wm.shell.Transition.FLAGS; +import static com.android.server.wm.shell.Transition.ID; +import static com.android.server.wm.shell.Transition.STATE; +import static com.android.server.wm.shell.Transition.TIMESTAMP; +import static com.android.server.wm.shell.Transition.TRANSITION_TYPE; +import static com.android.server.wm.shell.TransitionTraceProto.MAGIC_NUMBER; +import static com.android.server.wm.shell.TransitionTraceProto.MAGIC_NUMBER_H; +import static com.android.server.wm.shell.TransitionTraceProto.MAGIC_NUMBER_L; +import static com.android.server.wm.shell.TransitionTraceProto.TRANSITION; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.SystemClock; +import android.os.Trace; +import android.util.Log; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.util.TraceBuffer; +import com.android.server.wm.Transition.ChangeInfo; +import com.android.server.wm.shell.TransitionTraceProto; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; + +/** + * Helper class to collect and dump transition traces. + */ +public class TransitionTracer { + + private static final String LOG_TAG = "TransitionTracer"; + + /** + * Maximum buffer size, currently defined as 5 MB + */ + private static final int BUFFER_CAPACITY = 5120 * 1024; // 5 MB + static final String WINSCOPE_EXT = ".winscope"; + private static final String TRACE_FILE = "/data/misc/wmtrace/transition_trace" + WINSCOPE_EXT; + private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L; + + private final TransitionTraceBuffer mTraceBuffer = new TransitionTraceBuffer(); + + private final Object mEnabledLock = new Object(); + private volatile boolean mEnabled = false; + + private long mTraceStartTimestamp; + + private class TransitionTraceBuffer { + private final TraceBuffer mBuffer = new TraceBuffer(BUFFER_CAPACITY); + + private void pushTransitionState(Transition transition) { + final ProtoOutputStream outputStream = new ProtoOutputStream(); + final long transitionEntryToken = outputStream.start(TRANSITION); + + outputStream.write(ID, transition.getDebugId()); + outputStream.write(TIMESTAMP, SystemClock.elapsedRealtimeNanos()); + outputStream.write(TRANSITION_TYPE, transition.mType); + outputStream.write(STATE, transition.getState()); + outputStream.write(FLAGS, transition.getFlags()); + + for (int i = 0; i < transition.mChanges.size(); ++i) { + final WindowContainer window = transition.mChanges.keyAt(i); + final ChangeInfo changeInfo = transition.mChanges.valueAt(i); + writeChange(outputStream, window, changeInfo); + } + + outputStream.end(transitionEntryToken); + + mBuffer.add(outputStream); + } + + private void writeChange(ProtoOutputStream outputStream, WindowContainer window, + ChangeInfo changeInfo) { + Trace.beginSection("TransitionProto#addChange"); + final long changeEntryToken = outputStream.start(CHANGE); + + final int transitMode = changeInfo.getTransitMode(window); + final boolean hasChanged = changeInfo.hasChanged(window); + final int changeFlags = changeInfo.getChangeFlags(window); + + outputStream.write(TRANSIT_MODE, transitMode); + outputStream.write(HAS_CHANGED, hasChanged); + outputStream.write(CHANGE_FLAGS, changeFlags); + window.writeIdentifierToProto(outputStream, WINDOW_IDENTIFIER); + + outputStream.end(changeEntryToken); + Trace.endSection(); + } + + public void writeToFile(File file, ProtoOutputStream proto) throws IOException { + mBuffer.writeTraceToFile(file, proto); + } + + public void reset() { + mBuffer.resetBuffer(); + } + } + + /** + * Records the current state of a transition in the transition trace (if it is running). + * @param transition the transition that we want to record the state of. + */ + public void logState(com.android.server.wm.Transition transition) { + if (!mEnabled) { + return; + } + + Log.d(LOG_TAG, "Logging state of transition " + transition); + mTraceBuffer.pushTransitionState(transition); + } + + /** + * Starts collecting transitions for the trace. + * If called while a trace is already running, this will reset the trace. + */ + public void startTrace(@Nullable PrintWriter pw) { + if (IS_USER) { + LogAndPrintln.e(pw, "Tracing is not supported on user builds."); + return; + } + Trace.beginSection("TransitionTracer#startTrace"); + LogAndPrintln.i(pw, "Starting shell transition trace."); + synchronized (mEnabledLock) { + mTraceStartTimestamp = SystemClock.elapsedRealtime(); + mEnabled = true; + mTraceBuffer.reset(); + } + Trace.endSection(); + } + + /** + * Stops collecting the transition trace and dump to trace to file. + * + * Dumps the trace to @link{TRACE_FILE}. + */ + public void stopTrace(@Nullable PrintWriter pw) { + stopTrace(pw, new File(TRACE_FILE)); + } + + /** + * Stops collecting the transition trace and dump to trace to file. + * @param outputFile The file to dump the transition trace to. + */ + public void stopTrace(@Nullable PrintWriter pw, File outputFile) { + if (IS_USER) { + LogAndPrintln.e(pw, "Tracing is not supported on user builds."); + return; + } + Trace.beginSection("TransitionTracer#stopTrace"); + LogAndPrintln.i(pw, "Stopping shell transition trace."); + synchronized (mEnabledLock) { + if (!mEnabled) { + LogAndPrintln.e(pw, + "Error: Tracing can't be stopped because it hasn't been started."); + return; + } + + mEnabled = false; + writeTraceToFileLocked(pw, outputFile); + } + Trace.endSection(); + } + + boolean isEnabled() { + return mEnabled; + } + + private void writeTraceToFileLocked(@Nullable PrintWriter pw, File file) { + Trace.beginSection("TransitionTracer#writeTraceToFileLocked"); + try { + ProtoOutputStream proto = new ProtoOutputStream(); + proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE); + proto.write(TransitionTraceProto.TIMESTAMP, mTraceStartTimestamp); + int pid = android.os.Process.myPid(); + LogAndPrintln.i(pw, "Writing file to " + file.getAbsolutePath() + + " from process " + pid); + mTraceBuffer.writeToFile(file, proto); + } catch (IOException e) { + LogAndPrintln.e(pw, "Unable to write buffer to file", e); + } + Trace.endSection(); + } + + private static class LogAndPrintln { + private static void i(@Nullable PrintWriter pw, String msg) { + Log.i(LOG_TAG, msg); + if (pw != null) { + pw.println(msg); + pw.flush(); + } + } + + private static void e(@Nullable PrintWriter pw, String msg) { + Log.e(LOG_TAG, msg); + if (pw != null) { + pw.println("ERROR: " + msg); + pw.flush(); + } + } + + private static void e(@Nullable PrintWriter pw, String msg, @NonNull Exception e) { + Log.e(LOG_TAG, msg, e); + if (pw != null) { + pw.println("ERROR: " + msg + " ::\n " + e); + pw.flush(); + } + } + } +} diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 44cb4b900255..8b2b830e90e0 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -460,6 +460,7 @@ public class WindowManagerService extends IWindowManager.Stub final WindowManagerConstants mConstants; final WindowTracing mWindowTracing; + final TransitionTracer mTransitionTracer; private final DisplayAreaPolicy.Provider mDisplayAreaPolicyProvider; @@ -1240,6 +1241,7 @@ public class WindowManagerService extends IWindowManager.Stub mWindowTracing = WindowTracing.createDefaultAndStartLooper(this, Choreographer.getInstance()); + mTransitionTracer = new TransitionTracer(); LocalServices.addService(WindowManagerPolicy.class, mPolicy); @@ -5884,6 +5886,21 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public void startTransitionTrace() { + mTransitionTracer.startTrace(null /* printwriter */); + } + + @Override + public void stopTransitionTrace() { + mTransitionTracer.stopTrace(null /* printwriter */); + } + + @Override + public boolean isTransitionTraceEnabled() { + return mTransitionTracer.isEnabled(); + } + + @Override public boolean registerCrossWindowBlurEnabledListener( ICrossWindowBlurEnabledListener listener) { return mBlurController.registerCrossWindowBlurEnabledListener(listener); diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java index 8a6a4df0fd42..428a60fb0791 100644 --- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java +++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java @@ -149,6 +149,8 @@ public class WindowManagerShellCommand extends ShellCommand { return runReset(pw); case "disable-blur": return runSetBlurDisabled(pw); + case "shell": + return runWmShellCommand(pw); default: return handleDefaultCommands(cmd); } @@ -1171,6 +1173,47 @@ public class WindowManagerShellCommand extends ShellCommand { return 0; } + private int runWmShellCommand(PrintWriter pw) { + String arg = getNextArg(); + + switch (arg) { + case "tracing": + return runWmShellTracing(pw); + case "help": + default: + return runHelp(pw); + } + } + + private int runHelp(PrintWriter pw) { + pw.println("Window Manager Shell commands:"); + pw.println(" help"); + pw.println(" Print this help text."); + pw.println(" tracing <start/stop>"); + pw.println(" Start/stop shell transition tracing."); + + return 0; + } + + private int runWmShellTracing(PrintWriter pw) { + String arg = getNextArg(); + + switch (arg) { + case "start": + mInternal.mTransitionTracer.startTrace(pw); + break; + case "stop": + mInternal.mTransitionTracer.stopTrace(pw); + break; + default: + getErrPrintWriter() + .println("Error: expected 'start' or 'stop', but got '" + arg + "'"); + return -1; + } + + return 0; + } + private int runReset(PrintWriter pw) throws RemoteException { int displayId = getDisplayId(getNextArg()); diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 128b3292983e..45376ead9b20 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -147,7 +147,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } void setWindowManager(WindowManagerService wms) { - mTransitionController = new TransitionController(mService, wms.mTaskSnapshotController); + mTransitionController = new TransitionController(mService, wms.mTaskSnapshotController, + wms.mTransitionTracer); mTransitionController.registerLegacyListener(wms.mActivityManagerAppTransitionNotifier); } @@ -820,7 +821,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); break; } - tf1.setAdjacentTaskFragment(tf2, false /* moveAdjacentTogether */); + tf1.setAdjacentTaskFragment(tf2); effects |= TRANSACT_EFFECTS_LIFECYCLE; final Bundle bundle = hop.getLaunchOptions(); @@ -1209,7 +1210,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub Slog.e(TAG, "Attempt to set adjacent TaskFragment in PIP Task"); return 0; } - root1.setAdjacentTaskFragment(root2, hop.getMoveAdjacentTogether()); + root1.setAdjacentTaskFragment(root2); return TRANSACT_EFFECTS_LIFECYCLE; } diff --git a/services/core/java/com/android/server/wm/WindowOrientationListener.java b/services/core/java/com/android/server/wm/WindowOrientationListener.java index de87ab9dcce0..3e165e442d79 100644 --- a/services/core/java/com/android/server/wm/WindowOrientationListener.java +++ b/services/core/java/com/android/server/wm/WindowOrientationListener.java @@ -296,9 +296,9 @@ public abstract class WindowOrientationListener { /** * Whether the device is in the lock screen. - * @return returns true if the screen is locked. Otherwise, returns false. + * @return returns true if the key guard is showing on the lock screen. */ - public abstract boolean isKeyguardLocked(); + public abstract boolean isKeyguardShowingAndNotOccluded(); public void dumpDebug(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); @@ -1151,7 +1151,7 @@ public abstract class WindowOrientationListener { FrameworkStatsLog.DEVICE_ROTATED__ROTATION_EVENT_TYPE__ACTUAL_EVENT); if (isRotationResolverEnabled()) { - if (isKeyguardLocked()) { + if (isKeyguardShowingAndNotOccluded()) { if (mLastRotationResolution != ROTATION_UNSET && SystemClock.uptimeMillis() - mLastRotationResolutionTimeStamp < mRotationMemorizationTimeoutMillis) { diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java index 5f43800bd9d5..fd379bf1d9f4 100644 --- a/services/core/java/com/android/server/wm/WindowSurfaceController.java +++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java @@ -139,6 +139,12 @@ class WindowSurfaceController { "Destroying surface %s called by %s", this, Debug.getCallers(8)); try { if (mSurfaceControl != null) { + if (mAnimator.mIsWallpaper && !mAnimator.mWin.mWindowRemovalAllowed + && !mAnimator.mWin.mRemoveOnExit) { + // The wallpaper surface should have the same lifetime as its window. + Slog.e(TAG, "Unexpected removing wallpaper surface of " + mAnimator.mWin + + " by " + Debug.getCallers(8)); + } t.remove(mSurfaceControl); } } catch (RuntimeException e) { diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 22810b294883..ddbb930ee20c 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -18,7 +18,6 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; @@ -364,11 +363,7 @@ class WindowToken extends WindowContainer<WindowState> { @Override void assignLayer(SurfaceControl.Transaction t, int layer) { - if (windowType == TYPE_DOCK_DIVIDER) { - // See {@link DisplayContent#mSplitScreenDividerAnchor} - super.assignRelativeLayer(t, - mDisplayContent.getDefaultTaskDisplayArea().getSplitScreenDividerAnchor(), 1); - } else if (mRoundedCornerOverlay) { + if (mRoundedCornerOverlay) { super.assignLayer(t, WindowManagerPolicy.COLOR_FADE_LAYER + 1); } else { super.assignLayer(t, layer); diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd index 0e9a04f1fdde..09044e72f60b 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -57,6 +57,16 @@ <xs:element type="nonNegativeDecimal" name="screenBrightnessRampSlowIncrease"> <xs:annotation name="final"/> </xs:element> + <!-- Maximum time in milliseconds that a brightness increase animation + can take. --> + <xs:element type="xs:nonNegativeInteger" name="screenBrightnessRampIncreaseMaxMillis"> + <xs:annotation name="final"/> + </xs:element> + <!-- Maximum time in milliseconds that a brightness decrease animation + can take. --> + <xs:element type="xs:nonNegativeInteger" name="screenBrightnessRampDecreaseMaxMillis"> + <xs:annotation name="final"/> + </xs:element> <xs:element type="sensorDetails" name="lightSensor"> <xs:annotation name="final"/> </xs:element> diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt index 075edd77af1d..e8b13ca6356e 100644 --- a/services/core/xsd/display-device-config/schema/current.txt +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -48,8 +48,10 @@ package com.android.server.display.config { method public com.android.server.display.config.DisplayQuirks getQuirks(); method @NonNull public final java.math.BigDecimal getScreenBrightnessDefault(); method @NonNull public final com.android.server.display.config.NitsMap getScreenBrightnessMap(); + method public final java.math.BigInteger getScreenBrightnessRampDecreaseMaxMillis(); method public final java.math.BigDecimal getScreenBrightnessRampFastDecrease(); method public final java.math.BigDecimal getScreenBrightnessRampFastIncrease(); + method public final java.math.BigInteger getScreenBrightnessRampIncreaseMaxMillis(); method public final java.math.BigDecimal getScreenBrightnessRampSlowDecrease(); method public final java.math.BigDecimal getScreenBrightnessRampSlowIncrease(); method @NonNull public final com.android.server.display.config.ThermalThrottling getThermalThrottling(); @@ -64,8 +66,10 @@ package com.android.server.display.config { method public void setQuirks(com.android.server.display.config.DisplayQuirks); method public final void setScreenBrightnessDefault(@NonNull java.math.BigDecimal); method public final void setScreenBrightnessMap(@NonNull com.android.server.display.config.NitsMap); + method public final void setScreenBrightnessRampDecreaseMaxMillis(java.math.BigInteger); method public final void setScreenBrightnessRampFastDecrease(java.math.BigDecimal); method public final void setScreenBrightnessRampFastIncrease(java.math.BigDecimal); + method public final void setScreenBrightnessRampIncreaseMaxMillis(java.math.BigInteger); method public final void setScreenBrightnessRampSlowDecrease(java.math.BigDecimal); method public final void setScreenBrightnessRampSlowIncrease(java.math.BigDecimal); method public final void setThermalThrottling(@NonNull com.android.server.display.config.ThermalThrottling); diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java b/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java index c64ff9e128e6..3de65c19a1a4 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java @@ -185,7 +185,7 @@ public class WindowOrientationListenerTest { } @Override - public boolean isKeyguardLocked() { + public boolean isKeyguardShowingAndNotOccluded() { return mIsScreenLocked; } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 62acc7a240bf..ac5bc8621a4c 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -19,7 +19,6 @@ package com.android.server.notification; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE; import static android.app.ActivityTaskManager.INVALID_TASK_ID; -import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.Notification.FLAG_AUTO_CANCEL; import static android.app.Notification.FLAG_BUBBLE; import static android.app.Notification.FLAG_CAN_COLORIZE; @@ -9332,6 +9331,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), r.getSbn().getId(), r.getSbn().getTag(), r, false)) .isFalse(); + + // telecom manager is not ready - blocked + mService.setTelecomManager(mTelecomManager); + when(mTelecomManager.isInCall()).thenThrow(new IllegalStateException("not ready")); + assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), + r.getSbn().getId(), r.getSbn().getTag(), r, false)) + .isFalse(); } @Test @@ -9431,6 +9437,18 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mUsageStats).registerBlocked(any()); verify(mUsageStats, never()).registerPostedByApp(any()); + + // telecom is not ready - notifications should be blocked but no crashes + mService.setTelecomManager(mTelecomManager); + when(mTelecomManager.isInCall()).thenThrow(new IllegalStateException("not ready")); + reset(mUsageStats); + + mService.addEnqueuedNotification(r); + runnable.run(); + waitForIdle(); + + verify(mUsageStats).registerBlocked(any()); + verify(mUsageStats, never()).registerPostedByApp(any()); } @Test @@ -9572,7 +9590,21 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testMaybeShowReviewPermissionsNotification_flagOff() { + mService.setShowReviewPermissionsNotification(false); + reset(mMockNm); + + // If state is SHOULD_SHOW, it would show, but not if the flag is off! + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, + NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW); + mService.maybeShowInitialReviewPermissionsNotification(); + verify(mMockNm, never()).notify(anyString(), anyInt(), any(Notification.class)); + } + + @Test public void testMaybeShowReviewPermissionsNotification_unknown() { + mService.setShowReviewPermissionsNotification(true); reset(mMockNm); // Set up various possible states of the settings int and confirm whether or not the @@ -9588,6 +9620,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testMaybeShowReviewPermissionsNotification_shouldShow() { + mService.setShowReviewPermissionsNotification(true); reset(mMockNm); // If state is SHOULD_SHOW, it ... should show @@ -9602,6 +9635,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testMaybeShowReviewPermissionsNotification_alreadyShown() { + mService.setShowReviewPermissionsNotification(true); reset(mMockNm); // If state is either USER_INTERACTED or DISMISSED, we should not show this on boot @@ -9620,6 +9654,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testMaybeShowReviewPermissionsNotification_reshown() { + mService.setShowReviewPermissionsNotification(true); reset(mMockNm); // If we have re-shown the notification and the user did not subsequently interacted with @@ -9635,6 +9670,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testRescheduledReviewPermissionsNotification() { + mService.setShowReviewPermissionsNotification(true); reset(mMockNm); // when rescheduled, the notification goes through the NotificationManagerInternal service @@ -9653,4 +9689,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN)); } + + @Test + public void testRescheduledReviewPermissionsNotification_flagOff() { + mService.setShowReviewPermissionsNotification(false); + reset(mMockNm); + + // no notification should be sent if the flag is off + mInternalService.sendReviewPermissionsNotification(); + verify(mMockNm, never()).notify(anyString(), anyInt(), any(Notification.class)); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 8d50ceaf74e9..598a22bbde39 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -292,7 +292,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mStatsEventBuilderFactory = new WrappedSysUiStatsEvent.WrappedBuilderFactory(); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory); + mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); resetZenModeHelper(); mAudioAttributes = new AudioAttributes.Builder() @@ -621,7 +621,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testReadXml_oldXml_migrates() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory); + mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, true); String xml = "<ranking version=\"2\">\n" + "<package name=\"" + PKG_N_MR1 + "\" uid=\"" + UID_N_MR1 @@ -691,7 +691,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testReadXml_oldXml_backup_migratesWhenPkgInstalled() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory); + mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); when(mPm.getPackageUidAsUser("pkg1", USER_SYSTEM)).thenReturn(UNKNOWN_UID); when(mPm.getPackageUidAsUser("pkg2", USER_SYSTEM)).thenReturn(UNKNOWN_UID); @@ -769,7 +769,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testReadXml_newXml_noMigration_showPermissionNotification() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory); + mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, true); String xml = "<ranking version=\"3\">\n" + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n" @@ -824,9 +824,66 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test + public void testReadXml_newXml_permissionNotificationOff() throws Exception { + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, + mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); + + String xml = "<ranking version=\"3\">\n" + + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n" + + "<channel id=\"idn\" name=\"name\" importance=\"2\"/>\n" + + "<channel id=\"miscellaneous\" name=\"Uncategorized\" />\n" + + "</package>\n" + + "<package name=\"" + PKG_O + "\" >\n" + + "<channel id=\"ido\" name=\"name2\" importance=\"2\" show_badge=\"true\"/>\n" + + "</package>\n" + + "<package name=\"" + PKG_P + "\" >\n" + + "<channel id=\"idp\" name=\"name3\" importance=\"4\" locked=\"2\" />\n" + + "</package>\n" + + "</ranking>\n"; + NotificationChannel idn = new NotificationChannel("idn", "name", IMPORTANCE_LOW); + idn.setSound(null, new AudioAttributes.Builder() + .setUsage(USAGE_NOTIFICATION) + .setContentType(CONTENT_TYPE_SONIFICATION) + .setFlags(0) + .build()); + idn.setShowBadge(false); + NotificationChannel ido = new NotificationChannel("ido", "name2", IMPORTANCE_LOW); + ido.setShowBadge(true); + ido.setSound(null, new AudioAttributes.Builder() + .setUsage(USAGE_NOTIFICATION) + .setContentType(CONTENT_TYPE_SONIFICATION) + .setFlags(0) + .build()); + NotificationChannel idp = new NotificationChannel("idp", "name3", IMPORTANCE_HIGH); + idp.lockFields(2); + idp.setSound(null, new AudioAttributes.Builder() + .setUsage(USAGE_NOTIFICATION) + .setContentType(CONTENT_TYPE_SONIFICATION) + .setFlags(0) + .build()); + + loadByteArrayXml(xml.getBytes(), true, USER_SYSTEM); + + assertTrue(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1)); + + assertEquals(idn, mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, idn.getId(), false)); + compareChannels(ido, mHelper.getNotificationChannel(PKG_O, UID_O, ido.getId(), false)); + compareChannels(idp, mHelper.getNotificationChannel(PKG_P, UID_P, idp.getId(), false)); + + verify(mPermissionHelper, never()).setNotificationPermission(any()); + + // while this is the same case as above, if the permission helper is set to not show the + // review permissions notification it should not write anything to the settings int + assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN, + Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, + NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN)); + } + + @Test public void testReadXml_newXml_noMigration_noPermissionNotification() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory); + mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, true); String xml = "<ranking version=\"4\">\n" + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n" @@ -882,7 +939,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testReadXml_oldXml_migration_NoUid() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory); + mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); when(mPm.getPackageUidAsUser("something", USER_SYSTEM)).thenReturn(UNKNOWN_UID); String xml = "<ranking version=\"2\">\n" @@ -915,7 +972,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testReadXml_newXml_noMigration_NoUid() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory); + mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); when(mPm.getPackageUidAsUser("something", USER_SYSTEM)).thenReturn(UNKNOWN_UID); String xml = "<ranking version=\"3\">\n" @@ -947,7 +1004,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testChannelXmlForNonBackup_postMigration() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory); + mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>(); appPermissions.put(new Pair(1, "first"), new Pair(true, false)); @@ -1027,7 +1084,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testChannelXmlForBackup_postMigration() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory); + mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>(); appPermissions.put(new Pair(1, "first"), new Pair(true, false)); @@ -1113,7 +1170,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testChannelXmlForBackup_postMigration_noExternal() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory); + mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>(); appPermissions.put(new Pair(UID_P, PKG_P), new Pair(true, false)); @@ -1192,7 +1249,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test public void testChannelXmlForBackup_postMigration_noLocalSettings() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, - mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory); + mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false); ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>(); appPermissions.put(new Pair(1, "first"), new Pair(true, false)); @@ -2117,7 +2174,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory); + mAppOpsManager, mStatsEventBuilderFactory, false); // create notification channel that can bypass dnd, but app is blocked @@ -2145,7 +2202,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory); + mAppOpsManager, mStatsEventBuilderFactory, false); // create notification channel that can bypass dnd, but app is blocked // expected result: areChannelsBypassingDnd = false @@ -2168,7 +2225,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory); + mAppOpsManager, mStatsEventBuilderFactory, false); // create notification channel that can bypass dnd, but app is blocked // expected result: areChannelsBypassingDnd = false @@ -2220,7 +2277,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory); + mAppOpsManager, mStatsEventBuilderFactory, false); assertFalse(mHelper.areChannelsBypassingDnd()); verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any()); resetZenModeHelper(); @@ -2233,7 +2290,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory); + mAppOpsManager, mStatsEventBuilderFactory, false); assertFalse(mHelper.areChannelsBypassingDnd()); verify(mMockZenModeHelper, never()).setNotificationPolicy(any()); resetZenModeHelper(); @@ -3229,7 +3286,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { + "</ranking>\n"; mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory); + mAppOpsManager, mStatsEventBuilderFactory, false); loadByteArrayXml(preQXml.getBytes(), true, USER_SYSTEM); assertEquals(PreferencesHelper.DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS, @@ -3243,7 +3300,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory); + mAppOpsManager, mStatsEventBuilderFactory, false); loadStreamXml(baos, false, UserHandle.USER_ALL); assertEquals(!PreferencesHelper.DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS, @@ -3341,7 +3398,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory); + mAppOpsManager, mStatsEventBuilderFactory, false); loadStreamXml(baos, false, UserHandle.USER_ALL); assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O)); @@ -3354,7 +3411,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory); + mAppOpsManager, mStatsEventBuilderFactory, false); loadStreamXml(baos, false, UserHandle.USER_ALL); assertEquals("other", mHelper.getNotificationDelegate(PKG_O, UID_O)); @@ -3368,7 +3425,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory); + mAppOpsManager, mStatsEventBuilderFactory, false); loadStreamXml(baos, false, UserHandle.USER_ALL); assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O)); @@ -3382,7 +3439,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory); + mAppOpsManager, mStatsEventBuilderFactory, false); loadStreamXml(baos, false, UserHandle.USER_ALL); // appears disabled @@ -3402,7 +3459,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory); + mAppOpsManager, mStatsEventBuilderFactory, false); loadStreamXml(baos, false, UserHandle.USER_ALL); // appears disabled @@ -3422,7 +3479,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory); + mAppOpsManager, mStatsEventBuilderFactory, false); loadStreamXml(baos, false, UserHandle.USER_ALL); assertEquals(BUBBLE_PREFERENCE_NONE, mHelper.getBubblePreference(PKG_O, UID_O)); @@ -3478,7 +3535,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory); + mAppOpsManager, mStatsEventBuilderFactory, false); loadStreamXml(baos, false, UserHandle.USER_ALL); assertEquals(BUBBLE_PREFERENCE_SELECTED, mHelper.getBubblePreference(PKG_O, UID_O)); @@ -3516,7 +3573,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory); + mAppOpsManager, mStatsEventBuilderFactory, false); loadStreamXml(baos, false, UserHandle.USER_ALL); assertEquals(mHelper.getBubblePreference(PKG_O, UID_O), BUBBLE_PREFERENCE_NONE); @@ -4150,7 +4207,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testPlaceholderConversationId_shortcutRequired() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory); + mAppOpsManager, mStatsEventBuilderFactory, false); final String xml = "<ranking version=\"1\">\n" + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n" @@ -4170,7 +4227,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testNormalConversationId_shortcutRequired() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory); + mAppOpsManager, mStatsEventBuilderFactory, false); final String xml = "<ranking version=\"1\">\n" + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n" @@ -4190,7 +4247,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testNoConversationId_shortcutRequired() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory); + mAppOpsManager, mStatsEventBuilderFactory, false); final String xml = "<ranking version=\"1\">\n" + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n" @@ -4210,7 +4267,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testDeleted_noTime() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory); + mAppOpsManager, mStatsEventBuilderFactory, false); final String xml = "<ranking version=\"1\">\n" + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n" @@ -4230,7 +4287,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testDeleted_twice() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory); + mAppOpsManager, mStatsEventBuilderFactory, false); mHelper.createNotificationChannel( PKG_P, UID_P, new NotificationChannel("id", "id", 2), true, false); @@ -4242,7 +4299,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testDeleted_recentTime() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory); + mAppOpsManager, mStatsEventBuilderFactory, false); mHelper.createNotificationChannel( PKG_P, UID_P, new NotificationChannel("id", "id", 2), true, false); @@ -4260,7 +4317,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { parser.nextTag(); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory); + mAppOpsManager, mStatsEventBuilderFactory, false); mHelper.readXml(parser, true, USER_SYSTEM); NotificationChannel nc = mHelper.getNotificationChannel(PKG_P, UID_P, "id", true); @@ -4272,7 +4329,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testUnDelete_time() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory); + mAppOpsManager, mStatsEventBuilderFactory, false); mHelper.createNotificationChannel( PKG_P, UID_P, new NotificationChannel("id", "id", 2), true, false); @@ -4292,7 +4349,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testDeleted_longTime() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, - mAppOpsManager, mStatsEventBuilderFactory); + mAppOpsManager, mStatsEventBuilderFactory, false); long time = System.currentTimeMillis() - (DateUtils.DAY_IN_MILLIS * 30); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java index 4ed7d35a097f..b49e5cbfa9dc 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java @@ -114,6 +114,11 @@ public class TestableNotificationManagerService extends NotificationManagerServi mChannelToastsSent.add(uid); } + // Helper method for testing behavior when turning on/off the review permissions notification. + protected void setShowReviewPermissionsNotification(boolean setting) { + mShowReviewPermissionsNotification = setting; + } + public class StrongAuthTrackerFake extends NotificationManagerService.StrongAuthTracker { private int mGetStrongAuthForUserReturnValue = 0; StrongAuthTrackerFake(Context context) { diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index f11023f34459..5be1ecef0360 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -794,7 +794,7 @@ public class ActivityStarterTests extends WindowTestsBase { // Create adjacent tasks and put one activity under it final Task parent = new TaskBuilder(mSupervisor).build(); final Task adjacentParent = new TaskBuilder(mSupervisor).build(); - parent.setAdjacentTaskFragment(adjacentParent, true); + parent.setAdjacentTaskFragment(adjacentParent); final ActivityRecord activity = new ActivityBuilder(mAtm) .setParentTask(parent) .setCreateTask(true).build(); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java index 00e1ed226d7e..8656a4fecef1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java @@ -593,7 +593,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { .setCreatedByOrganizer(true); final Task splitRoot1 = builder.build(); final Task splitRoot2 = builder.build(); - splitRoot1.setAdjacentTaskFragment(splitRoot2, false /* moveTogether */); + splitRoot1.setAdjacentTaskFragment(splitRoot2); final ActivityRecord activity1 = createActivityRecordWithParentTask(splitRoot1); activity1.setVisible(false); activity1.mVisibleRequested = true; diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java index e8f1d2390c34..2a9fcb9d070b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java @@ -82,7 +82,7 @@ public class TaskDisplayAreaTests extends WindowTestsBase { mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); adjacentRootTask.mCreatedByOrganizer = true; final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea(); - adjacentRootTask.setAdjacentTaskFragment(rootTask, false /* moveTogether */); + adjacentRootTask.setAdjacentTaskFragment(rootTask); taskDisplayArea.setLaunchAdjacentFlagRootTask(adjacentRootTask); Task actualRootTask = taskDisplayArea.getLaunchRootTask( @@ -108,7 +108,7 @@ public class TaskDisplayAreaTests extends WindowTestsBase { final Task adjacentRootTask = createTask( mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); adjacentRootTask.mCreatedByOrganizer = true; - adjacentRootTask.setAdjacentTaskFragment(rootTask, false /* moveTogether */); + adjacentRootTask.setAdjacentTaskFragment(rootTask); taskDisplayArea.setLaunchRootTask(rootTask, new int[]{WINDOWING_MODE_MULTI_WINDOW}, new int[]{ACTIVITY_TYPE_STANDARD}); @@ -129,7 +129,7 @@ public class TaskDisplayAreaTests extends WindowTestsBase { mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); adjacentRootTask.mCreatedByOrganizer = true; final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea(); - adjacentRootTask.setAdjacentTaskFragment(rootTask, false /* moveTogether */); + adjacentRootTask.setAdjacentTaskFragment(rootTask); taskDisplayArea.setLaunchAdjacentFlagRootTask(adjacentRootTask); final Task actualRootTask = taskDisplayArea.getLaunchRootTask( @@ -756,7 +756,7 @@ public class TaskDisplayAreaTests extends WindowTestsBase { adjacentRootTask.mCreatedByOrganizer = true; final Task candidateTask = createTaskInRootTask(rootTask, 0 /* userId*/); final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea(); - adjacentRootTask.setAdjacentTaskFragment(rootTask, false /* moveTogether */); + adjacentRootTask.setAdjacentTaskFragment(rootTask); // Verify the launch root with candidate task Task actualRootTask = taskDisplayArea.getLaunchRootTask(WINDOWING_MODE_UNDEFINED, diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index 5340a79f4b94..7cdf5a8629cb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -32,7 +32,6 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; @@ -412,7 +411,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // Throw exception if the transaction is trying to change a window that is not organized by // the organizer. - mTransaction.setAdjacentRoots(mFragmentWindowToken, token2, false /* moveTogether */); + mTransaction.setAdjacentRoots(mFragmentWindowToken, token2); assertApplyTransactionDisallowed(mTransaction); @@ -630,7 +629,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { verify(mAtm.mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer), eq(errorToken), any(IllegalArgumentException.class)); - verify(mTaskFragment, never()).setAdjacentTaskFragment(any(), anyBoolean()); + verify(mTaskFragment, never()).setAdjacentTaskFragment(any()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java index 3ed484ac7391..228cb65aab38 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java @@ -202,7 +202,7 @@ public class TaskFragmentTest extends WindowTestsBase { doReturn(true).when(primaryActivity).supportsPictureInPicture(); doReturn(false).when(secondaryActivity).supportsPictureInPicture(); - primaryTf.setAdjacentTaskFragment(secondaryTf, false /* moveAdjacentTogether */); + primaryTf.setAdjacentTaskFragment(secondaryTf); primaryActivity.setState(RESUMED, "test"); secondaryActivity.setState(RESUMED, "test"); @@ -448,7 +448,7 @@ public class TaskFragmentTest extends WindowTestsBase { .setOrganizer(mOrganizer) .setFragmentToken(new Binder()) .build(); - tf0.setAdjacentTaskFragment(tf1, false /* moveAdjacentTogether */); + tf0.setAdjacentTaskFragment(tf1); tf0.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); tf1.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); task.setBounds(0, 0, 1200, 1000); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index 44b1d83580d6..71387147150e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -59,7 +59,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.clearInvocations; @@ -1417,25 +1416,6 @@ public class TaskTests extends WindowTestsBase { } @Test - public void testMoveToFront_moveAdjacentTask() { - final Task task1 = - createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); - final Task task2 = - createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); - spyOn(task2); - - task1.setAdjacentTaskFragment(task2, false /* moveTogether */); - task1.moveToFront("" /* reason */); - verify(task2, never()).moveToFrontInner(anyString(), isNull()); - - // Reset adjacent tasks to move together. - task1.setAdjacentTaskFragment(null, false /* moveTogether */); - task1.setAdjacentTaskFragment(task2, true /* moveTogether */); - task1.moveToFront("" /* reason */); - verify(task2).moveToFrontInner(anyString(), isNull()); - } - - @Test public void testResumeTask_doNotResumeTaskFragmentBehindTranslucent() { final Task task = createTask(mDisplayContent); final TaskFragment tfBehind = createTaskFragmentWithParentTask( diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index d9d819b5f00f..234bfa7ad772 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -88,7 +88,11 @@ public class TransitionTests extends WindowTestsBase { final SurfaceControl.Transaction mMockT = mock(SurfaceControl.Transaction.class); private Transition createTestTransition(int transitType) { - TransitionController controller = mock(TransitionController.class); + TransitionTracer tracer = mock(TransitionTracer.class); + final TransitionController controller = new TransitionController( + mock(ActivityTaskManagerService.class), mock(TaskSnapshotController.class), + mock(TransitionTracer.class)); + final BLASTSyncEngine sync = createTestBLASTSyncEngine(); final Transition t = new Transition(transitType, 0 /* flags */, controller, sync); t.startCollecting(0 /* timeoutMs */); @@ -584,7 +588,7 @@ public class TransitionTests extends WindowTestsBase { @Test public void testTimeout() { final TransitionController controller = new TransitionController(mAtm, - mock(TaskSnapshotController.class)); + mock(TaskSnapshotController.class), mock(TransitionTracer.class)); final BLASTSyncEngine sync = new BLASTSyncEngine(mWm); final CountDownLatch latch = new CountDownLatch(1); // When the timeout is reached, it will finish the sync-group and notify transaction ready. @@ -841,7 +845,8 @@ public class TransitionTests extends WindowTestsBase { @Test public void testIntermediateVisibility() { final TaskSnapshotController snapshotController = mock(TaskSnapshotController.class); - final TransitionController controller = new TransitionController(mAtm, snapshotController); + final TransitionController controller = new TransitionController(mAtm, snapshotController, + mock(TransitionTracer.class)); final ITransitionPlayer player = new ITransitionPlayer.Default(); controller.registerTransitionPlayer(player, null /* appThread */); final Transition openTransition = controller.createTransition(TRANSIT_OPEN); @@ -905,7 +910,8 @@ public class TransitionTests extends WindowTestsBase { @Test public void testTransientLaunch() { final TaskSnapshotController snapshotController = mock(TaskSnapshotController.class); - final TransitionController controller = new TransitionController(mAtm, snapshotController); + final TransitionController controller = new TransitionController(mAtm, snapshotController, + mock(TransitionTracer.class)); final ITransitionPlayer player = new ITransitionPlayer.Default(); controller.registerTransitionPlayer(player, null /* appThread */); final Transition openTransition = controller.createTransition(TRANSIT_OPEN); @@ -968,7 +974,8 @@ public class TransitionTests extends WindowTestsBase { @Test public void testNotReadyPushPop() { final TaskSnapshotController snapshotController = mock(TaskSnapshotController.class); - final TransitionController controller = new TransitionController(mAtm, snapshotController); + final TransitionController controller = new TransitionController(mAtm, snapshotController, + mock(TransitionTracer.class)); final ITransitionPlayer player = new ITransitionPlayer.Default(); controller.registerTransitionPlayer(player, null /* appThread */); final Transition openTransition = controller.createTransition(TRANSIT_OPEN); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index f08b9fddd6af..08bad70a1411 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -690,7 +690,7 @@ public class WindowOrganizerTests extends WindowTestsBase { final RunningTaskInfo info2 = task2.getTaskInfo(); WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.setAdjacentRoots(info1.token, info2.token, false /* moveTogether */); + wct.setAdjacentRoots(info1.token, info2.token); mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); assertEquals(task1.getAdjacentTaskFragment(), task2); assertEquals(task2.getAdjacentTaskFragment(), task1); @@ -700,8 +700,8 @@ public class WindowOrganizerTests extends WindowTestsBase { mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); assertEquals(dc.getDefaultTaskDisplayArea().mLaunchAdjacentFlagRootTask, task1); - task1.setAdjacentTaskFragment(null, false /* moveTogether */); - task2.setAdjacentTaskFragment(null, false /* moveTogether */); + task1.setAdjacentTaskFragment(null); + task2.setAdjacentTaskFragment(null); wct = new WindowContainerTransaction(); wct.clearLaunchAdjacentFlagRoot(info1.token); mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index e138d52b8ef6..b973fca74977 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -1550,7 +1550,7 @@ class WindowTestsBase extends SystemServiceTestsBase { mSecondary = mService.mTaskOrganizerController.createRootTask( display, WINDOWING_MODE_MULTI_WINDOW, null); - mPrimary.setAdjacentTaskFragment(mSecondary, true); + mPrimary.setAdjacentTaskFragment(mSecondary); display.getDefaultTaskDisplayArea().setLaunchAdjacentFlagRootTask(mSecondary); final Rect primaryBounds = new Rect(); diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java index 2df1d23c0497..77fca451547d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java @@ -328,7 +328,6 @@ public class ZOrderingTests extends WindowTestsBase { assertWindowHigher(mImeWindow, imeSystemOverlayTarget); assertWindowHigher(mImeWindow, mChildAppWindowAbove); assertWindowHigher(mImeWindow, mAppWindow); - assertWindowHigher(mImeWindow, mDockedDividerWindow); // The IME has a higher base layer than the status bar so we may expect it to go // above the status bar once they are both in the Non-App layer, as past versions of this @@ -349,7 +348,6 @@ public class ZOrderingTests extends WindowTestsBase { assertWindowHigher(mImeWindow, mChildAppWindowAbove); assertWindowHigher(mImeWindow, mAppWindow); - assertWindowHigher(mImeWindow, mDockedDividerWindow); assertWindowHigher(mImeWindow, mStatusBarWindow); // And, IME dialogs should always have an higher layer than the IME. @@ -489,77 +487,6 @@ public class ZOrderingTests extends WindowTestsBase { } @Test - public void testDockedDividerPosition() { - final Task pinnedTask = - createTask(mDisplayContent, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD); - final WindowState pinnedWindow = - createAppWindow(pinnedTask, ACTIVITY_TYPE_STANDARD, "pinnedWindow"); - - final Task belowTask = - createTask(mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); - final WindowState belowTaskWindow = - createAppWindow(belowTask, ACTIVITY_TYPE_STANDARD, "belowTaskWindow"); - - final Task splitScreenTask1 = - createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); - final WindowState splitWindow1 = - createAppWindow(splitScreenTask1, ACTIVITY_TYPE_STANDARD, "splitWindow1"); - final Task splitScreenTask2 = - createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); - final WindowState splitWindow2 = - createAppWindow(splitScreenTask2, ACTIVITY_TYPE_STANDARD, "splitWindow2"); - splitScreenTask1.setAdjacentTaskFragment(splitScreenTask2, true /* moveTogether */); - splitScreenTask2.setAdjacentTaskFragment(splitScreenTask1, true /* moveTogether */); - - final Task aboveTask = - createTask(mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); - final WindowState aboveTaskWindow = - createAppWindow(aboveTask, ACTIVITY_TYPE_STANDARD, "aboveTaskWindow"); - - mDisplayContent.assignChildLayers(mTransaction); - - assertWindowHigher(splitWindow1, belowTaskWindow); - assertWindowHigher(splitWindow2, belowTaskWindow); - assertWindowHigher(mDockedDividerWindow, splitWindow1); - assertWindowHigher(mDockedDividerWindow, splitWindow2); - assertWindowHigher(aboveTaskWindow, mDockedDividerWindow); - assertWindowHigher(pinnedWindow, aboveTaskWindow); - } - - - @Test - public void testDockedDividerPosition_noAboveTask() { - final Task pinnedTask = - createTask(mDisplayContent, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD); - final WindowState pinnedWindow = - createAppWindow(pinnedTask, ACTIVITY_TYPE_STANDARD, "pinnedWindow"); - - final Task belowTask = - createTask(mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); - final WindowState belowTaskWindow = - createAppWindow(belowTask, ACTIVITY_TYPE_STANDARD, "belowTaskWindow"); - - final Task splitScreenTask1 = - createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); - final WindowState splitWindow1 = - createAppWindow(splitScreenTask1, ACTIVITY_TYPE_STANDARD, "splitWindow1"); - final Task splitScreenTask2 = - createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); - final WindowState splitWindow2 = - createAppWindow(splitScreenTask2, ACTIVITY_TYPE_STANDARD, "splitWindow2"); - splitScreenTask1.setAdjacentTaskFragment(splitScreenTask2, true /* moveTogether */); - splitScreenTask2.setAdjacentTaskFragment(splitScreenTask1, true /* moveTogether */); - - mDisplayContent.assignChildLayers(mTransaction); - - assertWindowHigher(splitWindow1, belowTaskWindow); - assertWindowHigher(splitWindow2, belowTaskWindow); - assertWindowHigher(mDockedDividerWindow, splitWindow1); - assertWindowHigher(mDockedDividerWindow, splitWindow2); - assertWindowHigher(pinnedWindow, mDockedDividerWindow); - } - - @Test public void testAttachNavBarWhenEnteringRecents_expectNavBarHigherThanIme() { // create RecentsAnimationController IRecentsAnimationRunner mockRunner = mock(IRecentsAnimationRunner.class); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 43fcc8feaec2..73b2510e6cf1 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -35,6 +35,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.ShortcutServiceInternal; @@ -104,6 +105,7 @@ import com.android.server.pm.permission.LegacyPermissionManagerInternal; import com.android.server.soundtrigger.SoundTriggerInternal; import com.android.server.utils.TimingsTraceAndSlog; import com.android.server.wm.ActivityTaskManagerInternal; +import com.android.server.wm.ActivityTaskManagerInternal.ActivityTokens; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -126,6 +128,7 @@ public class VoiceInteractionManagerService extends SystemService { final ActivityManagerInternal mAmInternal; final ActivityTaskManagerInternal mAtmInternal; final UserManagerInternal mUserManagerInternal; + final PackageManagerInternal mPackageManagerInternal; final ArrayMap<Integer, VoiceInteractionManagerServiceStub.SoundTriggerSession> mLoadedKeyphraseIds = new ArrayMap<>(); ShortcutServiceInternal mShortcutServiceInternal; @@ -146,6 +149,8 @@ public class VoiceInteractionManagerService extends SystemService { LocalServices.getService(ActivityTaskManagerInternal.class)); mUserManagerInternal = Objects.requireNonNull( LocalServices.getService(UserManagerInternal.class)); + mPackageManagerInternal = Objects.requireNonNull( + LocalServices.getService(PackageManagerInternal.class)); LegacyPermissionManagerInternal permissionManagerInternal = LocalServices.getService( LegacyPermissionManagerInternal.class); @@ -369,6 +374,21 @@ public class VoiceInteractionManagerService extends SystemService { return new SoundTriggerSessionBinderProxy(session); } + @GuardedBy("this") + private void grantImplicitAccessLocked(int grantRecipientUid, @Nullable Intent intent) { + if (mImpl == null) { + Slog.w(TAG, "Cannot grant implicit access because mImpl is null."); + return; + } + + final int grantRecipientAppId = UserHandle.getAppId(grantRecipientUid); + final int grantRecipientUserId = UserHandle.getUserId(grantRecipientUid); + final int voiceInteractionUid = mImpl.mInfo.getServiceInfo().applicationInfo.uid; + mPackageManagerInternal.grantImplicitAccess( + grantRecipientUserId, intent, grantRecipientAppId, voiceInteractionUid, + /* direct= */ true); + } + private IVoiceInteractionSoundTriggerSession createSoundTriggerSessionForSelfIdentity( IBinder client) { Identity identity = new Identity(); @@ -386,6 +406,7 @@ public class VoiceInteractionManagerService extends SystemService { void startLocalVoiceInteraction(final IBinder token, Bundle options) { if (mImpl == null) return; + final int callingUid = Binder.getCallingUid(); final long caller = Binder.clearCallingIdentity(); try { mImpl.showSessionLocked(options, @@ -397,6 +418,11 @@ public class VoiceInteractionManagerService extends SystemService { @Override public void onShown() { + synchronized (VoiceInteractionManagerServiceStub.this) { + VoiceInteractionManagerServiceStub.this + .grantImplicitAccessLocked(callingUid, + /* intent= */ null); + } mAtmInternal.onLocalVoiceInteractionStarted(token, mImpl.mActiveSession.mSession, mImpl.mActiveSession.mInteractor); @@ -965,8 +991,16 @@ public class VoiceInteractionManagerService extends SystemService { final int callingUid = Binder.getCallingUid(); final long caller = Binder.clearCallingIdentity(); try { - return mImpl.startVoiceActivityLocked(callingFeatureId, callingPid, callingUid, - token, intent, resolvedType); + final ActivityInfo activityInfo = intent.resolveActivityInfo( + mContext.getPackageManager(), PackageManager.MATCH_ALL); + if (activityInfo != null) { + final int activityUid = activityInfo.applicationInfo.uid; + grantImplicitAccessLocked(activityUid, intent); + } else { + Slog.w(TAG, "Cannot find ActivityInfo in startVoiceActivity."); + } + return mImpl.startVoiceActivityLocked( + callingFeatureId, callingPid, callingUid, token, intent, resolvedType); } finally { Binder.restoreCallingIdentity(caller); } @@ -1005,6 +1039,15 @@ public class VoiceInteractionManagerService extends SystemService { } final long caller = Binder.clearCallingIdentity(); try { + // Getting the UID corresponding to the taskId, and grant the visibility to it. + final ActivityTokens tokens = mAtmInternal + .getAttachedNonFinishingActivityForTask(taskId, /* token= */ null); + final ComponentName componentName = mAtmInternal.getActivityName( + tokens.getActivityToken()); + grantImplicitAccessLocked(mPackageManagerInternal.getPackageUid( + componentName.getPackageName(), PackageManager.MATCH_ALL, + UserHandle.myUserId()), /* intent= */ null); + mImpl.requestDirectActionsLocked(token, taskId, assistToken, cancellationCallback, resultCallback); } finally { diff --git a/telephony/java/android/telephony/data/DataProfile.java b/telephony/java/android/telephony/data/DataProfile.java index fa1bae41f433..5e111639b100 100644 --- a/telephony/java/android/telephony/data/DataProfile.java +++ b/telephony/java/android/telephony/data/DataProfile.java @@ -232,13 +232,14 @@ public final class DataProfile implements Parcelable { } /** - * @return True if the profile is enabled. + * @return {@code true} if the profile is enabled. If the profile only has a + * {@link TrafficDescriptor}, but no {@link ApnSetting}, then this profile is always enabled. */ public boolean isEnabled() { if (mApnSetting != null) { return mApnSetting.isEnabled(); } - return false; + return true; } /** @@ -534,7 +535,7 @@ public final class DataProfile implements Parcelable { @Type private int mType = -1; - private boolean mEnabled; + private boolean mEnabled = true; @ApnType private int mSupportedApnTypesBitmask; diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java index f1e84b1d8a33..b44e8b42f052 100644 --- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java +++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java @@ -20,7 +20,7 @@ import java.util.List; public class Utils { - public static final int ASM_VERSION = Opcodes.ASM7; + public static final int ASM_VERSION = Opcodes.ASM9; /** * Reads a comma separated configuration similar to the Jack definition. |