diff options
139 files changed, 3312 insertions, 1573 deletions
diff --git a/Android.bp b/Android.bp index 874d76fe8d00..d4ca7066781a 100644 --- a/Android.bp +++ b/Android.bp @@ -911,6 +911,7 @@ cc_library { filegroup { name: "incremental_aidl", srcs: [ + "core/java/android/os/incremental/IIncrementalServiceConnector.aidl", "core/java/android/os/incremental/IncrementalFileSystemControlParcel.aidl", ], path: "core/java", diff --git a/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java index 8633c9613138..8139a2e963c5 100644 --- a/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java +++ b/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java @@ -30,6 +30,7 @@ import android.util.MergedConfiguration; import android.view.DisplayCutout; import android.view.IWindow; import android.view.IWindowSession; +import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.SurfaceControl; import android.view.View; @@ -120,6 +121,7 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase { new DisplayCutout.ParcelableWrapper(DisplayCutout.NO_CUTOUT); final MergedConfiguration mOutMergedConfiguration = new MergedConfiguration(); final InsetsState mOutInsetsState = new InsetsState(); + final InsetsSourceControl[] mOutControls = new InsetsSourceControl[0]; final IWindow mWindow; final View mView; final WindowManager.LayoutParams mParams; @@ -152,7 +154,7 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase { mViewVisibility.getAsInt(), mFlags, mFrameNumber, mOutFrame, mOutContentInsets, mOutVisibleInsets, mOutStableInsets, mOutBackDropFrame, mOutDisplayCutout, mOutMergedConfiguration, - mOutSurfaceControl, mOutInsetsState, mOutSurfaceSize, + mOutSurfaceControl, mOutInsetsState, mOutControls, mOutSurfaceSize, mOutBlastSurfaceControl); } } diff --git a/apct-tests/perftests/core/src/android/wm/WindowAddRemovePerfTest.java b/apct-tests/perftests/core/src/android/wm/WindowAddRemovePerfTest.java index 4ac3adfd19ce..c72cc9d635e0 100644 --- a/apct-tests/perftests/core/src/android/wm/WindowAddRemovePerfTest.java +++ b/apct-tests/perftests/core/src/android/wm/WindowAddRemovePerfTest.java @@ -29,6 +29,7 @@ import android.view.Display; import android.view.DisplayCutout; import android.view.IWindowSession; import android.view.InputChannel; +import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.View; import android.view.WindowManager; @@ -91,6 +92,7 @@ public class WindowAddRemovePerfTest extends WindowManagerPerfTestBase final DisplayCutout.ParcelableWrapper mOutDisplayCutout = new DisplayCutout.ParcelableWrapper(); final InsetsState mOutInsetsState = new InsetsState(); + final InsetsSourceControl[] mOutControls = new InsetsSourceControl[0]; TestWindow() { mLayoutParams.setTitle(TestWindow.class.getName()); @@ -109,7 +111,7 @@ public class WindowAddRemovePerfTest extends WindowManagerPerfTestBase long startTime = SystemClock.elapsedRealtimeNanos(); session.addToDisplay(this, mSeq, mLayoutParams, View.VISIBLE, Display.DEFAULT_DISPLAY, mOutFrame, mOutContentInsets, mOutStableInsets, - mOutDisplayCutout, inputChannel, mOutInsetsState); + mOutDisplayCutout, inputChannel, mOutInsetsState, mOutControls); final long elapsedTimeNsOfAdd = SystemClock.elapsedRealtimeNanos() - startTime; state.addExtraResult("add", elapsedTimeNsOfAdd); diff --git a/apex/statsd/Android.bp b/apex/statsd/Android.bp index 32e13e31eebe..15d74951019d 100644 --- a/apex/statsd/Android.bp +++ b/apex/statsd/Android.bp @@ -67,7 +67,6 @@ cc_library_shared { "liblog", // Has a stable abi - should not be copied into apex. "libstatssocket", ], - //TODO: is libc++_static correct? stl: "libc++_static", cflags: [ "-Wall", diff --git a/apex/statsd/tests/libstatspull/Android.bp b/apex/statsd/tests/libstatspull/Android.bp index 2d64f190839c..0df96e149d4f 100644 --- a/apex/statsd/tests/libstatspull/Android.bp +++ b/apex/statsd/tests/libstatspull/Android.bp @@ -32,7 +32,7 @@ android_test { "protos/**/*.proto", ], test_suites: [ - "general-tests", + "device-tests", ], platform_apis: true, privileged: true, diff --git a/api/test-current.txt b/api/test-current.txt index 629ce4e6470e..c29f1c4c657d 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -4814,7 +4814,14 @@ package android.view { public final class Display { method @NonNull public android.graphics.ColorSpace[] getSupportedWideColorGamut(); + method public int getType(); method public boolean hasAccess(int); + field public static final int TYPE_EXTERNAL = 2; // 0x2 + field public static final int TYPE_INTERNAL = 1; // 0x1 + field public static final int TYPE_OVERLAY = 4; // 0x4 + field public static final int TYPE_UNKNOWN = 0; // 0x0 + field public static final int TYPE_VIRTUAL = 5; // 0x5 + field public static final int TYPE_WIFI = 3; // 0x3 } public class FocusFinder { @@ -4852,6 +4859,13 @@ package android.view { method public abstract String asyncImpl() default ""; } + public final class SurfaceControl implements android.os.Parcelable { + ctor public SurfaceControl(@NonNull android.view.SurfaceControl); + method public static long acquireFrameRateFlexibilityToken(); + method public boolean isSameSurface(@NonNull android.view.SurfaceControl); + method public static void releaseFrameRateFlexibilityToken(long); + } + public class SurfaceControlViewHost { method public void relayout(android.view.WindowManager.LayoutParams); method public void setView(@NonNull android.view.View, @NonNull android.view.WindowManager.LayoutParams); diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp index d3d7e1d483e8..65061d0c9bda 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -113,17 +113,18 @@ cc_defaults { static_libs: [ "libbase", "libcutils", + "libgtest_prod", "libprotoutil", "libstatsmetadata", "libstatslog_statsd", "libsysutils", "libutils", + "statsd-aidl-ndk_platform", ], shared_libs: [ "libbinder_ndk", "libincident", "liblog", - "statsd-aidl-ndk_platform", ], } @@ -268,10 +269,11 @@ cc_binary { proto: { type: "lite", + static: true, }, + stl: "libc++_static", shared_libs: [ - "libgtest_prod", "libstatssocket", ], diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java index 2a7cfd306174..d5da0b42402c 100644 --- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java +++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java @@ -68,6 +68,7 @@ public class TestDrive { }; private static final String[] DEFAULT_PULL_SOURCES = { "AID_SYSTEM", + "AID_RADIO" }; private static final Logger LOGGER = Logger.getLogger(TestDrive.class.getName()); diff --git a/core/java/android/annotation/NonNull.java b/core/java/android/annotation/NonNull.java index a95bf3b8061e..c5aff9d6794e 100644 --- a/core/java/android/annotation/NonNull.java +++ b/core/java/android/annotation/NonNull.java @@ -30,8 +30,8 @@ import java.lang.annotation.Target; * <p> * This is a marker annotation and it has no specific attributes. * - * @paramDoc This value must never be {@code null}. - * @returnDoc This value will never be {@code null}. + * @paramDoc This value cannot be {@code null}. + * @returnDoc This value cannot be {@code null}. * @hide */ @Retention(SOURCE) diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index e476993f003f..b7ceb6ae1b4c 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -299,7 +299,6 @@ interface IActivityTaskManager { in int[] verticalSizeConfigurations, in int[] smallestWidthConfigurations); void suppressResizeConfigChanges(boolean suppress); - void moveTasksToFullscreenStack(int fromStackId, boolean onTop); boolean moveTopActivityToPinnedStack(int stackId, in Rect bounds); boolean isInMultiWindowMode(in IBinder token); boolean isInPictureInPictureMode(in IBinder token); diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index f216db6fc717..fc48e7f18f5f 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -979,17 +979,14 @@ public final class BluetoothAdapter { 8, BLUETOOTH_GET_STATE_CACHE_PROPERTY) { @Override protected Integer recompute(Void query) { + // This function must be called while holding the + // mServiceLock, and with mService not null. The public + // getState() method makes this guarantee. try { - mServiceLock.readLock().lock(); - if (mService != null) { - return mService.getState(); - } + return mService.getState(); } catch (RemoteException e) { - Log.e(TAG, "", e); - } finally { - mServiceLock.readLock().unlock(); + throw e.rethrowFromSystemServer(); } - return BluetoothAdapter.STATE_OFF; } }; @@ -1016,7 +1013,24 @@ public final class BluetoothAdapter { @RequiresPermission(Manifest.permission.BLUETOOTH) @AdapterState public int getState() { - int state = mBluetoothGetStateCache.query(null); + int state = BluetoothAdapter.STATE_OFF; + + try { + mServiceLock.readLock().lock(); + // The test for mService must either be outside the cache, or + // the cache must be invalidated when mService changes. + if (mService != null) { + state = mBluetoothGetStateCache.query(null); + } + } catch (RuntimeException e) { + if (e.getCause() instanceof RemoteException) { + Log.e(TAG, "", e.getCause()); + } else { + throw e; + } + } finally { + mServiceLock.readLock().unlock(); + } // Consider all internal states as OFF if (state == BluetoothAdapter.STATE_BLE_ON || state == BluetoothAdapter.STATE_BLE_TURNING_ON diff --git a/core/java/android/content/pm/FileSystemControlParcel.aidl b/core/java/android/content/pm/FileSystemControlParcel.aidl index f00feaeb2f5a..92df16ced8a3 100644 --- a/core/java/android/content/pm/FileSystemControlParcel.aidl +++ b/core/java/android/content/pm/FileSystemControlParcel.aidl @@ -17,6 +17,7 @@ package android.content.pm; import android.content.pm.IPackageInstallerSessionFileSystemConnector; +import android.os.incremental.IIncrementalServiceConnector; import android.os.incremental.IncrementalFileSystemControlParcel; /** @@ -26,6 +27,8 @@ import android.os.incremental.IncrementalFileSystemControlParcel; parcelable FileSystemControlParcel { // Incremental FS control descriptors. @nullable IncrementalFileSystemControlParcel incremental; + // Incremental FS service. + @nullable IIncrementalServiceConnector service; // Callback-based installation connector. @nullable IPackageInstallerSessionFileSystemConnector callback; } diff --git a/core/java/android/os/incremental/IIncrementalService.aidl b/core/java/android/os/incremental/IIncrementalService.aidl index d8308c7c3362..2dbaea860e2a 100644 --- a/core/java/android/os/incremental/IIncrementalService.aidl +++ b/core/java/android/os/incremental/IIncrementalService.aidl @@ -38,13 +38,6 @@ interface IIncrementalService { int createLinkedStorage(in @utf8InCpp String path, int otherStorageId, int createMode); /** - * Changes storage params. Returns 0 on success, and -errno on failure. - * Use enableReadLogs to switch pages read logs reporting on and off. - * Returns 0 on success, and - errno on failure: permission check or remount. - */ - int setStorageParams(int storageId, boolean enableReadLogs); - - /** * Bind-mounts a path under a storage to a full path. Can be permanent or temporary. */ const int BIND_TEMPORARY = 0; diff --git a/core/java/android/os/incremental/IIncrementalServiceConnector.aidl b/core/java/android/os/incremental/IIncrementalServiceConnector.aidl new file mode 100644 index 000000000000..5800ecf63a1e --- /dev/null +++ b/core/java/android/os/incremental/IIncrementalServiceConnector.aidl @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os.incremental; + +/** @hide */ +interface IIncrementalServiceConnector { + /** + * Changes storage params. Returns 0 on success, and -errno on failure. + * Use enableReadLogs to switch pages read logs reporting on and off. + * Returns 0 on success, and - errno on failure: permission check or remount. + */ + int setStorageParams(boolean enableReadLogs); +} diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java index 5f01408944e8..35518db32829 100644 --- a/core/java/android/os/incremental/IncrementalManager.java +++ b/core/java/android/os/incremental/IncrementalManager.java @@ -19,13 +19,11 @@ package android.os.incremental; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.content.Context; import android.content.pm.DataLoaderParams; import android.content.pm.IDataLoaderStatusListener; import android.os.RemoteException; -import android.system.ErrnoException; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; @@ -321,23 +319,6 @@ public final class IncrementalManager { return nativeUnsafeGetFileSignature(path); } - /** - * Sets storage parameters. - * - * @param enableReadLogs - enables or disables read logs. Caller has to have a permission. - */ - @RequiresPermission(android.Manifest.permission.LOADER_USAGE_STATS) - public void setStorageParams(int storageId, boolean enableReadLogs) throws ErrnoException { - try { - int res = mService.setStorageParams(storageId, enableReadLogs); - if (res < 0) { - throw new ErrnoException("setStorageParams", -res); - } - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } - } - /* Native methods */ private static native boolean nativeIsEnabled(); private static native boolean nativeIsIncrementalPath(@NonNull String path); diff --git a/core/java/android/service/dataloader/DataLoaderService.java b/core/java/android/service/dataloader/DataLoaderService.java index 05877a59368a..c047dc0d07c7 100644 --- a/core/java/android/service/dataloader/DataLoaderService.java +++ b/core/java/android/service/dataloader/DataLoaderService.java @@ -21,7 +21,6 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.app.Service; -import android.content.Context; import android.content.Intent; import android.content.pm.DataLoaderParams; import android.content.pm.DataLoaderParamsParcel; @@ -32,8 +31,6 @@ import android.content.pm.InstallationFile; import android.content.pm.InstallationFileParcel; import android.os.IBinder; import android.os.ParcelFileDescriptor; -import android.os.incremental.IncrementalManager; -import android.system.ErrnoException; import android.util.ExceptionUtils; import android.util.Slog; @@ -211,25 +208,6 @@ public abstract class DataLoaderService extends Service { private final long mNativeInstance; } - /* Used by native FileSystemConnector. */ - private boolean setStorageParams(int storageId, boolean enableReadLogs) { - IncrementalManager incrementalManager = (IncrementalManager) getSystemService( - Context.INCREMENTAL_SERVICE); - if (incrementalManager == null) { - Slog.e(TAG, "Failed to obtain incrementalManager: " + storageId); - return false; - } - try { - // This has to be done directly in incrementalManager as the storage - // might be missing still. - incrementalManager.setStorageParams(storageId, enableReadLogs); - } catch (ErrnoException e) { - Slog.e(TAG, "Failed to set params for storage: " + storageId, e); - return false; - } - return true; - } - /* Native methods */ private native boolean nativeCreateDataLoader(int storageId, @NonNull FileSystemControlParcel control, diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index f531e12b0748..f944dd78dc3d 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -55,6 +55,7 @@ import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEvent; import android.view.InputEventReceiver; +import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.MotionEvent; import android.view.SurfaceControl; @@ -190,6 +191,7 @@ public abstract class WallpaperService extends Service { new DisplayCutout.ParcelableWrapper(); DisplayCutout mDispatchedDisplayCutout = DisplayCutout.NO_CUTOUT; final InsetsState mInsetsState = new InsetsState(); + final InsetsSourceControl[] mTempControls = new InsetsSourceControl[0]; final MergedConfiguration mMergedConfiguration = new MergedConfiguration(); private final Point mSurfaceSize = new Point(); @@ -878,7 +880,7 @@ public abstract class WallpaperService extends Service { if (mSession.addToDisplay(mWindow, mWindow.mSeq, mLayout, View.VISIBLE, mDisplay.getDisplayId(), mWinFrame, mContentInsets, mStableInsets, mDisplayCutout, inputChannel, - mInsetsState) < 0) { + mInsetsState, mTempControls) < 0) { Log.w(TAG, "Failed to add window while updating wallpaper surface."); return; } @@ -903,7 +905,7 @@ public abstract class WallpaperService extends Service { View.VISIBLE, 0, -1, mWinFrame, mContentInsets, mVisibleInsets, mStableInsets, mBackdropFrame, mDisplayCutout, mMergedConfiguration, mSurfaceControl, - mInsetsState, mSurfaceSize, mTmpSurfaceControl); + mInsetsState, mTempControls, mSurfaceSize, mTmpSurfaceControl); if (mSurfaceControl.isValid()) { mSurfaceHolder.mSurface.copyFrom(mSurfaceControl); mSurfaceControl.release(); diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 0ccb1e055c46..4469fdbb12ec 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -253,12 +253,14 @@ public final class Display { * @hide */ @UnsupportedAppUsage + @TestApi public static final int TYPE_UNKNOWN = 0; /** * Display type: Physical display connected through an internal port. * @hide */ + @TestApi public static final int TYPE_INTERNAL = 1; /** @@ -266,6 +268,7 @@ public final class Display { * @hide */ @UnsupportedAppUsage + @TestApi public static final int TYPE_EXTERNAL = 2; /** @@ -273,12 +276,14 @@ public final class Display { * @hide */ @UnsupportedAppUsage + @TestApi public static final int TYPE_WIFI = 3; /** * Display type: Overlay display. * @hide */ + @TestApi public static final int TYPE_OVERLAY = 4; /** @@ -286,6 +291,7 @@ public final class Display { * @hide */ @UnsupportedAppUsage + @TestApi public static final int TYPE_VIRTUAL = 5; /** @@ -569,6 +575,7 @@ public final class Display { * @hide */ @UnsupportedAppUsage + @TestApi public int getType() { return mType; } diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 45e51f756489..81bfcb07ab6d 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -29,6 +29,7 @@ import android.view.IWindow; import android.view.IWindowId; import android.view.MotionEvent; import android.view.WindowManager; +import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.Surface; import android.view.SurfaceControl; @@ -46,7 +47,7 @@ interface IWindowSession { in int viewVisibility, in int layerStackId, out Rect outFrame, out Rect outContentInsets, out Rect outStableInsets, out DisplayCutout.ParcelableWrapper displayCutout, out InputChannel outInputChannel, - out InsetsState insetsState); + out InsetsState insetsState, out InsetsSourceControl[] activeControls); int addToDisplayWithoutInputChannel(IWindow window, int seq, in WindowManager.LayoutParams attrs, in int viewVisibility, in int layerStackId, out Rect outContentInsets, out Rect outStableInsets, out InsetsState insetsState); @@ -106,8 +107,8 @@ interface IWindowSession { out Rect outBackdropFrame, out DisplayCutout.ParcelableWrapper displayCutout, out MergedConfiguration outMergedConfiguration, out SurfaceControl outSurfaceControl, - out InsetsState insetsState, out Point outSurfaceSize, - out SurfaceControl outBlastSurfaceControl); + out InsetsState insetsState, out InsetsSourceControl[] activeControls, + out Point outSurfaceSize, out SurfaceControl outBlastSurfaceControl); /* * Notify the window manager that an application is relaunching and diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index 69bab4df2cae..6cb93746a9a4 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -91,6 +91,8 @@ public class InsetsSourceConsumer { if (mSourceControl == control) { return; } + SurfaceControl oldLeash = mSourceControl != null ? mSourceControl.getLeash() : null; + final InsetsSourceControl lastControl = mSourceControl; mSourceControl = control; @@ -116,6 +118,12 @@ public class InsetsSourceConsumer { // However make sure that the leash visibility is still up to date. if (applyLocalVisibilityOverride()) { mController.notifyVisibilityChanged(); + } + + // If we have a new leash, make sure visibility is up-to-date, even though we + // didn't want to run an animation above. + SurfaceControl newLeash = mSourceControl.getLeash(); + if (oldLeash == null || newLeash == null || !oldLeash.isSameSurface(newLeash)) { applyHiddenToControl(); } } diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java index f3ec65f997ba..e001b668f71a 100644 --- a/core/java/android/view/InsetsSourceControl.java +++ b/core/java/android/view/InsetsSourceControl.java @@ -44,8 +44,7 @@ public class InsetsSourceControl implements Parcelable { public InsetsSourceControl(InsetsSourceControl other) { mType = other.mType; if (other.mLeash != null) { - mLeash = new SurfaceControl(); - mLeash.copyFrom(other.mLeash); + mLeash = new SurfaceControl(other.mLeash); } else { mLeash = null; } diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index a37c1cbc76ad..ab65e3a5c849 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -32,6 +32,7 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; +import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.Bitmap; import android.graphics.ColorSpace; @@ -215,6 +216,10 @@ public final class SurfaceControl implements Parcelable { private static native void nativeSetFrameRate( long transactionObj, long nativeObject, float frameRate, int compatibility); + private static native long nativeGetHandle(long nativeObject); + + private static native long nativeAcquireFrameRateFlexibilityToken(); + private static native void nativeReleaseFrameRateFlexibilityToken(long token); private final CloseGuard mCloseGuard = CloseGuard.get(); private String mName; @@ -222,6 +227,7 @@ public final class SurfaceControl implements Parcelable { * @hide */ public long mNativeObject; + private long mNativeHandle; // TODO: Move this to native. private final Object mSizeLock = new Object(); @@ -424,12 +430,13 @@ public final class SurfaceControl implements Parcelable { mCloseGuard.open("release"); } mNativeObject = nativeObject; + mNativeHandle = mNativeObject != 0 ? nativeGetHandle(nativeObject) : 0; } /** * @hide */ - public void copyFrom(SurfaceControl other) { + public void copyFrom(@NonNull SurfaceControl other) { mName = other.mName; mWidth = other.mWidth; mHeight = other.mHeight; @@ -849,23 +856,19 @@ public final class SurfaceControl implements Parcelable { throw new OutOfResourcesException( "Couldn't allocate SurfaceControl native object"); } - + mNativeHandle = nativeGetHandle(mNativeObject); mCloseGuard.open("release"); } - /** This is a transfer constructor, useful for transferring a live SurfaceControl native - * object to another Java wrapper which could have some different behavior, e.g. - * event logging. + /** + * Copy constructor. Creates a new native object pointing to the same surface as {@code other}. + * + * @param other The object to copy the surface from. * @hide */ - public SurfaceControl(SurfaceControl other) { - mName = other.mName; - mWidth = other.mWidth; - mHeight = other.mHeight; - mNativeObject = other.mNativeObject; - other.mCloseGuard.close(); - other.mNativeObject = 0; - mCloseGuard.open("release"); + @TestApi + public SurfaceControl(@NonNull SurfaceControl other) { + copyFrom(other); } private SurfaceControl(Parcel in) { @@ -917,6 +920,18 @@ public final class SurfaceControl implements Parcelable { } /** + * Checks whether two {@link SurfaceControl} objects represent the same surface. + * + * @param other The other object to check + * @return {@code true} if these two {@link SurfaceControl} objects represent the same surface. + * @hide + */ + @TestApi + public boolean isSameSurface(@NonNull SurfaceControl other) { + return other.mNativeHandle == mNativeHandle; + } + + /** * Write to a protocol buffer output stream. Protocol buffer message definition is at {@link * android.view.SurfaceControlProto}. * @@ -973,6 +988,7 @@ public final class SurfaceControl implements Parcelable { if (mNativeObject != 0) { nativeRelease(mNativeObject); mNativeObject = 0; + mNativeHandle = 0; mCloseGuard.close(); } } @@ -2868,4 +2884,24 @@ public final class SurfaceControl implements Parcelable { } } } + + /** + * Acquire a frame rate flexibility token, which allows surface flinger to freely switch display + * frame rates. This is used by CTS tests to put the device in a consistent state. See + * ISurfaceComposer::acquireFrameRateFlexibilityToken(). + * @hide + */ + @TestApi + public static long acquireFrameRateFlexibilityToken() { + return nativeAcquireFrameRateFlexibilityToken(); + } + + /** + * Release a frame rate flexibility token. + * @hide + */ + @TestApi + public static void releaseFrameRateFlexibilityToken(long token) { + nativeReleaseFrameRateFlexibilityToken(token); + } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 1e96a1c21ac3..750b1eddd222 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -21,6 +21,7 @@ import static android.view.Display.INVALID_DISPLAY; import static android.view.InputDevice.SOURCE_CLASS_NONE; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; +import static android.view.InsetsState.LAST_TYPE; import static android.view.View.PFLAG_DRAW_ANIMATION; import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN; import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; @@ -558,7 +559,8 @@ public final class ViewRootImpl implements ViewParent, final DisplayCutout.ParcelableWrapper mPendingDisplayCutout = new DisplayCutout.ParcelableWrapper(DisplayCutout.NO_CUTOUT); boolean mPendingAlwaysConsumeSystemBars; - private InsetsState mTempInsets = new InsetsState(); + private final InsetsState mTempInsets = new InsetsState(); + private final InsetsSourceControl[] mTempControls = new InsetsSourceControl[LAST_TYPE + 1]; final ViewTreeObserver.InternalInsetsInfo mLastGivenInsets = new ViewTreeObserver.InternalInsetsInfo(); @@ -1003,7 +1005,7 @@ public final class ViewRootImpl implements ViewParent, getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame, mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mDisplayCutout, inputChannel, - mTempInsets); + mTempInsets, mTempControls); setFrame(mTmpFrame); } catch (RemoteException e) { mAdded = false; @@ -1028,6 +1030,7 @@ public final class ViewRootImpl implements ViewParent, (res & WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS) != 0; mPendingAlwaysConsumeSystemBars = mAttachInfo.mAlwaysConsumeSystemBars; mInsetsController.onStateChanged(mTempInsets); + mInsetsController.onControlsChanged(mTempControls); if (DEBUG_LAYOUT) Log.v(mTag, "Added window " + mWindow); if (res < WindowManagerGlobal.ADD_OKAY) { mAttachInfo.mRootView = null; @@ -7356,7 +7359,7 @@ public final class ViewRootImpl implements ViewParent, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber, mTmpFrame, mTmpRect, mTmpRect, mTmpRect, mPendingBackDropFrame, mPendingDisplayCutout, mPendingMergedConfiguration, mSurfaceControl, mTempInsets, - mSurfaceSize, mBlastSurfaceControl); + mTempControls, mSurfaceSize, mBlastSurfaceControl); if (mSurfaceControl.isValid()) { if (!mUseBLASTAdapter) { mSurface.copyFrom(mSurfaceControl); @@ -7386,6 +7389,7 @@ public final class ViewRootImpl implements ViewParent, } setFrame(mTmpFrame); mInsetsController.onStateChanged(mTempInsets); + mInsetsController.onControlsChanged(mTempControls); return relayoutResult; } diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index 144f8e3a7108..39ed4018c65c 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -104,7 +104,7 @@ public class WindowlessWindowManager implements IWindowSession { int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets, Rect outStableInsets, DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, - InsetsState outInsetsState) { + InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) { final SurfaceControl.Builder b = new SurfaceControl.Builder(mSurfaceSession) .setParent(mRootSurface) .setFormat(attrs.format) @@ -179,7 +179,8 @@ public class WindowlessWindowManager implements IWindowSession { Rect outStableInsets, Rect outBackdropFrame, DisplayCutout.ParcelableWrapper cutout, MergedConfiguration mergedConfiguration, SurfaceControl outSurfaceControl, InsetsState outInsetsState, - Point outSurfaceSize, SurfaceControl outBLASTSurfaceControl) { + InsetsSourceControl[] outActiveControls, Point outSurfaceSize, + SurfaceControl outBLASTSurfaceControl) { final State state; synchronized (this) { state = mStateForWindow.get(window.asBinder()); diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java index 71ccb595278b..849488d42bcf 100644 --- a/core/java/android/widget/RadioGroup.java +++ b/core/java/android/widget/RadioGroup.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.TypedArray; +import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.view.View; @@ -484,21 +485,21 @@ public class RadioGroup extends LinearLayout { super.onInitializeAccessibilityNodeInfo(info); if (this.getOrientation() == HORIZONTAL) { info.setCollectionInfo(AccessibilityNodeInfo.CollectionInfo.obtain(1, - getVisibleChildCount(), false, + getVisibleChildWithTextCount(), false, AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_SINGLE)); } else { info.setCollectionInfo( - AccessibilityNodeInfo.CollectionInfo.obtain(getVisibleChildCount(), + AccessibilityNodeInfo.CollectionInfo.obtain(getVisibleChildWithTextCount(), 1, false, AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_SINGLE)); } } - private int getVisibleChildCount() { + private int getVisibleChildWithTextCount() { int count = 0; for (int i = 0; i < getChildCount(); i++) { if (this.getChildAt(i) instanceof RadioButton) { - if (((RadioButton) this.getChildAt(i)).getVisibility() == VISIBLE) { + if (isVisibleWithText((RadioButton) this.getChildAt(i))) { count++; } } @@ -513,15 +514,19 @@ public class RadioGroup extends LinearLayout { int index = 0; for (int i = 0; i < getChildCount(); i++) { if (this.getChildAt(i) instanceof RadioButton) { - RadioButton radioButton = (RadioButton) this.getChildAt(i); - if (radioButton == child) { + RadioButton button = (RadioButton) this.getChildAt(i); + if (button == child) { return index; } - if (radioButton.getVisibility() == VISIBLE) { + if (isVisibleWithText(button)) { index++; } } } return -1; } + + private boolean isVisibleWithText(RadioButton button) { + return button.getVisibility() == VISIBLE && !TextUtils.isEmpty(button.getText()); + } }
\ No newline at end of file diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java index 9fc0da83c504..d43333e507a6 100644 --- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java @@ -55,6 +55,7 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { private static final String TAG = "AbstractMultiProfilePagerAdapter"; static final int PROFILE_PERSONAL = 0; static final int PROFILE_WORK = 1; + @IntDef({PROFILE_PERSONAL, PROFILE_WORK}) @interface Profile {} @@ -365,7 +366,7 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { UserHandle listUserHandle = listAdapter.getUserHandle(); if (!listUserHandle.equals(mWorkProfileUserHandle) || !mInjector.isQuietModeEnabled(mWorkProfileUserHandle) - || !hasResolvedAppsInWorkProfile(listAdapter)) { + || listAdapter.getCount() == 0) { return false; } DevicePolicyEventLogger @@ -382,20 +383,6 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { return true; } - /** - * Returns {@code true} if there is at least one app resolved in the work profile, - * regardless of whether the work profile is enabled or not. - */ - private boolean hasResolvedAppsInWorkProfile(ResolverListAdapter listAdapter) { - List<ResolverActivity.ResolvedComponentInfo> userStateIndependentWorkResolvers = - listAdapter.mResolverListController.getUserStateIndependentResolversAsUser( - listAdapter.getIntents(), mWorkProfileUserHandle); - return userStateIndependentWorkResolvers.stream() - .anyMatch(resolvedComponentInfo -> - resolvedComponentInfo.getResolveInfoAt(0).targetUserId - == UserHandle.USER_CURRENT); - } - private void maybeShowNoAppsAvailableEmptyState(ResolverListAdapter listAdapter) { UserHandle listUserHandle = listAdapter.getUserHandle(); if (mWorkProfileUserHandle != null @@ -529,6 +516,13 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { return false; } + boolean shouldShowEmptyStateScreen(ResolverListAdapter listAdapter) { + int count = listAdapter.getUnfilteredCount(); + return (count == 0 && listAdapter.getPlaceholderCount() == 0) + || (listAdapter.getUserHandle().equals(mWorkProfileUserHandle) + && isQuietModeEnabled(mWorkProfileUserHandle)); + } + protected class ProfileDescriptor { final ViewGroup rootView; private final ViewGroup mEmptyStateView; diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 35253b68aac7..fc3f20f7a556 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -994,7 +994,7 @@ public class ResolverActivity extends Activity implements if (isAutolaunching() || maybeAutolaunchActivity()) { return; } - if (isResolverListEmpty(listAdapter)) { + if (mMultiProfilePagerAdapter.shouldShowEmptyStateScreen(listAdapter)) { mMultiProfilePagerAdapter.showEmptyResolverListEmptyState(listAdapter); } else { mMultiProfilePagerAdapter.showListView(listAdapter); @@ -1641,16 +1641,11 @@ public class ResolverActivity extends Activity implements private void setupViewVisibilities() { ResolverListAdapter activeListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter(); - if (!isResolverListEmpty(activeListAdapter)) { + if (!mMultiProfilePagerAdapter.shouldShowEmptyStateScreen(activeListAdapter)) { addUseDifferentAppLabelIfNecessary(activeListAdapter); } } - private boolean isResolverListEmpty(ResolverListAdapter listAdapter) { - int count = listAdapter.getUnfilteredCount(); - return count == 0 && listAdapter.getPlaceholderCount() == 0; - } - /** * Add a label to signify that the user can pick a different app. * @param adapter The adapter used to provide data to item views. diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java index 3f897a5f26bf..033ac72dda4e 100644 --- a/core/java/com/android/internal/app/ResolverListController.java +++ b/core/java/com/android/internal/app/ResolverListController.java @@ -121,23 +121,13 @@ public class ResolverListController { List<Intent> intents, UserHandle userHandle) { int baseFlags = PackageManager.MATCH_DEFAULT_ONLY + | PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE | (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0) | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0); return getResolversForIntentAsUserInternal(intents, userHandle, baseFlags); } - /** - * Returns a list of resolved intents which is user state-independent. This means it will - * return the same results regardless of whether the {@code userHandle} user is disabled or not. - */ - public List<ResolverActivity.ResolvedComponentInfo> getUserStateIndependentResolversAsUser( - List<Intent> intents, - UserHandle userHandle) { - int baseFlags = PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; - return getResolversForIntentAsUserInternal(intents, userHandle, baseFlags); - } - private List<ResolverActivity.ResolvedComponentInfo> getResolversForIntentAsUserInternal( List<Intent> intents, UserHandle userHandle, diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java index 5a979ac97c54..26684201019c 100644 --- a/core/java/com/android/internal/widget/ConversationLayout.java +++ b/core/java/com/android/internal/widget/ConversationLayout.java @@ -40,7 +40,6 @@ import android.os.Bundle; import android.os.Parcelable; import android.text.TextUtils; import android.util.ArrayMap; -import android.util.ArraySet; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.view.Gravity; @@ -387,14 +386,17 @@ public class ConversationLayout extends FrameLayout /** @hide */ public void setUnreadCount(int unreadCount) { - mUnreadBadge.setVisibility(mIsCollapsed && unreadCount > 1 ? VISIBLE : GONE); - CharSequence text = unreadCount >= 100 - ? getResources().getString(R.string.unread_convo_overflow, 99) - : String.format(Locale.getDefault(), "%d", unreadCount); - mUnreadBadge.setText(text); - mUnreadBadge.setBackgroundTintList(ColorStateList.valueOf(mLayoutColor)); - boolean needDarkText = ColorUtils.calculateLuminance(mLayoutColor) > 0.5f; - mUnreadBadge.setTextColor(needDarkText ? Color.BLACK : Color.WHITE); + boolean visible = mIsCollapsed && unreadCount > 1; + mUnreadBadge.setVisibility(visible ? VISIBLE : GONE); + if (visible) { + CharSequence text = unreadCount >= 100 + ? getResources().getString(R.string.unread_convo_overflow, 99) + : String.format(Locale.getDefault(), "%d", unreadCount); + mUnreadBadge.setText(text); + mUnreadBadge.setBackgroundTintList(ColorStateList.valueOf(mLayoutColor)); + boolean needDarkText = ColorUtils.calculateLuminance(mLayoutColor) > 0.5f; + mUnreadBadge.setTextColor(needDarkText ? Color.BLACK : Color.WHITE); + } } private void addRemoteInputHistoryToMessages( @@ -537,37 +539,26 @@ public class ConversationLayout extends FrameLayout } private void updateImageMessages() { - boolean displayExternalImage = false; - ArraySet<View> newMessages = new ArraySet<>(); - if (mIsCollapsed) { - - // When collapsed, we're displaying all image messages in a dedicated container - // on the right of the layout instead of inline. Let's add all isolated images there - int imageIndex = 0; - for (int i = 0; i < mGroups.size(); i++) { - MessagingGroup messagingGroup = mGroups.get(i); - MessagingImageMessage isolatedMessage = messagingGroup.getIsolatedMessage(); - if (isolatedMessage != null) { - newMessages.add(isolatedMessage.getView()); - displayExternalImage = true; - if (imageIndex - != mImageMessageContainer.indexOfChild(isolatedMessage.getView())) { - mImageMessageContainer.removeView(isolatedMessage.getView()); - mImageMessageContainer.addView(isolatedMessage.getView(), imageIndex); - } - imageIndex++; - } + View newMessage = null; + if (mIsCollapsed && mGroups.size() > 0) { + + // When collapsed, we're displaying the image message in a dedicated container + // on the right of the layout instead of inline. Let's add the isolated image there + MessagingGroup messagingGroup = mGroups.get(mGroups.size() -1); + MessagingImageMessage isolatedMessage = messagingGroup.getIsolatedMessage(); + if (isolatedMessage != null) { + newMessage = isolatedMessage.getView(); } } // Remove all messages that don't belong into the image layout - for (int i = 0; i < mImageMessageContainer.getChildCount(); i++) { - View child = mImageMessageContainer.getChildAt(i); - if (!newMessages.contains(child)) { - mImageMessageContainer.removeView(child); - i--; + View previousMessage = mImageMessageContainer.getChildAt(0); + if (previousMessage != newMessage) { + mImageMessageContainer.removeView(previousMessage); + if (newMessage != null) { + mImageMessageContainer.addView(newMessage); } } - mImageMessageContainer.setVisibility(displayExternalImage ? VISIBLE : GONE); + mImageMessageContainer.setVisibility(newMessage != null ? VISIBLE : GONE); } private void bindFacePile() { diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index b2ca0a7bcbe3..1cfa12df32ab 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -29,11 +29,13 @@ #include <android_runtime/AndroidRuntime.h> #include <android_runtime/android_view_Surface.h> #include <android_runtime/android_view_SurfaceSession.h> +#include <gui/ISurfaceComposer.h> #include <gui/Surface.h> #include <gui/SurfaceComposerClient.h> #include <jni.h> #include <nativehelper/JNIHelp.h> #include <nativehelper/ScopedUtfChars.h> +#include <private/gui/ComposerService.h> #include <stdio.h> #include <system/graphics.h> #include <ui/ConfigStoreTypes.h> @@ -624,6 +626,23 @@ static void nativeSetFrameRate(JNIEnv* env, jclass clazz, jlong transactionObj, transaction->setFrameRate(ctrl, frameRate, static_cast<int8_t>(compatibility)); } +static jlong nativeAcquireFrameRateFlexibilityToken(JNIEnv* env, jclass clazz) { + sp<ISurfaceComposer> composer = ComposerService::getComposerService(); + sp<IBinder> token; + status_t result = composer->acquireFrameRateFlexibilityToken(&token); + if (result < 0) { + ALOGE("Failed acquiring frame rate flexibility token: %s (%d)", strerror(-result), result); + return 0; + } + token->incStrong((void*)nativeAcquireFrameRateFlexibilityToken); + return reinterpret_cast<jlong>(token.get()); +} + +static void nativeReleaseFrameRateFlexibilityToken(JNIEnv* env, jclass clazz, jlong tokenLong) { + sp<IBinder> token(reinterpret_cast<IBinder*>(tokenLong)); + token->decStrong((void*)nativeAcquireFrameRateFlexibilityToken); +} + static jlongArray nativeGetPhysicalDisplayIds(JNIEnv* env, jclass clazz) { const auto displayIds = SurfaceComposerClient::getPhysicalDisplayIds(); jlongArray array = env->NewLongArray(displayIds.size()); @@ -1411,6 +1430,12 @@ static void nativeSetGlobalShadowSettings(JNIEnv* env, jclass clazz, jfloatArray client->setGlobalShadowSettings(ambientColor, spotColor, lightPosY, lightPosZ, lightRadius); } + +static jlong nativeGetHandle(JNIEnv* env, jclass clazz, jlong nativeObject) { + SurfaceControl *surfaceControl = reinterpret_cast<SurfaceControl*>(nativeObject); + return reinterpret_cast<jlong>(surfaceControl->getHandle().get()); +} + // ---------------------------------------------------------------------------- static const JNINativeMethod sSurfaceControlMethods[] = { @@ -1474,6 +1499,10 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeSetShadowRadius }, {"nativeSetFrameRate", "(JJFI)V", (void*)nativeSetFrameRate }, + {"nativeAcquireFrameRateFlexibilityToken", "()J", + (void*)nativeAcquireFrameRateFlexibilityToken }, + {"nativeReleaseFrameRateFlexibilityToken", "(J)V", + (void*)nativeReleaseFrameRateFlexibilityToken }, {"nativeGetPhysicalDisplayIds", "()[J", (void*)nativeGetPhysicalDisplayIds }, {"nativeGetPhysicalDisplayToken", "(J)Landroid/os/IBinder;", @@ -1583,6 +1612,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeMirrorSurface }, {"nativeSetGlobalShadowSettings", "([F[FFFF)V", (void*)nativeSetGlobalShadowSettings }, + {"nativeGetHandle", "(J)J", + (void*)nativeGetHandle }, }; int register_android_view_SurfaceControl(JNIEnv* env) diff --git a/core/proto/android/stats/dnsresolver/dns_resolver.proto b/core/proto/android/stats/dnsresolver/dns_resolver.proto index 76f8f0febf59..61b9b25fe7cf 100644 --- a/core/proto/android/stats/dnsresolver/dns_resolver.proto +++ b/core/proto/android/stats/dnsresolver/dns_resolver.proto @@ -62,6 +62,13 @@ enum NsRcode { NS_R_NOTAUTH = 9; // Not authoritative for zone NS_R_NOTZONE = 10; // Zone of record different from zone section NS_R_MAX = 11; + // Define rcode=12~15(UNASSIGNED) in rcode enum type. + // Some DNS Servers might return undefined code to devices. + // Without the enum definition, that would be noise for our dashboard. + NS_R_UNASSIGNED12 = 12; // Unassigned + NS_R_UNASSIGNED13 = 13; // Unassigned + NS_R_UNASSIGNED14 = 14; // Unassigned + NS_R_UNASSIGNED15 = 15; // Unassigned // The following are EDNS extended rcodes NS_R_BADVERS = 16; // The following are TSIG errors @@ -170,12 +177,22 @@ enum NetworkType { NT_BLUETOOTH = 3; // Indicates this network uses an Ethernet transport. NT_ETHERNET = 4; - // Indicates this network uses a VPN transport. - NT_VPN = 5; + // Indicates this network uses a VPN transport, now deprecated. + NT_VPN = 5 [deprecated=true]; // Indicates this network uses a Wi-Fi Aware transport. NT_WIFI_AWARE = 6; // Indicates this network uses a LoWPAN transport. NT_LOWPAN = 7; + // Indicates this network uses a Cellular+VPN transport. + NT_CELLULAR_VPN = 8; + // Indicates this network uses a Wi-Fi+VPN transport. + NT_WIFI_VPN = 9; + // Indicates this network uses a Bluetooth+VPN transport. + NT_BLUETOOTH_VPN = 10; + // Indicates this network uses an Ethernet+VPN transport. + NT_ETHERNET_VPN = 11; + // Indicates this network uses a Wi-Fi+Cellular+VPN transport. + NT_WIFI_CELLULAR_VPN = 12; } enum CacheStatus{ diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index cbb379bf8207..d432dda4a1be 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -696,8 +696,7 @@ public class InsetsControllerTest { // Simulate binder behavior by copying SurfaceControl. Otherwise, InsetsController will // attempt to release mLeash directly. - SurfaceControl copy = new SurfaceControl(); - copy.copyFrom(mLeash); + SurfaceControl copy = new SurfaceControl(mLeash); return new InsetsSourceControl(type, copy, new Point()); } diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java index bc0cdc1e029b..583c75102d52 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java @@ -1339,15 +1339,8 @@ public class ChooserActivityTest { createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(workProfileTargets); - when(sOverrides.workResolverListController.getUserStateIndependentResolversAsUser( - Mockito.isA(List.class), - Mockito.isA(UserHandle.class))) - .thenReturn(new ArrayList<>(workResolvedComponentInfos)); sOverrides.isQuietModeEnabled = true; - // When work profile is disabled, we get 0 results when we query the work profile - // intents. - setupResolverControllers(personalResolvedComponentInfos, - /* workResolvedComponentInfos */ new ArrayList<>()); + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); Intent sendIntent = createSendTextIntent(); sendIntent.setType("TestType"); diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java index 0bf8663c7a85..eb39d58019d9 100644 --- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java @@ -614,15 +614,8 @@ public class ResolverActivityTest { createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(workProfileTargets); - when(sOverrides.workResolverListController.getUserStateIndependentResolversAsUser( - Mockito.isA(List.class), - Mockito.isA(UserHandle.class))) - .thenReturn(new ArrayList<>(workResolvedComponentInfos)); sOverrides.isQuietModeEnabled = true; - // When work profile is disabled, we get 0 results when we query the work profile - // intents. - setupResolverControllers(personalResolvedComponentInfos, - /* workResolvedComponentInfos */ new ArrayList<>()); + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); Intent sendIntent = createSendImageIntent(); sendIntent.setType("TestType"); diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index 41aa1ff80e3c..5088494d6a07 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -418,9 +418,9 @@ void SkiaPipeline::endCapture(SkSurface* surface) { auto data = picture->serialize(); savePictureAsync(data, mCapturedFile); mCaptureSequence = 0; + mCaptureMode = CaptureMode::None; } } - mCaptureMode = CaptureMode::None; mRecorder.reset(); } } diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp index 90bcd1c0e370..1208062d9da0 100644 --- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp +++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp @@ -403,3 +403,40 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, context_lost) { renderThread.destroyRenderingContext(); EXPECT_FALSE(pipeline->isSurfaceReady()); } + +RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, pictureCallback) { + // create a pipeline and add a picture callback + auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); + int callbackCount = 0; + pipeline->setPictureCapturedCallback( + [&callbackCount](sk_sp<SkPicture>&& picture) { callbackCount += 1; }); + + // create basic red frame and render it + auto redNode = TestUtils::createSkiaNode( + 0, 0, 1, 1, [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) { + redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver); + }); + LayerUpdateQueue layerUpdateQueue; + SkRect dirty = SkRectMakeLargest(); + std::vector<sp<RenderNode>> renderNodes; + renderNodes.push_back(redNode); + bool opaque = true; + android::uirenderer::Rect contentDrawBounds(0, 0, 1, 1); + auto surface = SkSurface::MakeRasterN32Premul(1, 1); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface, + SkMatrix::I()); + + // verify the callback was called + EXPECT_EQ(1, callbackCount); + + // render a second frame and check the callback count + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface, + SkMatrix::I()); + EXPECT_EQ(2, callbackCount); + + // unset the callback, render another frame, check callback was not invoked + pipeline->setPictureCapturedCallback(nullptr); + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface, + SkMatrix::I()); + EXPECT_EQ(2, callbackCount); +} diff --git a/media/java/android/media/MediaMetrics.java b/media/java/android/media/MediaMetrics.java index 88a829546989..540955f3b393 100644 --- a/media/java/android/media/MediaMetrics.java +++ b/media/java/android/media/MediaMetrics.java @@ -17,6 +17,7 @@ package android.media; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.TestApi; import android.os.Bundle; @@ -24,6 +25,7 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.util.Objects; /** * MediaMetrics is the Java interface to the MediaMetrics service. @@ -50,6 +52,77 @@ public class MediaMetrics { private static final Charset MEDIAMETRICS_CHARSET = StandardCharsets.UTF_8; /** + * Key interface. + * + * The presence of this {@code Key} interface on an object allows + * it to be used to set metrics. + * + * @param <T> type of value associated with {@code Key}. + */ + public interface Key<T> { + /** + * Returns the internal name of the key. + */ + @NonNull + String getName(); + + /** + * Returns the class type of the associated value. + */ + @NonNull + Class<T> getValueClass(); + } + + /** + * Returns a Key object with the correct interface for MediaMetrics. + * + * @param name The name of the key. + * @param type The class type of the value represented by the key. + * @param <T> The type of value. + * @return a new key interface. + */ + @NonNull + public static <T> Key<T> createKey(@NonNull String name, @NonNull Class<T> type) { + // Implementation specific. + return new Key<T>() { + private final String mName = name; + private final Class<T> mType = type; + + @Override + @NonNull + public String getName() { + return mName; + } + + @Override + @NonNull + public Class<T> getValueClass() { + return mType; + } + + /** + * Return true if the name and the type of two objects are the same. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Key)) { + return false; + } + Key<?> other = (Key<?>) obj; + return mName.equals(other.getName()) && mType.equals(other.getValueClass()); + } + + @Override + public int hashCode() { + return Objects.hash(mName, mType); + } + }; + } + + /** * Item records properties and delivers to the MediaMetrics service * */ @@ -202,6 +275,28 @@ public class MediaMetrics { } /** + * Sets a metrics typed key + * @param key + * @param value + * @param <T> + * @return + */ + @NonNull + public <T> Item set(@NonNull Key<T> key, @Nullable T value) { + if (value instanceof Integer) { + putInt(key.getName(), (int) value); + } else if (value instanceof Long) { + putLong(key.getName(), (long) value); + } else if (value instanceof Double) { + putDouble(key.getName(), (double) value); + } else if (value instanceof String) { + putString(key.getName(), (String) value); + } + // if value is null, etc. no error is raised. + return this; + } + + /** * Sets the property with key to an integer (32 bit) value. * * @param key diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 07f4a9a799ad..53e7921879d1 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -566,7 +566,7 @@ <!-- [CHAR LIMIT=50] Title for adb wireless pair by QR code preference --> <string name="adb_pair_method_qrcode_title">Pair device with QR code</string> <!-- [CHAR LIMIT=NONE] Summary for adb wireless pair by QR code preference --> - <string name="adb_pair_method_qrcode_summary">Pair new devices using QR code Scanner</string> + <string name="adb_pair_method_qrcode_summary">Pair new devices using QR code scanner</string> <!-- [CHAR LIMIT=50] Title for adb wireless pair by pairing code preference --> <string name="adb_pair_method_code_title">Pair device with pairing code</string> <!-- [CHAR LIMIT=NONE] Summary for adb wireless pair by pairing code preference --> @@ -604,7 +604,7 @@ <!-- [CHAR LIMIT=NONE] Adb Wireless QR code pairing scanner title --> <string name="adb_wireless_qrcode_pairing_title">Scan QR code</string> <!-- [CHAR LIMIT=NONE] Adb Wireless QR code pairing description --> - <string name="adb_wireless_qrcode_pairing_description">Pair device over Wi\u2011Fi by scanning a QR Code</string> + <string name="adb_wireless_qrcode_pairing_description">Pair device over Wi\u2011Fi by scanning a QR code</string> <!-- [CHAR LIMIT=NONE] Toast message when trying to enable Wi-Fi debugging and no Wi-Fi network connected --> <string name="adb_wireless_no_network_msg">Please connect to a Wi\u2011Fi network</string> <!--Adb wireless search Keywords [CHAR LIMIT=NONE]--> diff --git a/packages/SystemUI/res/layout/bubble_dismiss_target.xml b/packages/SystemUI/res/layout/bubble_dismiss_target.xml index ca085b69c35d..f5cd727a6d03 100644 --- a/packages/SystemUI/res/layout/bubble_dismiss_target.xml +++ b/packages/SystemUI/res/layout/bubble_dismiss_target.xml @@ -17,7 +17,7 @@ <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" - android:layout_height="@dimen/pip_dismiss_gradient_height" + android:layout_height="@dimen/floating_dismiss_gradient_height" android:layout_gravity="bottom|center_horizontal"> <FrameLayout diff --git a/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml b/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml index 50aa212c94a6..cb53fe619b24 100644 --- a/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml +++ b/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml @@ -27,7 +27,7 @@ android:paddingRight="@dimen/global_actions_grid_item_side_margin" android:layout_marginRight="3dp" android:layout_marginLeft="3dp" - android:background="@drawable/rounded_bg_full"> + android:background="@drawable/control_background"> <LinearLayout android:layout_width="@dimen/global_actions_grid_item_width" android:layout_height="@dimen/global_actions_grid_item_height" @@ -42,7 +42,7 @@ android:layout_marginLeft="@dimen/global_actions_grid_item_icon_side_margin" android:layout_marginRight="@dimen/global_actions_grid_item_icon_side_margin" android:scaleType="centerInside" - android:tint="@color/global_actions_text" /> + android:tint="@color/control_default_foreground" /> <TextView android:id="@*android:id/message" @@ -53,7 +53,7 @@ android:singleLine="true" android:gravity="center" android:textSize="12dp" - android:textColor="@color/global_actions_text" + android:textColor="@color/control_default_foreground" android:textAppearance="?android:attr/textAppearanceSmall" /> <TextView @@ -62,7 +62,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" - android:textColor="@color/global_actions_text" + android:textColor="@color/control_default_foreground" android:textAppearance="?android:attr/textAppearanceSmall" /> </LinearLayout> </LinearLayout> diff --git a/packages/SystemUI/res/layout/pip_dismiss_view.xml b/packages/SystemUI/res/layout/pip_dismiss_view.xml deleted file mode 100644 index 2cc4b220fe2b..000000000000 --- a/packages/SystemUI/res/layout/pip_dismiss_view.xml +++ /dev/null @@ -1,35 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2016 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. ---> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="@dimen/pip_dismiss_gradient_height" - android:alpha="0"> - - <TextView - android:id="@+id/pip_dismiss_text" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="bottom|center_horizontal" - android:text="@string/pip_phone_dismiss_hint" - android:textColor="#FFFFFFFF" - android:textSize="14sp" - android:shadowColor="@android:color/black" - android:shadowDx="-2" - android:shadowDy="2" - android:shadowRadius="0.01" /> - -</FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 9b9fbed0d904..bce5fac76cfc 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -955,7 +955,7 @@ <dimen name="recents_quick_scrub_onboarding_margin_start">8dp</dimen> <!-- The height of the gradient indicating the dismiss edge when moving a PIP. --> - <dimen name="pip_dismiss_gradient_height">176dp</dimen> + <dimen name="floating_dismiss_gradient_height">176dp</dimen> <!-- The bottom margin of the PIP drag to dismiss info text shown when moving a PIP. --> <dimen name="pip_dismiss_text_bottom_margin">24dp</dimen> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java index caee8ccb6970..88f4176f5eac 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java @@ -34,6 +34,7 @@ import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternChecker; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockscreenCredential; +import com.android.systemui.Dependency; import com.android.systemui.R; /** @@ -50,6 +51,7 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout private boolean mDismissing; protected boolean mResumed; private CountDownTimer mCountdownTimer = null; + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; // To avoid accidental lockout due to events while the device in in the pocket, ignore // any passwords with length less than or equal to this length. @@ -61,6 +63,7 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout public KeyguardAbsKeyInputView(Context context, AttributeSet attrs) { super(context, attrs); + mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class); } @Override @@ -151,6 +154,8 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL); LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED); } + + mKeyguardUpdateMonitor.setCredentialAttempted(); mPendingLockCheck = LockPatternChecker.checkCredential( mLockPatternUtils, password, diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java index d1544346a25a..af5196f92bcb 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java @@ -24,6 +24,8 @@ import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.media.MediaMetadata; +import android.media.session.MediaController; +import android.media.session.MediaSession; import android.util.Log; import android.view.View; import android.widget.ImageButton; @@ -40,6 +42,7 @@ import androidx.palette.graphics.Palette; import com.android.internal.util.ContrastColorUtil; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.media.MediaControllerFactory; import com.android.systemui.statusbar.notification.MediaNotificationProcessor; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.stack.MediaHeaderView; @@ -71,10 +74,11 @@ public class KeyguardMediaPlayer { private KeyguardMediaObserver mObserver; @Inject - public KeyguardMediaPlayer(Context context, @Background Executor backgroundExecutor) { + public KeyguardMediaPlayer(Context context, MediaControllerFactory factory, + @Background Executor backgroundExecutor) { mContext = context; mBackgroundExecutor = backgroundExecutor; - mViewModel = new KeyguardMediaViewModel(context); + mViewModel = new KeyguardMediaViewModel(context, factory); } /** Binds media controls to a view hierarchy. */ @@ -139,14 +143,16 @@ public class KeyguardMediaPlayer { private static final class KeyguardMediaViewModel { private final Context mContext; + private final MediaControllerFactory mMediaControllerFactory; private final MutableLiveData<KeyguardMedia> mMedia = new MutableLiveData<>(); private final Object mActionsLock = new Object(); private List<PendingIntent> mActions; private float mAlbumArtRadius; private int mAlbumArtSize; - KeyguardMediaViewModel(Context context) { + KeyguardMediaViewModel(Context context, MediaControllerFactory factory) { mContext = context; + mMediaControllerFactory = factory; loadDimens(); } @@ -162,6 +168,17 @@ public class KeyguardMediaPlayer { public void updateControls(NotificationEntry entry, Icon appIcon, MediaMetadata mediaMetadata) { + // Check the playback state of the media controller. If it is null, then the session was + // probably destroyed. Don't update in this case. + final MediaSession.Token token = entry.getSbn().getNotification().extras + .getParcelable(Notification.EXTRA_MEDIA_SESSION); + final MediaController controller = token != null + ? mMediaControllerFactory.create(token) : null; + if (controller != null && controller.getPlaybackState() == null) { + clearControls(); + return; + } + // Foreground and Background colors computed from album art Notification notif = entry.getSbn().getNotification(); int fgColor = notif.color; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java index 48c6bd114d4a..ad92f8f623e4 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java @@ -282,6 +282,7 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit @Override public void onPatternDetected(final List<LockPatternView.Cell> pattern) { + mKeyguardUpdateMonitor.setCredentialAttempted(); mLockPatternView.disableInput(); if (mPendingLockCheck != null) { mPendingLockCheck.cancel(false); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 57e3f14d7aed..88d6943b4071 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -223,6 +223,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private int mRingMode; private int mPhoneState; private boolean mKeyguardIsVisible; + private boolean mCredentialAttempted; private boolean mKeyguardGoingAway; private boolean mGoingToSleep; private boolean mBouncer; @@ -498,11 +499,21 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } /** + * Updates KeyguardUpdateMonitor's internal state to know if credential was attempted on + * bouncer. Note that this does not care if the credential was correct/incorrect. This is + * cleared when the user leaves the bouncer (unlocked, screen off, back to lockscreen, etc) + */ + public void setCredentialAttempted() { + mCredentialAttempted = true; + updateBiometricListeningState(); + } + + /** * Updates KeyguardUpdateMonitor's internal state to know if keyguard is goingAway */ public void setKeyguardGoingAway(boolean goingAway) { mKeyguardGoingAway = goingAway; - updateFingerprintListeningState(); + updateBiometricListeningState(); } /** @@ -664,6 +675,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab updateFingerprintListeningState(); } else { setFingerprintRunningState(BIOMETRIC_STATE_STOPPED); + mFingerprintCancelSignal = null; + mFaceCancelSignal = null; } if (msgId == FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE) { @@ -679,6 +692,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab getCurrentUser()); } + if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT + || msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) { + mFingerprintLockedOut = true; + } + for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { @@ -688,6 +706,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } private void handleFingerprintLockoutReset() { + mFingerprintLockedOut = false; updateFingerprintListeningState(); } @@ -1274,6 +1293,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private CancellationSignal mFaceCancelSignal; private FingerprintManager mFpm; private FaceManager mFaceManager; + private boolean mFingerprintLockedOut; /** * When we receive a @@ -1820,13 +1840,17 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } private boolean shouldListenForFingerprint() { + final boolean allowedOnBouncer = + !(mFingerprintLockedOut && mBouncer && mCredentialAttempted); + // Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an // instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware. final boolean shouldListen = (mKeyguardIsVisible || !mDeviceInteractive || (mBouncer && !mKeyguardGoingAway) || mGoingToSleep || shouldListenForFingerprintAssistant() || (mKeyguardOccluded && mIsDreaming)) && !mSwitchingUser && !isFingerprintDisabled(getCurrentUser()) - && (!mKeyguardGoingAway || !mDeviceInteractive) && mIsPrimaryUser; + && (!mKeyguardGoingAway || !mDeviceInteractive) && mIsPrimaryUser + && allowedOnBouncer; return shouldListen; } @@ -2372,6 +2396,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab // camera requests dismiss keyguard (tapping on photos for example). When these happen, // face auth should resume. mSecureCameraLaunched = false; + } else { + mCredentialAttempted = false; } for (int i = 0; i < mCallbacks.size(); i++) { diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java index 23fa645eff16..5442299881c0 100644 --- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java @@ -120,7 +120,7 @@ public class ImageWallpaper extends WallpaperService { private void init(DozeParameters dozeParameters) { mIsHighEndGfx = ActivityManager.isHighEndGfx(); mDisplayNeedsBlanking = dozeParameters.getDisplayNeedsBlanking(); - mNeedTransition = mIsHighEndGfx && !mDisplayNeedsBlanking; + mNeedTransition = false; // We will preserve EGL context when we are in lock screen or aod // to avoid janking in following transition, we need to release when back to home. @@ -137,7 +137,7 @@ public class ImageWallpaper extends WallpaperService { mRenderer = getRendererInstance(); getDisplayContext().getDisplay().getDisplayInfo(mDisplayInfo); setFixedSizeAllowed(true); - setOffsetNotificationsEnabled(true); + setOffsetNotificationsEnabled(mNeedTransition); updateSurfaceSize(); } diff --git a/packages/SystemUI/src/com/android/systemui/Interpolators.java b/packages/SystemUI/src/com/android/systemui/Interpolators.java index 6923079dd5c4..13d6a9bef266 100644 --- a/packages/SystemUI/src/com/android/systemui/Interpolators.java +++ b/packages/SystemUI/src/com/android/systemui/Interpolators.java @@ -49,6 +49,8 @@ public class Interpolators { public static final Interpolator CUSTOM_40_40 = new PathInterpolator(0.4f, 0f, 0.6f, 1f); public static final Interpolator HEADS_UP_APPEAR = new HeadsUpAppearInterpolator(); public static final Interpolator ICON_OVERSHOT = new PathInterpolator(0.4f, 0f, 0.2f, 1.4f); + public static final Interpolator SHADE_ANIMATION = + new PathInterpolator(0.6f, 0.02f, 0.4f, 0.98f); public static final Interpolator ICON_OVERSHOT_LESS = new PathInterpolator(0.4f, 0f, 0.2f, 1.1f); public static final Interpolator PANEL_CLOSE_ACCELERATED diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index eff693436451..044feaa117c8 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -514,7 +514,7 @@ public class BubbleStackView extends FrameLayout { mDismissTargetContainer = new FrameLayout(context); mDismissTargetContainer.setLayoutParams(new FrameLayout.LayoutParams( MATCH_PARENT, - getResources().getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height), + getResources().getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height), Gravity.BOTTOM)); mDismissTargetContainer.setClipChildren(false); mDismissTargetContainer.addView(targetView); @@ -523,7 +523,7 @@ public class BubbleStackView extends FrameLayout { // Start translated down so the target springs up. targetView.setTranslationY( - getResources().getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height)); + getResources().getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height)); // Save the MagneticTarget instance for the newly set up view - we'll add this to the // MagnetizedObjects. diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java index c16dce12041d..3f88f252bfe7 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java @@ -40,7 +40,7 @@ public class DozeDockHandler implements DozeMachine.Part { private int mDockState = DockManager.STATE_NONE; - public DozeDockHandler(AmbientDisplayConfiguration config, DozeMachine machine, + DozeDockHandler(AmbientDisplayConfiguration config, DozeMachine machine, DockManager dockManager) { mMachine = machine; mConfig = config; @@ -74,8 +74,13 @@ public class DozeDockHandler implements DozeMachine.Part { @Override public void onEvent(int dockState) { if (DEBUG) Log.d(TAG, "dock event = " + dockState); - final DozeMachine.State nextState; + mDockState = dockState; + if (isPulsing()) { + return; + } + + DozeMachine.State nextState; switch (mDockState) { case DockManager.STATE_DOCKED: nextState = State.DOZE_AOD_DOCKED; @@ -90,10 +95,15 @@ public class DozeDockHandler implements DozeMachine.Part { default: return; } - mMachine.requestState(nextState); } + private boolean isPulsing() { + DozeMachine.State state = mMachine.getState(); + return state == State.DOZE_REQUEST_PULSE || state == State.DOZE_PULSING + || state == State.DOZE_PULSING_BRIGHT; + } + void register() { if (mRegistered) { return; diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java index f7f9afdd2928..18bfd899a4e7 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java @@ -339,8 +339,8 @@ public class DozeMachine { return State.DOZE; } if ((mState == State.DOZE_AOD_PAUSED || mState == State.DOZE_AOD_PAUSING - || mState == State.DOZE_AOD || mState == State.DOZE) - && requestedState == State.DOZE_PULSE_DONE) { + || mState == State.DOZE_AOD || mState == State.DOZE + || mState == State.DOZE_AOD_DOCKED) && requestedState == State.DOZE_PULSE_DONE) { Log.i(TAG, "Dropping pulse done because current state is already done: " + mState); return mState; } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index fdd859373685..4dd5e87d2c93 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -489,6 +489,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mAdapter = new MyAdapter(); + mDepthController.setShowingHomeControls(shouldShowControls()); ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, getWalletPanelViewController(), mDepthController, mSysuiColorExtractor, mStatusBarService, mNotificationShadeWindowController, @@ -1780,8 +1781,12 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } if (mBackgroundDrawable == null) { mBackgroundDrawable = new ScrimDrawable(); - mScrimAlpha = mBlurUtils.supportsBlursOnWindows() - ? ScrimController.BLUR_SCRIM_ALPHA : ScrimController.BUSY_SCRIM_ALPHA; + if (mControlsUiController != null) { + mScrimAlpha = 1.0f; + } else { + mScrimAlpha = mBlurUtils.supportsBlursOnWindows() + ? ScrimController.BLUR_SCRIM_ALPHA : ScrimController.BUSY_SCRIM_ALPHA; + } } getWindow().setBackgroundDrawable(mBackgroundDrawable); } @@ -1841,8 +1846,9 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, if (!(mBackgroundDrawable instanceof ScrimDrawable)) { return; } - ((ScrimDrawable) mBackgroundDrawable).setColor(colors.supportsDarkText() ? Color.WHITE - : Color.BLACK, animate); + boolean hasControls = mControlsUiController != null; + ((ScrimDrawable) mBackgroundDrawable).setColor( + !hasControls && colors.supportsDarkText() ? Color.WHITE : Color.BLACK, animate); View decorView = getWindow().getDecorView(); if (colors.supportsDarkText()) { decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR | diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControllerFactory.java b/packages/SystemUI/src/com/android/systemui/media/MediaControllerFactory.java new file mode 100644 index 000000000000..71bc7c20c026 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControllerFactory.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 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.media; + +import android.content.Context; +import android.media.session.MediaController; +import android.media.session.MediaSession; + +import javax.inject.Inject; + +/** + * Testable wrapper around {@link MediaController} constructor. + */ +public class MediaControllerFactory { + + private final Context mContext; + + @Inject + public MediaControllerFactory(Context context) { + mContext = context; + } + + /** + * Creates a new MediaController from a session's token. + * + * @param token The token for the session. This value must never be null. + */ + public MediaController create(MediaSession.Token token) { + return new MediaController(mContext, token); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java index 1868536dca98..e24d29f1aedf 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java @@ -231,7 +231,12 @@ public class PipBoundsHandler { /** * @return {@link Rect} of the destination PiP window bounds. */ - Rect getDestinationBounds(float aspectRatio, Rect bounds, Size minimalSize) { + Rect getDestinationBounds(ComponentName componentName, float aspectRatio, Rect bounds, + Size minimalSize) { + if (!componentName.equals(mLastPipComponentName)) { + onResetReentryBoundsUnchecked(); + mLastPipComponentName = componentName; + } final Rect destinationBounds; if (bounds == null) { final Rect defaultBounds = getDefaultBounds(mReentrySnapFraction, mReentrySize); @@ -246,11 +251,7 @@ public class PipBoundsHandler { transformBoundsToAspectRatio(destinationBounds, aspectRatio, false /* useCurrentMinEdgeSize */); } - if (destinationBounds.equals(bounds)) { - return bounds; - } mAspectRatio = aspectRatio; - onResetReentryBoundsUnchecked(); mLastDestinationBounds.set(destinationBounds); return destinationBounds; } @@ -483,6 +484,7 @@ public class PipBoundsHandler { pw.println(prefix + TAG); pw.println(innerPrefix + "mLastPipComponentName=" + mLastPipComponentName); pw.println(innerPrefix + "mReentrySnapFraction=" + mReentrySnapFraction); + pw.println(innerPrefix + "mReentrySize=" + mReentrySize); pw.println(innerPrefix + "mDisplayInfo=" + mDisplayInfo); pw.println(innerPrefix + "mDefaultAspectRatio=" + mDefaultAspectRatio); pw.println(innerPrefix + "mMinAspectRatio=" + mMinAspectRatio); diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java index d9872d7dcf17..b10dd93fa695 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java @@ -247,7 +247,7 @@ public class PipTaskOrganizer extends TaskOrganizer { public void onTaskAppeared(ActivityManager.RunningTaskInfo info) { Objects.requireNonNull(info, "Requires RunningTaskInfo"); final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds( - getAspectRatioOrDefault(info.pictureInPictureParams), + info.topActivity, getAspectRatioOrDefault(info.pictureInPictureParams), null /* bounds */, getMinimalSize(info.topActivityInfo)); Objects.requireNonNull(destinationBounds, "Missing destination bounds"); mTaskInfo = info; @@ -303,7 +303,7 @@ public class PipTaskOrganizer extends TaskOrganizer { return; } final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds( - getAspectRatioOrDefault(newParams), + info.topActivity, getAspectRatioOrDefault(newParams), null /* bounds */, getMinimalSize(info.topActivityInfo)); Objects.requireNonNull(destinationBounds, "Missing destination bounds"); scheduleAnimateResizePip(destinationBounds, mEnterExitAnimationDuration, @@ -335,7 +335,7 @@ public class PipTaskOrganizer extends TaskOrganizer { } final Rect newDestinationBounds = mPipBoundsHandler.getDestinationBounds( - getAspectRatioOrDefault(mTaskInfo.pictureInPictureParams), + mTaskInfo.topActivity, getAspectRatioOrDefault(mTaskInfo.pictureInPictureParams), null /* bounds */, getMinimalSize(mTaskInfo.topActivityInfo)); if (newDestinationBounds.equals(currentDestinationBounds)) return; if (animator.getAnimationType() == ANIM_TYPE_BOUNDS) { diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java deleted file mode 100644 index b7258117c48c..000000000000 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) 2016 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.pip.phone; - -import android.content.Context; -import android.graphics.PixelFormat; -import android.graphics.Point; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.os.VibrationEffect; -import android.os.Vibrator; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.WindowManager; -import android.view.WindowManager.LayoutParams; -import android.widget.FrameLayout; - -import com.android.systemui.Interpolators; -import com.android.systemui.R; -import com.android.systemui.shared.system.WindowManagerWrapper; - -/** - * Displays the dismiss UI and target for floating objects. - */ -public class PipDismissViewController { - - // This delay controls how long to wait before we show the target when the user first moves - // the PIP, to prevent the target from animating if the user just wants to fling the PIP - public static final int SHOW_TARGET_DELAY = 100; - private static final int SHOW_TARGET_DURATION = 350; - private static final int HIDE_TARGET_DURATION = 225; - - private Context mContext; - private WindowManager mWindowManager; - private View mDismissView; - - // Used for dismissing a bubble -- bubble should be in the target to be considered a dismiss - private View mTargetView; - private int mTargetSlop; - private Point mWindowSize; - private int[] mLoc = new int[2]; - private boolean mIntersecting; - private Vibrator mVibe; - - public PipDismissViewController(Context context) { - mContext = context; - mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - mVibe = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); - } - - /** - * Creates the dismiss target for showing via {@link #showDismissTarget()}. - */ - public void createDismissTarget() { - if (mDismissView == null) { - // Determine sizes for the view - final Rect stableInsets = new Rect(); - WindowManagerWrapper.getInstance().getStableInsets(stableInsets); - mWindowSize = new Point(); - mWindowManager.getDefaultDisplay().getRealSize(mWindowSize); - final int gradientHeight = mContext.getResources().getDimensionPixelSize( - R.dimen.pip_dismiss_gradient_height); - final int bottomMargin = mContext.getResources().getDimensionPixelSize( - R.dimen.pip_dismiss_text_bottom_margin); - mTargetSlop = mContext.getResources().getDimensionPixelSize( - R.dimen.bubble_dismiss_slop); - - // Create a new view for the dismiss target - LayoutInflater inflater = LayoutInflater.from(mContext); - mDismissView = inflater.inflate(R.layout.pip_dismiss_view, null); - mDismissView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE); - mDismissView.forceHasOverlappingRendering(false); - - // Set the gradient background - Drawable gradient = mContext.getResources().getDrawable(R.drawable.pip_dismiss_scrim); - gradient.setAlpha((int) (255 * 0.85f)); - mDismissView.setBackground(gradient); - - // Adjust bottom margins of the text - mTargetView = mDismissView.findViewById(R.id.pip_dismiss_text); - FrameLayout.LayoutParams tlp = (FrameLayout.LayoutParams) mTargetView.getLayoutParams(); - tlp.bottomMargin = stableInsets.bottom + bottomMargin; - mTargetView.setLayoutParams(tlp); - - // Add the target to the window - LayoutParams lp = new LayoutParams( - LayoutParams.MATCH_PARENT, gradientHeight, - 0, mWindowSize.y - gradientHeight, - LayoutParams.TYPE_NAVIGATION_BAR_PANEL, - LayoutParams.FLAG_LAYOUT_IN_SCREEN - | LayoutParams.FLAG_NOT_TOUCHABLE - | LayoutParams.FLAG_NOT_FOCUSABLE, - PixelFormat.TRANSLUCENT); - lp.setTitle("pip-dismiss-overlay"); - lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; - lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL; - lp.setFitInsetsTypes(0 /* types */); - mWindowManager.addView(mDismissView, lp); - } - mDismissView.animate().cancel(); - } - - - /** - * Updates the dismiss target based on location of the view, only used for bubbles not for PIP. - * - * @return whether the view is within the dismiss target. - */ - public boolean updateTarget(View view) { - if (mDismissView == null) { - return false; - } - if (mDismissView.getAlpha() > 0) { - view.getLocationOnScreen(mLoc); - Rect viewRect = new Rect(mLoc[0], mLoc[1], mLoc[0] + view.getWidth(), - mLoc[1] + view.getHeight()); - mTargetView.getLocationOnScreen(mLoc); - Rect targetRect = new Rect(mLoc[0], mLoc[1], mLoc[0] + mTargetView.getWidth(), - mLoc[1] + mTargetView.getHeight()); - expandRect(targetRect, mTargetSlop); - boolean intersecting = targetRect.intersect(viewRect); - if (intersecting != mIntersecting) { - // TODO: is this the right effect? - mVibe.vibrate(VibrationEffect.get(intersecting - ? VibrationEffect.EFFECT_CLICK - : VibrationEffect.EFFECT_TICK)); - } - mIntersecting = intersecting; - return intersecting; - } - return false; - } - - /** - * Shows the dismiss target. - */ - public void showDismissTarget() { - mDismissView.animate() - .alpha(1f) - .setInterpolator(Interpolators.LINEAR) - .setStartDelay(SHOW_TARGET_DELAY) - .setDuration(SHOW_TARGET_DURATION) - .start(); - } - - /** - * Hides and destroys the dismiss target. - */ - public void destroyDismissTarget() { - if (mDismissView != null) { - mDismissView.animate() - .alpha(0f) - .setInterpolator(Interpolators.LINEAR) - .setStartDelay(0) - .setDuration(HIDE_TARGET_DURATION) - .withEndAction(new Runnable() { - @Override - public void run() { - mWindowManager.removeViewImmediate(mDismissView); - mDismissView = null; - } - }) - .start(); - } - } - - private void expandRect(Rect outRect, int expandAmount) { - outRect.left = Math.max(0, outRect.left - expandAmount); - outRect.top = Math.max(0, outRect.top - expandAmount); - outRect.right = Math.min(mWindowSize.x, outRect.right + expandAmount); - outRect.bottom = Math.min(mWindowSize.y, outRect.bottom + expandAmount); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java index 8397c65dbdb0..a192afceddb9 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java @@ -24,8 +24,6 @@ import android.annotation.Nullable; import android.app.ActivityManager.StackInfo; import android.app.IActivityTaskManager; import android.content.Context; -import android.graphics.Point; -import android.graphics.PointF; import android.graphics.Rect; import android.os.Debug; import android.os.RemoteException; @@ -40,6 +38,7 @@ import com.android.systemui.statusbar.FlingAnimationUtils; import com.android.systemui.util.FloatingContentCoordinator; import com.android.systemui.util.animation.FloatProperties; import com.android.systemui.util.animation.PhysicsAnimator; +import com.android.systemui.util.magnetictarget.MagnetizedObject; import java.io.PrintWriter; import java.util.function.Consumer; @@ -61,9 +60,6 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, /** Friction to use for PIP when it moves via physics fling animations. */ private static final float DEFAULT_FRICTION = 2f; - // The fraction of the stack height that the user has to drag offscreen to dismiss the PiP - private static final float DISMISS_OFFSCREEN_FRACTION = 0.3f; - private final Context mContext; private final IActivityTaskManager mActivityTaskManager; private final PipTaskOrganizer mPipTaskOrganizer; @@ -103,7 +99,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, /** * Update listener that resizes the PIP to {@link #mAnimatedBounds}. */ - private final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener = + final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener = (target, values) -> resizePipUnchecked(mAnimatedBounds); /** FlingConfig instances provided to PhysicsAnimator for fling gestures. */ @@ -122,6 +118,13 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private final Consumer<Rect> mUpdateBoundsCallback = mBounds::set; + /** + * Whether we're springing to the touch event location (vs. moving it to that position + * instantly). We spring-to-touch after PIP is dragged out of the magnetic target, since it was + * 'stuck' in the target and needs to catch up to the touch location. + */ + private boolean mSpringingToTouch = false; + public PipMotionHelper(Context context, IActivityTaskManager activityTaskManager, PipTaskOrganizer pipTaskOrganizer, PipMenuActivityController menuController, PipSnapAlgorithm snapAlgorithm, FlingAnimationUtils flingAnimationUtils, @@ -211,9 +214,35 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, mFloatingContentCoordinator.onContentMoved(this); } - cancelAnimations(); - resizePipUnchecked(toBounds); - mBounds.set(toBounds); + if (!mSpringingToTouch) { + // If we are moving PIP directly to the touch event locations, cancel any animations and + // move PIP to the given bounds. + cancelAnimations(); + resizePipUnchecked(toBounds); + mBounds.set(toBounds); + } else { + // If PIP is 'catching up' after being stuck in the dismiss target, update the animation + // to spring towards the new touch location. + mAnimatedBoundsPhysicsAnimator + .spring(FloatProperties.RECT_X, toBounds.left, mSpringConfig) + .spring(FloatProperties.RECT_Y, toBounds.top, mSpringConfig) + .withEndActions(() -> mSpringingToTouch = false); + + startBoundsAnimator(toBounds.left /* toX */, toBounds.top /* toY */); + } + } + + /** Set whether we're springing-to-touch to catch up after being stuck in the dismiss target. */ + void setSpringingToTouch(boolean springingToTouch) { + if (springingToTouch) { + mAnimatedBounds.set(mBounds); + } + + mSpringingToTouch = springingToTouch; + } + + void prepareForAnimation() { + mAnimatedBounds.set(mBounds); } /** @@ -278,24 +307,11 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, } /** - * @return whether the PiP at the current bounds should be dismissed. - */ - boolean shouldDismissPip() { - Point displaySize = new Point(); - mContext.getDisplay().getRealSize(displaySize); - final int y = displaySize.y - mStableInsets.bottom; - if (mBounds.bottom > y) { - float offscreenFraction = (float) (mBounds.bottom - y) / mBounds.height(); - return offscreenFraction >= DISMISS_OFFSCREEN_FRACTION; - } - return false; - } - - /** * Flings the PiP to the closest snap target. */ void flingToSnapTarget( - float velocityX, float velocityY, Runnable updateAction, @Nullable Runnable endAction) { + float velocityX, float velocityY, + @Nullable Runnable updateAction, @Nullable Runnable endAction) { mAnimatedBounds.set(mBounds); mAnimatedBoundsPhysicsAnimator .flingThenSpring( @@ -303,9 +319,13 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, true /* flingMustReachMinOrMax */) .flingThenSpring( FloatProperties.RECT_Y, velocityY, mFlingConfigY, mSpringConfig) - .addUpdateListener((target, values) -> updateAction.run()) .withEndActions(endAction); + if (updateAction != null) { + mAnimatedBoundsPhysicsAnimator.addUpdateListener( + (target, values) -> updateAction.run()); + } + final float xEndValue = velocityX < 0 ? mMovementBounds.left : mMovementBounds.right; final float estimatedFlingYEndValue = PhysicsAnimator.estimateFlingEndValue(mBounds.top, velocityY, mFlingConfigY); @@ -338,16 +358,14 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, * Animates the dismissal of the PiP off the edge of the screen. */ void animateDismiss(float velocityX, float velocityY, @Nullable Runnable updateAction) { - final float velocity = PointF.length(velocityX, velocityY); - final boolean isFling = velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond(); - final Point dismissEndPoint = getDismissEndPoint(mBounds, velocityX, velocityY, isFling); - mAnimatedBounds.set(mBounds); - // Animate to the dismiss end point, and then dismiss PIP. + // Animate off the bottom of the screen, then dismiss PIP. mAnimatedBoundsPhysicsAnimator - .spring(FloatProperties.RECT_X, dismissEndPoint.x, velocityX, mSpringConfig) - .spring(FloatProperties.RECT_Y, dismissEndPoint.y, velocityY, mSpringConfig) + .spring(FloatProperties.RECT_Y, + mBounds.bottom + mBounds.height(), + velocityY, + mSpringConfig) .withEndActions(this::dismissPip); // If we were provided with an update action, run it whenever there's an update. @@ -356,7 +374,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, (target, values) -> updateAction.run()); } - startBoundsAnimator(dismissEndPoint.x /* toX */, dismissEndPoint.y /* toY */); + startBoundsAnimator(mBounds.left /* toX */, mBounds.bottom + mBounds.height() /* toY */); } /** @@ -408,6 +426,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private void cancelAnimations() { mAnimatedBoundsPhysicsAnimator.cancel(); mAnimatingToBounds.setEmpty(); + mSpringingToTouch = false; } /** Set new fling configs whose min/max values respect the given movement bounds. */ @@ -426,7 +445,9 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, * the 'real' bounds to equal the final animated bounds. */ private void startBoundsAnimator(float toX, float toY) { - cancelAnimations(); + if (!mSpringingToTouch) { + cancelAnimations(); + } // Set animatingToBounds directly to avoid allocating a new Rect, but then call // setAnimatingToBounds to run the normal logic for changing animatingToBounds. @@ -484,47 +505,29 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, } /** - * @return the coordinates the PIP should animate to based on the direction of velocity when - * dismissing. + * Returns a MagnetizedObject wrapper for PIP's animated bounds. This is provided to the + * magnetic dismiss target so it can calculate PIP's size and position. */ - private Point getDismissEndPoint(Rect pipBounds, float velX, float velY, boolean isFling) { - Point displaySize = new Point(); - mContext.getDisplay().getRealSize(displaySize); - final float bottomBound = displaySize.y + pipBounds.height() * .1f; - if (isFling && velX != 0 && velY != 0) { - // Line is defined by: y = mx + b, m = slope, b = y-intercept - // Find the slope - final float slope = velY / velX; - // Sub in slope and PiP position to solve for y-intercept: b = y - mx - final float yIntercept = pipBounds.top - slope * pipBounds.left; - // Now find the point on this line when y = bottom bound: x = (y - b) / m - final float x = (bottomBound - yIntercept) / slope; - return new Point((int) x, (int) bottomBound); - } else { - // If it wasn't a fling the velocity on 'up' is not reliable for direction of movement, - // just animate downwards. - return new Point(pipBounds.left, (int) bottomBound); - } - } + MagnetizedObject<Rect> getMagnetizedPip() { + return new MagnetizedObject<Rect>( + mContext, mAnimatedBounds, FloatProperties.RECT_X, FloatProperties.RECT_Y) { + @Override + public float getWidth(@NonNull Rect animatedPipBounds) { + return animatedPipBounds.width(); + } - /** - * @return whether the gesture it towards the dismiss area based on the velocity when - * dismissing. - */ - public boolean isGestureToDismissArea(Rect pipBounds, float velX, float velY, - boolean isFling) { - Point endpoint = getDismissEndPoint(pipBounds, velX, velY, isFling); - // Center the point - endpoint.x += pipBounds.width() / 2; - endpoint.y += pipBounds.height() / 2; - - // The dismiss area is the middle third of the screen, half the PIP's height from the bottom - Point size = new Point(); - mContext.getDisplay().getRealSize(size); - final int left = size.x / 3; - Rect dismissArea = new Rect(left, size.y - (pipBounds.height() / 2), left * 2, - size.y + pipBounds.height()); - return dismissArea.contains(endpoint.x, endpoint.y); + @Override + public float getHeight(@NonNull Rect animatedPipBounds) { + return animatedPipBounds.height(); + } + + @Override + public void getLocationOnScreen( + @NonNull Rect animatedPipBounds, @NonNull int[] loc) { + loc[0] = animatedPipBounds.left; + loc[1] = animatedPipBounds.top; + } + }; } public void dump(PrintWriter pw, String prefix) { diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java index 7cc2759ad59a..bbb493966533 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -20,11 +20,13 @@ import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STAT import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_FULL; import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_NONE; +import android.annotation.SuppressLint; import android.app.IActivityManager; import android.app.IActivityTaskManager; import android.content.ComponentName; import android.content.Context; import android.content.res.Resources; +import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; @@ -33,14 +35,23 @@ import android.os.RemoteException; import android.util.Log; import android.util.Pair; import android.util.Size; +import android.view.Gravity; import android.view.IPinnedStackController; import android.view.InputEvent; import android.view.MotionEvent; +import android.view.View; import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityWindowInfo; +import android.widget.FrameLayout; + +import androidx.annotation.NonNull; +import androidx.dynamicanimation.animation.DynamicAnimation; +import androidx.dynamicanimation.animation.SpringForce; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.logging.MetricsLoggerWrapper; @@ -51,7 +62,10 @@ import com.android.systemui.pip.PipTaskOrganizer; import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.statusbar.FlingAnimationUtils; import com.android.systemui.util.DeviceConfigProxy; +import com.android.systemui.util.DismissCircleView; import com.android.systemui.util.FloatingContentCoordinator; +import com.android.systemui.util.animation.PhysicsAnimator; +import com.android.systemui.util.magnetictarget.MagnetizedObject; import java.io.PrintWriter; @@ -62,9 +76,6 @@ import java.io.PrintWriter; public class PipTouchHandler { private static final String TAG = "PipTouchHandler"; - // Allow the PIP to be flung from anywhere on the screen to the bottom to be dismissed. - private static final boolean ENABLE_FLING_DISMISS = false; - private static final int SHOW_DISMISS_AFFORDANCE_DELAY = 225; private static final int BOTTOM_OFFSET_BUFFER_DP = 1; @@ -73,17 +84,45 @@ public class PipTouchHandler { // Allow PIP to resize to a slightly bigger state upon touch private final boolean mEnableResize; private final Context mContext; + private final WindowManager mWindowManager; private final IActivityManager mActivityManager; private final PipBoundsHandler mPipBoundsHandler; private PipResizeGestureHandler mPipResizeGestureHandler; private IPinnedStackController mPinnedStackController; private final PipMenuActivityController mMenuController; - private final PipDismissViewController mDismissViewController; private final PipSnapAlgorithm mSnapAlgorithm; private final AccessibilityManager mAccessibilityManager; private boolean mShowPipMenuOnAnimationEnd = false; + /** + * MagnetizedObject wrapper for PIP. This allows the magnetic target library to locate and move + * PIP. + */ + private MagnetizedObject<Rect> mMagnetizedPip; + + /** + * Container for the dismiss circle, so that it can be animated within the container via + * translation rather than within the WindowManager via slow layout animations. + */ + private ViewGroup mTargetViewContainer; + + /** Circle view used to render the dismiss target. */ + private DismissCircleView mTargetView; + + /** + * MagneticTarget instance wrapping the target view and allowing us to set its magnetic radius. + */ + private MagnetizedObject.MagneticTarget mMagneticTarget; + + /** PhysicsAnimator instance for animating the dismiss target in/out. */ + private PhysicsAnimator<View> mMagneticTargetAnimator; + + /** Default configuration to use for springing the dismiss target in/out. */ + private final PhysicsAnimator.SpringConfig mTargetSpringConfig = + new PhysicsAnimator.SpringConfig( + SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_NO_BOUNCY); + // The current movement bounds private Rect mMovementBounds = new Rect(); // The current resized bounds, changed by user resize. @@ -104,21 +143,20 @@ public class PipTouchHandler { private int mDeferResizeToNormalBoundsUntilRotation = -1; private int mDisplayRotation; + /** + * Runnable that can be posted delayed to show the target. This needs to be saved as a member + * variable so we can pass it to removeCallbacks. + */ + private Runnable mShowTargetAction = this::showDismissTargetMaybe; + private Handler mHandler = new Handler(); - private Runnable mShowDismissAffordance = new Runnable() { - @Override - public void run() { - if (mEnableDismissDragToEdge) { - mDismissViewController.showDismissTarget(); - } - } - }; // Behaviour states private int mMenuState = MENU_STATE_NONE; private boolean mIsImeShowing; private int mImeHeight; private int mImeOffset; + private int mDismissAreaHeight; private boolean mIsShelfShowing; private int mShelfHeight; private int mMovementBoundsExtraOffsets; @@ -168,6 +206,7 @@ public class PipTouchHandler { } } + @SuppressLint("InflateParams") public PipTouchHandler(Context context, IActivityManager activityManager, IActivityTaskManager activityTaskManager, PipMenuActivityController menuController, InputConsumerController inputConsumerController, @@ -180,9 +219,9 @@ public class PipTouchHandler { mContext = context; mActivityManager = activityManager; mAccessibilityManager = context.getSystemService(AccessibilityManager.class); + mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); mMenuController = menuController; mMenuController.addListener(new PipMenuListener()); - mDismissViewController = new PipDismissViewController(context); mSnapAlgorithm = pipSnapAlgorithm; mFlingAnimationUtils = new FlingAnimationUtils(context.getResources().getDisplayMetrics(), 2.5f); @@ -200,6 +239,7 @@ public class PipTouchHandler { mExpandedShortestEdgeSize = res.getDimensionPixelSize( R.dimen.pip_expanded_shortest_edge_size); mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset); + mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height); mEnableDismissDragToEdge = res.getBoolean(R.bool.config_pipEnableDismissDragToEdge); mEnableResize = res.getBoolean(R.bool.config_pipEnableResizeForMenu); @@ -212,6 +252,56 @@ public class PipTouchHandler { mFloatingContentCoordinator = floatingContentCoordinator; mConnection = new PipAccessibilityInteractionConnection(mMotionHelper, this::onAccessibilityShowMenu, mHandler); + + final int targetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size); + mTargetView = new DismissCircleView(context); + final FrameLayout.LayoutParams newParams = + new FrameLayout.LayoutParams(targetSize, targetSize); + newParams.gravity = Gravity.CENTER; + mTargetView.setLayoutParams(newParams); + + mTargetViewContainer = new FrameLayout(context); + mTargetViewContainer.setClipChildren(false); + mTargetViewContainer.addView(mTargetView); + + mMagnetizedPip = mMotionHelper.getMagnetizedPip(); + mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0); + mMagnetizedPip.setPhysicsAnimatorUpdateListener(mMotionHelper.mResizePipUpdateListener); + mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() { + @Override + public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) { + mMotionHelper.prepareForAnimation(); + + // Show the dismiss target, in case the initial touch event occurred within the + // magnetic field radius. + showDismissTargetMaybe(); + } + + @Override + public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target, + float velX, float velY, boolean wasFlungOut) { + if (wasFlungOut) { + mMotionHelper.flingToSnapTarget(velX, velY, null, null); + hideDismissTarget(); + } else { + mMotionHelper.setSpringingToTouch(true); + } + } + + @Override + public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) { + mHandler.post(() -> { + mMotionHelper.animateDismiss(0, 0, null); + hideDismissTarget(); + }); + + + MetricsLoggerWrapper.logPictureInPictureDismissByDrag(mContext, + PipUtils.getTopPipActivity(mContext, mActivityManager)); + } + }); + + mMagneticTargetAnimator = PhysicsAnimator.getInstance(mTargetView); } public void setTouchGesture(PipTouchGesture gesture) { @@ -231,7 +321,8 @@ public class PipTouchHandler { } public void onActivityPinned() { - cleanUpDismissTarget(); + createDismissTargetMaybe(); + mShowPipMenuOnAnimationEnd = true; mPipResizeGestureHandler.onActivityPinned(); mFloatingContentCoordinator.onContentAdded(mMotionHelper); @@ -264,6 +355,10 @@ public class PipTouchHandler { public void onConfigurationChanged() { mMotionHelper.onConfigurationChanged(); mMotionHelper.synchronizePinnedStackBounds(); + + // Recreate the dismiss target for the new orientation. + cleanUpDismissTarget(); + createDismissTargetMaybe(); } public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) { @@ -351,6 +446,74 @@ public class PipTouchHandler { } } + /** Adds the magnetic target view to the WindowManager so it's ready to be animated in. */ + private void createDismissTargetMaybe() { + if (!mTargetViewContainer.isAttachedToWindow()) { + mHandler.removeCallbacks(mShowTargetAction); + mMagneticTargetAnimator.cancel(); + + final Point windowSize = new Point(); + mWindowManager.getDefaultDisplay().getRealSize(windowSize); + WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + WindowManager.LayoutParams.MATCH_PARENT, + mDismissAreaHeight, + 0, windowSize.y - mDismissAreaHeight, + WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + PixelFormat.TRANSLUCENT); + lp.setTitle("pip-dismiss-overlay"); + lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; + lp.setFitInsetsTypes(0 /* types */); + + mTargetViewContainer.setVisibility(View.INVISIBLE); + mWindowManager.addView(mTargetViewContainer, lp); + } + } + + /** Makes the dismiss target visible and animates it in, if it isn't already visible. */ + private void showDismissTargetMaybe() { + createDismissTargetMaybe(); + + if (mTargetViewContainer.getVisibility() != View.VISIBLE) { + + mTargetView.setTranslationY(mTargetViewContainer.getHeight()); + mTargetViewContainer.setVisibility(View.VISIBLE); + + // Set the magnetic field radius to half of PIP's width. + mMagneticTarget.setMagneticFieldRadiusPx(mMotionHelper.getBounds().width()); + + // Cancel in case we were in the middle of animating it out. + mMagneticTargetAnimator.cancel(); + mMagneticTargetAnimator + .spring(DynamicAnimation.TRANSLATION_Y, 0f, mTargetSpringConfig) + .start(); + } + } + + /** Animates the magnetic dismiss target out and then sets it to GONE. */ + private void hideDismissTarget() { + mHandler.removeCallbacks(mShowTargetAction); + mMagneticTargetAnimator + .spring(DynamicAnimation.TRANSLATION_Y, + mTargetViewContainer.getHeight(), + mTargetSpringConfig) + .withEndActions(() -> mTargetViewContainer.setVisibility(View.GONE)) + .start(); + } + + /** + * Removes the dismiss target and cancels any pending callbacks to show it. + */ + private void cleanUpDismissTarget() { + mHandler.removeCallbacks(mShowTargetAction); + + if (mTargetViewContainer.isAttachedToWindow()) { + mWindowManager.removeView(mTargetViewContainer); + } + } + private void onRegistrationChanged(boolean isRegistered) { mAccessibilityManager.setPictureInPictureActionReplacingConnection(isRegistered ? mConnection : null); @@ -375,8 +538,24 @@ public class PipTouchHandler { if (mPinnedStackController == null) { return true; } + MotionEvent ev = (MotionEvent) inputEvent; + if (mMagnetizedPip.maybeConsumeMotionEvent(ev)) { + // If the first touch event occurs within the magnetic field, pass the ACTION_DOWN event + // to the touch state. Touch state needs a DOWN event in order to later process MOVE + // events it'll receive if the object is dragged out of the magnetic field. + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + mTouchState.onTouchEvent(ev); + } + + // Continue tracking velocity when the object is in the magnetic field, since we want to + // respect touch input velocity if the object is dragged out and then flung. + mTouchState.addMovementToVelocityTracker(ev); + + return true; + } + // Update the touch state mTouchState.onTouchEvent(ev); @@ -600,17 +779,13 @@ public class PipTouchHandler { mDelta.set(0f, 0f); mStartPosition.set(bounds.left, bounds.top); mMovementWithinDismiss = touchState.getDownTouchPosition().y >= mMovementBounds.bottom; + mMotionHelper.setSpringingToTouch(false); // If the menu is still visible then just poke the menu // so that it will timeout after the user stops touching it if (mMenuState != MENU_STATE_NONE) { mMenuController.pokeMenu(); } - - if (mEnableDismissDragToEdge) { - mDismissViewController.createDismissTarget(); - mHandler.postDelayed(mShowDismissAffordance, SHOW_DISMISS_AFFORDANCE_DELAY); - } } @Override @@ -623,8 +798,10 @@ public class PipTouchHandler { mSavedSnapFraction = -1f; if (mEnableDismissDragToEdge) { - mHandler.removeCallbacks(mShowDismissAffordance); - mDismissViewController.showDismissTarget(); + if (mTargetViewContainer.getVisibility() != View.VISIBLE) { + mHandler.removeCallbacks(mShowTargetAction); + showDismissTargetMaybe(); + } } } @@ -644,10 +821,6 @@ public class PipTouchHandler { mTmpBounds.offsetTo((int) left, (int) top); mMotionHelper.movePip(mTmpBounds, true /* isDragging */); - if (mEnableDismissDragToEdge) { - updateDismissFraction(); - } - final PointF curPos = touchState.getLastTouchPosition(); if (mMovementWithinDismiss) { // Track if movement remains near the bottom edge to identify swipe to dismiss @@ -661,9 +834,7 @@ public class PipTouchHandler { @Override public boolean onUp(PipTouchState touchState) { if (mEnableDismissDragToEdge) { - // Clean up the dismiss target regardless of the touch state in case the touch - // enabled state changes while the user is interacting - cleanUpDismissTarget(); + hideDismissTarget(); } if (!touchState.isUserInteracting()) { @@ -671,26 +842,8 @@ public class PipTouchHandler { } final PointF vel = touchState.getVelocity(); - final boolean isHorizontal = Math.abs(vel.x) > Math.abs(vel.y); final float velocity = PointF.length(vel.x, vel.y); final boolean isFling = velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond(); - final boolean isUpWithinDimiss = ENABLE_FLING_DISMISS - && touchState.getLastTouchPosition().y >= mMovementBounds.bottom - && mMotionHelper.isGestureToDismissArea(mMotionHelper.getBounds(), vel.x, - vel.y, isFling); - final boolean isFlingToBot = isFling && vel.y > 0 && !isHorizontal - && (mMovementWithinDismiss || isUpWithinDimiss); - if (mEnableDismissDragToEdge) { - // Check if the user dragged or flung the PiP offscreen to dismiss it - if (mMotionHelper.shouldDismissPip() || isFlingToBot) { - MetricsLoggerWrapper.logPictureInPictureDismissByDrag(mContext, - PipUtils.getTopPipActivity(mContext, mActivityManager)); - mMotionHelper.animateDismiss( - vel.x, vel.y, - PipTouchHandler.this::updateDismissFraction /* updateAction */); - return true; - } - } if (touchState.isDragging()) { Runnable endAction = null; @@ -749,14 +902,6 @@ public class PipTouchHandler { } /** - * Removes the dismiss target and cancels any pending callbacks to show it. - */ - private void cleanUpDismissTarget() { - mHandler.removeCallbacks(mShowDismissAffordance); - mDismissViewController.destroyDismissTarget(); - } - - /** * @return whether the menu will resize as a part of showing the full menu. */ private boolean willResizeMenu() { diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java index e3f65ef812fb..dc286c1c2de5 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java @@ -92,7 +92,7 @@ public class PipTouchState { // Initialize the velocity tracker initOrResetVelocityTracker(); - addMovement(ev); + addMovementToVelocityTracker(ev); mActivePointerId = ev.getPointerId(0); if (DEBUG) { @@ -120,7 +120,7 @@ public class PipTouchState { } // Update the velocity tracker - addMovement(ev); + addMovementToVelocityTracker(ev); int pointerIndex = ev.findPointerIndex(mActivePointerId); if (pointerIndex == -1) { Log.e(TAG, "Invalid active pointer id on MOVE: " + mActivePointerId); @@ -151,7 +151,7 @@ public class PipTouchState { } // Update the velocity tracker - addMovement(ev); + addMovementToVelocityTracker(ev); int pointerIndex = ev.getActionIndex(); int pointerId = ev.getPointerId(pointerIndex); @@ -174,7 +174,7 @@ public class PipTouchState { } // Update the velocity tracker - addMovement(ev); + addMovementToVelocityTracker(ev); mVelocityTracker.computeCurrentVelocity(1000, mViewConfig.getScaledMaximumFlingVelocity()); mVelocity.set(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity()); @@ -318,6 +318,20 @@ public class PipTouchState { return -1; } + void addMovementToVelocityTracker(MotionEvent event) { + if (mVelocityTracker == null) { + return; + } + + // Add movement to velocity tracker using raw screen X and Y coordinates instead + // of window coordinates because the window frame may be moving at the same time. + float deltaX = event.getRawX() - event.getX(); + float deltaY = event.getRawY() - event.getY(); + event.offsetLocation(deltaX, deltaY); + mVelocityTracker.addMovement(event); + event.offsetLocation(-deltaX, -deltaY); + } + private void initOrResetVelocityTracker() { if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); @@ -333,16 +347,6 @@ public class PipTouchState { } } - private void addMovement(MotionEvent event) { - // Add movement to velocity tracker using raw screen X and Y coordinates instead - // of window coordinates because the window frame may be moving at the same time. - float deltaX = event.getRawX() - event.getX(); - float deltaY = event.getRawY() - event.getY(); - event.offsetLocation(deltaX, deltaY); - mVelocityTracker.addMovement(event); - event.offsetLocation(-deltaX, -deltaY); - } - public void dump(PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; pw.println(prefix + TAG); diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java index 9c175bc2b756..8efeef1ffa0a 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java +++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java @@ -30,6 +30,14 @@ import com.android.systemui.R; */ public class PipControlsView extends LinearLayout { + public PipControlsView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public PipControlsView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + public PipControlsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt index fd44f04a0d80..729f934937da 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt @@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator import android.app.WallpaperManager import android.util.Log +import android.util.MathUtils import android.view.Choreographer import android.view.View import androidx.annotation.VisibleForTesting @@ -37,6 +38,7 @@ import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK import com.android.systemui.statusbar.phone.NotificationShadeWindowController import com.android.systemui.statusbar.phone.PanelExpansionListener +import com.android.systemui.statusbar.phone.ScrimController import com.android.systemui.statusbar.policy.KeyguardStateController import java.io.FileDescriptor import java.io.PrintWriter @@ -74,6 +76,7 @@ class NotificationShadeDepthController @Inject constructor( var shadeSpring = DepthAnimation() @VisibleForTesting var globalActionsSpring = DepthAnimation() + var showingHomeControls: Boolean = false @VisibleForTesting var brightnessMirrorSpring = DepthAnimation() @@ -105,6 +108,16 @@ class NotificationShadeDepthController @Inject constructor( } /** + * Force stop blur effect when necessary. + */ + private var scrimsVisible: Boolean = false + set(value) { + if (field == value) return + field = value + scheduleUpdate() + } + + /** * Blur radius of the wake-up animation on this frame. */ private var wakeAndUnlockBlurRadius = 0 @@ -133,7 +146,20 @@ class NotificationShadeDepthController @Inject constructor( shadeRadius = 0f } } - val blur = max(shadeRadius.toInt(), globalActionsSpring.radius) + + // Home controls have black background, this means that we should not have blur when they + // are fully visible, otherwise we'll enter Client Composition unnecessarily. + var globalActionsRadius = globalActionsSpring.radius + if (showingHomeControls) { + globalActionsRadius = 0 + } + var blur = max(shadeRadius.toInt(), globalActionsRadius) + + // Make blur be 0 if it is necessary to stop blur effect. + if (scrimsVisible) { + blur = 0 + } + blurUtils.applyBlur(blurRoot?.viewRootImpl ?: root.viewRootImpl, blur) try { wallpaperManager.setWallpaperZoomOut(root.windowToken, @@ -193,6 +219,10 @@ class NotificationShadeDepthController @Inject constructor( brightnessMirrorSpring.finishIfRunning() } } + + override fun onDozeAmountChanged(linear: Float, eased: Float) { + wakeAndUnlockBlurRadius = blurUtils.blurRadiusOfRatio(eased) + } } init { @@ -201,6 +231,10 @@ class NotificationShadeDepthController @Inject constructor( keyguardStateController.addCallback(keyguardStateCallback) } statusBarStateController.addCallback(statusBarStateCallback) + notificationShadeWindowController.setScrimsVisibilityListener { + // Stop blur effect when scrims is opaque to avoid unnecessary GPU composition. + visibility -> scrimsVisible = visibility == ScrimController.OPAQUE + } } /** @@ -216,8 +250,12 @@ class NotificationShadeDepthController @Inject constructor( private fun updateShadeBlur() { var newBlur = 0 - if (statusBarStateController.state == StatusBarState.SHADE) { - newBlur = blurUtils.blurRadiusOfRatio(shadeExpansion) + val state = statusBarStateController.state + if (state == StatusBarState.SHADE || state == StatusBarState.SHADE_LOCKED) { + val animatedBlur = + Interpolators.SHADE_ANIMATION.getInterpolation( + MathUtils.constrain(shadeExpansion / 0.15f, 0f, 1f)) + newBlur = blurUtils.blurRadiusOfRatio(0.35f * animatedBlur + 0.65f * shadeExpansion) } shadeSpring.animateTo(newBlur) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt index 1696f0715865..53ec57090321 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt @@ -103,12 +103,20 @@ class ConversationNotificationManager @Inject constructor( override fun onEntryInflated(entry: NotificationEntry) { if (!entry.ranking.isConversation) return fun updateCount(isExpanded: Boolean) { - if (isExpanded && !notifPanelCollapsed) { + if (isExpanded && (!notifPanelCollapsed || entry.isPinnedAndExpanded())) { resetCount(entry.key) entry.row?.let(::resetBadgeUi) } } - entry.row?.setOnExpansionChangedListener(::updateCount) + entry.row?.setOnExpansionChangedListener { isExpanded -> + if (entry.row?.isShown == true && isExpanded) { + entry.row.performOnIntrinsicHeightReached { + updateCount(isExpanded) + } + } else { + updateCount(isExpanded) + } + } updateCount(entry.row?.isExpanded == true) } @@ -169,7 +177,8 @@ class ConversationNotificationManager @Inject constructor( private fun resetBadgeUi(row: ExpandableNotificationRow): Unit = (row.layouts?.asSequence() ?: emptySequence()) - .mapNotNull { layout -> layout.contractedChild as? ConversationLayout } + .flatMap { layout -> layout.allViews.asSequence()} + .mapNotNull { view -> view as? ConversationLayout } .forEach { convoLayout -> convoLayout.setUnreadCount(0) } private data class ConversationState(val unreadCount: Int, val notification: Notification) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index dd7be2775209..c1ba26d034ed 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -601,6 +601,13 @@ public final class NotificationEntry extends ListEntry { return row != null && row.isPinned(); } + /** + * Is this entry pinned and was expanded while doing so + */ + public boolean isPinnedAndExpanded() { + return row != null && row.isPinnedAndExpanded(); + } + public void setRowPinned(boolean pinned) { if (row != null) row.setPinned(pinned); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 2917346153d2..5c578dfc5744 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -284,6 +284,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (isPinned()) { nowExpanded = !mExpandedWhenPinned; mExpandedWhenPinned = nowExpanded; + // Also notify any expansion changed listeners. This is necessary since the + // expansion doesn't actually change (it's already system expanded) but it + // changes visually + if (mExpansionChangedListener != null) { + mExpansionChangedListener.onExpansionChanged(nowExpanded); + } } else { nowExpanded = !isExpanded(); setUserExpanded(nowExpanded); @@ -326,6 +332,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private NotificationInlineImageResolver mImageResolver; private NotificationMediaManager mMediaManager; @Nullable private OnExpansionChangedListener mExpansionChangedListener; + @Nullable private Runnable mOnIntrinsicHeightReachedRunnable; private SystemNotificationAsyncTask mSystemNotificationAsyncTask = new SystemNotificationAsyncTask(); @@ -358,6 +365,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return Arrays.copyOf(mLayouts, mLayouts.length); } + /** + * Is this entry pinned and was expanded while doing so + */ + public boolean isPinnedAndExpanded() { + if (!isPinned()) { + return false; + } + return mExpandedWhenPinned; + } + @Override public boolean isGroupExpansionChanging() { if (isChildInGroup()) { @@ -2690,6 +2707,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (mMenuRow != null && mMenuRow.getMenuView() != null) { mMenuRow.onParentHeightUpdate(); } + handleIntrinsicHeightReached(); } @Override @@ -2907,6 +2925,24 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mExpansionChangedListener = listener; } + /** + * Perform an action when the notification height has reached its intrinsic height. + * + * @param runnable the runnable to run + */ + public void performOnIntrinsicHeightReached(@Nullable Runnable runnable) { + mOnIntrinsicHeightReachedRunnable = runnable; + handleIntrinsicHeightReached(); + } + + private void handleIntrinsicHeightReached() { + if (mOnIntrinsicHeightReachedRunnable != null + && getActualHeight() == getIntrinsicHeight()) { + mOnIntrinsicHeightReachedRunnable.run(); + mOnIntrinsicHeightReachedRunnable = null; + } + } + @Override public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfoInternal(info); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java index 893e8490eb90..e4e3ebcf7671 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java @@ -151,9 +151,12 @@ public final class NotifBindPipeline { * the real work once rather than repeatedly start and cancel it. */ private void requestPipelineRun(NotificationEntry entry) { - mLogger.logRequestPipelineRun(entry.getKey()); - final BindEntry bindEntry = getBindEntry(entry); + if (bindEntry.row == null) { + // Row is not managed yet but may be soon. Stop for now. + return; + } + mLogger.logRequestPipelineRun(entry.getKey()); // Abort any existing pipeline run mStage.abortStage(entry, bindEntry.row); @@ -177,10 +180,6 @@ public final class NotifBindPipeline { final BindEntry bindEntry = mBindEntries.get(entry); final ExpandableNotificationRow row = bindEntry.row; - if (row == null) { - // Row is not managed yet but may be soon. Stop for now. - return; - } mStage.executeStage(entry, row, (en) -> onPipelineComplete(en)); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index b18bf01ea91f..3c3f1b21fb3c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.row; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; import android.app.PendingIntent; @@ -988,6 +989,14 @@ public class NotificationContentView extends FrameLayout { } } + public @NonNull View[] getAllViews() { + return new View[] { + mContractedChild, + mHeadsUpChild, + mExpandedChild, + mSingleLineView }; + } + public NotificationViewWrapper getVisibleWrapper(int visibleType) { switch (visibleType) { case VISIBLE_TYPE_EXPANDED: diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index f06cfec9480a..82e02b47974c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -203,7 +203,9 @@ public class KeyguardBouncer { Log.wtf(TAG, "onFullyShown when view was null"); } else { mKeyguardView.onResume(); - mRoot.announceForAccessibility(mKeyguardView.getAccessibilityTitleForCurrentMode()); + if (mRoot != null) { + mRoot.announceForAccessibility(mKeyguardView.getAccessibilityTitleForCurrentMode()); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java index d70484e9cf41..a19d35ac4e81 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java @@ -50,19 +50,22 @@ public class LockIcon extends KeyguardAffordanceView { static final int STATE_BIOMETRICS_ERROR = 3; private float mDozeAmount; private int mIconColor; - private StateProvider mStateProvider; private int mOldState; + private int mState; private boolean mPulsing; private boolean mDozing; private boolean mKeyguardJustShown; + private boolean mPredrawRegistered; private final SparseArray<Drawable> mDrawableCache = new SparseArray<>(); private final OnPreDrawListener mOnPreDrawListener = new OnPreDrawListener() { @Override public boolean onPreDraw() { getViewTreeObserver().removeOnPreDrawListener(this); + mPredrawRegistered = false; - int newState = mStateProvider.getState(); + int newState = mState; + mOldState = mState; Drawable icon = getIcon(newState); setImageDrawable(icon, false); @@ -80,7 +83,7 @@ public class LockIcon extends KeyguardAffordanceView { @Override public void onAnimationEnd(Drawable drawable) { if (getDrawable() == animation - && newState == mStateProvider.getState() + && newState == mState && newState == STATE_SCANNING_FACE) { animation.start(); } else { @@ -100,10 +103,6 @@ public class LockIcon extends KeyguardAffordanceView { super(context, attrs); } - void setStateProvider(StateProvider stateProvider) { - mStateProvider = stateProvider; - } - @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); @@ -135,13 +134,16 @@ public class LockIcon extends KeyguardAffordanceView { return false; } - void update(int oldState, boolean pulsing, boolean dozing, boolean keyguardJustShown) { - mOldState = oldState; + void update(int newState, boolean pulsing, boolean dozing, boolean keyguardJustShown) { + mState = newState; mPulsing = pulsing; mDozing = dozing; mKeyguardJustShown = keyguardJustShown; - getViewTreeObserver().addOnPreDrawListener(mOnPreDrawListener); + if (!mPredrawRegistered) { + mPredrawRegistered = true; + getViewTreeObserver().addOnPreDrawListener(mOnPreDrawListener); + } } void setDozeAmount(float dozeAmount) { @@ -175,7 +177,7 @@ public class LockIcon extends KeyguardAffordanceView { return mDrawableCache.get(iconRes); } - static int getIconForState(int state) { + private static int getIconForState(int state) { int iconRes; switch (state) { case STATE_LOCKED: @@ -196,7 +198,7 @@ public class LockIcon extends KeyguardAffordanceView { return iconRes; } - static int getAnimationIndexForTransition(int oldState, int newState, boolean pulsing, + private static int getAnimationIndexForTransition(int oldState, int newState, boolean pulsing, boolean dozing, boolean keyguardJustShown) { // Never animate when screen is off @@ -260,9 +262,4 @@ public class LockIcon extends KeyguardAffordanceView { } return LOCK_ANIM_RES_IDS[0][lockAnimIndex]; } - - interface StateProvider { - int getState(); - } - } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java index f7c861b84a68..a633e1979bad 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java @@ -352,7 +352,6 @@ public class LockscreenLockIconController { mLockIcon.setOnClickListener(this::handleClick); mLockIcon.setOnLongClickListener(this::handleLongClick); mLockIcon.setAccessibilityDelegate(mAccessibilityDelegate); - mLockIcon.setStateProvider(this::getState); if (mLockIcon.isAttachedToWindow()) { mOnAttachStateChangeListener.onViewAttachedToWindow(mLockIcon); @@ -462,7 +461,7 @@ public class LockscreenLockIconController { shouldUpdate = false; } if (shouldUpdate && mLockIcon != null) { - mLockIcon.update(mLastState, mPulsing, mDozing, mKeyguardJustShown); + mLockIcon.update(state, mPulsing, mDozing, mKeyguardJustShown); } mLastState = state; mKeyguardJustShown = false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java index e1e679f06eef..462b42a44c17 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java @@ -61,6 +61,7 @@ import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; +import java.util.function.Consumer; import javax.inject.Inject; import javax.inject.Singleton; @@ -92,6 +93,7 @@ public class NotificationShadeWindowController implements Callback, Dumpable, private final State mCurrentState = new State(); private OtherwisedCollapsedListener mListener; private ForcePluginOpenListener mForcePluginOpenListener; + private Consumer<Integer> mScrimsVisibilityListener; private final ArrayList<WeakReference<StatusBarWindowCallback>> mCallbacks = Lists.newArrayList(); @@ -150,6 +152,16 @@ public class NotificationShadeWindowController implements Callback, Dumpable, mCallbacks.add(new WeakReference<StatusBarWindowCallback>(callback)); } + /** + * Register a listener to monitor scrims visibility + * @param listener A listener to monitor scrims visibility + */ + public void setScrimsVisibilityListener(Consumer<Integer> listener) { + if (listener != null && mScrimsVisibilityListener != listener) { + mScrimsVisibilityListener = listener; + } + } + private boolean shouldEnableKeyguardScreenRotation() { Resources res = mContext.getResources(); return SystemProperties.getBoolean("lockscreen.rot_override", false) @@ -477,6 +489,7 @@ public class NotificationShadeWindowController implements Callback, Dumpable, public void setScrimsVisibility(int scrimsVisibility) { mCurrentState.mScrimsVisibility = scrimsVisibility; apply(mCurrentState); + mScrimsVisibilityListener.accept(scrimsVisibility); } /** diff --git a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt b/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt index 812a1e4bc121..f27bdbfbeda0 100644 --- a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt +++ b/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt @@ -160,6 +160,18 @@ abstract class MagnetizedObject<T : Any>( lateinit var magnetListener: MagnetizedObject.MagnetListener /** + * Optional update listener to provide to the PhysicsAnimator that is used to spring the object + * into the target. + */ + var physicsAnimatorUpdateListener: PhysicsAnimator.UpdateListener<T>? = null + + /** + * Optional end listener to provide to the PhysicsAnimator that is used to spring the object + * into the target. + */ + var physicsAnimatorEndListener: PhysicsAnimator.EndListener<T>? = null + + /** * Sets whether forcefully flinging the object vertically towards a target causes it to be * attracted to the target and then released immediately, despite never being dragged within the * magnetic field. @@ -479,6 +491,14 @@ abstract class MagnetizedObject<T : Any>( .spring(yProperty, yProperty.getValue(underlyingObject) + yDiff, velY, springConfig) + if (physicsAnimatorUpdateListener != null) { + animator.addUpdateListener(physicsAnimatorUpdateListener!!) + } + + if (physicsAnimatorEndListener != null) { + animator.addEndListener(physicsAnimatorEndListener!!) + } + if (after != null) { animator.withEndActions(after) } @@ -560,13 +580,15 @@ abstract class MagnetizedObject<T : Any>( private val tempLoc = IntArray(2) fun updateLocationOnScreen() { - targetView.getLocationOnScreen(tempLoc) - - // Add half of the target size to get the center, and subtract translation since the - // target could be animating in while we're doing this calculation. - centerOnScreen.set( - tempLoc[0] + targetView.width / 2f - targetView.translationX, - tempLoc[1] + targetView.height / 2f - targetView.translationY) + targetView.post { + targetView.getLocationOnScreen(tempLoc) + + // Add half of the target size to get the center, and subtract translation since the + // target could be animating in while we're doing this calculation. + centerOnScreen.set( + tempLoc[0] + targetView.width / 2f - targetView.translationX, + tempLoc[1] + targetView.height / 2f - targetView.translationY) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java index bae5bb41aa5a..c22b718fa50f 100644 --- a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java +++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java @@ -272,13 +272,18 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } mAnimation.cancel(); } - mAnimationDirection = show ? DIRECTION_SHOW : DIRECTION_HIDE; final float defaultY = mImeSourceControl.getSurfacePosition().y; final float x = mImeSourceControl.getSurfacePosition().x; final float hiddenY = defaultY + imeSource.getFrame().height(); final float shownY = defaultY; final float startY = show ? hiddenY : shownY; final float endY = show ? shownY : hiddenY; + if (mAnimationDirection == DIRECTION_NONE && mImeShowing && show) { + // IME is already showing, so set seek to end + seekValue = shownY; + seek = true; + } + mAnimationDirection = show ? DIRECTION_SHOW : DIRECTION_HIDE; mImeShowing = show; mAnimation = ValueAnimator.ofFloat(startY, endY); mAnimation.setDuration( diff --git a/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java b/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java index 1140b9aa3abb..e5da603321cd 100644 --- a/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java +++ b/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java @@ -258,12 +258,13 @@ public class SystemWindows { Rect outVisibleInsets, Rect outStableInsets, DisplayCutout.ParcelableWrapper cutout, MergedConfiguration mergedConfiguration, SurfaceControl outSurfaceControl, InsetsState outInsetsState, - Point outSurfaceSize, SurfaceControl outBLASTSurfaceControl) { + InsetsSourceControl[] outActiveControls, Point outSurfaceSize, + SurfaceControl outBLASTSurfaceControl) { int res = super.relayout(window, seq, attrs, requestedWidth, requestedHeight, viewVisibility, flags, frameNumber, outFrame, outOverscanInsets, outContentInsets, outVisibleInsets, outStableInsets, cutout, mergedConfiguration, outSurfaceControl, outInsetsState, - outSurfaceSize, outBLASTSurfaceControl); + outActiveControls, outSurfaceSize, outBLASTSurfaceControl); if (res != 0) { return res; } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt index 072bc446fd21..4bcf917fa95d 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt @@ -16,8 +16,12 @@ package com.android.keyguard +import android.app.Notification import android.graphics.drawable.Icon import android.media.MediaMetadata +import android.media.session.MediaController +import android.media.session.MediaSession +import android.media.session.PlaybackState import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View @@ -28,7 +32,9 @@ import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.media.MediaControllerFactory import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat @@ -38,6 +44,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.any import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever @@ -48,9 +55,12 @@ import org.mockito.Mockito.`when` as whenever public class KeyguardMediaPlayerTest : SysuiTestCase() { private lateinit var keyguardMediaPlayer: KeyguardMediaPlayer + @Mock private lateinit var mockMediaFactory: MediaControllerFactory + @Mock private lateinit var mockMediaController: MediaController + private lateinit var playbackState: PlaybackState private lateinit var fakeExecutor: FakeExecutor private lateinit var mediaMetadata: MediaMetadata.Builder - private lateinit var entry: NotificationEntryBuilder + private lateinit var entry: NotificationEntry @Mock private lateinit var mockView: View private lateinit var songView: TextView private lateinit var artistView: TextView @@ -70,8 +80,16 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() { @Before public fun setup() { + playbackState = PlaybackState.Builder().run { + build() + } + mockMediaController = mock(MediaController::class.java) + whenever(mockMediaController.getPlaybackState()).thenReturn(playbackState) + mockMediaFactory = mock(MediaControllerFactory::class.java) + whenever(mockMediaFactory.create(any())).thenReturn(mockMediaController) + fakeExecutor = FakeExecutor(FakeSystemClock()) - keyguardMediaPlayer = KeyguardMediaPlayer(context, fakeExecutor) + keyguardMediaPlayer = KeyguardMediaPlayer(context, mockMediaFactory, fakeExecutor) mockIcon = mock(Icon::class.java) mockView = mock(View::class.java) @@ -81,7 +99,9 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() { whenever<TextView>(mockView.findViewById(R.id.header_artist)).thenReturn(artistView) mediaMetadata = MediaMetadata.Builder() - entry = NotificationEntryBuilder() + entry = NotificationEntryBuilder().build() + entry.getSbn().getNotification().extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, + MediaSession.Token(1, null)) ArchTaskExecutor.getInstance().setDelegate(taskExecutor) @@ -109,7 +129,7 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() { @Test public fun testUpdateControls() { - keyguardMediaPlayer.updateControls(entry.build(), mockIcon, mediaMetadata.build()) + keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build()) FakeExecutor.exhaustExecutors(fakeExecutor) verify(mockView).setVisibility(View.VISIBLE) } @@ -122,11 +142,22 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() { } @Test + public fun testUpdateControlsNullPlaybackState() { + // GIVEN that the playback state is null (ie. the media session was destroyed) + whenever(mockMediaController.getPlaybackState()).thenReturn(null) + // WHEN updated + keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build()) + FakeExecutor.exhaustExecutors(fakeExecutor) + // THEN the controls are cleared (ie. visibility is set to GONE) + verify(mockView).setVisibility(View.GONE) + } + + @Test public fun testSongName() { val song: String = "Song" mediaMetadata.putText(MediaMetadata.METADATA_KEY_TITLE, song) - keyguardMediaPlayer.updateControls(entry.build(), mockIcon, mediaMetadata.build()) + keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build()) assertThat(fakeExecutor.runAllReady()).isEqualTo(1) assertThat(songView.getText()).isEqualTo(song) @@ -137,7 +168,7 @@ public class KeyguardMediaPlayerTest : SysuiTestCase() { val artist: String = "Artist" mediaMetadata.putText(MediaMetadata.METADATA_KEY_ARTIST, artist) - keyguardMediaPlayer.updateControls(entry.build(), mockIcon, mediaMetadata.build()) + keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build()) assertThat(fakeExecutor.runAllReady()).isEqualTo(1) assertThat(artistView.getText()).isEqualTo(artist) diff --git a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java index 475023e2506d..5227aaf01249 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java @@ -160,7 +160,7 @@ public class ImageWallpaperTest extends SysuiTestCase { LOW_BMP_HEIGHT /* bmpHeight */, LOW_BMP_WIDTH /* surfaceWidth */, LOW_BMP_HEIGHT /* surfaceHeight */, - true /* assertion */); + false /* assertion */); } @Test @@ -172,7 +172,7 @@ public class ImageWallpaperTest extends SysuiTestCase { INVALID_BMP_HEIGHT /* bmpHeight */, ImageWallpaper.GLEngine.MIN_SURFACE_WIDTH /* surfaceWidth */, ImageWallpaper.GLEngine.MIN_SURFACE_HEIGHT /* surfaceHeight */, - true /* assertion */); + false /* assertion */); } private void verifySurfaceSizeAndAssertTransition(int bmpWidth, int bmpHeight, diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java index c9bb4016c7bf..9985d21e8515 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java @@ -20,8 +20,10 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.hardware.display.AmbientDisplayConfiguration; import android.testing.AndroidTestingRunner; @@ -56,6 +58,7 @@ public class DozeDockHandlerTest extends SysuiTestCase { mDockManagerFake = spy(new DockManagerFake()); mDockHandler = new DozeDockHandler(mConfig, mMachine, mDockManagerFake); + when(mMachine.getState()).thenReturn(State.DOZE_AOD); doReturn(true).when(mConfig).alwaysOnEnabled(anyInt()); mDockHandler.transitionTo(DozeMachine.State.UNINITIALIZED, DozeMachine.State.INITIALIZED); } @@ -101,4 +104,31 @@ public class DozeDockHandlerTest extends SysuiTestCase { verify(mMachine).requestState(eq(State.DOZE)); } + + @Test + public void onEvent_dockedWhilePulsing_wontRequestStateChange() { + when(mMachine.getState()).thenReturn(State.DOZE_PULSING); + + mDockManagerFake.setDockEvent(DockManager.STATE_DOCKED); + + verify(mMachine, never()).requestState(any(State.class)); + } + + @Test + public void onEvent_noneWhilePulsing_wontRequestStateChange() { + when(mMachine.getState()).thenReturn(State.DOZE_PULSING); + + mDockManagerFake.setDockEvent(DockManager.STATE_NONE); + + verify(mMachine, never()).requestState(any(State.class)); + } + + @Test + public void onEvent_hideWhilePulsing_wontRequestStateChange() { + when(mMachine.getState()).thenReturn(State.DOZE_PULSING); + + mDockManagerFake.setDockEvent(DockManager.STATE_DOCKED_HIDE); + + verify(mMachine, never()).requestState(any(State.class)); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java index c483314918fc..1f07f46bf764 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java @@ -254,6 +254,17 @@ public class DozeMachineTest extends SysuiTestCase { } @Test + public void testPulseDone_whileDockedAoD_staysDockedAod() { + when(mDockManager.isDocked()).thenReturn(true); + mMachine.requestState(INITIALIZED); + mMachine.requestState(DOZE_AOD_DOCKED); + + mMachine.requestState(DOZE_PULSE_DONE); + + verify(mPartMock, never()).transitionTo(DOZE_AOD_DOCKED, DOZE_PULSE_DONE); + } + + @Test public void testPulseDone_dozeSuppressed_afterDocked_goesToDoze() { when(mHost.isDozeSuppressed()).thenReturn(true); when(mDockManager.isDocked()).thenReturn(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java index 0bf0f04d2d43..425bf88ebec0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java @@ -17,6 +17,7 @@ package com.android.systemui.pip; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.content.ComponentName; @@ -56,11 +57,15 @@ public class PipBoundsHandlerTest extends SysuiTestCase { private PipBoundsHandler mPipBoundsHandler; private DisplayInfo mDefaultDisplayInfo; + private ComponentName mTestComponentName1; + private ComponentName mTestComponentName2; @Before public void setUp() throws Exception { initializeMockResources(); mPipBoundsHandler = new PipBoundsHandler(mContext, new PipSnapAlgorithm(mContext)); + mTestComponentName1 = new ComponentName(mContext, "component1"); + mTestComponentName2 = new ComponentName(mContext, "component2"); mPipBoundsHandler.onDisplayInfoChanged(mDefaultDisplayInfo); } @@ -121,7 +126,7 @@ public class PipBoundsHandlerTest extends SysuiTestCase { }; for (float aspectRatio : aspectRatios) { final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds( - aspectRatio, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE); + mTestComponentName1, aspectRatio, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE); final float actualAspectRatio = destinationBounds.width() / (destinationBounds.height() * 1f); assertEquals("Destination bounds matches the given aspect ratio", @@ -137,7 +142,7 @@ public class PipBoundsHandlerTest extends SysuiTestCase { }; for (float aspectRatio : invalidAspectRatios) { final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds( - aspectRatio, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE); + mTestComponentName1, aspectRatio, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE); final float actualAspectRatio = destinationBounds.width() / (destinationBounds.height() * 1f); assertEquals("Destination bounds fallbacks to default aspect ratio", @@ -153,7 +158,7 @@ public class PipBoundsHandlerTest extends SysuiTestCase { currentBounds.right = (int) (currentBounds.height() * aspectRatio) + currentBounds.left; final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds( - aspectRatio, currentBounds, EMPTY_MINIMAL_SIZE); + mTestComponentName1, aspectRatio, currentBounds, EMPTY_MINIMAL_SIZE); final float actualAspectRatio = destinationBounds.width() / (destinationBounds.height() * 1f); @@ -177,7 +182,7 @@ public class PipBoundsHandlerTest extends SysuiTestCase { final float aspectRatio = aspectRatios[i]; final Size minimalSize = minimalSizes[i]; final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds( - aspectRatio, EMPTY_CURRENT_BOUNDS, minimalSize); + mTestComponentName1, aspectRatio, EMPTY_CURRENT_BOUNDS, minimalSize); assertTrue("Destination bounds is no smaller than minimal requirement", (destinationBounds.width() == minimalSize.getWidth() && destinationBounds.height() >= minimalSize.getHeight()) @@ -198,7 +203,7 @@ public class PipBoundsHandlerTest extends SysuiTestCase { final Size minSize = new Size(currentBounds.width() / 2, currentBounds.height() / 2); final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds( - aspectRatio, currentBounds, minSize); + mTestComponentName1, aspectRatio, currentBounds, minSize); assertTrue("Destination bounds ignores minimal size", destinationBounds.width() > minSize.getWidth() @@ -206,81 +211,92 @@ public class PipBoundsHandlerTest extends SysuiTestCase { } @Test + public void getDestinationBounds_withDifferentComponentName_ignoreLastPosition() { + final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1, + DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE); + + oldPosition.offset(0, -100); + mPipBoundsHandler.onSaveReentryBounds(mTestComponentName1, oldPosition); + + final Rect newPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName2, + DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE); + + assertNonBoundsInclusionWithMargin("ignore saved bounds", oldPosition, newPosition); + } + + @Test public void setShelfHeight_offsetBounds() { final int shelfHeight = 100; - final Rect oldPosition = mPipBoundsHandler.getDestinationBounds( + final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1, DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE); mPipBoundsHandler.setShelfHeight(true, shelfHeight); - final Rect newPosition = mPipBoundsHandler.getDestinationBounds( + final Rect newPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1, DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE); oldPosition.offset(0, -shelfHeight); - assertBoundsWithMargin("offsetBounds by shelf", oldPosition, newPosition); + assertBoundsInclusionWithMargin("offsetBounds by shelf", oldPosition, newPosition); } @Test public void onImeVisibilityChanged_offsetBounds() { final int imeHeight = 100; - final Rect oldPosition = mPipBoundsHandler.getDestinationBounds( + final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1, DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE); mPipBoundsHandler.onImeVisibilityChanged(true, imeHeight); - final Rect newPosition = mPipBoundsHandler.getDestinationBounds( + final Rect newPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1, DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE); oldPosition.offset(0, -imeHeight); - assertBoundsWithMargin("offsetBounds by IME", oldPosition, newPosition); + assertBoundsInclusionWithMargin("offsetBounds by IME", oldPosition, newPosition); } @Test public void onSaveReentryBounds_restoreLastPosition() { - final ComponentName componentName = new ComponentName(mContext, "component1"); - final Rect oldPosition = mPipBoundsHandler.getDestinationBounds( + final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1, DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE); oldPosition.offset(0, -100); - mPipBoundsHandler.onSaveReentryBounds(componentName, oldPosition); + mPipBoundsHandler.onSaveReentryBounds(mTestComponentName1, oldPosition); - final Rect newPosition = mPipBoundsHandler.getDestinationBounds( + final Rect newPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1, DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE); - assertBoundsWithMargin("restoreLastPosition", oldPosition, newPosition); + assertBoundsInclusionWithMargin("restoreLastPosition", oldPosition, newPosition); } @Test public void onResetReentryBounds_useDefaultBounds() { - final ComponentName componentName = new ComponentName(mContext, "component1"); - final Rect defaultBounds = mPipBoundsHandler.getDestinationBounds( + final Rect defaultBounds = mPipBoundsHandler.getDestinationBounds(mTestComponentName1, DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE); final Rect newBounds = new Rect(defaultBounds); newBounds.offset(0, -100); - mPipBoundsHandler.onSaveReentryBounds(componentName, newBounds); + mPipBoundsHandler.onSaveReentryBounds(mTestComponentName1, newBounds); - mPipBoundsHandler.onResetReentryBounds(componentName); - final Rect actualBounds = mPipBoundsHandler.getDestinationBounds( + mPipBoundsHandler.onResetReentryBounds(mTestComponentName1); + final Rect actualBounds = mPipBoundsHandler.getDestinationBounds(mTestComponentName1, DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE); - assertBoundsWithMargin("useDefaultBounds", defaultBounds, actualBounds); + assertBoundsInclusionWithMargin("useDefaultBounds", defaultBounds, actualBounds); } @Test public void onResetReentryBounds_componentMismatch_restoreLastPosition() { - final ComponentName componentName = new ComponentName(mContext, "component1"); - final Rect defaultBounds = mPipBoundsHandler.getDestinationBounds( + final Rect defaultBounds = mPipBoundsHandler.getDestinationBounds(mTestComponentName1, DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE); final Rect newBounds = new Rect(defaultBounds); newBounds.offset(0, -100); - mPipBoundsHandler.onSaveReentryBounds(componentName, newBounds); + mPipBoundsHandler.onSaveReentryBounds(mTestComponentName1, newBounds); - mPipBoundsHandler.onResetReentryBounds(new ComponentName(mContext, "component2")); - final Rect actualBounds = mPipBoundsHandler.getDestinationBounds( + mPipBoundsHandler.onResetReentryBounds(mTestComponentName2); + final Rect actualBounds = mPipBoundsHandler.getDestinationBounds(mTestComponentName1, DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE); - assertBoundsWithMargin("restoreLastPosition", newBounds, actualBounds); + assertBoundsInclusionWithMargin("restoreLastPosition", newBounds, actualBounds); } - private void assertBoundsWithMargin(String from, Rect expected, Rect actual) { + private void assertBoundsInclusionWithMargin(String from, Rect expected, Rect actual) { final Rect expectedWithMargin = new Rect(expected); expectedWithMargin.inset(-ROUNDING_ERROR_MARGIN, -ROUNDING_ERROR_MARGIN); assertTrue(from + ": expect " + expected @@ -288,4 +304,13 @@ public class PipBoundsHandlerTest extends SysuiTestCase { + " with error margin " + ROUNDING_ERROR_MARGIN, expectedWithMargin.contains(actual)); } + + private void assertNonBoundsInclusionWithMargin(String from, Rect expected, Rect actual) { + final Rect expectedWithMargin = new Rect(expected); + expectedWithMargin.inset(-ROUNDING_ERROR_MARGIN, -ROUNDING_ERROR_MARGIN); + assertFalse(from + ": expect " + expected + + " not contains " + actual + + " with error margin " + ROUNDING_ERROR_MARGIN, + expectedWithMargin.contains(actual)); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt index 6b7a3bfce5ad..c874915e9124 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt @@ -117,12 +117,27 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() { } @Test - fun updateGlobalDialogVisibility_appliesBlur() { + fun updateGlobalDialogVisibility_animatesBlur() { notificationShadeDepthController.updateGlobalDialogVisibility(0.5f, root) verify(globalActionsSpring).animateTo(eq(maxBlur / 2), safeEq(root)) } @Test + fun updateGlobalDialogVisibility_appliesBlur_withoutHomeControls() { + `when`(globalActionsSpring.radius).thenReturn(maxBlur) + notificationShadeDepthController.updateBlurCallback.doFrame(0) + verify(blurUtils).applyBlur(any(), eq(maxBlur)) + } + + @Test + fun updateGlobalDialogVisibility_appliesBlur_unlessHomeControls() { + notificationShadeDepthController.showingHomeControls = true + `when`(globalActionsSpring.radius).thenReturn(maxBlur) + notificationShadeDepthController.updateBlurCallback.doFrame(0) + verify(blurUtils).applyBlur(any(), eq(0)) + } + + @Test fun updateBlurCallback_setsBlurAndZoom() { notificationShadeDepthController.updateBlurCallback.doFrame(0) verify(wallpaperManager).setWallpaperZoomOut(any(), anyFloat()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/magnetictarget/MagnetizedObjectTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/magnetictarget/MagnetizedObjectTest.kt index f1672b1c644d..f6b7b74d4bfc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/magnetictarget/MagnetizedObjectTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/magnetictarget/MagnetizedObjectTest.kt @@ -106,6 +106,10 @@ class MagnetizedObjectTest : SysuiTestCase() { location[1] = targetCenterY - targetSize / 2 // y = 800 } }.`when`(targetView).getLocationOnScreen(ArgumentMatchers.any()) + doAnswer { invocation -> + (invocation.arguments[0] as Runnable).run() + true + }.`when`(targetView).post(ArgumentMatchers.any()) `when`(targetView.context).thenReturn(context) magneticTarget = MagnetizedObject.MagneticTarget(targetView, magneticFieldRadius) @@ -408,6 +412,10 @@ class MagnetizedObjectTest : SysuiTestCase() { `when`(secondTargetView.width).thenReturn(targetSize) // width = 200 `when`(secondTargetView.height).thenReturn(targetSize) // height = 200 doAnswer { invocation -> + (invocation.arguments[0] as Runnable).run() + true + }.`when`(secondTargetView).post(ArgumentMatchers.any()) + doAnswer { invocation -> (invocation.arguments[0] as IntArray).also { location -> // Return the top left of the target. location[0] = secondTargetCenterX - targetSize / 2 // x = 0 diff --git a/packages/Tethering/tests/integration/Android.bp b/packages/Tethering/tests/integration/Android.bp index 1a1c30d1d5f9..620261b375d2 100644 --- a/packages/Tethering/tests/integration/Android.bp +++ b/packages/Tethering/tests/integration/Android.bp @@ -39,4 +39,9 @@ android_test { "android.test.base", "android.test.mock", ], + jni_libs: [ + // For mockito extended + "libdexmakerjvmtiagent", + "libstaticjvmtiagent", + ], } diff --git a/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java index dbd68ef77cb7..b02bb23f9807 100644 --- a/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java +++ b/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java @@ -205,7 +205,7 @@ public class EthernetTetheringTest { requestWithStaticIpv4(localAddr, clientAddr)); mTetheringEventCallback.awaitInterfaceTethered(); - assertInterfaceHasIpAddress(iface, clientAddr); + assertInterfaceHasIpAddress(iface, localAddr); byte[] client1 = MacAddress.fromString("1:2:3:4:5:6").toByteArray(); byte[] client2 = MacAddress.fromString("a:b:c:d:e:f").toByteArray(); diff --git a/packages/Tethering/tests/unit/AndroidManifest.xml b/packages/Tethering/tests/unit/AndroidManifest.xml index 530bc0788a78..4ff1d3777f25 100644 --- a/packages/Tethering/tests/unit/AndroidManifest.xml +++ b/packages/Tethering/tests/unit/AndroidManifest.xml @@ -20,7 +20,16 @@ <application android:debuggable="true"> <uses-library android:name="android.test.runner" /> + <service + android:name="com.android.server.connectivity.tethering.MockTetheringService" + android:permission="android.permission.TETHER_PRIVILEGED" + android:exported="true"> + <intent-filter> + <action android:name="com.android.server.connectivity.tethering.TetheringService"/> + </intent-filter> + </service> </application> + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" android:targetPackage="com.android.networkstack.tethering.tests.unit" android:label="Tethering service tests"> diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/MockTetheringService.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/MockTetheringService.java new file mode 100644 index 000000000000..355ece9a44a1 --- /dev/null +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/MockTetheringService.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2020 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.connectivity.tethering; + +import static org.mockito.Mockito.mock; + +import android.content.Intent; +import android.net.ITetheringConnector; +import android.os.Binder; +import android.os.IBinder; + +public class MockTetheringService extends TetheringService { + private final Tethering mTethering = mock(Tethering.class); + + @Override + public IBinder onBind(Intent intent) { + return new MockTetheringConnector(super.onBind(intent)); + } + + @Override + public Tethering makeTethering(TetheringDependencies deps) { + return mTethering; + } + + public Tethering getTethering() { + return mTethering; + } + + public class MockTetheringConnector extends Binder { + final IBinder mBase; + MockTetheringConnector(IBinder base) { + mBase = base; + } + + public ITetheringConnector getTetheringConnector() { + return ITetheringConnector.Stub.asInterface(mBase); + } + + public MockTetheringService getService() { + return MockTetheringService.this; + } + } +} diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringServiceTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringServiceTest.java new file mode 100644 index 000000000000..d9d3e73eb4e3 --- /dev/null +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringServiceTest.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2020 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.connectivity.tethering; + +import static android.net.TetheringManager.TETHERING_WIFI; +import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.content.Intent; +import android.net.IIntResultListener; +import android.net.ITetheringConnector; +import android.net.ITetheringEventCallback; +import android.net.TetheringRequestParcel; +import android.os.ResultReceiver; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.rule.ServiceTestRule; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.connectivity.tethering.MockTetheringService.MockTetheringConnector; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public final class TetheringServiceTest { + private static final String TEST_IFACE_NAME = "test_wlan0"; + private static final String TEST_CALLER_PKG = "test_pkg"; + @Mock private ITetheringEventCallback mITetheringEventCallback; + @Rule public ServiceTestRule mServiceTestRule; + private Tethering mTethering; + private Intent mMockServiceIntent; + private ITetheringConnector mTetheringConnector; + + private class TestTetheringResult extends IIntResultListener.Stub { + private int mResult = -1; // Default value that does not match any result code. + @Override + public void onResult(final int resultCode) { + mResult = resultCode; + } + + public void assertResult(final int expected) { + assertEquals(expected, mResult); + } + } + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mServiceTestRule = new ServiceTestRule(); + mMockServiceIntent = new Intent( + InstrumentationRegistry.getTargetContext(), + MockTetheringService.class); + final MockTetheringConnector mockConnector = + (MockTetheringConnector) mServiceTestRule.bindService(mMockServiceIntent); + mTetheringConnector = mockConnector.getTetheringConnector(); + final MockTetheringService service = mockConnector.getService(); + mTethering = service.getTethering(); + verify(mTethering).startStateMachineUpdaters(); + when(mTethering.hasTetherableConfiguration()).thenReturn(true); + } + + @After + public void tearDown() throws Exception { + mServiceTestRule.unbindService(); + } + + @Test + public void testTether() throws Exception { + when(mTethering.tether(TEST_IFACE_NAME)).thenReturn(TETHER_ERROR_NO_ERROR); + final TestTetheringResult result = new TestTetheringResult(); + mTetheringConnector.tether(TEST_IFACE_NAME, TEST_CALLER_PKG, result); + verify(mTethering).hasTetherableConfiguration(); + verify(mTethering).tether(TEST_IFACE_NAME); + verifyNoMoreInteractions(mTethering); + result.assertResult(TETHER_ERROR_NO_ERROR); + } + + @Test + public void testUntether() throws Exception { + when(mTethering.untether(TEST_IFACE_NAME)).thenReturn(TETHER_ERROR_NO_ERROR); + final TestTetheringResult result = new TestTetheringResult(); + mTetheringConnector.untether(TEST_IFACE_NAME, TEST_CALLER_PKG, result); + verify(mTethering).hasTetherableConfiguration(); + verify(mTethering).untether(TEST_IFACE_NAME); + verifyNoMoreInteractions(mTethering); + result.assertResult(TETHER_ERROR_NO_ERROR); + } + + @Test + public void testSetUsbTethering() throws Exception { + when(mTethering.setUsbTethering(true /* enable */)).thenReturn(TETHER_ERROR_NO_ERROR); + final TestTetheringResult result = new TestTetheringResult(); + mTetheringConnector.setUsbTethering(true /* enable */, TEST_CALLER_PKG, result); + verify(mTethering).hasTetherableConfiguration(); + verify(mTethering).setUsbTethering(true /* enable */); + verifyNoMoreInteractions(mTethering); + result.assertResult(TETHER_ERROR_NO_ERROR); + } + + @Test + public void testStartTethering() throws Exception { + final TestTetheringResult result = new TestTetheringResult(); + final TetheringRequestParcel request = new TetheringRequestParcel(); + request.tetheringType = TETHERING_WIFI; + mTetheringConnector.startTethering(request, TEST_CALLER_PKG, result); + verify(mTethering).hasTetherableConfiguration(); + verify(mTethering).startTethering(eq(request), eq(result)); + verifyNoMoreInteractions(mTethering); + } + + @Test + public void testStopTethering() throws Exception { + final TestTetheringResult result = new TestTetheringResult(); + mTetheringConnector.stopTethering(TETHERING_WIFI, TEST_CALLER_PKG, result); + verify(mTethering).hasTetherableConfiguration(); + verify(mTethering).stopTethering(TETHERING_WIFI); + verifyNoMoreInteractions(mTethering); + result.assertResult(TETHER_ERROR_NO_ERROR); + } + + @Test + public void testRequestLatestTetheringEntitlementResult() throws Exception { + final ResultReceiver result = new ResultReceiver(null); + mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, result, + true /* showEntitlementUi */, TEST_CALLER_PKG); + verify(mTethering).hasTetherableConfiguration(); + verify(mTethering).requestLatestTetheringEntitlementResult(eq(TETHERING_WIFI), + eq(result), eq(true) /* showEntitlementUi */); + verifyNoMoreInteractions(mTethering); + } + + @Test + public void testRegisterTetheringEventCallback() throws Exception { + mTetheringConnector.registerTetheringEventCallback(mITetheringEventCallback, + TEST_CALLER_PKG); + verify(mTethering).registerTetheringEventCallback(eq(mITetheringEventCallback)); + verifyNoMoreInteractions(mTethering); + } + + @Test + public void testUnregisterTetheringEventCallback() throws Exception { + mTetheringConnector.unregisterTetheringEventCallback(mITetheringEventCallback, + TEST_CALLER_PKG); + verify(mTethering).unregisterTetheringEventCallback( + eq(mITetheringEventCallback)); + verifyNoMoreInteractions(mTethering); + } + + @Test + public void testStopAllTethering() throws Exception { + final TestTetheringResult result = new TestTetheringResult(); + mTetheringConnector.stopAllTethering(TEST_CALLER_PKG, result); + verify(mTethering).hasTetherableConfiguration(); + verify(mTethering).untetherAll(); + verifyNoMoreInteractions(mTethering); + result.assertResult(TETHER_ERROR_NO_ERROR); + } + + @Test + public void testIsTetheringSupported() throws Exception { + final TestTetheringResult result = new TestTetheringResult(); + mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, result); + verify(mTethering).hasTetherableConfiguration(); + verifyNoMoreInteractions(mTethering); + result.assertResult(TETHER_ERROR_NO_ERROR); + } +} diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java index a59c6fd9e193..3a580dd8e5bd 100644 --- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java @@ -140,7 +140,9 @@ import com.android.networkstack.tethering.R; import com.android.testutils.MiscAssertsKt; import org.junit.After; +import org.junit.AfterClass; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -439,6 +441,18 @@ public class TetheringTest { return buildMobileUpstreamState(false, true, true); } + // See FakeSettingsProvider#clearSettingsProvider() that this needs to be called before and + // after use. + @BeforeClass + public static void setupOnce() { + FakeSettingsProvider.clearSettingsProvider(); + } + + @AfterClass + public static void tearDownOnce() { + FakeSettingsProvider.clearSettingsProvider(); + } + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 2100c1a8be44..7230b00f87ad 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -413,7 +413,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub && component.getPackageName().equals(packageName)) || userState.mCrashedServices.removeIf(component -> component != null && component.getPackageName().equals(packageName)); - if (reboundAService) { + // Reloads the installed services info to make sure the rebound service could + // get a new one. + userState.mInstalledServices.clear(); + final boolean configurationChanged = + readConfigurationForUserStateLocked(userState); + if (reboundAService || configurationChanged) { onUserStateChangedLocked(userState); } migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, packageName); diff --git a/services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java b/services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java new file mode 100644 index 000000000000..3612e093c8bd --- /dev/null +++ b/services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2020 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.autofill; + +import android.annotation.NonNull; +import android.content.ComponentName; +import android.os.Bundle; +import android.os.Handler; +import android.view.autofill.AutofillId; +import android.view.inputmethod.InlineSuggestionsRequest; +import android.view.inputmethod.InlineSuggestionsResponse; + +import com.android.internal.annotations.GuardedBy; +import com.android.server.inputmethod.InputMethodManagerInternal; + +import java.util.Collections; +import java.util.Optional; +import java.util.function.Consumer; + + +/** + * Controls the interaction with the IME for the inline suggestion sessions. + */ +final class AutofillInlineSessionController { + @NonNull + private final InputMethodManagerInternal mInputMethodManagerInternal; + private final int mUserId; + @NonNull + private final ComponentName mComponentName; + @NonNull + private final Object mLock; + @NonNull + private final Handler mHandler; + + @GuardedBy("mLock") + private AutofillInlineSuggestionsRequestSession mSession; + + AutofillInlineSessionController(InputMethodManagerInternal inputMethodManagerInternal, + int userId, ComponentName componentName, Handler handler, Object lock) { + mInputMethodManagerInternal = inputMethodManagerInternal; + mUserId = userId; + mComponentName = componentName; + mHandler = handler; + mLock = lock; + } + + + /** + * Requests the IME to create an {@link InlineSuggestionsRequest} for {@code autofillId}. + * + * @param autofillId the Id of the field for which the request is for. + * @param requestConsumer the callback which will be invoked when IME responded or if it times + * out waiting for IME response. + */ + @GuardedBy("mLock") + void onCreateInlineSuggestionsRequestLocked(@NonNull AutofillId autofillId, + @NonNull Consumer<InlineSuggestionsRequest> requestConsumer, @NonNull Bundle uiExtras) { + // TODO(b/151123764): rename the method to better reflect what it does. + if (mSession != null) { + // Send an empty response to IME and destroy the existing session. + mSession.onInlineSuggestionsResponseLocked(mSession.getAutofillIdLocked(), + new InlineSuggestionsResponse(Collections.EMPTY_LIST)); + mSession.destroySessionLocked(); + } + // TODO(b/151123764): consider reusing the same AutofillInlineSession object for the + // same field. + mSession = new AutofillInlineSuggestionsRequestSession(mInputMethodManagerInternal, mUserId, + mComponentName, mHandler, mLock, autofillId, requestConsumer, uiExtras); + mSession.onCreateInlineSuggestionsRequestLocked(); + + } + + /** + * Returns the {@link InlineSuggestionsRequest} provided by IME for the last request. + * + * <p> The caller is responsible for making sure Autofill hears back from IME before calling + * this method, using the {@code requestConsumer} provided when calling {@link + * #onCreateInlineSuggestionsRequestLocked(AutofillId, Consumer, Bundle)}. + */ + @GuardedBy("mLock") + Optional<InlineSuggestionsRequest> getInlineSuggestionsRequestLocked() { + if (mSession != null) { + return mSession.getInlineSuggestionsRequestLocked(); + } + return Optional.empty(); + } + + /** + * Requests the IME to hide the current suggestions, if any. Returns true if the message is sent + * to the IME. + */ + @GuardedBy("mLock") + boolean hideInlineSuggestionsUiLocked(@NonNull AutofillId autofillId) { + if (mSession != null) { + return mSession.onInlineSuggestionsResponseLocked(autofillId, + new InlineSuggestionsResponse(Collections.EMPTY_LIST)); + } + return false; + } + + /** + * Requests showing the inline suggestion in the IME when the IME becomes visible and is focused + * on the {@code autofillId}. + * + * @return false if there is no session, or if the IME callback is not available in the session. + */ + @GuardedBy("mLock") + boolean onInlineSuggestionsResponseLocked(@NonNull AutofillId autofillId, + @NonNull InlineSuggestionsResponse inlineSuggestionsResponse) { + // TODO(b/151123764): rename the method to better reflect what it does. + if (mSession != null) { + return mSession.onInlineSuggestionsResponseLocked(autofillId, + inlineSuggestionsResponse); + } + return false; + } +} diff --git a/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java new file mode 100644 index 000000000000..ca230b6936ff --- /dev/null +++ b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java @@ -0,0 +1,419 @@ +/* + * Copyright (C) 2020 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.autofill; + +import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; +import static com.android.server.autofill.Helper.sDebug; + +import android.annotation.BinderThread; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.os.Bundle; +import android.os.Handler; +import android.os.RemoteException; +import android.util.Log; +import android.util.Slog; +import android.view.autofill.AutofillId; +import android.view.inputmethod.InlineSuggestionsRequest; +import android.view.inputmethod.InlineSuggestionsResponse; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.view.IInlineSuggestionsRequestCallback; +import com.android.internal.view.IInlineSuggestionsResponseCallback; +import com.android.internal.view.InlineSuggestionsRequestInfo; +import com.android.server.inputmethod.InputMethodManagerInternal; + +import java.lang.ref.WeakReference; +import java.util.Optional; +import java.util.function.Consumer; + +/** + * Maintains an inline suggestion session with the IME. + * + * <p> Each session corresponds to one request from the Autofill manager service to create an + * {@link InlineSuggestionsRequest}. It's responsible for receiving callbacks from the IME and + * sending {@link android.view.inputmethod.InlineSuggestionsResponse} to IME. + */ +final class AutofillInlineSuggestionsRequestSession { + + private static final String TAG = AutofillInlineSuggestionsRequestSession.class.getSimpleName(); + private static final int INLINE_REQUEST_TIMEOUT_MS = 200; + + @NonNull + private final InputMethodManagerInternal mInputMethodManagerInternal; + private final int mUserId; + @NonNull + private final ComponentName mComponentName; + @NonNull + private final Object mLock; + @NonNull + private final Handler mHandler; + @NonNull + private final Bundle mUiExtras; + + @GuardedBy("mLock") + @NonNull + private AutofillId mAutofillId; + @GuardedBy("mLock") + @Nullable + private Consumer<InlineSuggestionsRequest> mImeRequestConsumer; + + @GuardedBy("mLock") + private boolean mImeRequestReceived; + @GuardedBy("mLock") + @Nullable + private InlineSuggestionsRequest mImeRequest; + @GuardedBy("mLock") + @Nullable + private IInlineSuggestionsResponseCallback mResponseCallback; + @GuardedBy("mLock") + @Nullable + private Runnable mTimeoutCallback; + + @GuardedBy("mLock") + @Nullable + private AutofillId mImeCurrentFieldId; + @GuardedBy("mLock") + private boolean mImeInputStarted; + @GuardedBy("mLock") + private boolean mImeInputViewStarted; + @GuardedBy("mLock") + @Nullable + private InlineSuggestionsResponse mInlineSuggestionsResponse; + @GuardedBy("mLock") + private boolean mPreviousResponseIsNotEmpty; + + @GuardedBy("mLock") + private boolean mDestroyed = false; + + AutofillInlineSuggestionsRequestSession( + @NonNull InputMethodManagerInternal inputMethodManagerInternal, int userId, + @NonNull ComponentName componentName, @NonNull Handler handler, @NonNull Object lock, + @NonNull AutofillId autofillId, + @NonNull Consumer<InlineSuggestionsRequest> requestConsumer, @NonNull Bundle uiExtras) { + mInputMethodManagerInternal = inputMethodManagerInternal; + mUserId = userId; + mComponentName = componentName; + mHandler = handler; + mLock = lock; + mUiExtras = uiExtras; + + mAutofillId = autofillId; + mImeRequestConsumer = requestConsumer; + } + + @GuardedBy("mLock") + @NonNull + AutofillId getAutofillIdLocked() { + return mAutofillId; + } + + /** + * Returns the {@link InlineSuggestionsRequest} provided by IME. + * + * <p> The caller is responsible for making sure Autofill hears back from IME before calling + * this method, using the {@link #mImeRequestConsumer}. + */ + @GuardedBy("mLock") + Optional<InlineSuggestionsRequest> getInlineSuggestionsRequestLocked() { + if (mDestroyed) { + return Optional.empty(); + } + return Optional.ofNullable(mImeRequest); + } + + /** + * Requests showing the inline suggestion in the IME when the IME becomes visible and is focused + * on the {@code autofillId}. + * + * @return false if the IME callback is not available. + */ + @GuardedBy("mLock") + boolean onInlineSuggestionsResponseLocked(@NonNull AutofillId autofillId, + @NonNull InlineSuggestionsResponse inlineSuggestionsResponse) { + if (mDestroyed) { + return false; + } + if (sDebug) Log.d(TAG, "onInlineSuggestionsResponseLocked called for:" + autofillId); + if (mImeRequest == null || mResponseCallback == null) { + return false; + } + // TODO(b/151123764): each session should only correspond to one field. + mAutofillId = autofillId; + mInlineSuggestionsResponse = inlineSuggestionsResponse; + maybeUpdateResponseToImeLocked(); + return true; + } + + /** + * This method must be called when the session is destroyed, to avoid further callbacks from/to + * the IME. + */ + @GuardedBy("mLock") + void destroySessionLocked() { + mDestroyed = true; + } + + /** + * Requests the IME to create an {@link InlineSuggestionsRequest}. + * + * <p> This method should only be called once per session. + */ + @GuardedBy("mLock") + void onCreateInlineSuggestionsRequestLocked() { + if (sDebug) Log.d(TAG, "onCreateInlineSuggestionsRequestLocked called: " + mAutofillId); + if (mDestroyed) { + return; + } + mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(mUserId, + new InlineSuggestionsRequestInfo(mComponentName, mAutofillId, mUiExtras), + new InlineSuggestionsRequestCallbackImpl(this)); + mTimeoutCallback = () -> { + Log.w(TAG, "Timed out waiting for IME callback InlineSuggestionsRequest."); + handleOnReceiveImeRequest(null, null); + }; + mHandler.postDelayed(mTimeoutCallback, INLINE_REQUEST_TIMEOUT_MS); + } + + /** + * Optionally sends inline response to the IME, depending on the current state. + */ + @GuardedBy("mLock") + private void maybeUpdateResponseToImeLocked() { + if (sDebug) Log.d(TAG, "maybeUpdateResponseToImeLocked called"); + if (mDestroyed || mResponseCallback == null) { + return; + } + if (!mImeInputViewStarted && mPreviousResponseIsNotEmpty) { + // 1. if previous response is not empty, and IME just become invisible, then send + // empty response to make sure existing responses don't stick around on the IME. + // Although the inline suggestions should disappear when IME hides which removes them + // from the view hierarchy, but we still send an empty response to be extra safe. + + // TODO(b/149945531): clear the existing suggestions when IME is hide, once the bug is + // fixed. + //if (sDebug) Log.d(TAG, "Send empty inline response"); + //updateResponseToImeUncheckLocked(new InlineSuggestionsResponse(Collections + // .EMPTY_LIST)); + //mPreviousResponseIsNotEmpty = false; + } else if (mImeInputViewStarted && mInlineSuggestionsResponse != null && match(mAutofillId, + mImeCurrentFieldId)) { + // 2. if IME is visible, and response is not null, send the response + boolean isEmptyResponse = mInlineSuggestionsResponse.getInlineSuggestions().isEmpty(); + if (isEmptyResponse && !mPreviousResponseIsNotEmpty) { + // No-op if both the previous response and current response are empty. + return; + } + if (sDebug) { + Log.d(TAG, "Send inline response: " + + mInlineSuggestionsResponse.getInlineSuggestions().size()); + } + updateResponseToImeUncheckLocked(mInlineSuggestionsResponse); + // TODO(b/149945531): don't set the response to null so it's cached, once the bug is + // fixed. + mInlineSuggestionsResponse = null; + mPreviousResponseIsNotEmpty = !isEmptyResponse; + } + } + + /** + * Sends the {@code response} to the IME, assuming all the relevant checks are already done. + */ + @GuardedBy("mLock") + private void updateResponseToImeUncheckLocked(InlineSuggestionsResponse response) { + if (mDestroyed) { + return; + } + try { + mResponseCallback.onInlineSuggestionsResponse(mAutofillId, response); + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException sending InlineSuggestionsResponse to IME"); + } + } + + /** + * Handles the {@code request} and {@code callback} received from the IME. + * + * <p> Should only invoked in the {@link #mHandler} thread. + */ + private void handleOnReceiveImeRequest(@Nullable InlineSuggestionsRequest request, + @Nullable IInlineSuggestionsResponseCallback callback) { + synchronized (mLock) { + if (mDestroyed || mImeRequestReceived) { + return; + } + mImeRequestReceived = true; + + if (mTimeoutCallback != null) { + if (sDebug) Log.d(TAG, "removing timeout callback"); + mHandler.removeCallbacks(mTimeoutCallback); + mTimeoutCallback = null; + } + if (request != null && callback != null) { + mImeRequest = request; + mResponseCallback = callback; + handleOnReceiveImeStatusUpdated(mAutofillId, true, false); + } + if (mImeRequestConsumer != null) { + // Note that mImeRequest is only set if both request and callback are non-null. + mImeRequestConsumer.accept(mImeRequest); + mImeRequestConsumer = null; + } + } + } + + /** + * Handles the IME status updates received from the IME. + * + * <p> Should only be invoked in the {@link #mHandler} thread. + */ + private void handleOnReceiveImeStatusUpdated(boolean imeInputStarted, + boolean imeInputViewStarted) { + synchronized (mLock) { + if (mDestroyed) { + return; + } + if (mImeCurrentFieldId != null) { + boolean imeInputStartedChanged = (mImeInputStarted != imeInputStarted); + boolean imeInputViewStartedChanged = (mImeInputViewStarted != imeInputViewStarted); + mImeInputStarted = imeInputStarted; + mImeInputViewStarted = imeInputViewStarted; + if (imeInputStartedChanged || imeInputViewStartedChanged) { + maybeUpdateResponseToImeLocked(); + } + } + } + } + + /** + * Handles the IME status updates received from the IME. + * + * <p> Should only be invoked in the {@link #mHandler} thread. + */ + private void handleOnReceiveImeStatusUpdated(@Nullable AutofillId imeFieldId, + boolean imeInputStarted, boolean imeInputViewStarted) { + synchronized (mLock) { + if (mDestroyed) { + return; + } + if (imeFieldId != null) { + mImeCurrentFieldId = imeFieldId; + } + handleOnReceiveImeStatusUpdated(imeInputStarted, imeInputViewStarted); + } + } + + private static final class InlineSuggestionsRequestCallbackImpl extends + IInlineSuggestionsRequestCallback.Stub { + + private final WeakReference<AutofillInlineSuggestionsRequestSession> mSession; + + private InlineSuggestionsRequestCallbackImpl( + AutofillInlineSuggestionsRequestSession session) { + mSession = new WeakReference<>(session); + } + + @BinderThread + @Override + public void onInlineSuggestionsUnsupported() throws RemoteException { + if (sDebug) Log.d(TAG, "onInlineSuggestionsUnsupported() called."); + final AutofillInlineSuggestionsRequestSession session = mSession.get(); + if (session != null) { + session.mHandler.sendMessage(obtainMessage( + AutofillInlineSuggestionsRequestSession::handleOnReceiveImeRequest, session, + null, null)); + } + } + + @BinderThread + @Override + public void onInlineSuggestionsRequest(InlineSuggestionsRequest request, + IInlineSuggestionsResponseCallback callback) { + if (sDebug) Log.d(TAG, "onInlineSuggestionsRequest() received: " + request); + final AutofillInlineSuggestionsRequestSession session = mSession.get(); + if (session != null) { + session.mHandler.sendMessage(obtainMessage( + AutofillInlineSuggestionsRequestSession::handleOnReceiveImeRequest, session, + request, callback)); + } + } + + @Override + public void onInputMethodStartInput(AutofillId imeFieldId) throws RemoteException { + if (sDebug) Log.d(TAG, "onInputMethodStartInput() received on " + imeFieldId); + final AutofillInlineSuggestionsRequestSession session = mSession.get(); + if (session != null) { + session.mHandler.sendMessage(obtainMessage( + AutofillInlineSuggestionsRequestSession::handleOnReceiveImeStatusUpdated, + session, imeFieldId, true, false)); + } + } + + @Override + public void onInputMethodShowInputRequested(boolean requestResult) throws RemoteException { + if (sDebug) { + Log.d(TAG, "onInputMethodShowInputRequested() received: " + requestResult); + } + } + + @BinderThread + @Override + public void onInputMethodStartInputView() { + if (sDebug) Log.d(TAG, "onInputMethodStartInputView() received"); + final AutofillInlineSuggestionsRequestSession session = mSession.get(); + if (session != null) { + session.mHandler.sendMessage(obtainMessage( + AutofillInlineSuggestionsRequestSession::handleOnReceiveImeStatusUpdated, + session, true, true)); + } + } + + @BinderThread + @Override + public void onInputMethodFinishInputView() { + if (sDebug) Log.d(TAG, "onInputMethodFinishInputView() received"); + final AutofillInlineSuggestionsRequestSession session = mSession.get(); + if (session != null) { + session.mHandler.sendMessage(obtainMessage( + AutofillInlineSuggestionsRequestSession::handleOnReceiveImeStatusUpdated, + session, true, false)); + } + } + + @Override + public void onInputMethodFinishInput() throws RemoteException { + if (sDebug) Log.d(TAG, "onInputMethodFinishInput() received"); + final AutofillInlineSuggestionsRequestSession session = mSession.get(); + if (session != null) { + session.mHandler.sendMessage(obtainMessage( + AutofillInlineSuggestionsRequestSession::handleOnReceiveImeStatusUpdated, + session, false, false)); + } + } + } + + private static boolean match(@Nullable AutofillId autofillId, + @Nullable AutofillId imeClientFieldId) { + // The IME doesn't have information about the virtual view id for the child views in the + // web view, so we are only comparing the parent view id here. This means that for cases + // where there are two input fields in the web view, they will have the same view id + // (although different virtual child id), and we will not be able to distinguish them. + return autofillId != null && imeClientFieldId != null + && autofillId.getViewId() == imeClientFieldId.getViewId(); + } +} diff --git a/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java b/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java deleted file mode 100644 index e2d511212a71..000000000000 --- a/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java +++ /dev/null @@ -1,403 +0,0 @@ -/* - * Copyright (C) 2020 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.autofill; - -import static com.android.server.autofill.Helper.sDebug; - -import android.annotation.BinderThread; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.ComponentName; -import android.os.Bundle; -import android.os.Handler; -import android.os.RemoteException; -import android.util.Log; -import android.util.Slog; -import android.view.autofill.AutofillId; -import android.view.inputmethod.InlineSuggestionsRequest; -import android.view.inputmethod.InlineSuggestionsResponse; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.view.IInlineSuggestionsRequestCallback; -import com.android.internal.view.IInlineSuggestionsResponseCallback; -import com.android.internal.view.InlineSuggestionsRequestInfo; -import com.android.server.inputmethod.InputMethodManagerInternal; - -import java.util.Collections; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.function.Consumer; - -/** - * Maintains an autofill inline suggestion session that communicates with the IME. - * - * <p> - * The same session may be reused for multiple input fields involved in the same autofill - * {@link Session}. Therefore, one {@link InlineSuggestionsRequest} and one - * {@link IInlineSuggestionsResponseCallback} may be used to generate and callback with inline - * suggestions for different input fields. - * - * <p> - * This class is the sole place in Autofill responsible for directly communicating with the IME. It - * receives the IME input view start/finish events, with the associated IME field Id. It uses the - * information to decide when to send the {@link InlineSuggestionsResponse} to IME. As a result, - * some of the response will be cached locally and only be sent when the IME is ready to show them. - * - * <p> - * See {@link android.inputmethodservice.InlineSuggestionSession} comments for InputMethodService - * side flow. - * - * <p> - * This class should hold the same lock as {@link Session} as they call into each other. - */ -final class InlineSuggestionSession { - - private static final String TAG = "AfInlineSuggestionSession"; - private static final int INLINE_REQUEST_TIMEOUT_MS = 200; - - @NonNull - private final InputMethodManagerInternal mInputMethodManagerInternal; - private final int mUserId; - @NonNull - private final ComponentName mComponentName; - @NonNull - private final Object mLock; - @NonNull - private final ImeStatusListener mImeStatusListener; - @NonNull - private final Handler mHandler; - - /** - * To avoid the race condition, one should not access {@code mPendingImeResponse} without - * holding the {@code mLock}. For consuming the existing value, tt's recommended to use - * {@link #getPendingImeResponse()} to get a copy of the reference to avoid blocking call. - */ - @GuardedBy("mLock") - @Nullable - private CompletableFuture<ImeResponse> mPendingImeResponse; - - @GuardedBy("mLock") - @Nullable - private AutofillResponse mPendingAutofillResponse; - - @GuardedBy("mLock") - private boolean mIsLastResponseNonEmpty = false; - - @Nullable - @GuardedBy("mLock") - private AutofillId mImeFieldId = null; - - @GuardedBy("mLock") - private boolean mImeInputViewStarted = false; - - InlineSuggestionSession(InputMethodManagerInternal inputMethodManagerInternal, - int userId, ComponentName componentName, Handler handler, Object lock) { - mInputMethodManagerInternal = inputMethodManagerInternal; - mUserId = userId; - mComponentName = componentName; - mHandler = handler; - mLock = lock; - mImeStatusListener = new ImeStatusListener() { - @Override - public void onInputMethodStartInput(AutofillId imeFieldId) { - synchronized (mLock) { - mImeFieldId = imeFieldId; - mImeInputViewStarted = false; - } - } - - @Override - public void onInputMethodStartInputView() { - synchronized (mLock) { - mImeInputViewStarted = true; - AutofillResponse pendingAutofillResponse = mPendingAutofillResponse; - if (pendingAutofillResponse != null - && pendingAutofillResponse.mAutofillId.equalsIgnoreSession( - mImeFieldId)) { - mPendingAutofillResponse = null; - onInlineSuggestionsResponseLocked(pendingAutofillResponse.mAutofillId, - pendingAutofillResponse.mResponse); - } - } - } - - @Override - public void onInputMethodFinishInputView() { - synchronized (mLock) { - mImeInputViewStarted = false; - } - } - - @Override - public void onInputMethodFinishInput() { - synchronized (mLock) { - mImeFieldId = null; - } - } - }; - } - - public void onCreateInlineSuggestionsRequest(@NonNull AutofillId autofillId, - @NonNull Consumer<InlineSuggestionsRequest> requestConsumer) { - if (sDebug) Log.d(TAG, "onCreateInlineSuggestionsRequest called for " + autofillId); - - synchronized (mLock) { - // Clean up all the state about the previous request. - hideInlineSuggestionsUi(autofillId); - mImeFieldId = null; - mImeInputViewStarted = false; - if (mPendingImeResponse != null && !mPendingImeResponse.isDone()) { - mPendingImeResponse.complete(null); - } - mPendingImeResponse = new CompletableFuture<>(); - // TODO(b/146454892): pipe the uiExtras from the ExtServices. - mInputMethodManagerInternal.onCreateInlineSuggestionsRequest( - mUserId, - new InlineSuggestionsRequestInfo(mComponentName, autofillId, new Bundle()), - new InlineSuggestionsRequestCallbackImpl(autofillId, mPendingImeResponse, - mImeStatusListener, requestConsumer, mHandler, mLock)); - } - } - - public Optional<InlineSuggestionsRequest> getInlineSuggestionsRequest() { - final CompletableFuture<ImeResponse> pendingImeResponse = getPendingImeResponse(); - if (pendingImeResponse == null || !pendingImeResponse.isDone()) { - return Optional.empty(); - } - return Optional.ofNullable(pendingImeResponse.getNow(null)).map(ImeResponse::getRequest); - } - - public boolean hideInlineSuggestionsUi(@NonNull AutofillId autofillId) { - synchronized (mLock) { - if (mIsLastResponseNonEmpty) { - return onInlineSuggestionsResponseLocked(autofillId, - new InlineSuggestionsResponse(Collections.EMPTY_LIST)); - } - return false; - } - } - - public boolean onInlineSuggestionsResponse(@NonNull AutofillId autofillId, - @NonNull InlineSuggestionsResponse inlineSuggestionsResponse) { - synchronized (mLock) { - return onInlineSuggestionsResponseLocked(autofillId, inlineSuggestionsResponse); - } - } - - private boolean onInlineSuggestionsResponseLocked(@NonNull AutofillId autofillId, - @NonNull InlineSuggestionsResponse inlineSuggestionsResponse) { - final CompletableFuture<ImeResponse> completedImsResponse = getPendingImeResponse(); - if (completedImsResponse == null || !completedImsResponse.isDone()) { - if (sDebug) Log.d(TAG, "onInlineSuggestionsResponseLocked without IMS request"); - return false; - } - // There is no need to wait on the CompletableFuture since it should have been completed. - ImeResponse imeResponse = completedImsResponse.getNow(null); - if (imeResponse == null) { - if (sDebug) Log.d(TAG, "onInlineSuggestionsResponseLocked with pending IMS response"); - return false; - } - - // TODO(b/151846600): IME doesn't have access to the virtual id of the webview, so we - // only compare the view id for now. - if (!mImeInputViewStarted || mImeFieldId == null - || autofillId.getViewId() != mImeFieldId.getViewId()) { - if (sDebug) { - Log.d(TAG, - "onInlineSuggestionsResponseLocked not sent because input view is not " - + "started for " + autofillId); - } - mPendingAutofillResponse = new AutofillResponse(autofillId, inlineSuggestionsResponse); - // TODO(b/149442582): Although we are not sending the response to IME right away, we - // still return true to indicate that the response may be sent eventually, such that - // the dropdown UI will not be shown. This may not be the desired behavior in the - // auto-focus case where IME isn't shown after switching back to an activity. We may - // revisit this. - return true; - } - - try { - imeResponse.mCallback.onInlineSuggestionsResponse(autofillId, - inlineSuggestionsResponse); - mIsLastResponseNonEmpty = !inlineSuggestionsResponse.getInlineSuggestions().isEmpty(); - if (sDebug) { - Log.d(TAG, "Autofill sends inline response to IME: " - + inlineSuggestionsResponse.getInlineSuggestions().size()); - } - return true; - } catch (RemoteException e) { - Slog.e(TAG, "RemoteException sending InlineSuggestionsResponse to IME"); - return false; - } - } - - @Nullable - @GuardedBy("mLock") - private CompletableFuture<ImeResponse> getPendingImeResponse() { - synchronized (mLock) { - return mPendingImeResponse; - } - } - - private static final class InlineSuggestionsRequestCallbackImpl - extends IInlineSuggestionsRequestCallback.Stub { - - private final Object mLock; - private final AutofillId mAutofillId; - @GuardedBy("mLock") - private final CompletableFuture<ImeResponse> mResponse; - @GuardedBy("mLock") - private final Consumer<InlineSuggestionsRequest> mRequestConsumer; - private final ImeStatusListener mImeStatusListener; - private final Handler mHandler; - private final Runnable mTimeoutCallback; - - private InlineSuggestionsRequestCallbackImpl(AutofillId autofillId, - CompletableFuture<ImeResponse> response, - ImeStatusListener imeStatusListener, - Consumer<InlineSuggestionsRequest> requestConsumer, - Handler handler, Object lock) { - mAutofillId = autofillId; - mResponse = response; - mImeStatusListener = imeStatusListener; - mRequestConsumer = requestConsumer; - mLock = lock; - - mHandler = handler; - mTimeoutCallback = () -> { - Log.w(TAG, "Timed out waiting for IME callback InlineSuggestionsRequest."); - completeIfNot(null); - }; - mHandler.postDelayed(mTimeoutCallback, INLINE_REQUEST_TIMEOUT_MS); - } - - private void completeIfNot(@Nullable ImeResponse response) { - synchronized (mLock) { - if (mResponse.isDone()) { - return; - } - mResponse.complete(response); - mRequestConsumer.accept(response == null ? null : response.mRequest); - mHandler.removeCallbacks(mTimeoutCallback); - } - } - - @BinderThread - @Override - public void onInlineSuggestionsUnsupported() throws RemoteException { - if (sDebug) Log.d(TAG, "onInlineSuggestionsUnsupported() called."); - completeIfNot(null); - } - - @BinderThread - @Override - public void onInlineSuggestionsRequest(InlineSuggestionsRequest request, - IInlineSuggestionsResponseCallback callback) { - if (sDebug) Log.d(TAG, "onInlineSuggestionsRequest() received: " + request); - mImeStatusListener.onInputMethodStartInput(mAutofillId); - if (request != null && callback != null) { - completeIfNot(new ImeResponse(request, callback)); - } else { - completeIfNot(null); - } - } - - @Override - public void onInputMethodStartInput(AutofillId imeFieldId) throws RemoteException { - if (sDebug) Log.d(TAG, "onInputMethodStartInput() received on " + imeFieldId); - mImeStatusListener.onInputMethodStartInput(imeFieldId); - } - - @Override - public void onInputMethodShowInputRequested(boolean requestResult) throws RemoteException { - if (sDebug) { - Log.d(TAG, "onInputMethodShowInputRequested() received: " + requestResult); - } - // TODO(b/151123764): use this signal to adjust the timeout on Autofill side waiting for - // IME to show. - } - - @BinderThread - @Override - public void onInputMethodStartInputView() { - if (sDebug) Log.d(TAG, "onInputMethodStartInputView() received"); - mImeStatusListener.onInputMethodStartInputView(); - } - - @BinderThread - @Override - public void onInputMethodFinishInputView() { - if (sDebug) Log.d(TAG, "onInputMethodFinishInputView() received"); - mImeStatusListener.onInputMethodFinishInputView(); - } - - @Override - public void onInputMethodFinishInput() throws RemoteException { - if (sDebug) Log.d(TAG, "onInputMethodFinishInput() received"); - mImeStatusListener.onInputMethodFinishInput(); - } - } - - private interface ImeStatusListener { - void onInputMethodStartInput(AutofillId imeFieldId); - - void onInputMethodStartInputView(); - - void onInputMethodFinishInputView(); - - void onInputMethodFinishInput(); - } - - /** - * A data class wrapping Autofill responses for the inline suggestion request. - */ - private static class AutofillResponse { - @NonNull - final AutofillId mAutofillId; - - @NonNull - final InlineSuggestionsResponse mResponse; - - AutofillResponse(@NonNull AutofillId autofillId, - @NonNull InlineSuggestionsResponse response) { - mAutofillId = autofillId; - mResponse = response; - } - - } - - /** - * A data class wrapping IME responses for the create inline suggestions request. - */ - private static class ImeResponse { - @NonNull - final InlineSuggestionsRequest mRequest; - - @NonNull - final IInlineSuggestionsResponseCallback mCallback; - - ImeResponse(@NonNull InlineSuggestionsRequest request, - @NonNull IInlineSuggestionsResponseCallback callback) { - mRequest = request; - mCallback = callback; - } - - InlineSuggestionsRequest getRequest() { - return mRequest; - } - } -} diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 12905696ff98..4ecffd8d29b0 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -304,7 +304,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private boolean mForAugmentedAutofillOnly; @Nullable - private final InlineSuggestionSession mInlineSuggestionSession; + private final AutofillInlineSessionController mInlineSessionController; /** * Receiver of assist data from the app's {@link Activity}. @@ -720,8 +720,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer = mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ true); if (inlineSuggestionsRequestConsumer != null) { - mInlineSuggestionSession.onCreateInlineSuggestionsRequest(mCurrentViewId, - inlineSuggestionsRequestConsumer); + // TODO(b/146454892): pipe the uiExtras from the ExtServices. + mInlineSessionController.onCreateInlineSuggestionsRequestLocked(mCurrentViewId, + inlineSuggestionsRequestConsumer, Bundle.EMPTY); } } else { mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ false); @@ -777,8 +778,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mForAugmentedAutofillOnly = forAugmentedAutofillOnly; setClientLocked(client); - mInlineSuggestionSession = new InlineSuggestionSession(inputMethodManagerInternal, userId, - componentName, handler, mLock); + mInlineSessionController = new AutofillInlineSessionController(inputMethodManagerInternal, + userId, componentName, handler, mLock); mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED) .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags)); @@ -2561,7 +2562,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (sVerbose) Slog.v(TAG, "Exiting view " + id); mUi.hideFillUi(this); hideAugmentedAutofillLocked(viewState); - mInlineSuggestionSession.hideInlineSuggestionsUi(mCurrentViewId); + mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId); mCurrentViewId = null; } break; @@ -2779,7 +2780,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private boolean requestShowInlineSuggestionsLocked(@NonNull FillResponse response, @Nullable String filterText) { final Optional<InlineSuggestionsRequest> inlineSuggestionsRequest = - mInlineSuggestionSession.getInlineSuggestionsRequest(); + mInlineSessionController.getInlineSuggestionsRequestLocked(); if (!inlineSuggestionsRequest.isPresent()) { Log.w(TAG, "InlineSuggestionsRequest unavailable"); return false; @@ -2801,7 +2802,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState inlineSuggestionsRequest.get(), response, filterText, mCurrentViewId, this, () -> { synchronized (mLock) { - mInlineSuggestionSession.hideInlineSuggestionsUi(mCurrentViewId); + mInlineSessionController.hideInlineSuggestionsUiLocked( + mCurrentViewId); } }, remoteRenderService); if (inlineSuggestionsResponse == null) { @@ -2809,7 +2811,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return false; } - return mInlineSuggestionSession.onInlineSuggestionsResponse(mCurrentViewId, + return mInlineSessionController.onInlineSuggestionsResponseLocked(mCurrentViewId, inlineSuggestionsResponse); } @@ -3106,8 +3108,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState focusedId, currentValue, inlineSuggestionsRequest, /*inlineSuggestionsCallback=*/ - response -> mInlineSuggestionSession.onInlineSuggestionsResponse( - mCurrentViewId, response), + response -> { + synchronized (mLock) { + return mInlineSessionController + .onInlineSuggestionsResponseLocked( + mCurrentViewId, response); + } + }, /*onErrorCallback=*/ () -> { synchronized (mLock) { cancelAugmentedAutofillLocked(); @@ -3125,11 +3132,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState && (mForAugmentedAutofillOnly || !isInlineSuggestionsEnabledByAutofillProviderLocked())) { if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill"); - mInlineSuggestionSession.onCreateInlineSuggestionsRequest(mCurrentViewId, - /*requestConsumer=*/ requestAugmentedAutofill); + // TODO(b/146454892): pipe the uiExtras from the ExtServices. + mInlineSessionController.onCreateInlineSuggestionsRequestLocked(mCurrentViewId, + /*requestConsumer=*/ requestAugmentedAutofill, Bundle.EMPTY); } else { requestAugmentedAutofill.accept( - mInlineSuggestionSession.getInlineSuggestionsRequest().orElse(null)); + mInlineSessionController.getInlineSuggestionsRequestLocked().orElse(null)); } if (mAugmentedAutofillDestroyer == null) { mAugmentedAutofillDestroyer = () -> remoteService.onDestroyAutofillWindowsRequest(); diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java index 061ff42de60b..58972a5346c7 100644 --- a/services/core/java/com/android/server/Watchdog.java +++ b/services/core/java/com/android/server/Watchdog.java @@ -123,6 +123,8 @@ public class Watchdog extends Thread { "android.hardware.neuralnetworks@1.0::IDevice", "android.hardware.power.stats@1.0::IPowerStats", "android.hardware.sensors@1.0::ISensors", + "android.hardware.sensors@2.0::ISensors", + "android.hardware.sensors@2.1::ISensors", "android.hardware.vr@1.0::IVr", "android.system.suspend@1.0::ISystemSuspend" ); diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java index ad3c8a61182f..d8acf0e331af 100644 --- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java @@ -809,11 +809,11 @@ public class GnssLocationProvider extends AbstractLocationProvider implements locationRequest.setProvider(provider); // Ignore location settings if in emergency mode. This is only allowed for - // isUserEmergency request (introduced in HAL v2.0), or DBH request in HAL v1.1. + // isUserEmergency request (introduced in HAL v2.0), or HAL v1.1. if (mNIHandler.getInEmergency()) { GnssConfiguration.HalInterfaceVersion halVersion = mGnssConfiguration.getHalInterfaceVersion(); - if (isUserEmergency || (halVersion.mMajor < 2 && !independentFromGnss)) { + if (isUserEmergency || halVersion.mMajor < 2) { locationRequest.setLocationSettingsIgnored(true); durationMillis *= EMERGENCY_LOCATION_UPDATE_DURATION_MULTIPLIER; } diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 52e9d7c67605..193107996d1e 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -808,7 +808,7 @@ class MediaRouter2ServiceImpl { return; } - // Can be null if the session is system's. + // Can be null if the session is system's or RCN. RouterRecord routerRecord = managerRecord.mUserRecord.mHandler .findRouterforSessionLocked(uniqueSessionId); @@ -829,7 +829,7 @@ class MediaRouter2ServiceImpl { return; } - // Can be null if the session is system's. + // Can be null if the session is system's or RCN. RouterRecord routerRecord = managerRecord.mUserRecord.mHandler .findRouterforSessionLocked(uniqueSessionId); @@ -850,7 +850,7 @@ class MediaRouter2ServiceImpl { return; } - // Can be null if the session is system's. + // Can be null if the session is system's or RCN. RouterRecord routerRecord = managerRecord.mUserRecord.mHandler .findRouterforSessionLocked(uniqueSessionId); @@ -1232,7 +1232,7 @@ class MediaRouter2ServiceImpl { route.getOriginalId(), sessionHints); } - // routerRecord can be null if the session is system's. + // routerRecord can be null if the session is system's or RCN. private void selectRouteOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) { if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route, @@ -1250,7 +1250,7 @@ class MediaRouter2ServiceImpl { route.getOriginalId()); } - // routerRecord can be null if the session is system's. + // routerRecord can be null if the session is system's or RCN. private void deselectRouteOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) { @@ -1270,7 +1270,7 @@ class MediaRouter2ServiceImpl { route.getOriginalId()); } - // routerRecord can be null if the session is system's. + // routerRecord can be null if the session is system's or RCN. private void transferToRouteOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) { @@ -1289,6 +1289,8 @@ class MediaRouter2ServiceImpl { route.getOriginalId()); } + // routerRecord is null if and only if the session is created without the request, which + // includes the system's session and RCN cases. private boolean checkArgumentsForSessionControl(@Nullable RouterRecord routerRecord, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route, @NonNull String description) { @@ -1305,12 +1307,6 @@ class MediaRouter2ServiceImpl { return true; } - //TODO(b/152950479): Handle RCN case. - if (routerRecord == null) { - Slog.w(TAG, "Ignoring " + description + " route from unknown router."); - return false; - } - RouterRecord matchingRecord = mSessionToRouterMap.get(uniqueSessionId); if (matchingRecord != routerRecord) { Slog.w(TAG, "Ignoring " + description + " route from non-matching router. " diff --git a/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java b/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java new file mode 100644 index 000000000000..0bdf3f22ee9a --- /dev/null +++ b/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2020 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.net; + +import static android.net.NetworkTemplate.getCollapsedRatType; + +import android.annotation.NonNull; +import android.content.Context; +import android.telephony.Annotation; +import android.telephony.PhoneStateListener; +import android.telephony.ServiceState; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.Log; + +import com.android.internal.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executor; + +/** + * Helper class that watches for events that are triggered per subscription. + */ +// TODO (b/152176562): Write tests to verify subscription changes generate corresponding +// register/unregister calls. +public class NetworkStatsSubscriptionsMonitor extends + SubscriptionManager.OnSubscriptionsChangedListener { + + /** + * Interface that this monitor uses to delegate event handling to NetworkStatsService. + */ + public interface Delegate { + /** + * Notify that the collapsed RAT type has been changed for any subscription. The method + * will also be triggered for any existing sub when start and stop monitoring. + * + * @param subscriberId IMSI of the subscription. + * @param collapsedRatType collapsed RAT type. + * @see android.net.NetworkTemplate#getCollapsedRatType(int). + */ + void onCollapsedRatTypeChanged(@NonNull String subscriberId, + @Annotation.NetworkType int collapsedRatType); + } + private final Delegate mDelegate; + + /** + * Receivers that watches for {@link ServiceState} changes for each subscription, to + * monitor the transitioning between Radio Access Technology(RAT) types for each sub. + */ + @NonNull + private final CopyOnWriteArrayList<RatTypeListener> mRatListeners = + new CopyOnWriteArrayList<>(); + + @NonNull + private final SubscriptionManager mSubscriptionManager; + @NonNull + private final TelephonyManager mTeleManager; + + @NonNull + private final Executor mExecutor; + + NetworkStatsSubscriptionsMonitor(@NonNull Context context, @NonNull Executor executor, + @NonNull Delegate delegate) { + super(); + mSubscriptionManager = (SubscriptionManager) context.getSystemService( + Context.TELEPHONY_SUBSCRIPTION_SERVICE); + mTeleManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + mExecutor = executor; + mDelegate = delegate; + } + + @Override + public void onSubscriptionsChanged() { + // Collect active subId list, hidden subId such as opportunistic subscriptions are + // also needed to track CBRS. + final List<Integer> newSubs = getActiveSubIdList(mSubscriptionManager); + + for (final int subId : newSubs) { + final RatTypeListener match = CollectionUtils.find(mRatListeners, + it -> it.mSubId == subId); + if (match != null) continue; + + // Create listener for every newly added sub. Also store subscriberId into it to + // prevent binder call to telephony when querying RAT. + final String subscriberId = mTeleManager.getSubscriberId(subId); + if (TextUtils.isEmpty(subscriberId)) { + Log.wtf(NetworkStatsService.TAG, + "Empty subscriberId for newly added sub: " + subId); + } + final RatTypeListener listener = + new RatTypeListener(mExecutor, this, subId, subscriberId); + mRatListeners.add(listener); + + // Register listener to the telephony manager that associated with specific sub. + mTeleManager.createForSubscriptionId(subId) + .listen(listener, PhoneStateListener.LISTEN_SERVICE_STATE); + } + + for (final RatTypeListener listener : new ArrayList<>(mRatListeners)) { + // If the new list contains the subId of the listener, keeps it. + final Integer match = CollectionUtils.find(newSubs, it -> it == listener.mSubId); + if (match != null) continue; + + handleRemoveRatTypeListener(listener); + } + } + + @NonNull + private List<Integer> getActiveSubIdList(@NonNull SubscriptionManager subscriptionManager) { + final ArrayList<Integer> ret = new ArrayList<>(); + final int[] ids = subscriptionManager.getCompleteActiveSubscriptionIdList(); + for (int id : ids) ret.add(id); + return ret; + } + + /** + * Get a collapsed RatType for the given subscriberId. + * + * @param subscriberId the target subscriberId + * @return collapsed RatType for the given subscriberId + */ + public int getRatTypeForSubscriberId(@NonNull String subscriberId) { + final RatTypeListener match = CollectionUtils.find(mRatListeners, + it -> TextUtils.equals(subscriberId, it.mSubscriberId)); + return match != null ? match.mLastCollapsedRatType : TelephonyManager.NETWORK_TYPE_UNKNOWN; + } + + /** + * Start monitoring events that triggered per subscription. + */ + public void start() { + mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor, this); + } + + /** + * Unregister subscription changes and all listeners for each subscription. + */ + public void stop() { + mSubscriptionManager.removeOnSubscriptionsChangedListener(this); + + for (final RatTypeListener listener : new ArrayList<>(mRatListeners)) { + handleRemoveRatTypeListener(listener); + } + } + + private void handleRemoveRatTypeListener(@NonNull RatTypeListener listener) { + mTeleManager.createForSubscriptionId(listener.mSubId) + .listen(listener, PhoneStateListener.LISTEN_NONE); + mRatListeners.remove(listener); + + // Removal of subscriptions doesn't generate RAT changed event, fire it for every + // RatTypeListener. + mDelegate.onCollapsedRatTypeChanged( + listener.mSubscriberId, TelephonyManager.NETWORK_TYPE_UNKNOWN); + } + + static class RatTypeListener extends PhoneStateListener { + // Unique id for the subscription. See {@link SubscriptionInfo#getSubscriptionId}. + @NonNull + private final int mSubId; + + // IMSI to identifying the corresponding network from {@link NetworkState}. + // See {@link TelephonyManager#getSubscriberId}. + @NonNull + private final String mSubscriberId; + + private volatile int mLastCollapsedRatType = TelephonyManager.NETWORK_TYPE_UNKNOWN; + @NonNull + private final NetworkStatsSubscriptionsMonitor mMonitor; + + RatTypeListener(@NonNull Executor executor, + @NonNull NetworkStatsSubscriptionsMonitor monitor, int subId, + @NonNull String subscriberId) { + super(executor); + mSubId = subId; + mSubscriberId = subscriberId; + mMonitor = monitor; + } + + @Override + public void onServiceStateChanged(@NonNull ServiceState ss) { + final int networkType = ss.getDataNetworkType(); + final int collapsedRatType = getCollapsedRatType(networkType); + if (collapsedRatType == mLastCollapsedRatType) return; + + if (NetworkStatsService.LOGD) { + Log.d(NetworkStatsService.TAG, "subtype changed for sub(" + mSubId + "): " + + mLastCollapsedRatType + " -> " + collapsedRatType); + } + mLastCollapsedRatType = collapsedRatType; + mMonitor.mDelegate.onCollapsedRatTypeChanged(mSubscriberId, mLastCollapsedRatType); + } + } +} diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index f37af3aef657..9fb468e8db6e 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -724,6 +724,30 @@ public class Installer extends SystemService { } /** + * Deletes all snapshots of credential encrypted user data, where the snapshot id is not + * included in {@code retainSnapshotIds}. + * + * @param userId id of the user whose user data snapshots to delete. + * @param retainSnapshotIds ids of the snapshots that should not be deleted. + * + * @return {@code true} if the operation was successful, or {@code false} if a remote call + * shouldn't be continued. See {@link #checkBeforeRemote}. + * + * @throws InstallerException if failed to delete user data snapshot. + */ + public boolean destroyCeSnapshotsNotSpecified(@UserIdInt int userId, + int[] retainSnapshotIds) throws InstallerException { + if (!checkBeforeRemote()) return false; + + try { + mInstalld.destroyCeSnapshotsNotSpecified(null, userId, retainSnapshotIds); + return true; + } catch (Exception e) { + throw InstallerException.from(e); + } + } + + /** * Migrates obb data from its legacy location {@code /data/media/obb} to * {@code /data/media/0/Android/obb}. This call is idempotent and a fast no-op if data has * already been migrated. diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 1248ec01e020..f07fa501d3a8 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -1830,7 +1830,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { throws PackageManagerException { final List<File> addedFiles = getAddedApksLocked(); if (addedFiles.isEmpty()) { - throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged"); + throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, + String.format("Session: %d. No packages staged in %s", sessionId, + stageDir.getAbsolutePath())); } if (ArrayUtils.size(addedFiles) > 1) { @@ -1921,7 +1923,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final List<File> addedFiles = getAddedApksLocked(); if (addedFiles.isEmpty() && removeSplitList.size() == 0) { - throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged"); + throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, + String.format("Session: %d. No packages staged in %s", sessionId, + stageDir.getAbsolutePath())); } // Verify that all staged packages are internally consistent diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index bc3a0352484b..f693555d9761 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -9684,6 +9684,20 @@ public class PackageManagerService extends IPackageManager.Stub @Override public void notifyDexLoad(String loadingPackageName, Map<String, String> classLoaderContextMap, String loaderIsa) { + if (PLATFORM_PACKAGE_NAME.equals(loadingPackageName) + && Binder.getCallingUid() != Process.SYSTEM_UID) { + Slog.w(TAG, "Non System Server process reporting dex loads as system server. uid=" + + Binder.getCallingUid()); + // Do not record dex loads from processes pretending to be system server. + // Only the system server should be assigned the package "android", so reject calls + // that don't satisfy the constraint. + // + // notifyDexLoad is a PM API callable from the app process. So in theory, apps could + // craft calls to this API and pretend to be system server. Doing so poses no particular + // danger for dex load reporting or later dexopt, however it is a sensible check to do + // in order to verify the expectations. + return; + } int userId = UserHandle.getCallingUserId(); ApplicationInfo ai = getApplicationInfo(loadingPackageName, /*flags*/ 0, userId); if (ai == null) { @@ -11310,7 +11324,7 @@ public class PackageManagerService extends IPackageManager.Stub Slog.i(TAG, "Using ABIS and native lib paths from settings : " + parsedPackage.getPackageName() + " " + AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage) - + ", " + + ", " + AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage)); } } @@ -17572,11 +17586,13 @@ public class PackageManagerService extends IPackageManager.Stub } if (needToVerify) { + final boolean needsVerification = needsNetworkVerificationLPr(packageName); final int verificationId = mIntentFilterVerificationToken++; for (ParsedActivity a : activities) { for (ParsedIntentInfo filter : a.getIntents()) { - if (filter.handlesWebUris(true) - && needsNetworkVerificationLPr(a.getPackageName())) { + // Run verification against hosts mentioned in any web-nav intent filter, + // even if the filter matches non-web schemes as well + if (needsVerification && filter.handlesWebUris(false)) { if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG, "Verification needed for IntentFilter:" + filter.toString()); mIntentFilterVerifier.addOneIntentFilterVerification( diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java index e8765ad973f3..ebdf85691e58 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -17,13 +17,17 @@ package com.android.server.pm.dex; import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; +import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo; import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo; +import static java.util.function.Function.identity; + import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; +import android.content.pm.PackagePartitions; import android.os.FileUtils; import android.os.RemoteException; import android.os.SystemProperties; @@ -67,13 +71,12 @@ import java.util.zip.ZipEntry; */ public class DexManager { private static final String TAG = "DexManager"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final String PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB = "pm.dexopt.priv-apps-oob"; private static final String PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB_LIST = "pm.dexopt.priv-apps-oob-list"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - private final Context mContext; // Maps package name to code locations. @@ -178,12 +181,14 @@ public class DexManager { boolean primaryOrSplit = searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY || searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT; - if (primaryOrSplit && !isUsedByOtherApps) { + if (primaryOrSplit && !isUsedByOtherApps + && !PLATFORM_PACKAGE_NAME.equals(searchResult.mOwningPackageName)) { // If the dex file is the primary apk (or a split) and not isUsedByOtherApps // do not record it. This case does not bring any new usable information // and can be safely skipped. // Note this is just an optimization that makes things easier to read in the // package-dex-use file since we don't need to pollute it with redundant info. + // However, we always record system server packages. continue; } @@ -217,6 +222,23 @@ public class DexManager { } /** + * Check if the dexPath belongs to system server. + * System server can load code from different location, so we cast a wide-net here, and + * assume that if the paths is on any of the registered system partitions then it can be loaded + * by system server. + */ + private boolean isSystemServerDexPathSupportedForOdex(String dexPath) { + ArrayList<PackagePartitions.SystemPartition> partitions = + PackagePartitions.getOrderedPartitions(identity()); + for (int i = 0; i < partitions.size(); i++) { + if (partitions.get(i).containsPath(dexPath)) { + return true; + } + } + return false; + } + + /** * Read the dex usage from disk and populate the code cache locations. * @param existingPackages a map containing information about what packages * are available to what users. Only packages in this list will be @@ -607,12 +629,6 @@ public class DexManager { */ private DexSearchResult getDexPackage( ApplicationInfo loadingAppInfo, String dexPath, int userId) { - // Ignore framework code. - // TODO(calin): is there a better way to detect it? - if (dexPath.startsWith("/system/framework/")) { - return new DexSearchResult("framework", DEX_SEARCH_NOT_FOUND); - } - // First, check if the package which loads the dex file actually owns it. // Most of the time this will be true and we can return early. PackageCodeLocations loadingPackageCodeLocations = @@ -635,6 +651,28 @@ public class DexManager { } } + // We could not find the owning package amongst regular apps. + // If the loading package is system server, see if the dex file resides + // on any of the potentially system server owning location and if so, + // assuming system server ownership. + // + // Note: We don't have any way to detect which code paths are actually + // owned by system server. We can only assume that such paths are on + // system partitions. + if (PLATFORM_PACKAGE_NAME.equals(loadingAppInfo.packageName)) { + if (isSystemServerDexPathSupportedForOdex(dexPath)) { + // We record system server dex files as secondary dex files. + // The reason is that we only record the class loader context for secondary dex + // files and we expect that all primary apks are loaded with an empty class loader. + // System server dex files may be loaded in non-empty class loader so we need to + // keep track of their context. + return new DexSearchResult(PLATFORM_PACKAGE_NAME, DEX_SEARCH_FOUND_SECONDARY); + } else { + Slog.wtf(TAG, "System server loads dex files outside paths supported for odex: " + + dexPath); + } + } + if (DEBUG) { // TODO(calin): Consider checking for /data/data symlink. // /data/data/ symlinks /data/user/0/ and there's nothing stopping apps diff --git a/services/core/java/com/android/server/pm/dex/SystemServerDexLoadReporter.java b/services/core/java/com/android/server/pm/dex/SystemServerDexLoadReporter.java new file mode 100644 index 000000000000..807c82d887e3 --- /dev/null +++ b/services/core/java/com/android/server/pm/dex/SystemServerDexLoadReporter.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm.dex; + +import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; + +import android.content.pm.IPackageManager; +import android.os.RemoteException; +import android.util.Log; +import android.util.Slog; + +import dalvik.system.BaseDexClassLoader; +import dalvik.system.VMRuntime; + +import java.util.Map; + +/** + * Reports dex file use to the package manager on behalf of system server. + */ +public class SystemServerDexLoadReporter implements BaseDexClassLoader.Reporter { + private static final String TAG = "SystemServerDexLoadReporter"; + + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private final IPackageManager mPackageManager; + + private SystemServerDexLoadReporter(IPackageManager pm) { + mPackageManager = pm; + } + + @Override + public void report(Map<String, String> classLoaderContextMap) { + if (DEBUG) { + Slog.i(TAG, "Reporting " + classLoaderContextMap); + } + if (classLoaderContextMap.isEmpty()) { + Slog.wtf(TAG, "Bad call to DexLoadReporter: empty classLoaderContextMap"); + return; + } + + try { + mPackageManager.notifyDexLoad( + PLATFORM_PACKAGE_NAME, + classLoaderContextMap, + VMRuntime.getRuntime().vmInstructionSet()); + } catch (RemoteException ignored) { + // We're in system server, it can't happen. + } + } + + /** + * Configures system server dex file reporting. + * <p>The method will install a reporter in the BaseDexClassLoader and also + * force the reporting of any dex files already loaded by the system server. + */ + public static void configureSystemServerDexReporter(IPackageManager pm) { + Slog.i(TAG, "Configuring system server dex reporter"); + + SystemServerDexLoadReporter reporter = new SystemServerDexLoadReporter(pm); + BaseDexClassLoader.setReporter(reporter); + ClassLoader currrentClassLoader = reporter.getClass().getClassLoader(); + if (currrentClassLoader instanceof BaseDexClassLoader) { + ((BaseDexClassLoader) currrentClassLoader).reportClassLoaderChain(); + } else { + Slog.wtf(TAG, "System server class loader is not a BaseDexClassLoader. type=" + + currrentClassLoader.getClass().getName()); + } + } +} diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index a726d39b8595..e3faffa0699b 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -882,7 +882,7 @@ public final class DefaultPermissionGrantPolicy { public void grantDefaultPermissionsToDefaultBrowser(String packageName, int userId) { Log.i(TAG, "Granting permissions to default browser for user:" + userId); - grantPermissionsToSystemPackage(packageName, userId, ALWAYS_LOCATION_PERMISSIONS); + grantPermissionsToSystemPackage(packageName, userId, FOREGROUND_LOCATION_PERMISSIONS); } private String getDefaultSystemHandlerActivityPackage(String intentAction, int userId) { diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java index 161f30449a52..27288d852fb2 100644 --- a/services/core/java/com/android/server/policy/PermissionPolicyService.java +++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java @@ -30,6 +30,7 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.AppOpsManager; import android.app.AppOpsManagerInternal; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -47,6 +48,7 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManagerInternal; import android.permission.PermissionControllerManager; +import android.provider.Settings; import android.provider.Telephony; import android.telecom.TelecomManager; import android.util.ArrayMap; @@ -70,7 +72,9 @@ import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.policy.PermissionPolicyInternal.OnInitializedCallback; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutionException; /** @@ -180,8 +184,6 @@ public final class PermissionPolicyService extends SystemService { intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); intentFilter.addDataScheme("package"); - - /* TODO ntmyren: enable receiver when test flakes are fixed getContext().registerReceiverAsUser(new BroadcastReceiver() { final List<Integer> mUserSetupUids = new ArrayList<>(200); final Map<UserHandle, PermissionControllerManager> mPermControllerManagers = @@ -232,7 +234,6 @@ public final class PermissionPolicyService extends SystemService { manager.updateUserSensitiveForApp(uid); } }, UserHandle.ALL, intentFilter, null, null); - */ } /** diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index 5f871ad4f9e4..83e99b008b68 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -505,6 +505,11 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { rollbackIds[i] = mRollbacks.get(i).info.getRollbackId(); } ApexManager.getInstance().destroyCeSnapshotsNotSpecified(userId, rollbackIds); + try { + mInstaller.destroyCeSnapshotsNotSpecified(userId, rollbackIds); + } catch (Installer.InstallerException ie) { + Slog.e(TAG, "Failed to delete snapshots for user: " + userId, ie); + } } @WorkerThread diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 5ce63de87ee9..c47d2151a958 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -42,7 +42,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.activityTypeToString; import static android.content.Intent.ACTION_MAIN; import static android.content.Intent.CATEGORY_HOME; @@ -109,7 +108,6 @@ import static android.view.WindowManager.TRANSIT_DOCK_TASK_FROM_RECENTS; import static android.view.WindowManager.TRANSIT_TASK_CLOSE; import static android.view.WindowManager.TRANSIT_TASK_OPEN_BEHIND; import static android.view.WindowManager.TRANSIT_UNSET; -import static android.view.WindowManager.TRANSIT_WALLPAPER_OPEN; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; @@ -5812,18 +5810,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } @VisibleForTesting - boolean shouldAnimate(int transit) { - if (task != null && !task.shouldAnimate()) { - return false; - } - final boolean isSplitScreenPrimary = - getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; - final boolean allowSplitScreenPrimaryAnimation = transit != TRANSIT_WALLPAPER_OPEN; - - // We animate always if it's not split screen primary, and only some special cases in split - // screen primary because it causes issues with stack clipping when we run an un-minimize - // animation at the same time. - return !isSplitScreenPrimary || allowSplitScreenPrimaryAnimation; + boolean shouldAnimate() { + return task == null || task.shouldAnimate(); } /** diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index f5eba96a96d1..78d2afc64f96 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -20,7 +20,6 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.PINNED_WINDOWING_MODE_ELEVATION_IN_DIP; @@ -685,9 +684,7 @@ class ActivityStack extends Task { return; } - setWindowingMode(windowingMode, false /* animate */, false /* showRecents */, - false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */, - false /* creating */); + setWindowingMode(windowingMode, false /* creating */); } /** @@ -709,27 +706,22 @@ class ActivityStack extends Task { * @param preferredWindowingMode the preferred windowing mode. This may not be honored depending * on the state of things. For example, WINDOWING_MODE_UNDEFINED will resolve to the * previous non-transient mode if this stack is currently in a transient mode. - * @param animate Can be used to prevent animation. - * @param showRecents Controls whether recents is shown on the other side of a split while - * entering split mode. - * @param enteringSplitScreenMode {@code true} if entering split mode. - * @param deferEnsuringVisibility Whether visibility updates are deferred. This is set when - * many operations (which can effect visibility) are being performed in bulk. * @param creating {@code true} if this is being run during ActivityStack construction. */ - void setWindowingMode(int preferredWindowingMode, boolean animate, boolean showRecents, - boolean enteringSplitScreenMode, boolean deferEnsuringVisibility, boolean creating) { + void setWindowingMode(int preferredWindowingMode, boolean creating) { mWmService.inSurfaceTransaction(() -> setWindowingModeInSurfaceTransaction( - preferredWindowingMode, animate, showRecents, enteringSplitScreenMode, - deferEnsuringVisibility, creating)); + preferredWindowingMode, creating)); } - private void setWindowingModeInSurfaceTransaction(int preferredWindowingMode, boolean animate, - boolean showRecents, boolean enteringSplitScreenMode, boolean deferEnsuringVisibility, + private void setWindowingModeInSurfaceTransaction(int preferredWindowingMode, boolean creating) { + final TaskDisplayArea taskDisplayArea = getDisplayArea(); + if (taskDisplayArea == null) { + Slog.d(TAG, "taskDisplayArea is null, bail early"); + return; + } final int currentMode = getWindowingMode(); final int currentOverrideMode = getRequestedOverrideWindowingMode(); - final TaskDisplayArea taskDisplayArea = getDisplayArea(); final Task topTask = getTopMostTask(); int windowingMode = preferredWindowingMode; if (preferredWindowingMode == WINDOWING_MODE_UNDEFINED @@ -753,12 +745,9 @@ class ActivityStack extends Task { final boolean alreadyInSplitScreenMode = taskDisplayArea.isSplitScreenModeActivated(); - // Don't send non-resizeable notifications if the windowing mode changed was a side effect - // of us entering split-screen mode. - final boolean sendNonResizeableNotification = !enteringSplitScreenMode; // Take any required action due to us not supporting the preferred windowing mode. if (alreadyInSplitScreenMode && windowingMode == WINDOWING_MODE_FULLSCREEN - && sendNonResizeableNotification && isActivityTypeStandardOrUndefined()) { + && isActivityTypeStandardOrUndefined()) { final boolean preferredSplitScreen = preferredWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY || preferredWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; @@ -794,7 +783,7 @@ class ActivityStack extends Task { if (currentMode == WINDOWING_MODE_PINNED) { mAtmService.getTaskChangeNotificationController().notifyActivityUnpinned(); } - if (sendNonResizeableNotification && likelyResolvedMode != WINDOWING_MODE_FULLSCREEN + if (likelyResolvedMode != WINDOWING_MODE_FULLSCREEN && topActivity != null && !topActivity.noDisplay && topActivity.isNonResizableOrForcedResizable(likelyResolvedMode)) { // Inform the user that they are starting an app that may not work correctly in @@ -806,7 +795,7 @@ class ActivityStack extends Task { mAtmService.deferWindowLayout(); try { - if (!animate && topActivity != null) { + if (topActivity != null) { mStackSupervisor.mNoAnimActivities.add(topActivity); } super.setWindowingMode(windowingMode); @@ -845,36 +834,11 @@ class ActivityStack extends Task { false /*preserveWindows*/, true /*deferResume*/); } } finally { - if (showRecents && !alreadyInSplitScreenMode && isOnHomeDisplay() - && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - // Make sure recents stack exist when creating a dock stack as it normally needs to - // be on the other side of the docked stack and we make visibility decisions based - // on that. - // TODO: This is only here to help out with the case where recents stack doesn't - // exist yet. For that case the initial size of the split-screen stack will be the - // the one where the home stack is visible since recents isn't visible yet, but the - // divider will be off. I think we should just make the initial bounds that of home - // so that the divider matches and remove this logic. - // TODO: This is currently only called when entering split-screen while in another - // task, and from the tests - // TODO (b/78247419): Fix the rotation animation from fullscreen to minimized mode - final boolean isRecentsComponentHome = - mAtmService.getRecentTasks().isRecentsComponentHomeActivity(mCurrentUser); - final ActivityStack recentStack = taskDisplayArea.getOrCreateStack( - WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, - isRecentsComponentHome ? ACTIVITY_TYPE_HOME : ACTIVITY_TYPE_RECENTS, - true /* onTop */); - recentStack.moveToFront("setWindowingMode"); - // If task moved to docked stack - show recents if needed. - mWmService.showRecentApps(); - } mAtmService.continueWindowLayout(); } - if (!deferEnsuringVisibility) { - mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS); - mRootWindowContainer.resumeFocusedStacksTopActivities(); - } + mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS); + mRootWindowContainer.resumeFocusedStacksTopActivities(); } @Override diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index 02601ff4b6e3..1e5a924e1d4d 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -80,8 +80,6 @@ import static com.android.server.wm.Task.LOCK_TASK_AUTH_LAUNCHABLE; import static com.android.server.wm.Task.LOCK_TASK_AUTH_LAUNCHABLE_PRIV; import static com.android.server.wm.Task.LOCK_TASK_AUTH_WHITELISTED; import static com.android.server.wm.Task.REPARENT_KEEP_STACK_AT_FRONT; -import static com.android.server.wm.Task.REPARENT_LEAVE_STACK_IN_PLACE; -import static com.android.server.wm.Task.REPARENT_MOVE_STACK_TO_FRONT; import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowContainer.POSITION_TOP; @@ -140,7 +138,6 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.ReferrerIntent; import com.android.internal.os.TransferPipe; -import com.android.internal.os.logging.MetricsLoggerWrapper; import com.android.internal.util.ArrayUtils; import com.android.internal.util.function.pooled.PooledConsumer; import com.android.internal.util.function.pooled.PooledLambda; @@ -382,71 +379,6 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { private boolean mInitialized; - private final MoveTaskToFullscreenHelper mMoveTaskToFullscreenHelper = - new MoveTaskToFullscreenHelper(); - private class MoveTaskToFullscreenHelper { - private TaskDisplayArea mToDisplayArea; - private boolean mOnTop; - private Task mTopTask; - private boolean mSchedulePictureInPictureModeChange; - - void process(ActivityStack fromStack, TaskDisplayArea toDisplayArea, boolean onTop, - boolean schedulePictureInPictureModeChange) { - mSchedulePictureInPictureModeChange = schedulePictureInPictureModeChange; - mToDisplayArea = toDisplayArea; - mOnTop = onTop; - mTopTask = fromStack.getTopMostTask(); - - final PooledConsumer c = PooledLambda.obtainConsumer( - MoveTaskToFullscreenHelper::processLeafTask, this, PooledLambda.__(Task.class)); - fromStack.forAllLeafTasks(c, false /* traverseTopToBottom */); - c.recycle(); - mToDisplayArea = null; - mTopTask = null; - } - - private void processLeafTask(Task task) { - // This is a one level task that we don't need to create stack for reparenting to. - if (task.isRootTask() && DisplayContent.alwaysCreateStack(WINDOWING_MODE_FULLSCREEN, - task.getActivityType())) { - final ActivityStack stack = (ActivityStack) task; - stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - if (mToDisplayArea.getDisplayId() != stack.getDisplayId()) { - stack.reparent(mToDisplayArea, mOnTop); - } else if (mOnTop) { - mToDisplayArea.positionStackAtTop(stack, false /* includingParents */); - } else { - mToDisplayArea.positionStackAtBottom(stack); - } - return; - } - - final ActivityStack toStack = mToDisplayArea.getOrCreateStack(null, mTmpOptions, task, - task.getActivityType(), mOnTop); - if (task == toStack) { - // The task was reused as the root task. - return; - } - - if (mOnTop) { - final boolean isTopTask = task == mTopTask; - // Defer resume until all the tasks have been moved to the fullscreen stack - task.reparent(toStack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, isTopTask /*animate*/, - DEFER_RESUME, mSchedulePictureInPictureModeChange, - "moveTasksToFullscreenStack - onTop"); - MetricsLoggerWrapper.logPictureInPictureFullScreen(mService.mContext, - task.effectiveUid, task.realActivity.flattenToString()); - } else { - // Position the tasks in the fullscreen stack in order at the bottom of the - // stack. Also defer resume until all the tasks have been moved to the - // fullscreen stack. - task.reparent(toStack, ON_TOP, REPARENT_LEAVE_STACK_IN_PLACE, - !ANIMATE, DEFER_RESUME, mSchedulePictureInPictureModeChange, - "moveTasksToFullscreenStack - NOT_onTop"); - } - } - } - /** * Description of a request to start a new activity, which has been held * due to app switches being disabled. @@ -1501,41 +1433,43 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { mResizingTasksDuringAnimation.clear(); } - /** - * TODO: This should just change the windowing mode and resize vs. actually moving task around. - * Can do that once we are no longer using static stack ids. - */ - private void moveTasksToFullscreenStackInSurfaceTransaction(ActivityStack fromStack, - int toDisplayId, boolean onTop) { + void setSplitScreenResizing(boolean resizing) { + if (resizing == mDockedStackResizing) { + return; + } - mService.deferWindowLayout(); - try { - final int windowingMode = fromStack.getWindowingMode(); - final TaskDisplayArea toDisplayArea = mRootWindowContainer - .getDisplayContent(toDisplayId).getDefaultTaskDisplayArea(); + mDockedStackResizing = resizing; + mWindowManager.setDockedStackResizing(resizing); + } - if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { - // We are moving all tasks from the docked stack to the fullscreen stack, - // which is dismissing the docked stack, so resize all other stacks to - // fullscreen here already so we don't end up with resize trashing. - for (int i = toDisplayArea.getStackCount() - 1; i >= 0; --i) { - final ActivityStack otherStack = toDisplayArea.getStackAt(i); - if (!otherStack.inSplitScreenSecondaryWindowingMode()) { - continue; - } - otherStack.setWindowingMode(WINDOWING_MODE_UNDEFINED); - } - } + private void removePinnedStackInSurfaceTransaction(ActivityStack stack) { + /** + * Workaround: Force-stop all the activities in the pinned stack before we reparent them + * to the fullscreen stack. This is to guarantee that when we are removing a stack, + * that the client receives onStop() before it is reparented. We do this by detaching + * the stack from the display so that it will be considered invisible when + * ensureActivitiesVisible() is called, and all of its activities will be marked + * invisible as well and added to the stopping list. After which we process the + * stopping list by handling the idle. + */ + stack.cancelAnimation(); + stack.setForceHidden(FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, true /* set */); + stack.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS); + stack.setForceHidden(FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, false /* set */); + activityIdleInternal(null /* idleActivity */, false /* fromTimeout */, + true /* processPausingActivities */, null /* configuration */); - // If we are moving from the pinned stack, then the animation takes care of updating - // the picture-in-picture mode. - final boolean schedulePictureInPictureModeChange = - windowingMode == WINDOWING_MODE_PINNED; + // Reparent all the tasks to the bottom of the display + final DisplayContent toDisplay = + mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY); - if (fromStack.hasChild()) { - mTmpOptions.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN); - mMoveTaskToFullscreenHelper.process( - fromStack, toDisplayArea, onTop, schedulePictureInPictureModeChange); + mService.deferWindowLayout(); + try { + stack.setWindowingMode(WINDOWING_MODE_UNDEFINED); + if (toDisplay.getDisplayId() != stack.getDisplayId()) { + stack.reparent(toDisplay.getDefaultTaskDisplayArea(), false /* onTop */); + } else { + toDisplay.mTaskContainers.positionStackAtBottom(stack); } mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS); @@ -1545,41 +1479,9 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { } } - void moveTasksToFullscreenStackLocked(ActivityStack fromStack, boolean onTop) { - // TODO(b/153089193): Support moving within the same task display area - mWindowManager.inSurfaceTransaction(() -> - moveTasksToFullscreenStackInSurfaceTransaction(fromStack, DEFAULT_DISPLAY, onTop)); - } - - void setSplitScreenResizing(boolean resizing) { - if (resizing == mDockedStackResizing) { - return; - } - - mDockedStackResizing = resizing; - mWindowManager.setDockedStackResizing(resizing); - } - private void removeStackInSurfaceTransaction(ActivityStack stack) { if (stack.getWindowingMode() == WINDOWING_MODE_PINNED) { - /** - * Workaround: Force-stop all the activities in the pinned stack before we reparent them - * to the fullscreen stack. This is to guarantee that when we are removing a stack, - * that the client receives onStop() before it is reparented. We do this by detaching - * the stack from the display so that it will be considered invisible when - * ensureActivitiesVisible() is called, and all of its activities will be marked - * invisible as well and added to the stopping list. After which we process the - * stopping list by handling the idle. - */ - stack.cancelAnimation(); - stack.setForceHidden(FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, true /* set */); - stack.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS); - stack.setForceHidden(FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, false /* set */); - activityIdleInternal(null /* idleActivity */, false /* fromTimeout */, - true /* processPausingActivities */, null /* configuration */); - - // Move all the tasks to the bottom of the fullscreen stack - moveTasksToFullscreenStackLocked(stack, !ON_TOP); + removePinnedStackInSurfaceTransaction(stack); } else { final PooledConsumer c = PooledLambda.obtainConsumer( ActivityStackSupervisor::processRemoveTask, this, PooledLambda.__(Task.class)); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 0b1968765300..c253cd2c3297 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -3324,33 +3324,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { Slog.w(TAG, "resizeTask: taskId=" + taskId + " not found"); return; } - // Place the task in the right stack if it isn't there already based on - // the requested bounds. - // The stack transition logic is: - // - a null bounds on a freeform task moves that task to fullscreen - // - a non-null bounds on a non-freeform (fullscreen OR docked) task moves - // that task to freeform - // - otherwise the task is not moved - ActivityStack stack = task.getStack(); if (!task.getWindowConfiguration().canResizeTask()) { throw new IllegalArgumentException("resizeTask not allowed on task=" + task); } - if (bounds == null && stack.getWindowingMode() == WINDOWING_MODE_FREEFORM) { - stack = stack.getDisplayArea().getOrCreateStack( - WINDOWING_MODE_FULLSCREEN, stack.getActivityType(), ON_TOP); - } else if (bounds != null && stack.getWindowingMode() != WINDOWING_MODE_FREEFORM) { - stack = stack.getDisplayArea().getOrCreateStack( - WINDOWING_MODE_FREEFORM, stack.getActivityType(), ON_TOP); - } // Reparent the task to the right stack if necessary boolean preserveWindow = (resizeMode & RESIZE_MODE_PRESERVE_WINDOW) != 0; - if (stack != task.getStack()) { - // Defer resume until the task is resized below - task.reparent(stack, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, - DEFER_RESUME, "resizeTask"); - preserveWindow = false; - } // After reparenting (which only resizes the task to the stack bounds), resize the // task to the actual bounds provided @@ -4022,28 +4001,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } - @Override - // TODO: API should just be about changing windowing modes... - public void moveTasksToFullscreenStack(int fromStackId, boolean onTop) { - enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, - "moveTasksToFullscreenStack()"); - synchronized (mGlobalLock) { - final long origId = Binder.clearCallingIdentity(); - try { - final ActivityStack stack = mRootWindowContainer.getStack(fromStackId); - if (stack != null){ - if (!stack.isActivityTypeStandardOrUndefined()) { - throw new IllegalArgumentException( - "You can't move tasks from non-standard stacks."); - } - mStackSupervisor.moveTasksToFullscreenStackLocked(stack, onTop); - } - } finally { - Binder.restoreCallingIdentity(origId); - } - } - } - /** * Moves the top activity in the input stackId to the pinned stack. * diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index f86aeb2244dc..78ee1de78079 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -76,7 +76,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerInternal.AppTransitionListener; import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM; -import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM; import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE; import android.annotation.DrawableRes; @@ -1807,15 +1806,11 @@ public class AppTransition implements Dump { } int getAppStackClipMode() { - // When dismiss keyguard animation occurs, clip before the animation to prevent docked - // app from showing beyond the divider - if (mNextAppTransition == TRANSIT_KEYGUARD_GOING_AWAY - || mNextAppTransition == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER) { - return STACK_CLIP_BEFORE_ANIM; - } return mNextAppTransition == TRANSIT_ACTIVITY_RELAUNCH || mNextAppTransition == TRANSIT_DOCK_TASK_FROM_RECENTS || mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL + || mNextAppTransition == TRANSIT_KEYGUARD_GOING_AWAY + || mNextAppTransition == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER ? STACK_CLIP_NONE : STACK_CLIP_AFTER_ANIM; } diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java index 90fdf19d9781..edd14b7bebf3 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -253,6 +253,12 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { super.prepareSurfaces(); getBounds(mTmpDimBoundsRect); + // If SystemUI is dragging for recents, we want to reset the dim state so any dim layer + // on the display level fades out. + if (forAllTasks(task -> !task.canAffectSystemUiFlags())) { + mDimmer.resetDimStates(); + } + if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) { scheduleAnimation(); } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index a93b962c33b4..2816633045b9 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -197,7 +197,6 @@ import android.view.ViewRootImpl; import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowManagerPolicyConstants.PointerEventListener; -import android.window.ITaskOrganizer; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; @@ -1082,8 +1081,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return null; } mShellRoots.put(windowType, root); - SurfaceControl out = new SurfaceControl(); - out.copyFrom(rootLeash); + SurfaceControl out = new SurfaceControl(rootLeash); return out; } @@ -3554,6 +3552,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo onWallpaper, goingToShade, subtle)); } }, true /* traverseTopToBottom */); + for (int i = mShellRoots.size() - 1; i >= 0; --i) { + mShellRoots.valueAt(i).startAnimation(policy.createHiddenByKeyguardExit( + onWallpaper, goingToShade, subtle)); + } } /** @return {@code true} if there is window to wait before enabling the screen. */ diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index f088dbcb0174..e9d3d56ee283 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -696,6 +696,10 @@ public class DisplayPolicy { mForceShowSystemBarsFromExternal = forceShowSystemBars; } + boolean getForceShowSystemBars() { + return mForceShowSystemBarsFromExternal; + } + public boolean hasNavigationBar() { return mHasNavigationBar; } @@ -1494,9 +1498,7 @@ public class DisplayPolicy { final int behavior = mLastBehavior; boolean navVisible = ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL ? (sysui & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0 - : mNavigationBar != null && mNavigationBar.getControllableInsetProvider() != null - && mNavigationBar.getControllableInsetProvider().isClientVisible() - && !mDisplayContent.getInsetsPolicy().isTransient(ITYPE_NAVIGATION_BAR); + : isNavigationBarRequestedVisible(); boolean navTranslucent = (sysui & (View.NAVIGATION_BAR_TRANSLUCENT | View.NAVIGATION_BAR_TRANSPARENT)) != 0; boolean immersive = (sysui & View.SYSTEM_UI_FLAG_IMMERSIVE) != 0 @@ -1533,6 +1535,14 @@ public class DisplayPolicy { mLastNotificationShadeForcesShowingNavigation = notificationShadeForcesShowingNavigation; } + boolean isNavigationBarRequestedVisible() { + final InsetsSourceProvider provider = + mDisplayContent.getInsetsStateController().peekSourceProvider(ITYPE_NAVIGATION_BAR); + return provider == null + ? InsetsState.getDefaultVisibility(ITYPE_NAVIGATION_BAR) + : provider.isClientVisible(); + } + void updateHideNavInputEventReceiver(boolean navVisible, boolean navAllowedHidden) { // When the navigation bar isn't visible, we put up a fake input window to catch all // touch events. This way we can detect when the user presses anywhere to bring back the diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index e4e57168efe7..61a199a816df 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -99,7 +99,7 @@ class InsetsPolicy { } private void updateHideNavInputEventReceiver() { - mPolicy.updateHideNavInputEventReceiver(!isHidden(ITYPE_NAVIGATION_BAR), + mPolicy.updateHideNavInputEventReceiver(mPolicy.isNavigationBarRequestedVisible(), mFocusedWin != null && mFocusedWin.mAttrs.insetsFlags.behavior != BEHAVIOR_SHOW_BARS_BY_TOUCH); } @@ -304,7 +304,10 @@ class InsetsPolicy { // We need to force system bars when the docked stack is visible, when the freeform stack // is visible but also when we are resizing for the transitions when docked stack // visibility changes. - return isDockedStackVisible || isFreeformStackVisible || isResizing; + return isDockedStackVisible + || isFreeformStackVisible + || isResizing + || mPolicy.getForceShowSystemBars(); } @VisibleForTesting diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 5f3732a58824..7d8a56e6c468 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -51,6 +51,7 @@ import android.view.IWindowId; import android.view.IWindowSession; import android.view.IWindowSessionCallback; import android.view.InputChannel; +import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.SurfaceControl; import android.view.SurfaceSession; @@ -158,10 +159,10 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets, Rect outStableInsets, DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, - InsetsState outInsetsState) { + InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) { return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame, outContentInsets, outStableInsets, outDisplayCutout, outInputChannel, - outInsetsState); + outInsetsState, outActiveControls); } @Override @@ -171,7 +172,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, new Rect() /* outFrame */, outContentInsets, outStableInsets, new DisplayCutout.ParcelableWrapper() /* cutout */, null /* outInputChannel */, - outInsetsState); + outInsetsState, null); } @Override @@ -191,7 +192,8 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { Rect outStableInsets, Rect outBackdropFrame, DisplayCutout.ParcelableWrapper cutout, MergedConfiguration mergedConfiguration, SurfaceControl outSurfaceControl, InsetsState outInsetsState, - Point outSurfaceSize, SurfaceControl outBLASTSurfaceControl) { + InsetsSourceControl[] outActiveControls, Point outSurfaceSize, + SurfaceControl outBLASTSurfaceControl) { if (false) Slog.d(TAG_WM, ">>>>>> ENTERED relayout from " + Binder.getCallingPid()); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mRelayoutTag); @@ -199,8 +201,8 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { requestedWidth, requestedHeight, viewFlags, flags, frameNumber, outFrame, outContentInsets, outVisibleInsets, outStableInsets, outBackdropFrame, cutout, - mergedConfiguration, outSurfaceControl, outInsetsState, outSurfaceSize, - outBLASTSurfaceControl); + mergedConfiguration, outSurfaceControl, outInsetsState, outActiveControls, + outSurfaceSize, outBLASTSurfaceControl); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); if (false) Slog.d(TAG_WM, "<<<<<< EXITING relayout to " + Binder.getCallingPid()); diff --git a/services/core/java/com/android/server/wm/ShellRoot.java b/services/core/java/com/android/server/wm/ShellRoot.java index 9732637fdd4d..701feff8c6be 100644 --- a/services/core/java/com/android/server/wm/ShellRoot.java +++ b/services/core/java/com/android/server/wm/ShellRoot.java @@ -16,12 +16,20 @@ package com.android.server.wm; +import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; + +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION; +import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION; + import android.annotation.NonNull; +import android.graphics.Point; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import android.view.DisplayInfo; import android.view.IWindow; import android.view.SurfaceControl; +import android.view.animation.Animation; /** * Represents a piece of the hierarchy under which a client Shell can manage sub-windows. @@ -70,5 +78,29 @@ public class ShellRoot { IWindow getClient() { return mClient; } + + void startAnimation(Animation anim) { + // Only do this for the divider + if (mToken.windowType != TYPE_DOCK_DIVIDER) { + return; + } + + DisplayInfo displayInfo = mToken.getFixedRotationTransformDisplayInfo(); + if (displayInfo == null) { + displayInfo = mDisplayContent.getDisplayInfo(); + } + + // Mostly copied from WindowState to enable keyguard transition animation + anim.initialize(displayInfo.logicalWidth, displayInfo.logicalHeight, + displayInfo.appWidth, displayInfo.appHeight); + anim.restrictDuration(MAX_ANIMATION_DURATION); + anim.scaleCurrentDuration(mDisplayContent.mWmService.getWindowAnimationScaleLocked()); + final AnimationAdapter adapter = new LocalAnimationAdapter( + new WindowAnimationSpec(anim, new Point(0, 0), false /* canSkipFirstFrame */, + 0 /* windowCornerRadius */), + mDisplayContent.mWmService.mSurfaceAnimationRunner); + mToken.startAnimation(mToken.getPendingTransaction(), adapter, false /* hidden */, + ANIMATION_TYPE_WINDOW_ANIMATION, null /* animationFinishedCallback */); + } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index cb897db9a2d0..d31939dec509 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3348,6 +3348,21 @@ class Task extends WindowContainer<WindowContainer> { @Override Dimmer getDimmer() { + // If the window is in multi-window mode, we want to dim at the Task level to ensure the dim + // bounds match the area the app lives in + if (inMultiWindowMode()) { + return mDimmer; + } + + // If we're not at the root task level, we want to keep traversing through the parents to + // find the root. + // Once at the root task level, we want to check {@link #isTranslucent(ActivityRecord)}. + // If true, we want to get the Dimmer from the level above since we don't want to animate + // the dim with the Task. + if (!isRootTask() || isTranslucent(null)) { + return super.getDimmer(); + } + return mDimmer; } @@ -4106,20 +4121,18 @@ class Task extends WindowContainer<WindowContainer> { * Any time any of these conditions are updated, the updating code should call * sendTaskAppeared. */ - private boolean taskAppearedReady() { + boolean taskAppearedReady() { return mSurfaceControl != null && mTaskOrganizer != null && getHasBeenVisible(); } private void sendTaskAppeared() { - if (taskAppearedReady() && !mTaskAppearedSent) { - mTaskAppearedSent = true; + if (mTaskOrganizer != null) { mAtmService.mTaskOrganizerController.onTaskAppeared(mTaskOrganizer, this); } } private void sendTaskVanished() { - if (mTaskOrganizer != null && mTaskAppearedSent) { - mTaskAppearedSent = false; + if (mTaskOrganizer != null) { mAtmService.mTaskOrganizerController.onTaskVanished(mTaskOrganizer, this); } } diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 1bc7244996ab..ec3c99bf0808 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -936,9 +936,7 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { } } else { addStack(stack, onTop ? POSITION_TOP : POSITION_BOTTOM); - stack.setWindowingMode(windowingMode, false /* animate */, false /* showRecents */, - false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */, - true /* creating */); + stack.setWindowingMode(windowingMode, true /* creating */); } return stack; } @@ -1148,7 +1146,7 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { for (int i = getStackCount() - 1; i >= 0; --i) { final ActivityStack stack = getStackAt(i); // Collect the root tasks that are currently being organized. - if (stack.isOrganized()) { + if (stack.mCreatedByOrganizer) { for (int k = stack.getChildCount() - 1; k >= 0; --k) { final ActivityStack childStack = (ActivityStack) stack.getChildAt(k); if (childStack.getActivityType() == activityType) { @@ -1614,6 +1612,11 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { } } + @Override + boolean canCreateRemoteAnimationTarget() { + return true; + } + /** * Callback for when the order of the stacks in the display changes. */ diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 2bbf8dbb274c..22702dd6b566 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -106,19 +106,29 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } void addTask(Task t) { - mOrganizedTasks.add(t); - try { - mOrganizer.onTaskAppeared(t.getTaskInfo()); - } catch (Exception e) { - Slog.e(TAG, "Exception sending taskAppeared callback" + e); + if (t.mTaskAppearedSent) return; + + if (!mOrganizedTasks.contains(t)) { + mOrganizedTasks.add(t); + } + if (t.taskAppearedReady()) { + try { + t.mTaskAppearedSent = true; + mOrganizer.onTaskAppeared(t.getTaskInfo()); + } catch (Exception e) { + Slog.e(TAG, "Exception sending taskAppeared callback" + e); + } } } void removeTask(Task t) { - try { - mOrganizer.onTaskVanished(t.getTaskInfo()); - } catch (Exception e) { - Slog.e(TAG, "Exception sending taskVanished callback" + e); + if (t.mTaskAppearedSent) { + try { + t.mTaskAppearedSent = false; + mOrganizer.onTaskVanished(t.getTaskInfo()); + } catch (Exception e) { + Slog.e(TAG, "Exception sending taskVanished callback" + e); + } } mOrganizedTasks.remove(t); } diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java index eb005e0f7eda..14e5c6cbf28d 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java @@ -68,6 +68,7 @@ import android.util.MergedConfiguration; import android.util.Slog; import android.view.DisplayCutout; import android.view.IWindowSession; +import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.Surface; import android.view.SurfaceControl; @@ -164,6 +165,7 @@ class TaskSnapshotSurface implements StartingSurface { final Rect tmpContentInsets = new Rect(); final Rect tmpStableInsets = new Rect(); final InsetsState mTmpInsetsState = new InsetsState(); + final InsetsSourceControl[] mTempControls = new InsetsSourceControl[0]; final MergedConfiguration tmpMergedConfiguration = new MergedConfiguration(); final TaskDescription taskDescription = new TaskDescription(); taskDescription.setBackgroundColor(WHITE); @@ -231,8 +233,8 @@ class TaskSnapshotSurface implements StartingSurface { } try { final int res = session.addToDisplay(window, window.mSeq, layoutParams, - View.GONE, activity.getDisplayContent().getDisplayId(), tmpFrame, tmpRect, tmpRect, - tmpCutout, null, mTmpInsetsState); + View.GONE, activity.getDisplayContent().getDisplayId(), tmpFrame, tmpRect, + tmpRect, tmpCutout, null, mTmpInsetsState, mTempControls); if (res < 0) { Slog.w(TAG, "Failed to add snapshot starting window res=" + res); return null; @@ -249,7 +251,7 @@ class TaskSnapshotSurface implements StartingSurface { session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, -1, tmpFrame, tmpContentInsets, tmpRect, tmpStableInsets, tmpRect, tmpCutout, tmpMergedConfiguration, surfaceControl, mTmpInsetsState, - sTmpSurfaceSize, sTmpSurfaceControl); + mTempControls, sTmpSurfaceSize, sTmpSurfaceControl); } catch (RemoteException e) { // Local call. } @@ -377,6 +379,7 @@ class TaskSnapshotSurface implements StartingSurface { frame = null; mTmpDstFrame.set(mFrame); } + mTmpDstFrame.offsetTo(0, 0); // Scale the mismatch dimensions to fill the task bounds mTmpSnapshotSize.set(0, 0, buffer.getWidth(), buffer.getHeight()); diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 569b8f61c4f4..f6e952c4cea1 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -2295,6 +2295,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) { mLastLayer = -1; reassignLayer(t); + + // Leash is now responsible for position, so set our position to 0. + t.setPosition(mSurfaceControl, 0, 0); + mLastSurfacePosition.set(0, 0); } @Override @@ -2302,6 +2306,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< mLastLayer = -1; mSurfaceFreezer.unfreeze(t); reassignLayer(t); + updateSurfacePosition(t); } /** @@ -2365,11 +2370,15 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } } - void updateSurfacePosition() { + final void updateSurfacePosition() { + updateSurfacePosition(getPendingTransaction()); + } + + void updateSurfacePosition(Transaction t) { // Avoid fighting with the organizer over Surface position. if (isOrganized()) return; - if (mSurfaceControl == null) { + if (mSurfaceControl == null || mSurfaceAnimator.hasLeash()) { return; } @@ -2378,7 +2387,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return; } - getPendingTransaction().setPosition(mSurfaceControl, mTmpPos.x, mTmpPos.y); + t.setPosition(mSurfaceControl, mTmpPos.x, mTmpPos.y); mLastSurfacePosition.set(mTmpPos.x, mTmpPos.y); } @@ -2501,9 +2510,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< // We need to copy the SurfaceControl instead of returning the original // because the Parcel FLAGS PARCELABLE_WRITE_RETURN_VALUE cause SurfaceControls // to release themselves. - SurfaceControl sc = new SurfaceControl(); - sc.copyFrom(wc.getSurfaceControl()); - return sc; + return new SurfaceControl(wc.getSurfaceControl()); } WindowContainerToken toWindowContainerToken() { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index a1e0eb730ff7..8d3a4f27a257 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -227,6 +227,7 @@ import android.view.InputChannel; import android.view.InputDevice; import android.view.InputEvent; import android.view.InputWindowHandle; +import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.KeyEvent; import android.view.MagnificationSpec; @@ -1356,7 +1357,7 @@ public class WindowManagerService extends IWindowManager.Stub LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets, Rect outStableInsets, DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, - InsetsState outInsetsState) { + InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) { int[] appOp = new int[1]; final boolean isRoundedCornerOverlay = (attrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0; @@ -1643,8 +1644,7 @@ public class WindowManagerService extends IWindowManager.Stub outStableInsets, outDisplayCutout)) { res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS; } - outInsetsState.set(win.getInsetsState(), - win.mClient instanceof IWindow.Stub /* copySource */); + outInsetsState.set(win.getInsetsState(), win.isClientLocal()); if (mInTouchMode) { res |= WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE; @@ -1678,6 +1678,8 @@ public class WindowManagerService extends IWindowManager.Stub } displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/); + getInsetsSourceControls(win, outActiveControls); + ProtoLog.v(WM_DEBUG_ADD_REMOVE, "addWindow: New client %s" + ": window=%s Callers=%s", client.asBinder(), win, Debug.getCallers(5)); @@ -2080,7 +2082,8 @@ public class WindowManagerService extends IWindowManager.Stub Rect outVisibleInsets, Rect outStableInsets, Rect outBackdropFrame, DisplayCutout.ParcelableWrapper outCutout, MergedConfiguration mergedConfiguration, SurfaceControl outSurfaceControl, InsetsState outInsetsState, - Point outSurfaceSize, SurfaceControl outBLASTSurfaceControl) { + InsetsSourceControl[] outActiveControls, Point outSurfaceSize, + SurfaceControl outBLASTSurfaceControl) { int result = 0; boolean configChanged; final int pid = Binder.getCallingPid(); @@ -2375,8 +2378,8 @@ public class WindowManagerService extends IWindowManager.Stub outStableInsets); outCutout.set(win.getWmDisplayCutout().getDisplayCutout()); outBackdropFrame.set(win.getBackdropFrame(win.getFrameLw())); - outInsetsState.set(win.getInsetsState(), - win.mClient instanceof IWindow.Stub /* copySource */); + outInsetsState.set(win.getInsetsState(), win.isClientLocal()); + getInsetsSourceControls(win, outActiveControls); if (DEBUG) { Slog.v(TAG_WM, "Relayout given client " + client.asBinder() + ", requestedWidth=" + requestedWidth @@ -2413,6 +2416,21 @@ public class WindowManagerService extends IWindowManager.Stub return result; } + private void getInsetsSourceControls(WindowState win, InsetsSourceControl[] outControls) { + if (outControls != null) { + final InsetsSourceControl[] controls = + win.getDisplayContent().getInsetsStateController().getControlsForDispatch(win); + Arrays.fill(outControls, null); + if (controls != null) { + final int length = Math.min(controls.length, outControls.length); + for (int i = 0; i < length; i++) { + outControls[i] = win.isClientLocal() + ? new InsetsSourceControl(controls[i]) : controls[i]; + } + } + } + } + private boolean tryStartExitingAnimation(WindowState win, WindowStateAnimator winAnimator, boolean focusMayChange) { // Try starting an animation; if there isn't one, we diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 3ebc0f410fd7..5a76bac67d64 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -3479,6 +3479,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } + boolean isClientLocal() { + return mClient instanceof IWindow.Stub; + } + void updateLocationInParentDisplayIfNeeded() { final int embeddedDisplayContentsSize = mEmbeddedDisplayContents.size(); // If there is any embedded display which is re-parented to this window, we need to @@ -5233,23 +5237,14 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) { super.onAnimationLeashCreated(t, leash); - - // Leash is now responsible for position, so set our position to 0. - t.setPosition(mSurfaceControl, 0, 0); - mLastSurfacePosition.set(0, 0); } @Override public void onAnimationLeashLost(Transaction t) { super.onAnimationLeashLost(t); - updateSurfacePosition(t); } @Override - void updateSurfacePosition() { - updateSurfacePosition(getPendingTransaction()); - } - @VisibleForTesting void updateSurfacePosition(Transaction t) { if (mSurfaceControl == null) { diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 7457a1d05335..23091a00a80b 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -535,8 +535,8 @@ class WindowToken extends WindowContainer<WindowState> { } @Override - void updateSurfacePosition() { - super.updateSurfacePosition(); + void updateSurfacePosition(SurfaceControl.Transaction t) { + super.updateSurfacePosition(t); if (isFixedRotationTransforming()) { // The window is layouted in a simulated rotated display but the real display hasn't // rotated, so here transforms its surface to fit in the real display. diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp index 97de1800cae2..2dbbc5ac6806 100644 --- a/services/incremental/BinderIncrementalService.cpp +++ b/services/incremental/BinderIncrementalService.cpp @@ -155,11 +155,6 @@ binder::Status BinderIncrementalService::deleteStorage(int32_t storageId) { return ok(); } -binder::Status BinderIncrementalService::setStorageParams(int32_t storage, bool enableReadLogs, int32_t* _aidl_return) { - *_aidl_return = mImpl.setStorageParams(storage, enableReadLogs); - return ok(); -} - binder::Status BinderIncrementalService::makeDirectory(int32_t storageId, const std::string& path, int32_t* _aidl_return) { *_aidl_return = mImpl.makeDir(storageId, path); diff --git a/services/incremental/BinderIncrementalService.h b/services/incremental/BinderIncrementalService.h index d0357d924586..28613e101b7c 100644 --- a/services/incremental/BinderIncrementalService.h +++ b/services/incremental/BinderIncrementalService.h @@ -71,7 +71,6 @@ public: binder::Status configureNativeBinaries(int32_t storageId, const std::string& apkFullPath, const std::string& libDirRelativePath, const std::string& abi, bool* _aidl_return) final; - binder::Status setStorageParams(int32_t storage, bool enableReadLogs, int32_t* _aidl_return) final; private: android::incremental::IncrementalService mImpl; diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp index d36eae89c1ff..d1153e6cf6e7 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -73,7 +73,7 @@ struct Constants { }; static const Constants& constants() { - static Constants c; + static constexpr Constants c; return c; } @@ -159,6 +159,9 @@ std::string makeBindMdName() { } } // namespace +const bool IncrementalService::sEnablePerfLogging = + android::base::GetBoolProperty("incremental.perflogging", false); + IncrementalService::IncFsMount::~IncFsMount() { incrementalService.mDataLoaderManager->destroyDataLoader(mountId); LOG(INFO) << "Unmounting and cleaning up mount " << mountId << " with root '" << root << '\''; @@ -578,6 +581,7 @@ StorageId IncrementalService::findStorageId(std::string_view path) const { int IncrementalService::setStorageParams(StorageId storageId, bool enableReadLogs) { const auto ifs = getIfs(storageId); if (!ifs) { + LOG(ERROR) << "setStorageParams failed, invalid storageId: " << storageId; return -EINVAL; } @@ -719,7 +723,10 @@ int IncrementalService::bind(StorageId storage, std::string_view source, std::st if (storageInfo == ifs->storages.end()) { return -EINVAL; } - std::string normSource = normalizePathToStorage(ifs, storage, source); + std::string normSource = normalizePathToStorageLocked(storageInfo, source); + if (normSource.empty()) { + return -EINVAL; + } l.unlock(); std::unique_lock l2(mLock, std::defer_lock); return addBindMount(*ifs, storage, storageInfo->second.name, std::move(normSource), @@ -768,22 +775,28 @@ int IncrementalService::unbind(StorageId storage, std::string_view target) { return 0; } -std::string IncrementalService::normalizePathToStorage(const IncrementalService::IfsMountPtr ifs, - StorageId storage, std::string_view path) { - const auto storageInfo = ifs->storages.find(storage); - if (storageInfo == ifs->storages.end()) { - return {}; - } +std::string IncrementalService::normalizePathToStorageLocked( + IncFsMount::StorageMap::iterator storageIt, std::string_view path) { std::string normPath; if (path::isAbsolute(path)) { normPath = path::normalize(path); + if (!path::startsWith(normPath, storageIt->second.name)) { + return {}; + } } else { - normPath = path::normalize(path::join(storageInfo->second.name, path)); + normPath = path::normalize(path::join(storageIt->second.name, path)); } - if (!path::startsWith(normPath, storageInfo->second.name)) { + return normPath; +} + +std::string IncrementalService::normalizePathToStorage(const IncrementalService::IfsMountPtr& ifs, + StorageId storage, std::string_view path) { + std::unique_lock l(ifs->lock); + const auto storageInfo = ifs->storages.find(storage); + if (storageInfo == ifs->storages.end()) { return {}; } - return normPath; + return normalizePathToStorageLocked(storageInfo, path); } int IncrementalService::makeFile(StorageId storage, std::string_view path, int mode, FileId id, @@ -791,7 +804,8 @@ int IncrementalService::makeFile(StorageId storage, std::string_view path, int m if (auto ifs = getIfs(storage)) { std::string normPath = normalizePathToStorage(ifs, storage, path); if (normPath.empty()) { - LOG(ERROR) << "Internal error: storageId " << storage << " failed to normalize: " << path; + LOG(ERROR) << "Internal error: storageId " << storage + << " failed to normalize: " << path; return -EINVAL; } auto err = mIncFs->makeFile(ifs->control, normPath, mode, id, params); @@ -799,10 +813,6 @@ int IncrementalService::makeFile(StorageId storage, std::string_view path, int m LOG(ERROR) << "Internal error: storageId " << storage << " failed to makeFile: " << err; return err; } - std::vector<uint8_t> metadataBytes; - if (params.metadata.data && params.metadata.size > 0) { - metadataBytes.assign(params.metadata.data, params.metadata.data + params.metadata.size); - } return 0; } return -EINVAL; @@ -842,8 +852,9 @@ int IncrementalService::makeDirs(StorageId storageId, std::string_view path, int int IncrementalService::link(StorageId sourceStorageId, std::string_view oldPath, StorageId destStorageId, std::string_view newPath) { - if (auto ifsSrc = getIfs(sourceStorageId), ifsDest = getIfs(destStorageId); - ifsSrc && ifsSrc == ifsDest) { + auto ifsSrc = getIfs(sourceStorageId); + auto ifsDest = sourceStorageId == destStorageId ? ifsSrc : getIfs(destStorageId); + if (ifsSrc && ifsSrc == ifsDest) { std::string normOldPath = normalizePathToStorage(ifsSrc, sourceStorageId, oldPath); std::string normNewPath = normalizePathToStorage(ifsDest, destStorageId, newPath); if (normOldPath.empty() || normNewPath.empty()) { @@ -1143,6 +1154,7 @@ bool IncrementalService::prepareDataLoader(IncrementalService::IncFsMount& ifs, fsControlParcel.incremental->pendingReads.reset( base::unique_fd(::dup(ifs.control.pendingReads()))); fsControlParcel.incremental->log.reset(base::unique_fd(::dup(ifs.control.logs()))); + fsControlParcel.service = new IncrementalServiceConnector(*this, ifs.mountId); sp<IncrementalDataLoaderListener> listener = new IncrementalDataLoaderListener(*this, externalListener ? *externalListener @@ -1156,11 +1168,25 @@ bool IncrementalService::prepareDataLoader(IncrementalService::IncFsMount& ifs, return true; } -// Extract lib filse from zip, create new files in incfs and write data to them +template <class Duration> +static long elapsedMcs(Duration start, Duration end) { + return std::chrono::duration_cast<std::chrono::microseconds>(end - start).count(); +} + +// Extract lib files from zip, create new files in incfs and write data to them bool IncrementalService::configureNativeBinaries(StorageId storage, std::string_view apkFullPath, std::string_view libDirRelativePath, std::string_view abi) { + namespace sc = std::chrono; + using Clock = sc::steady_clock; + auto start = Clock::now(); + const auto ifs = getIfs(storage); + if (!ifs) { + LOG(ERROR) << "Invalid storage " << storage; + return false; + } + // First prepare target directories if they don't exist yet if (auto res = makeDirs(storage, libDirRelativePath, 0755)) { LOG(ERROR) << "Failed to prepare target lib directory " << libDirRelativePath @@ -1168,112 +1194,145 @@ bool IncrementalService::configureNativeBinaries(StorageId storage, std::string_ return false; } - std::unique_ptr<ZipFileRO> zipFile(ZipFileRO::open(apkFullPath.data())); + auto mkDirsTs = Clock::now(); + + std::unique_ptr<ZipFileRO> zipFile(ZipFileRO::open(path::c_str(apkFullPath))); if (!zipFile) { LOG(ERROR) << "Failed to open zip file at " << apkFullPath; return false; } void* cookie = nullptr; const auto libFilePrefix = path::join(constants().libDir, abi); - if (!zipFile.get()->startIteration(&cookie, libFilePrefix.c_str() /* prefix */, - constants().libSuffix.data() /* suffix */)) { + if (!zipFile->startIteration(&cookie, libFilePrefix.c_str() /* prefix */, + constants().libSuffix.data() /* suffix */)) { LOG(ERROR) << "Failed to start zip iteration for " << apkFullPath; return false; } + auto endIteration = [&zipFile](void* cookie) { zipFile->endIteration(cookie); }; + auto iterationCleaner = std::unique_ptr<void, decltype(endIteration)>(cookie, endIteration); + + auto openZipTs = Clock::now(); + + std::vector<IncFsDataBlock> instructions; ZipEntryRO entry = nullptr; - bool success = true; - while ((entry = zipFile.get()->nextEntry(cookie)) != nullptr) { + while ((entry = zipFile->nextEntry(cookie)) != nullptr) { + auto startFileTs = Clock::now(); + char fileName[PATH_MAX]; - if (zipFile.get()->getEntryFileName(entry, fileName, sizeof(fileName))) { + if (zipFile->getEntryFileName(entry, fileName, sizeof(fileName))) { continue; } const auto libName = path::basename(fileName); const auto targetLibPath = path::join(libDirRelativePath, libName); const auto targetLibPathAbsolute = normalizePathToStorage(ifs, storage, targetLibPath); // If the extract file already exists, skip - struct stat st; - if (stat(targetLibPathAbsolute.c_str(), &st) == 0) { - LOG(INFO) << "Native lib file already exists: " << targetLibPath - << "; skipping extraction"; + if (access(targetLibPathAbsolute.c_str(), F_OK) == 0) { + if (sEnablePerfLogging) { + LOG(INFO) << "incfs: Native lib file already exists: " << targetLibPath + << "; skipping extraction, spent " + << elapsedMcs(startFileTs, Clock::now()) << "mcs"; + } continue; } - uint32_t uncompressedLen; - if (!zipFile.get()->getEntryInfo(entry, nullptr, &uncompressedLen, nullptr, nullptr, - nullptr, nullptr)) { + uint32_t uncompressedLen, compressedLen; + if (!zipFile->getEntryInfo(entry, nullptr, &uncompressedLen, &compressedLen, nullptr, + nullptr, nullptr)) { LOG(ERROR) << "Failed to read native lib entry: " << fileName; - success = false; - break; + return false; } // Create new lib file without signature info - incfs::NewFileParams libFileParams{}; - libFileParams.size = uncompressedLen; - libFileParams.signature = {}; - // Metadata of the new lib file is its relative path - IncFsSpan libFileMetadata; - libFileMetadata.data = targetLibPath.c_str(); - libFileMetadata.size = targetLibPath.size(); - libFileParams.metadata = libFileMetadata; + incfs::NewFileParams libFileParams = { + .size = uncompressedLen, + .signature = {}, + // Metadata of the new lib file is its relative path + .metadata = {targetLibPath.c_str(), (IncFsSize)targetLibPath.size()}, + }; incfs::FileId libFileId = idFromMetadata(targetLibPath); - if (auto res = makeFile(storage, targetLibPath, 0777, libFileId, libFileParams)) { + if (auto res = mIncFs->makeFile(ifs->control, targetLibPathAbsolute, 0777, libFileId, + libFileParams)) { LOG(ERROR) << "Failed to make file for: " << targetLibPath << " errno: " << res; - success = false; // If one lib file fails to be created, abort others as well - break; + return false; } + + auto makeFileTs = Clock::now(); + // If it is a zero-byte file, skip data writing if (uncompressedLen == 0) { + if (sEnablePerfLogging) { + LOG(INFO) << "incfs: Extracted " << libName << "(" << compressedLen << " -> " + << uncompressedLen << " bytes): " << elapsedMcs(startFileTs, makeFileTs) + << "mcs, make: " << elapsedMcs(startFileTs, makeFileTs); + } continue; } // Write extracted data to new file - std::vector<uint8_t> libData(uncompressedLen); - if (!zipFile.get()->uncompressEntry(entry, &libData[0], uncompressedLen)) { + // NOTE: don't zero-initialize memory, it may take a while + auto libData = std::unique_ptr<uint8_t[]>(new uint8_t[uncompressedLen]); + if (!zipFile->uncompressEntry(entry, libData.get(), uncompressedLen)) { LOG(ERROR) << "Failed to extract native lib zip entry: " << fileName; - success = false; - break; + return false; } + + auto extractFileTs = Clock::now(); + const auto writeFd = mIncFs->openForSpecialOps(ifs->control, libFileId); if (!writeFd.ok()) { LOG(ERROR) << "Failed to open write fd for: " << targetLibPath << " errno: " << writeFd; - success = false; - break; + return false; } - const int numBlocks = uncompressedLen / constants().blockSize + 1; - std::vector<IncFsDataBlock> instructions; - auto remainingData = std::span(libData); - for (int i = 0; i < numBlocks - 1; i++) { + + auto openFileTs = Clock::now(); + + const int numBlocks = (uncompressedLen + constants().blockSize - 1) / constants().blockSize; + instructions.clear(); + instructions.reserve(numBlocks); + auto remainingData = std::span(libData.get(), uncompressedLen); + for (int i = 0; i < numBlocks; i++) { + const auto blockSize = std::min<uint16_t>(constants().blockSize, remainingData.size()); auto inst = IncFsDataBlock{ - .fileFd = writeFd, + .fileFd = writeFd.get(), .pageIndex = static_cast<IncFsBlockIndex>(i), .compression = INCFS_COMPRESSION_KIND_NONE, .kind = INCFS_BLOCK_KIND_DATA, - .dataSize = static_cast<uint16_t>(constants().blockSize), + .dataSize = blockSize, .data = reinterpret_cast<const char*>(remainingData.data()), }; instructions.push_back(inst); - remainingData = remainingData.subspan(constants().blockSize); + remainingData = remainingData.subspan(blockSize); } - // Last block - auto inst = IncFsDataBlock{ - .fileFd = writeFd, - .pageIndex = static_cast<IncFsBlockIndex>(numBlocks - 1), - .compression = INCFS_COMPRESSION_KIND_NONE, - .kind = INCFS_BLOCK_KIND_DATA, - .dataSize = static_cast<uint16_t>(remainingData.size()), - .data = reinterpret_cast<const char*>(remainingData.data()), - }; - instructions.push_back(inst); + auto prepareInstsTs = Clock::now(); + size_t res = mIncFs->writeBlocks(instructions); if (res != instructions.size()) { LOG(ERROR) << "Failed to write data into: " << targetLibPath; - success = false; + return false; + } + + if (sEnablePerfLogging) { + auto endFileTs = Clock::now(); + LOG(INFO) << "incfs: Extracted " << libName << "(" << compressedLen << " -> " + << uncompressedLen << " bytes): " << elapsedMcs(startFileTs, endFileTs) + << "mcs, make: " << elapsedMcs(startFileTs, makeFileTs) + << " extract: " << elapsedMcs(makeFileTs, extractFileTs) + << " open: " << elapsedMcs(extractFileTs, openFileTs) + << " prepare: " << elapsedMcs(openFileTs, prepareInstsTs) + << " write:" << elapsedMcs(prepareInstsTs, endFileTs); } - instructions.clear(); } - zipFile.get()->endIteration(cookie); - return success; + + if (sEnablePerfLogging) { + auto end = Clock::now(); + LOG(INFO) << "incfs: configureNativeBinaries complete in " << elapsedMcs(start, end) + << "mcs, make dirs: " << elapsedMcs(start, mkDirsTs) + << " open zip: " << elapsedMcs(mkDirsTs, openZipTs) + << " extract all: " << elapsedMcs(openZipTs, end); + } + + return true; } void IncrementalService::registerAppOpsCallback(const std::string& packageName) { @@ -1394,4 +1453,10 @@ void IncrementalService::AppOpsListener::opChanged(int32_t, const String16&) { incrementalService.onAppOpChanged(packageName); } +binder::Status IncrementalService::IncrementalServiceConnector::setStorageParams( + bool enableReadLogs, int32_t* _aidl_return) { + *_aidl_return = incrementalService.setStorageParams(storage, enableReadLogs); + return binder::Status::ok(); +} + } // namespace android::incremental diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h index 58002974e180..db14a794457e 100644 --- a/services/incremental/IncrementalService.h +++ b/services/incremental/IncrementalService.h @@ -39,6 +39,7 @@ #include "ServiceWrappers.h" #include "android/content/pm/BnDataLoaderStatusListener.h" +#include "android/os/incremental/BnIncrementalServiceConnector.h" #include "incfs.h" #include "path.h" @@ -139,7 +140,7 @@ public: DataLoaderStatusListener externalListener) : incrementalService(incrementalService), externalListener(externalListener) {} // Callbacks interface - binder::Status onStatusChanged(MountId mount, int newStatus) override; + binder::Status onStatusChanged(MountId mount, int newStatus) final; private: IncrementalService& incrementalService; @@ -149,14 +150,27 @@ public: class AppOpsListener : public android::BnAppOpsCallback { public: AppOpsListener(IncrementalService& incrementalService, std::string packageName) : incrementalService(incrementalService), packageName(std::move(packageName)) {} - void opChanged(int32_t op, const String16& packageName) override; + void opChanged(int32_t op, const String16& packageName) final; private: IncrementalService& incrementalService; const std::string packageName; }; + class IncrementalServiceConnector : public BnIncrementalServiceConnector { + public: + IncrementalServiceConnector(IncrementalService& incrementalService, int32_t storage) + : incrementalService(incrementalService), storage(storage) {} + binder::Status setStorageParams(bool enableReadLogs, int32_t* _aidl_return) final; + + private: + IncrementalService& incrementalService; + int32_t const storage; + }; + private: + static const bool sEnablePerfLogging; + struct IncFsMount { struct Bind { StorageId storage; @@ -227,8 +241,10 @@ private: void deleteStorage(IncFsMount& ifs); void deleteStorageLocked(IncFsMount& ifs, std::unique_lock<std::mutex>&& ifsLock); MountMap::iterator getStorageSlotLocked(); - std::string normalizePathToStorage(const IfsMountPtr incfs, StorageId storage, + std::string normalizePathToStorage(const IfsMountPtr& incfs, StorageId storage, std::string_view path); + std::string normalizePathToStorageLocked(IncFsMount::StorageMap::iterator storageIt, + std::string_view path); binder::Status applyStorageParams(IncFsMount& ifs, bool enableReadLogs); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index e5ffbacb357b..e2a247394a81 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -136,6 +136,7 @@ import com.android.server.pm.OtaDexoptService; import com.android.server.pm.PackageManagerService; import com.android.server.pm.ShortcutService; import com.android.server.pm.UserManagerService; +import com.android.server.pm.dex.SystemServerDexLoadReporter; import com.android.server.policy.PermissionPolicyService; import com.android.server.policy.PhoneWindowManager; import com.android.server.policy.role.LegacyRoleResolutionPolicy; @@ -837,6 +838,11 @@ public final class SystemServer { Watchdog.getInstance().resumeWatchingCurrentThread("packagemanagermain"); } + // Now that the package manager has started, register the dex load reporter to capture any + // dex files loaded by system server. + // These dex files will be optimized by the BackgroundDexOptService. + SystemServerDexLoadReporter.configureSystemServerDexReporter(mPackageManagerService); + mFirstBoot = mPackageManagerService.isFirstBoot(); mPackageManager = mSystemContext.getPackageManager(); t.traceEnd(); diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java index d69e1b8786b4..8398585ca74a 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java @@ -85,6 +85,9 @@ public class DexManagerTests { private TestData mBarUser0UnsupportedClassLoader; private TestData mBarUser0DelegateLastClassLoader; + private TestData mSystemServerJar; + private TestData mSystemServerJarInvalid; + private int mUser0; private int mUser1; @@ -108,6 +111,9 @@ public class DexManagerTests { mBarUser0DelegateLastClassLoader = new TestData(bar, isa, mUser0, DELEGATE_LAST_CLASS_LOADER_NAME); + mSystemServerJar = new TestData("android", isa, mUser0, PATH_CLASS_LOADER_NAME); + mSystemServerJarInvalid = new TestData("android", isa, mUser0, PATH_CLASS_LOADER_NAME); + mDexManager = new DexManager(/*Context*/ null, mPM, /*PackageDexOptimizer*/ null, mInstaller, mInstallLock); @@ -587,6 +593,25 @@ public class DexManagerTests { assertHasDclInfo(mFooUser0, mFooUser0, fooSecondaries); } + + @Test + public void testNotifySystemServerUse() { + List<String> dexFiles = new ArrayList<String>(); + dexFiles.add("/system/framework/foo"); + notifyDexLoad(mSystemServerJar, dexFiles, mUser0); + PackageUseInfo pui = getPackageUseInfo(mSystemServerJar); + assertIsUsedByOtherApps(mSystemServerJar, pui, false); + } + + @Test + public void testNotifySystemServerInvalidUse() { + List<String> dexFiles = new ArrayList<String>(); + dexFiles.add("/data/foo"); + notifyDexLoad(mSystemServerJarInvalid, dexFiles, mUser0); + assertNoUseInfo(mSystemServerJarInvalid); + assertNoDclInfo(mSystemServerJarInvalid); + } + private void assertSecondaryUse(TestData testData, PackageUseInfo pui, List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId, String[] expectedContexts) { diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java index 89bc65b5a44d..b6eb9010ce90 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java @@ -30,7 +30,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; -import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -41,7 +40,6 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; import android.platform.test.annotations.Presubmit; import android.util.IntArray; @@ -49,7 +47,6 @@ import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.test.InsetsModeSession; -import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import org.junit.AfterClass; @@ -168,6 +165,18 @@ public class InsetsPolicyTest extends WindowTestsBase { } @Test + public void testControlsForDispatch_forceShowSystemBarsFromExternal_appHasNoControl() { + mDisplayContent.getDisplayPolicy().setForceShowSystemBars(true); + addWindow(TYPE_STATUS_BAR, "statusBar"); + addWindow(TYPE_NAVIGATION_BAR, "navBar"); + + final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch(); + + // The focused app window cannot control system bars. + assertNull(controls); + } + + @Test public void testShowTransientBars_bothCanBeTransient_appGetsBothFakeControls() { addNonFocusableWindow(TYPE_STATUS_BAR, "statusBar") .getControllableInsetProvider().getSource().setVisible(false); diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java index add4e9cf3948..ea933dfe42dc 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java @@ -1124,8 +1124,6 @@ public class RecentTasksTest extends ActivityTestsBase { } }); assertSecurityException(expectCallable, - () -> mService.moveTasksToFullscreenStack(INVALID_STACK_ID, true)); - assertSecurityException(expectCallable, () -> mService.startActivityFromRecents(0, new Bundle())); assertSecurityException(expectCallable, () -> mService.getTaskSnapshot(0, true)); assertSecurityException(expectCallable, () -> mService.registerTaskStackListener(null)); diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java index 6d2b7b1e86fe..f19550ced0bf 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -23,7 +23,6 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; -import static android.view.WindowManager.TRANSIT_ACTIVITY_CLOSE; import static com.android.dx.mockito.inline.extended.ExtendedMockito.atLeast; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; @@ -318,7 +317,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { // Assume activity transition should animate when no // IRecentsAnimationController#setDeferCancelUntilNextTransition called. assertFalse(mController.shouldDeferCancelWithScreenshot()); - assertTrue(activity.shouldAnimate(TRANSIT_ACTIVITY_CLOSE)); + assertTrue(activity.shouldAnimate()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java index 2ce9c2b9ced0..06ca6c110613 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java @@ -192,6 +192,21 @@ public class TaskOrganizerTests extends WindowTestsBase { } @Test + public void testTaskNoDraw() throws RemoteException { + final ActivityStack stack = createStack(); + final Task task = createTask(stack, false /* fakeDraw */); + final ITaskOrganizer organizer = registerMockOrganizer(); + + stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + verify(organizer, never()).onTaskAppeared(any()); + assertTrue(stack.isOrganized()); + + mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer); + verify(organizer, never()).onTaskVanished(any()); + assertFalse(stack.isOrganized()); + } + + @Test public void testClearOrganizer() throws RemoteException { final ActivityStack stack = createStack(); final Task task = createTask(stack); 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 1ca2e318b0d7..397f73c3e67c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -134,6 +134,7 @@ class WindowTestsBase extends SystemServiceTestsBase { mChildAppWindowBelow = createCommonWindow(mAppWindow, TYPE_APPLICATION_MEDIA_OVERLAY, "mChildAppWindowBelow"); + mDisplayContent.getDisplayPolicy().setForceShowSystemBars(false); } // Adding a display will cause freezing the display. Make sure to wait until it's diff --git a/telephony/common/android/telephony/LocationAccessPolicy.java b/telephony/common/android/telephony/LocationAccessPolicy.java index f3e9de0d2688..3048ad7c1fb0 100644 --- a/telephony/common/android/telephony/LocationAccessPolicy.java +++ b/telephony/common/android/telephony/LocationAccessPolicy.java @@ -311,7 +311,7 @@ public final class LocationAccessPolicy { } // If the user or profile is current, permission is granted. // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission. - return isCurrentProfile(context, uid) || checkInteractAcrossUsersFull(context, uid, pid); + return isCurrentProfile(context, uid) || checkInteractAcrossUsersFull(context, pid, uid); } private static boolean isLocationModeEnabled(@NonNull Context context, @UserIdInt int userId) { diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java index 2b1d9e58c4d5..18e25921555a 100644 --- a/telephony/java/com/android/internal/telephony/DctConstants.java +++ b/telephony/java/com/android/internal/telephony/DctConstants.java @@ -113,6 +113,7 @@ public class DctConstants { public static final int EVENT_5G_TIMER_HYSTERESIS = BASE + 53; public static final int EVENT_5G_TIMER_WATCHDOG = BASE + 54; public static final int EVENT_CARRIER_CONFIG_CHANGED = BASE + 55; + public static final int EVENT_SIM_STATE_UPDATED = BASE + 56; /***** Constants *****/ diff --git a/tests/net/common/java/android/net/DependenciesTest.java b/tests/net/common/java/android/net/DependenciesTest.java new file mode 100644 index 000000000000..ac1c28a45462 --- /dev/null +++ b/tests/net/common/java/android/net/DependenciesTest.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +/** + * A simple class that tests dependencies to java standard tools from the + * Network stack. These tests are not meant to be comprehensive tests of + * the relevant APIs : such tests belong in the relevant test suite for + * these dependencies. Instead, this just makes sure coverage is present + * by calling the methods in the exact way (or a representative way of how) + * they are called in the network stack. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class DependenciesTest { + // Used to in ipmemorystore's RegularMaintenanceJobService to convert + // 24 hours into seconds + @Test + public void testTimeUnit() { + final int hours = 24; + final long inSeconds = TimeUnit.HOURS.toMillis(hours); + assertEquals(inSeconds, hours * 60 * 60 * 1000); + } + + private byte[] makeTrivialArray(final int size) { + final byte[] src = new byte[size]; + for (int i = 0; i < size; ++i) { + src[i] = (byte) i; + } + return src; + } + + // Used in ApfFilter to find an IP address from a byte array + @Test + public void testArrays() { + final int size = 128; + final byte[] src = makeTrivialArray(size); + + // Test copy + final int copySize = 16; + final int offset = 24; + final byte[] expected = new byte[copySize]; + for (int i = 0; i < copySize; ++i) { + expected[i] = (byte) (offset + i); + } + + final byte[] copy = Arrays.copyOfRange(src, offset, offset + copySize); + assertArrayEquals(expected, copy); + assertArrayEquals(new byte[0], Arrays.copyOfRange(src, size, size)); + } + + // Used mainly in the Dhcp code + @Test + public void testCopyOf() { + final byte[] src = makeTrivialArray(128); + final byte[] copy = Arrays.copyOf(src, src.length); + assertArrayEquals(src, copy); + assertFalse(src == copy); + + assertArrayEquals(new byte[0], Arrays.copyOf(src, 0)); + + final int excess = 16; + final byte[] biggerCopy = Arrays.copyOf(src, src.length + excess); + for (int i = src.length; i < src.length + excess; ++i) { + assertEquals(0, biggerCopy[i]); + } + for (int i = src.length - 1; i >= 0; --i) { + assertEquals(src[i], biggerCopy[i]); + } + } + + // Used mainly in DnsUtils but also various other places + @Test + public void testAsList() { + final int size = 24; + final Object[] src = new Object[size]; + final ArrayList<Object> expected = new ArrayList<>(size); + for (int i = 0; i < size; ++i) { + final Object o = new Object(); + src[i] = o; + expected.add(o); + } + assertEquals(expected, Arrays.asList(src)); + } +} diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java index 6e9dc8eaf2dc..3f8261d5ad7f 100644 --- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java +++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java @@ -17,6 +17,8 @@ package android.net; import static android.net.NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED; +import static android.net.NetworkCapabilities.MAX_TRANSPORT; +import static android.net.NetworkCapabilities.MIN_TRANSPORT; import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS; import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS; @@ -32,10 +34,12 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVIT import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P; import static android.net.NetworkCapabilities.RESTRICTED_CAPABILITIES; +import static android.net.NetworkCapabilities.SIGNAL_STRENGTH_UNSPECIFIED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_TEST; import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE; import static android.net.NetworkCapabilities.UNRESTRICTED_CAPABILITIES; import static com.android.testutils.ParcelUtilsKt.assertParcelSane; @@ -45,10 +49,15 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import android.net.wifi.aware.DiscoverySession; +import android.net.wifi.aware.PeerHandle; +import android.net.wifi.aware.WifiAwareNetworkSpecifier; import android.os.Build; +import android.os.Process; import android.test.suitebuilder.annotation.SmallTest; import android.util.ArraySet; @@ -61,6 +70,7 @@ import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mockito; import java.util.Arrays; import java.util.Set; @@ -74,6 +84,9 @@ public class NetworkCapabilitiesTest { @Rule public DevSdkIgnoreRule mDevSdkIgnoreRule = new DevSdkIgnoreRule(); + private DiscoverySession mDiscoverySession = Mockito.mock(DiscoverySession.class); + private PeerHandle mPeerHandle = Mockito.mock(PeerHandle.class); + private boolean isAtLeastR() { // BuildCompat.isAtLeastR() is used to check the Android version before releasing Android R. // Build.VERSION.SDK_INT > Build.VERSION_CODES.Q is used to check the Android version after @@ -685,4 +698,238 @@ public class NetworkCapabilitiesTest { assertEquals(TRANSPORT_VPN, transportTypes[2]); assertEquals(TRANSPORT_TEST, transportTypes[3]); } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testTelephonyNetworkSpecifier() { + final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier(1); + final NetworkCapabilities nc1 = new NetworkCapabilities.Builder() + .addTransportType(TRANSPORT_WIFI) + .setNetworkSpecifier(specifier) + .build(); + assertEquals(specifier, nc1.getNetworkSpecifier()); + try { + final NetworkCapabilities nc2 = new NetworkCapabilities.Builder() + .setNetworkSpecifier(specifier) + .build(); + fail("Must have a single transport type. Without transport type or multiple transport" + + " types is invalid."); + } catch (IllegalStateException expected) { } + } + + @Test + public void testWifiAwareNetworkSpecifier() { + final NetworkCapabilities nc = new NetworkCapabilities() + .addTransportType(TRANSPORT_WIFI_AWARE); + // If NetworkSpecifier is not set, the default value is null. + assertNull(nc.getNetworkSpecifier()); + final WifiAwareNetworkSpecifier specifier = new WifiAwareNetworkSpecifier.Builder( + mDiscoverySession, mPeerHandle).build(); + nc.setNetworkSpecifier(specifier); + assertEquals(specifier, nc.getNetworkSpecifier()); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testAdministratorUidsAndOwnerUid() { + // Test default owner uid. + // If the owner uid is not set, the default value should be Process.INVALID_UID. + final NetworkCapabilities nc1 = new NetworkCapabilities.Builder().build(); + assertEquals(Process.INVALID_UID, nc1.getOwnerUid()); + // Test setAdministratorUids and getAdministratorUids. + final int[] administratorUids = {1001, 10001}; + final NetworkCapabilities nc2 = new NetworkCapabilities.Builder() + .setAdministratorUids(administratorUids) + .build(); + assertTrue(Arrays.equals(administratorUids, nc2.getAdministratorUids())); + // Test setOwnerUid and getOwnerUid. + // The owner UID must be included in administrator UIDs, or throw IllegalStateException. + try { + final NetworkCapabilities nc3 = new NetworkCapabilities.Builder() + .setOwnerUid(1001) + .build(); + fail("The owner UID must be included in administrator UIDs."); + } catch (IllegalStateException expected) { } + final NetworkCapabilities nc4 = new NetworkCapabilities.Builder() + .setAdministratorUids(administratorUids) + .setOwnerUid(1001) + .build(); + assertEquals(1001, nc4.getOwnerUid()); + try { + final NetworkCapabilities nc5 = new NetworkCapabilities.Builder() + .setAdministratorUids(null) + .build(); + fail("Should not set null into setAdministratorUids"); + } catch (NullPointerException expected) { } + } + + @Test + public void testLinkBandwidthKbps() { + final NetworkCapabilities nc = new NetworkCapabilities(); + // The default value of LinkDown/UpstreamBandwidthKbps should be LINK_BANDWIDTH_UNSPECIFIED. + assertEquals(LINK_BANDWIDTH_UNSPECIFIED, nc.getLinkDownstreamBandwidthKbps()); + assertEquals(LINK_BANDWIDTH_UNSPECIFIED, nc.getLinkUpstreamBandwidthKbps()); + nc.setLinkDownstreamBandwidthKbps(512); + nc.setLinkUpstreamBandwidthKbps(128); + assertEquals(512, nc.getLinkDownstreamBandwidthKbps()); + assertNotEquals(128, nc.getLinkDownstreamBandwidthKbps()); + assertEquals(128, nc.getLinkUpstreamBandwidthKbps()); + assertNotEquals(512, nc.getLinkUpstreamBandwidthKbps()); + } + + @Test + public void testSignalStrength() { + final NetworkCapabilities nc = new NetworkCapabilities(); + // The default value of signal strength should be SIGNAL_STRENGTH_UNSPECIFIED. + assertEquals(SIGNAL_STRENGTH_UNSPECIFIED, nc.getSignalStrength()); + nc.setSignalStrength(-80); + assertEquals(-80, nc.getSignalStrength()); + assertNotEquals(-50, nc.getSignalStrength()); + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testDeduceRestrictedCapability() { + final NetworkCapabilities nc = new NetworkCapabilities(); + // Default capabilities don't have restricted capability. + assertFalse(nc.deduceRestrictedCapability()); + // If there is a force restricted capability, then the network capabilities is restricted. + nc.addCapability(NET_CAPABILITY_OEM_PAID); + nc.addCapability(NET_CAPABILITY_INTERNET); + assertTrue(nc.deduceRestrictedCapability()); + // Except for the force restricted capability, if there is any unrestricted capability in + // capabilities, then the network capabilities is not restricted. + nc.removeCapability(NET_CAPABILITY_OEM_PAID); + nc.addCapability(NET_CAPABILITY_CBS); + assertFalse(nc.deduceRestrictedCapability()); + // Except for the force restricted capability, the network capabilities will only be treated + // as restricted when there is no any unrestricted capability. + nc.removeCapability(NET_CAPABILITY_INTERNET); + assertTrue(nc.deduceRestrictedCapability()); + } + + private void assertNoTransport(NetworkCapabilities nc) { + for (int i = MIN_TRANSPORT; i <= MAX_TRANSPORT; i++) { + assertFalse(nc.hasTransport(i)); + } + } + + // Checks that all transport types from MIN_TRANSPORT to maxTransportType are set and all + // transport types from maxTransportType + 1 to MAX_TRANSPORT are not set when positiveSequence + // is true. If positiveSequence is false, then the check sequence is opposite. + private void checkCurrentTransportTypes(NetworkCapabilities nc, int maxTransportType, + boolean positiveSequence) { + for (int i = MIN_TRANSPORT; i <= maxTransportType; i++) { + if (positiveSequence) { + assertTrue(nc.hasTransport(i)); + } else { + assertFalse(nc.hasTransport(i)); + } + } + for (int i = MAX_TRANSPORT; i > maxTransportType; i--) { + if (positiveSequence) { + assertFalse(nc.hasTransport(i)); + } else { + assertTrue(nc.hasTransport(i)); + } + } + } + + @Test + public void testMultipleTransportTypes() { + final NetworkCapabilities nc = new NetworkCapabilities(); + assertNoTransport(nc); + // Test adding multiple transport types. + for (int i = MIN_TRANSPORT; i <= MAX_TRANSPORT; i++) { + nc.addTransportType(i); + checkCurrentTransportTypes(nc, i, true /* positiveSequence */); + } + // Test removing multiple transport types. + for (int i = MIN_TRANSPORT; i <= MAX_TRANSPORT; i++) { + nc.removeTransportType(i); + checkCurrentTransportTypes(nc, i, false /* positiveSequence */); + } + assertNoTransport(nc); + nc.addTransportType(TRANSPORT_WIFI); + assertTrue(nc.hasTransport(TRANSPORT_WIFI)); + assertFalse(nc.hasTransport(TRANSPORT_VPN)); + nc.addTransportType(TRANSPORT_VPN); + assertTrue(nc.hasTransport(TRANSPORT_WIFI)); + assertTrue(nc.hasTransport(TRANSPORT_VPN)); + nc.removeTransportType(TRANSPORT_WIFI); + assertFalse(nc.hasTransport(TRANSPORT_WIFI)); + assertTrue(nc.hasTransport(TRANSPORT_VPN)); + nc.removeTransportType(TRANSPORT_VPN); + assertFalse(nc.hasTransport(TRANSPORT_WIFI)); + assertFalse(nc.hasTransport(TRANSPORT_VPN)); + assertNoTransport(nc); + } + + @Test + public void testAddAndRemoveTransportType() { + final NetworkCapabilities nc = new NetworkCapabilities(); + try { + nc.addTransportType(-1); + fail("Should not set invalid transport type into addTransportType"); + } catch (IllegalArgumentException expected) { } + try { + nc.removeTransportType(-1); + fail("Should not set invalid transport type into removeTransportType"); + } catch (IllegalArgumentException e) { } + } + + private class TestTransportInfo implements TransportInfo { + TestTransportInfo() { + } + } + + @Test @IgnoreUpTo(Build.VERSION_CODES.Q) + public void testBuilder() { + final int ownerUid = 1001; + final int signalStrength = -80; + final int requestUid = 10100; + final int[] administratorUids = {ownerUid, 10001}; + final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier(1); + final TestTransportInfo transportInfo = new TestTransportInfo(); + final String ssid = "TEST_SSID"; + final String packageName = "com.google.test.networkcapabilities"; + final NetworkCapabilities nc = new NetworkCapabilities.Builder() + .addTransportType(TRANSPORT_WIFI) + .addTransportType(TRANSPORT_CELLULAR) + .removeTransportType(TRANSPORT_CELLULAR) + .addCapability(NET_CAPABILITY_EIMS) + .addCapability(NET_CAPABILITY_CBS) + .removeCapability(NET_CAPABILITY_CBS) + .setAdministratorUids(administratorUids) + .setOwnerUid(ownerUid) + .setLinkDownstreamBandwidthKbps(512) + .setLinkUpstreamBandwidthKbps(128) + .setNetworkSpecifier(specifier) + .setTransportInfo(transportInfo) + .setSignalStrength(signalStrength) + .setSsid(ssid) + .setRequestorUid(requestUid) + .setRequestorPackageName(packageName) + .build(); + assertEquals(1, nc.getTransportTypes().length); + assertEquals(TRANSPORT_WIFI, nc.getTransportTypes()[0]); + assertTrue(nc.hasCapability(NET_CAPABILITY_EIMS)); + assertFalse(nc.hasCapability(NET_CAPABILITY_CBS)); + assertTrue(Arrays.equals(administratorUids, nc.getAdministratorUids())); + assertEquals(ownerUid, nc.getOwnerUid()); + assertEquals(512, nc.getLinkDownstreamBandwidthKbps()); + assertNotEquals(128, nc.getLinkDownstreamBandwidthKbps()); + assertEquals(128, nc.getLinkUpstreamBandwidthKbps()); + assertNotEquals(512, nc.getLinkUpstreamBandwidthKbps()); + assertEquals(specifier, nc.getNetworkSpecifier()); + assertEquals(transportInfo, nc.getTransportInfo()); + assertEquals(signalStrength, nc.getSignalStrength()); + assertNotEquals(-50, nc.getSignalStrength()); + assertEquals(ssid, nc.getSsid()); + assertEquals(requestUid, nc.getRequestorUid()); + assertEquals(packageName, nc.getRequestorPackageName()); + // Cannot assign null into NetworkCapabilities.Builder + try { + final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder(null); + fail("Should not set null into NetworkCapabilities.Builder"); + } catch (NullPointerException expected) { } + assertEquals(nc, new NetworkCapabilities.Builder(nc).build()); + } } diff --git a/tests/net/common/java/android/net/netstats/NetworkStatsApiTest.kt b/tests/net/common/java/android/net/netstats/NetworkStatsApiTest.kt index 9119d62fb023..7b22e45db90a 100644 --- a/tests/net/common/java/android/net/netstats/NetworkStatsApiTest.kt +++ b/tests/net/common/java/android/net/netstats/NetworkStatsApiTest.kt @@ -31,7 +31,6 @@ import android.net.NetworkStats.TAG_NONE import android.os.Build import androidx.test.filters.SmallTest import com.android.testutils.DevSdkIgnoreRule -import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo import com.android.testutils.assertFieldCountEquals import com.android.testutils.assertNetworkStatsEquals import com.android.testutils.assertParcelingIsLossless @@ -47,70 +46,22 @@ import kotlin.test.assertEquals class NetworkStatsApiTest { @Rule @JvmField - val ignoreRule = DevSdkIgnoreRule() + val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = Build.VERSION_CODES.Q) private val testStatsEmpty = NetworkStats(0L, 0) + // Note that these variables need to be initialized outside of constructor, initialize + // here with methods that don't exist in Q devices will result in crash. + // stats1 and stats2 will have some entries with common keys, which are expected to // be merged if performing add on these 2 stats. - private val testStats1 = NetworkStats(0L, 0) - // Entries which only appear in set1. - .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, - METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 20, 3, 57, 40, 3)) - .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, - METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 31, 7, 24, 5, 8)) - .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, - METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 25, 3, 47, 8, 2)) - .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE, - METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 37, 52, 1, 10, 4)) - // Entries which are common for set1 and set2. - .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, - METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 101, 2, 103, 4, 5)) - .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80, - METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 17, 2, 11, 1, 0)) - .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE, - METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 40, 1, 0, 0, 8)) - .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE, - METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 1, 6, 2, 0)) - - private val testStats2 = NetworkStats(0L, 0) - // Entries which are common for set1 and set2. - .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80, - METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 15, 2, 31, 1)) - .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE, - METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 13, 61, 10, 1, 45)) - .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE, - METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 11, 2, 3, 4, 7)) - .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE, - METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 4, 3, 2, 1, 0)) - // Entry which only appears in set2. - .addEntry(Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE, - METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0)) + private lateinit var testStats1: NetworkStats + private lateinit var testStats2: NetworkStats // This is a result of adding stats1 and stats2, while the merging of common key items is // subject to test later, this should not be initialized with for a loop to add stats1 // and stats2 above. - private val testStats3 = NetworkStats(0L, 9) - // Entries which are unique either in stats1 or stats2. - .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, - METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 101, 2, 103, 4, 5)) - .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, - METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 31, 7, 24, 5, 8)) - .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, - METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 25, 3, 47, 8, 2)) - .addEntry(Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE, - METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0)) - // Entries which are common for stats1 and stats2 are being merged. - .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, - METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 20, 3, 57, 40, 3)) - .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80, - METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 20, 17, 13, 32, 1)) - .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE, - METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 50, 113, 11, 11, 49)) - .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE, - METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 51, 3, 3, 4, 15)) - .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE, - METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 7, 4, 8, 3, 0)) + private lateinit var testStats3: NetworkStats companion object { private const val TEST_IFACE = "test0" @@ -120,13 +71,67 @@ class NetworkStatsApiTest { @Before fun setUp() { + testStats1 = NetworkStats(0L, 0) + // Entries which only appear in set1. + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 20, 3, 57, 40, 3)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 31, 7, 24, 5, 8)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 25, 3, 47, 8, 2)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 37, 52, 1, 10, 4)) + // Entries which are common for set1 and set2. + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 101, 2, 103, 4, 5)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 17, 2, 11, 1, 0)) + .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 40, 1, 0, 0, 8)) + .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 1, 6, 2, 0)) assertEquals(8, testStats1.size()) + + testStats2 = NetworkStats(0L, 0) + // Entries which are common for set1 and set2. + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 15, 2, 31, 1)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 13, 61, 10, 1, 45)) + .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 11, 2, 3, 4, 7)) + .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 4, 3, 2, 1, 0)) + // Entry which only appears in set2. + .addEntry(Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0)) assertEquals(5, testStats2.size()) + + testStats3 = NetworkStats(0L, 9) + // Entries which are unique either in stats1 or stats2. + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 101, 2, 103, 4, 5)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 31, 7, 24, 5, 8)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 25, 3, 47, 8, 2)) + .addEntry(Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0)) + // Entries which are common for stats1 and stats2 are being merged. + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 20, 3, 57, 40, 3)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 20, 17, 13, 32, 1)) + .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 50, 113, 11, 11, 49)) + .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 51, 3, 3, 4, 15)) + .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 7, 4, 8, 3, 0)) assertEquals(9, testStats3.size()) } @Test - @IgnoreUpTo(Build.VERSION_CODES.Q) fun testAddEntry() { val expectedEntriesInStats2 = arrayOf( Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80, @@ -156,7 +161,6 @@ class NetworkStatsApiTest { } @Test - @IgnoreUpTo(Build.VERSION_CODES.Q) fun testAdd() { var stats = NetworkStats(0L, 0) assertNetworkStatsEquals(testStatsEmpty, stats) @@ -168,7 +172,6 @@ class NetworkStatsApiTest { } @Test - @IgnoreUpTo(Build.VERSION_CODES.Q) fun testParcelUnparcel() { assertParcelingIsLossless(testStatsEmpty) assertParcelingIsLossless(testStats1) @@ -177,7 +180,6 @@ class NetworkStatsApiTest { } @Test - @IgnoreUpTo(Build.VERSION_CODES.Q) fun testDescribeContents() { assertEquals(0, testStatsEmpty.describeContents()) assertEquals(0, testStats1.describeContents()) @@ -186,7 +188,6 @@ class NetworkStatsApiTest { } @Test - @IgnoreUpTo(Build.VERSION_CODES.Q) fun testSubtract() { // STATS3 - STATS2 = STATS1 assertNetworkStatsEquals(testStats1, testStats3.subtract(testStats2)) @@ -195,7 +196,6 @@ class NetworkStatsApiTest { } @Test - @IgnoreUpTo(Build.VERSION_CODES.Q) fun testMethodsDontModifyReceiver() { listOf(testStatsEmpty, testStats1, testStats2, testStats3).forEach { val origStats = it.clone() diff --git a/tools/stats_log_api_gen/Collation.h b/tools/stats_log_api_gen/Collation.h index d56f2be7ecb3..b513463ec98f 100644 --- a/tools/stats_log_api_gen/Collation.h +++ b/tools/stats_log_api_gen/Collation.h @@ -160,7 +160,7 @@ struct AtomDecl { int exclusiveField = 0; int defaultState = INT_MAX; int triggerStateReset = INT_MAX; - bool nested; + bool nested = true; int uidField = 0; |