Merge "Don't ask libc for PT feature w/o kernel support." into rvc-dev
diff --git a/Android.bp b/Android.bp
index 874d76f..d4ca706 100644
--- a/Android.bp
+++ b/Android.bp
@@ -911,6 +911,7 @@
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 8633c96..8139a2e 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.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 @@
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 @@
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 4ac3adf..c72cc9d 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.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 @@
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 @@
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 32e13e3..15d7495 100644
--- a/apex/statsd/Android.bp
+++ b/apex/statsd/Android.bp
@@ -67,7 +67,6 @@
"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 2d64f19..0df96e1 100644
--- a/apex/statsd/tests/libstatspull/Android.bp
+++ b/apex/statsd/tests/libstatspull/Android.bp
@@ -32,7 +32,7 @@
"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 629ce4e..c29f1c4 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -4814,7 +4814,14 @@
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 @@
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 d3d7e1d..65061d0 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -113,17 +113,18 @@
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 @@
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 2a7cfd3..d5da0b4 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 @@
};
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 a95bf3b..c5aff9d 100644
--- a/core/java/android/annotation/NonNull.java
+++ b/core/java/android/annotation/NonNull.java
@@ -30,8 +30,8 @@
* <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 e476993..b7ceb6a 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -299,7 +299,6 @@
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 f216db6..fc48e7f 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -979,17 +979,14 @@
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 @@
@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 f00feae..92df16c 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 @@
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 d8308c7..2dbaea8 100644
--- a/core/java/android/os/incremental/IIncrementalService.aidl
+++ b/core/java/android/os/incremental/IIncrementalService.aidl
@@ -38,13 +38,6 @@
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 0000000..5800ecf
--- /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 5f01408..35518db 100644
--- a/core/java/android/os/incremental/IncrementalManager.java
+++ b/core/java/android/os/incremental/IncrementalManager.java
@@ -19,13 +19,11 @@
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 @@
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 05877a5..c047dc0 100644
--- a/core/java/android/service/dataloader/DataLoaderService.java
+++ b/core/java/android/service/dataloader/DataLoaderService.java
@@ -21,7 +21,6 @@
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.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 @@
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 f531e12..f944dd7 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -55,6 +55,7 @@
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 @@
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 @@
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 @@
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 0ccb1e0..4469fdb 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -253,12 +253,14 @@
* @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 @@
* @hide
*/
@UnsupportedAppUsage
+ @TestApi
public static final int TYPE_EXTERNAL = 2;
/**
@@ -273,12 +276,14 @@
* @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 @@
* @hide
*/
@UnsupportedAppUsage
+ @TestApi
public static final int TYPE_VIRTUAL = 5;
/**
@@ -569,6 +575,7 @@
* @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 45e51f7..81bfcb0 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -29,6 +29,7 @@
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 @@
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 @@
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 69bab4d..6cb9374 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -91,6 +91,8 @@
if (mSourceControl == control) {
return;
}
+ SurfaceControl oldLeash = mSourceControl != null ? mSourceControl.getLeash() : null;
+
final InsetsSourceControl lastControl = mSourceControl;
mSourceControl = control;
@@ -116,6 +118,12 @@
// 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 f3ec65f..e001b66 100644
--- a/core/java/android/view/InsetsSourceControl.java
+++ b/core/java/android/view/InsetsSourceControl.java
@@ -44,8 +44,7 @@
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 a37c1cb..ab65e3a 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -32,6 +32,7 @@
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 @@
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 @@
* @hide
*/
public long mNativeObject;
+ private long mNativeHandle;
// TODO: Move this to native.
private final Object mSizeLock = new Object();
@@ -424,12 +430,13 @@
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 @@
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 @@
}
/**
+ * 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 @@
if (mNativeObject != 0) {
nativeRelease(mNativeObject);
mNativeObject = 0;
+ mNativeHandle = 0;
mCloseGuard.close();
}
}
@@ -2868,4 +2884,24 @@
}
}
}
+
+ /**
+ * 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 1e96a1c..750b1ed 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -21,6 +21,7 @@
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 @@
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 @@
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 @@
(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 @@
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 @@
}
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 144f8e3..39ed401 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -104,7 +104,7 @@
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 @@
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 71ccb59..849488d 100644
--- a/core/java/android/widget/RadioGroup.java
+++ b/core/java/android/widget/RadioGroup.java
@@ -22,6 +22,7 @@
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 @@
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 @@
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 9fc0da8..d43333e 100644
--- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
@@ -55,6 +55,7 @@
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 @@
UserHandle listUserHandle = listAdapter.getUserHandle();
if (!listUserHandle.equals(mWorkProfileUserHandle)
|| !mInjector.isQuietModeEnabled(mWorkProfileUserHandle)
- || !hasResolvedAppsInWorkProfile(listAdapter)) {
+ || listAdapter.getCount() == 0) {
return false;
}
DevicePolicyEventLogger
@@ -382,20 +383,6 @@
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 @@
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 35253b6..fc3f20f 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -994,7 +994,7 @@
if (isAutolaunching() || maybeAutolaunchActivity()) {
return;
}
- if (isResolverListEmpty(listAdapter)) {
+ if (mMultiProfilePagerAdapter.shouldShowEmptyStateScreen(listAdapter)) {
mMultiProfilePagerAdapter.showEmptyResolverListEmptyState(listAdapter);
} else {
mMultiProfilePagerAdapter.showListView(listAdapter);
@@ -1641,16 +1641,11 @@
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 3f897a5..033ac72 100644
--- a/core/java/com/android/internal/app/ResolverListController.java
+++ b/core/java/com/android/internal/app/ResolverListController.java
@@ -121,23 +121,13 @@
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 5a979ac..2668420 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.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 @@
/** @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 @@
}
private void updateImageMessages() {
- boolean displayExternalImage = false;
- ArraySet<View> newMessages = new ArraySet<>();
- if (mIsCollapsed) {
+ View newMessage = null;
+ if (mIsCollapsed && mGroups.size() > 0) {
- // 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++;
- }
+ // 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 b2ca0a7..1cfa12d 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 @@
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 @@
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 @@
(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 @@
(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 76f8f0f..61b9b25 100644
--- a/core/proto/android/stats/dnsresolver/dns_resolver.proto
+++ b/core/proto/android/stats/dnsresolver/dns_resolver.proto
@@ -62,6 +62,13 @@
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 @@
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 cbb379b..d432dda 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -696,8 +696,7 @@
// 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 bc0cdc1..583c751 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 @@
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 0bf8663..eb39d58 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 @@
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 41aa1ff..5088494 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -418,9 +418,9 @@
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 90bcd1c..1208062 100644
--- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp
+++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
@@ -403,3 +403,40 @@
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 88a8295..540955f 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.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 @@
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 @@
}
/**
+ * 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 07f4a9a..53e7921 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 ca085b6..f5cd727 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 50aa212..cb53fe6 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 2cc4b22..0000000
--- 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 9b9fbed..bce5fac 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 caee8cc..88f4176 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.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 @@
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 KeyguardAbsKeyInputView(Context context, AttributeSet attrs) {
super(context, attrs);
+ mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
}
@Override
@@ -151,6 +154,8 @@
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 d154434..af5196f 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.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 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 @@
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 @@
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 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 48c6bd1..ad92f8f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
@@ -282,6 +282,7 @@
@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 57e3f14..88d6943 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -223,6 +223,7 @@
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 @@
}
/**
+ * 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 @@
updateFingerprintListeningState();
} else {
setFingerprintRunningState(BIOMETRIC_STATE_STOPPED);
+ mFingerprintCancelSignal = null;
+ mFaceCancelSignal = null;
}
if (msgId == FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE) {
@@ -679,6 +692,11 @@
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 @@
}
private void handleFingerprintLockoutReset() {
+ mFingerprintLockedOut = false;
updateFingerprintListeningState();
}
@@ -1274,6 +1293,7 @@
private CancellationSignal mFaceCancelSignal;
private FingerprintManager mFpm;
private FaceManager mFaceManager;
+ private boolean mFingerprintLockedOut;
/**
* When we receive a
@@ -1820,13 +1840,17 @@
}
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 @@
// 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 23fa645..5442299 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -120,7 +120,7 @@
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 @@
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 6923079..13d6a9b 100644
--- a/packages/SystemUI/src/com/android/systemui/Interpolators.java
+++ b/packages/SystemUI/src/com/android/systemui/Interpolators.java
@@ -49,6 +49,8 @@
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 eff6934..044feaa 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 @@
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 @@
// 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 c16dce1..3f88f25 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 @@
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 @@
@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 @@
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 f7f9afd..18bfd89 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 @@
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 fdd8593..4dd5e87 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 @@
mAdapter = new MyAdapter();
+ mDepthController.setShowingHomeControls(shouldShowControls());
ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, getWalletPanelViewController(),
mDepthController, mSysuiColorExtractor, mStatusBarService,
mNotificationShadeWindowController,
@@ -1780,8 +1781,12 @@
}
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 @@
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 0000000..71bc7c2
--- /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 1868536..e24d29f 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 @@
/**
* @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 @@
transformBoundsToAspectRatio(destinationBounds, aspectRatio,
false /* useCurrentMinEdgeSize */);
}
- if (destinationBounds.equals(bounds)) {
- return bounds;
- }
mAspectRatio = aspectRatio;
- onResetReentryBoundsUnchecked();
mLastDestinationBounds.set(destinationBounds);
return destinationBounds;
}
@@ -483,6 +484,7 @@
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 d9872d7..b10dd93 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 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 @@
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 @@
}
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 b725811..0000000
--- 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 8397c65..a192afc 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.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.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 @@
/** 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 @@
/**
* 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 @@
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 @@
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 @@
}
/**
- * @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 @@
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 @@
* 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 @@
(target, values) -> updateAction.run());
}
- startBoundsAnimator(dismissEndPoint.x /* toX */, dismissEndPoint.y /* toY */);
+ startBoundsAnimator(mBounds.left /* toX */, mBounds.bottom + mBounds.height() /* toY */);
}
/**
@@ -408,6 +426,7 @@
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 @@
* 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 @@
}
/**
- * @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;
+ @Override
+ public float getHeight(@NonNull Rect animatedPipBounds) {
+ return animatedPipBounds.height();
+ }
- // 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 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 7cc2759..bbb4939 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_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.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.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 @@
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 @@
// 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 @@
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 @@
}
}
+ @SuppressLint("InflateParams")
public PipTouchHandler(Context context, IActivityManager activityManager,
IActivityTaskManager activityTaskManager, PipMenuActivityController menuController,
InputConsumerController inputConsumerController,
@@ -180,9 +219,9 @@
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 @@
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 @@
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 void onActivityPinned() {
- cleanUpDismissTarget();
+ createDismissTargetMaybe();
+
mShowPipMenuOnAnimationEnd = true;
mPipResizeGestureHandler.onActivityPinned();
mFloatingContentCoordinator.onContentAdded(mMotionHelper);
@@ -264,6 +355,10 @@
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 @@
}
}
+ /** 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 @@
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 @@
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 @@
mSavedSnapFraction = -1f;
if (mEnableDismissDragToEdge) {
- mHandler.removeCallbacks(mShowDismissAffordance);
- mDismissViewController.showDismissTarget();
+ if (mTargetViewContainer.getVisibility() != View.VISIBLE) {
+ mHandler.removeCallbacks(mShowTargetAction);
+ showDismissTargetMaybe();
+ }
}
}
@@ -644,10 +821,6 @@
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 @@
@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 @@
}
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 @@
}
/**
- * 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 e3f65ef..dc286c1 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 @@
// Initialize the velocity tracker
initOrResetVelocityTracker();
- addMovement(ev);
+ addMovementToVelocityTracker(ev);
mActivePointerId = ev.getPointerId(0);
if (DEBUG) {
@@ -120,7 +120,7 @@
}
// 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 @@
}
// Update the velocity tracker
- addMovement(ev);
+ addMovementToVelocityTracker(ev);
int pointerIndex = ev.getActionIndex();
int pointerId = ev.getPointerId(pointerIndex);
@@ -174,7 +174,7 @@
}
// Update the velocity tracker
- addMovement(ev);
+ addMovementToVelocityTracker(ev);
mVelocityTracker.computeCurrentVelocity(1000,
mViewConfig.getScaledMaximumFlingVelocity());
mVelocity.set(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
@@ -318,6 +318,20 @@
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 @@
}
}
- 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 9c175bc..8efeef1 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 @@
*/
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 fd44f04..729f934 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.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.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 @@
var shadeSpring = DepthAnimation()
@VisibleForTesting
var globalActionsSpring = DepthAnimation()
+ var showingHomeControls: Boolean = false
@VisibleForTesting
var brightnessMirrorSpring = DepthAnimation()
@@ -105,6 +108,16 @@
}
/**
+ * 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 @@
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 @@
brightnessMirrorSpring.finishIfRunning()
}
}
+
+ override fun onDozeAmountChanged(linear: Float, eased: Float) {
+ wakeAndUnlockBlurRadius = blurUtils.blurRadiusOfRatio(eased)
+ }
}
init {
@@ -201,6 +231,10 @@
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 @@
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 1696f07..53ec570 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 @@
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 @@
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 dd7be27..c1ba26d 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 @@
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 2917346..5c578df 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 @@
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 @@
private NotificationInlineImageResolver mImageResolver;
private NotificationMediaManager mMediaManager;
@Nullable private OnExpansionChangedListener mExpansionChangedListener;
+ @Nullable private Runnable mOnIntrinsicHeightReachedRunnable;
private SystemNotificationAsyncTask mSystemNotificationAsyncTask =
new SystemNotificationAsyncTask();
@@ -358,6 +365,16 @@
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 @@
if (mMenuRow != null && mMenuRow.getMenuView() != null) {
mMenuRow.onParentHeightUpdate();
}
+ handleIntrinsicHeightReached();
}
@Override
@@ -2907,6 +2925,24 @@
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 893e849..e4e3ebc 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 @@
* 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 @@
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 b18bf01..3c3f1b2 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 @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 f06cfec..82e02b4 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 @@
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 d70484e..a19d35a 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 @@
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 @@
@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 @@
super(context, attrs);
}
- void setStateProvider(StateProvider stateProvider) {
- mStateProvider = stateProvider;
- }
-
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
@@ -135,13 +134,16 @@
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 @@
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 @@
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 @@
}
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 f7c861b..a633e19 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 @@
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 @@
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 e1e679f..462b42a 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.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 @@
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 @@
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 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 812a1e4..f27bdbf 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 @@
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 @@
.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 @@
private val tempLoc = IntArray(2)
fun updateLocationOnScreen() {
- targetView.getLocationOnScreen(tempLoc)
+ 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)
+ // 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 bae5bb4..c22b718f 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 @@
}
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 1140b9a..e5da603 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 @@
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 072bc44..4bcf917 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 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.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 @@
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 @@
@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 @@
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 @@
@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 @@
}
@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 @@
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 475023e..5227aaf 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 @@
LOW_BMP_HEIGHT /* bmpHeight */,
LOW_BMP_WIDTH /* surfaceWidth */,
LOW_BMP_HEIGHT /* surfaceHeight */,
- true /* assertion */);
+ false /* assertion */);
}
@Test
@@ -172,7 +172,7 @@
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 c9bb401..9985d21 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.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 @@
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 @@
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 c483314..1f07f46 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 @@
}
@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 0bf0f04..425bf88 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 @@
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 @@
};
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 @@
};
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 @@
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 @@
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 @@
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 @@
}
@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 @@
+ " 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 6b7a3bf..c874915 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 @@
}
@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 f1672b1..f6b7b74 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 @@
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 @@
`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 1a1c30d..620261b 100644
--- a/packages/Tethering/tests/integration/Android.bp
+++ b/packages/Tethering/tests/integration/Android.bp
@@ -39,4 +39,9 @@
"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 dbd68ef..b02bb23 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 @@
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 530bc07..4ff1d37 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 0000000..355ece9
--- /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 0000000..d9d3e73
--- /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 a59c6fd..3a580dd 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.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 @@
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 2100c1a..7230b00 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 @@
&& 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 0000000..3612e09
--- /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 0000000..ca230b6
--- /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 e2d5112..0000000
--- 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 1290569..4ecffd8 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
inlineSuggestionsRequest.get(), response, filterText, mCurrentViewId,
this, () -> {
synchronized (mLock) {
- mInlineSuggestionSession.hideInlineSuggestionsUi(mCurrentViewId);
+ mInlineSessionController.hideInlineSuggestionsUiLocked(
+ mCurrentViewId);
}
}, remoteRenderService);
if (inlineSuggestionsResponse == null) {
@@ -2809,7 +2811,7 @@
return false;
}
- return mInlineSuggestionSession.onInlineSuggestionsResponse(mCurrentViewId,
+ return mInlineSessionController.onInlineSuggestionsResponseLocked(mCurrentViewId,
inlineSuggestionsResponse);
}
@@ -3106,8 +3108,13 @@
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 @@
&& (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 061ff42..58972a5 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -123,6 +123,8 @@
"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 ad3c8a6..d8acf0e 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 @@
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 52e9d7c..1931079 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 0000000..0bdf3f2
--- /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 f37af3a..9fb468e 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 @@
}
/**
+ * 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 1248ec0..f07fa50 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 @@
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 @@
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 bc3a035..f693555 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 @@
@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 @@
Slog.i(TAG, "Using ABIS and native lib paths from settings : " +
parsedPackage.getPackageName() + " " +
AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage)
- + ", "
+ + ", "
+ AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage));
}
}
@@ -17572,11 +17586,13 @@
}
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 e8765ad..ebdf856 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 @@
*/
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 @@
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 @@
}
/**
+ * 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 @@
*/
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 @@
}
}
+ // 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 0000000..807c82d
--- /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 a726d39..e3faffa 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 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 161f304..27288d8 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.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.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.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 @@
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 @@
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 5f871ad4..83e99b0 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 @@
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 5ce63de..c47d215 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_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_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 @@
}
@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 f5eba96..78d2afc 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.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 @@
return;
}
- setWindowingMode(windowingMode, false /* animate */, false /* showRecents */,
- false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */,
- false /* creating */);
+ setWindowingMode(windowingMode, false /* creating */);
}
/**
@@ -709,27 +706,22 @@
* @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 @@
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 @@
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 @@
mAtmService.deferWindowLayout();
try {
- if (!animate && topActivity != null) {
+ if (topActivity != null) {
mStackSupervisor.mNoAnimActivities.add(topActivity);
}
super.setWindowingMode(windowingMode);
@@ -845,36 +834,11 @@
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 02601ff..1e5a924 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_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.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 @@
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,56 +1433,6 @@
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) {
-
- mService.deferWindowLayout();
- try {
- final int windowingMode = fromStack.getWindowingMode();
- final TaskDisplayArea toDisplayArea = mRootWindowContainer
- .getDisplayContent(toDisplayId).getDefaultTaskDisplayArea();
-
- 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);
- }
- }
-
- // 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;
-
- if (fromStack.hasChild()) {
- mTmpOptions.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN);
- mMoveTaskToFullscreenHelper.process(
- fromStack, toDisplayArea, onTop, schedulePictureInPictureModeChange);
- }
-
- mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
- mRootWindowContainer.resumeFocusedStacksTopActivities();
- } finally {
- mService.continueWindowLayout();
- }
- }
-
- 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;
@@ -1560,26 +1442,46 @@
mWindowManager.setDockedStackResizing(resizing);
}
+ 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 */);
+
+ // Reparent all the tasks to the bottom of the display
+ final DisplayContent toDisplay =
+ mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY);
+
+ 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);
+ mRootWindowContainer.resumeFocusedStacksTopActivities();
+ } finally {
+ mService.continueWindowLayout();
+ }
+ }
+
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 0b19687..c253cd2 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 @@
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 @@
}
}
- @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 f86aeb2..78ee1de7 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_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 @@
}
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 90fdf19..edd14b7 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 @@
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 a93b962..2816633 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.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 @@
return null;
}
mShellRoots.put(windowType, root);
- SurfaceControl out = new SurfaceControl();
- out.copyFrom(rootLeash);
+ SurfaceControl out = new SurfaceControl(rootLeash);
return out;
}
@@ -3554,6 +3552,10 @@
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 f088dbc..e9d3d56 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 @@
mForceShowSystemBarsFromExternal = forceShowSystemBars;
}
+ boolean getForceShowSystemBars() {
+ return mForceShowSystemBarsFromExternal;
+ }
+
public boolean hasNavigationBar() {
return mHasNavigationBar;
}
@@ -1494,9 +1498,7 @@
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 @@
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 e4e5716..61a199a 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 @@
}
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 @@
// 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 5f3732a..7d8a56e 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.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 @@
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 @@
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 @@
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 @@
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 9732637..701feff 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 @@
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 cb897db..d31939d 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 @@
@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 @@
* 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 1bc72449..ec3c99b 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 @@
}
} 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 @@
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 @@
}
}
+ @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 2bbf8db..22702dd 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 @@
}
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 eb005e0..14e5c6c 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.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 @@
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 @@
}
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 @@
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 @@
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 569b8f6..f6e952c 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 @@
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 @@
mLastLayer = -1;
mSurfaceFreezer.unfreeze(t);
reassignLayer(t);
+ updateSurfacePosition(t);
}
/**
@@ -2365,11 +2370,15 @@
}
}
- 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 @@
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 @@
// 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 a1e0eb7..8d3a4f2 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.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 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
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 3ebc0f4..5a76bac 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 @@
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 @@
@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 7457a1d..23091a0 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 @@
}
@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 97de1800..2dbbc5a 100644
--- a/services/incremental/BinderIncrementalService.cpp
+++ b/services/incremental/BinderIncrementalService.cpp
@@ -155,11 +155,6 @@
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 d0357d9..28613e1 100644
--- a/services/incremental/BinderIncrementalService.h
+++ b/services/incremental/BinderIncrementalService.h
@@ -71,7 +71,6 @@
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 d36eae8..d1153e6 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -73,7 +73,7 @@
};
static const Constants& constants() {
- static Constants c;
+ static constexpr Constants c;
return c;
}
@@ -159,6 +159,9 @@
}
} // 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 @@
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 @@
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 @@
return 0;
}
-std::string IncrementalService::normalizePathToStorage(const IncrementalService::IfsMountPtr ifs,
+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(storageIt->second.name, path));
+ }
+ 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 {};
}
- std::string normPath;
- if (path::isAbsolute(path)) {
- normPath = path::normalize(path);
- } else {
- normPath = path::normalize(path::join(storageInfo->second.name, path));
- }
- if (!path::startsWith(normPath, storageInfo->second.name)) {
- return {};
- }
- return normPath;
+ return normalizePathToStorageLocked(storageInfo, path);
}
int IncrementalService::makeFile(StorageId storage, std::string_view path, int mode, FileId id,
@@ -791,7 +804,8 @@
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 @@
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::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 @@
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 @@
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 @@
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;
}
- instructions.clear();
+
+ 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);
+ }
}
- 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 @@
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 5800297..db14a79 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 @@
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 @@
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 @@
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 e5ffbac..e2a2473 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.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 @@
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 d69e1b8..8398585 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 @@
private TestData mBarUser0UnsupportedClassLoader;
private TestData mBarUser0DelegateLastClassLoader;
+ private TestData mSystemServerJar;
+ private TestData mSystemServerJarInvalid;
+
private int mUser0;
private int mUser1;
@@ -108,6 +111,9 @@
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 @@
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 89bc65b..b6eb901 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_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.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.InsetsState;
import android.view.test.InsetsModeSession;
-import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import org.junit.AfterClass;
@@ -168,6 +165,18 @@
}
@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 add4e9c..ea933df 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 @@
}
});
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 6d2b7b1..f19550c 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.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 @@
// 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 2ce9c2b..06ca6c1 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 @@
}
@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 1ca2e318..397f73c 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 @@
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 f3e9de0..3048ad7 100644
--- a/telephony/common/android/telephony/LocationAccessPolicy.java
+++ b/telephony/common/android/telephony/LocationAccessPolicy.java
@@ -311,7 +311,7 @@
}
// 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 2b1d9e5..18e2592 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -113,6 +113,7 @@
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 0000000..ac1c28a
--- /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 6e9dc8e..3f8261d 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_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.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 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 @@
@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 @@
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 9119d62..7b22e45 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.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 @@
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 @@
@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 @@
}
@Test
- @IgnoreUpTo(Build.VERSION_CODES.Q)
fun testAdd() {
var stats = NetworkStats(0L, 0)
assertNetworkStatsEquals(testStatsEmpty, stats)
@@ -168,7 +172,6 @@
}
@Test
- @IgnoreUpTo(Build.VERSION_CODES.Q)
fun testParcelUnparcel() {
assertParcelingIsLossless(testStatsEmpty)
assertParcelingIsLossless(testStats1)
@@ -177,7 +180,6 @@
}
@Test
- @IgnoreUpTo(Build.VERSION_CODES.Q)
fun testDescribeContents() {
assertEquals(0, testStatsEmpty.describeContents())
assertEquals(0, testStats1.describeContents())
@@ -186,7 +188,6 @@
}
@Test
- @IgnoreUpTo(Build.VERSION_CODES.Q)
fun testSubtract() {
// STATS3 - STATS2 = STATS1
assertNetworkStatsEquals(testStats1, testStats3.subtract(testStats2))
@@ -195,7 +196,6 @@
}
@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 d56f2be..b513463e 100644
--- a/tools/stats_log_api_gen/Collation.h
+++ b/tools/stats_log_api_gen/Collation.h
@@ -160,7 +160,7 @@
int exclusiveField = 0;
int defaultState = INT_MAX;
int triggerStateReset = INT_MAX;
- bool nested;
+ bool nested = true;
int uidField = 0;