summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/ActivityThread.java28
-rw-r--r--core/java/android/app/IActivityManager.aidl2
-rw-r--r--core/java/android/app/RemoteServiceException.java103
-rw-r--r--core/java/android/provider/Settings.java8
-rw-r--r--core/java/android/service/wallpaper/EngineWindowPage.java6
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java89
-rw-r--r--core/java/android/widget/RemoteViews.java402
-rw-r--r--core/tests/coretests/src/android/app/activity/ActivityThreadTest.java7
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java20
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java71
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java119
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java41
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java234
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java137
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java (renamed from core/java/android/app/ForegroundServiceDidNotStartInTimeException.java)25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java60
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java96
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java33
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java1
-rw-r--r--packages/SystemUI/animation/res/values/ids.xml1
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt103
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java24
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java42
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIFactory.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java67
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java83
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java51
-rw-r--r--packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java17
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java34
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt77
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java71
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java50
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt98
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java69
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java17
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java8
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java8
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java4
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueue.java12
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java6
-rw-r--r--services/core/java/com/android/server/am/ServiceRecord.java4
-rw-r--r--services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java2
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java10
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java13
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java2
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java2
74 files changed, 2006 insertions, 699 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 431755e092e3..17bb797bf8c5 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -39,6 +39,12 @@ import static com.android.internal.annotations.VisibleForTesting.Visibility.PACK
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.RemoteServiceException.BadForegroundServiceNotificationException;
+import android.app.RemoteServiceException.CannotDeliverBroadcastException;
+import android.app.RemoteServiceException.CannotPostForegroundServiceNotificationException;
+import android.app.RemoteServiceException.CrashedByAdbException;
+import android.app.RemoteServiceException.ForegroundServiceDidNotStartInTimeException;
+import android.app.RemoteServiceException.MissingRequestPasswordComplexityPermissionException;
import android.app.assist.AssistContent;
import android.app.assist.AssistStructure;
import android.app.backup.BackupAgent;
@@ -1921,11 +1927,27 @@ public final class ActivityThread extends ClientTransactionHandler
private void throwRemoteServiceException(String message, int typeId) {
// Use a switch to ensure all the type IDs are unique.
switch (typeId) {
- case ForegroundServiceDidNotStartInTimeException.TYPE_ID: // 1
+ case ForegroundServiceDidNotStartInTimeException.TYPE_ID:
throw new ForegroundServiceDidNotStartInTimeException(message);
- case RemoteServiceException.TYPE_ID: // 0
+
+ case CannotDeliverBroadcastException.TYPE_ID:
+ throw new CannotDeliverBroadcastException(message);
+
+ case CannotPostForegroundServiceNotificationException.TYPE_ID:
+ throw new CannotPostForegroundServiceNotificationException(message);
+
+ case BadForegroundServiceNotificationException.TYPE_ID:
+ throw new BadForegroundServiceNotificationException(message);
+
+ case MissingRequestPasswordComplexityPermissionException.TYPE_ID:
+ throw new MissingRequestPasswordComplexityPermissionException(message);
+
+ case CrashedByAdbException.TYPE_ID:
+ throw new CrashedByAdbException(message);
+
default:
- throw new RemoteServiceException(message);
+ throw new RemoteServiceException(message
+ + " (with unwknown typeId:" + typeId + ")");
}
}
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 0210a94eafcc..9e9e28b8bd4c 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -319,8 +319,6 @@ interface IActivityManager {
void handleApplicationStrictModeViolation(in IBinder app, int penaltyMask,
in StrictMode.ViolationInfo crashInfo);
boolean isTopActivityImmersive();
- void crashApplication(int uid, int initialPid, in String packageName, int userId,
- in String message, boolean force);
void crashApplicationWithType(int uid, int initialPid, in String packageName, int userId,
in String message, boolean force, int exceptionTypeId);
/** @deprecated -- use getProviderMimeTypeAsync */
diff --git a/core/java/android/app/RemoteServiceException.java b/core/java/android/app/RemoteServiceException.java
index 4b32463e2996..1038530d92d3 100644
--- a/core/java/android/app/RemoteServiceException.java
+++ b/core/java/android/app/RemoteServiceException.java
@@ -19,20 +19,109 @@ package android.app;
import android.util.AndroidRuntimeException;
/**
- * Exception used by {@link ActivityThread} to crash an app process.
+ * Exception used by {@link ActivityThread} to crash an app process for an unknown cause.
+ * An exception of this class is no longer supposed to be thrown. Instead, we use fine-grained
+ * sub-exceptions.
+ *
+ * Subclasses must be registered in
+ * {@link android.app.ActivityThread#throwRemoteServiceException(java.lang.String, int)}.
*
* @hide
*/
public class RemoteServiceException extends AndroidRuntimeException {
+ public RemoteServiceException(String msg) {
+ super(msg);
+ }
+
/**
- * The type ID passed to {@link IApplicationThread#scheduleCrash}.
+ * Exception used to crash an app process when it didn't call {@link Service#startForeground}
+ * in time after the service was started with
+ * {@link android.content.Context#startForegroundService}.
*
- * Assign a unique ID to each subclass. See the above method for the numbers that are already
- * taken.
+ * @hide
*/
- public static final int TYPE_ID = 0;
+ public static class ForegroundServiceDidNotStartInTimeException extends RemoteServiceException {
+ /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */
+ public static final int TYPE_ID = 1;
- public RemoteServiceException(String msg) {
- super(msg);
+ public ForegroundServiceDidNotStartInTimeException(String msg) {
+ super(msg);
+ }
+ }
+
+ /**
+ * Exception used to crash an app process when the system received a RemoteException
+ * while delivering a broadcast to an app process.
+ *
+ * @hide
+ */
+ public static class CannotDeliverBroadcastException extends RemoteServiceException {
+ /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */
+ public static final int TYPE_ID = 2;
+
+ public CannotDeliverBroadcastException(String msg) {
+ super(msg);
+ }
+ }
+
+ /**
+ * Exception used to crash an app process when the system received a RemoteException
+ * while posting a notification of a foreground service.
+ *
+ * @hide
+ */
+ public static class CannotPostForegroundServiceNotificationException
+ extends RemoteServiceException {
+ /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */
+ public static final int TYPE_ID = 3;
+
+ public CannotPostForegroundServiceNotificationException(String msg) {
+ super(msg);
+ }
+ }
+
+ /**
+ * Exception used to crash an app process when the system finds an error in a foreground service
+ * notification.
+ *
+ * @hide
+ */
+ public static class BadForegroundServiceNotificationException extends RemoteServiceException {
+ /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */
+ public static final int TYPE_ID = 4;
+
+ public BadForegroundServiceNotificationException(String msg) {
+ super(msg);
+ }
+ }
+
+ /**
+ * Exception used to crash an app process when it calls a setting activity that requires
+ * the {@code REQUEST_PASSWORD_COMPLEXITY} permission.
+ *
+ * @hide
+ */
+ public static class MissingRequestPasswordComplexityPermissionException
+ extends RemoteServiceException {
+ /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */
+ public static final int TYPE_ID = 5;
+
+ public MissingRequestPasswordComplexityPermissionException(String msg) {
+ super(msg);
+ }
+ }
+
+ /**
+ * Exception used to crash an app process by {@code adb shell am crash}.
+ *
+ * @hide
+ */
+ public static class CrashedByAdbException extends RemoteServiceException {
+ /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */
+ public static final int TYPE_ID = 6;
+
+ public CrashedByAdbException(String msg) {
+ super(msg);
+ }
}
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 1b6a03c7a736..50155f992aaf 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9604,6 +9604,14 @@ public final class Settings {
public static final String LOCKSCREEN_SHOW_WALLET = "lockscreen_show_wallet";
/**
+ * Whether to use the lockscreen double-line clock
+ *
+ * @hide
+ */
+ public static final String LOCKSCREEN_USE_DOUBLE_LINE_CLOCK =
+ "lockscreen_use_double_line_clock";
+
+ /**
* Specifies whether the web action API is enabled.
*
* @hide
diff --git a/core/java/android/service/wallpaper/EngineWindowPage.java b/core/java/android/service/wallpaper/EngineWindowPage.java
index 5ed0ad6f2aeb..006e3cdf03f8 100644
--- a/core/java/android/service/wallpaper/EngineWindowPage.java
+++ b/core/java/android/service/wallpaper/EngineWindowPage.java
@@ -24,7 +24,6 @@ import android.util.ArraySet;
import java.util.Map;
import java.util.Set;
-import java.util.function.Consumer;
/**
* This class represents a page of a launcher page used by the wallpaper
@@ -84,11 +83,6 @@ public class EngineWindowPage {
return mCallbackAreas;
}
- /** run operations on this page */
- public synchronized void execSync(Consumer<EngineWindowPage> run) {
- run.accept(this);
- }
-
/** nullify the area color */
public void removeColor(RectF colorArea) {
mRectFColors.remove(colorArea);
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index c77399f692f0..7b8410bba6ec 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -1490,7 +1490,7 @@ public abstract class WallpaperService extends Service {
//below is the default implementation
if (xOffset % xOffsetStep > MIN_PAGE_ALLOWED_MARGIN
|| !mSurfaceHolder.getSurface().isValid()) return;
- int xPage;
+ int xCurrentPage;
int xPages;
if (!validStep(xOffsetStep)) {
if (DEBUG) {
@@ -1498,30 +1498,34 @@ public abstract class WallpaperService extends Service {
}
xOffset = 0;
xOffsetStep = 1;
- xPage = 0;
+ xCurrentPage = 0;
xPages = 1;
} else {
xPages = Math.round(1 / xOffsetStep) + 1;
xOffsetStep = (float) 1 / (float) xPages;
float shrink = (float) (xPages - 1) / (float) xPages;
xOffset *= shrink;
- xPage = Math.round(xOffset / xOffsetStep);
+ xCurrentPage = Math.round(xOffset / xOffsetStep);
}
if (DEBUG) {
- Log.d(TAG, "xPages " + xPages + " xPage " + xPage);
+ Log.d(TAG, "xPages " + xPages + " xPage " + xCurrentPage);
Log.d(TAG, "xOffsetStep " + xOffsetStep + " xOffset " + xOffset);
}
- EngineWindowPage current;
- synchronized (mLock) {
+
+ float finalXOffsetStep = xOffsetStep;
+ float finalXOffset = xOffset;
+ mHandler.post(() -> {
+ int xPage = xCurrentPage;
+ EngineWindowPage current;
if (mWindowPages.length == 0 || (mWindowPages.length != xPages)) {
mWindowPages = new EngineWindowPage[xPages];
- initWindowPages(mWindowPages, xOffsetStep);
+ initWindowPages(mWindowPages, finalXOffsetStep);
}
if (mLocalColorsToAdd.size() != 0) {
for (RectF colorArea : mLocalColorsToAdd) {
if (!isValid(colorArea)) continue;
mLocalColorAreas.add(colorArea);
- int colorPage = getRectFPage(colorArea, xOffsetStep);
+ int colorPage = getRectFPage(colorArea, finalXOffsetStep);
EngineWindowPage currentPage = mWindowPages[colorPage];
if (currentPage == null) {
currentPage = new EngineWindowPage();
@@ -1539,7 +1543,8 @@ public abstract class WallpaperService extends Service {
Log.e(TAG, "error xPage >= mWindowPages.length page: " + xPage);
Log.e(TAG, "error on page " + xPage + " out of " + xPages);
Log.e(TAG,
- "error on xOffsetStep " + xOffsetStep + " xOffset " + xOffset);
+ "error on xOffsetStep " + finalXOffsetStep
+ + " xOffset " + finalXOffset);
}
xPage = mWindowPages.length - 1;
}
@@ -1547,13 +1552,14 @@ public abstract class WallpaperService extends Service {
if (current == null) {
if (DEBUG) Log.d(TAG, "making page " + xPage + " out of " + xPages);
if (DEBUG) {
- Log.d(TAG, "xOffsetStep " + xOffsetStep + " xOffset " + xOffset);
+ Log.d(TAG, "xOffsetStep " + finalXOffsetStep + " xOffset "
+ + finalXOffset);
}
current = new EngineWindowPage();
mWindowPages[xPage] = current;
}
- }
- updatePage(current, xPage, xPages, xOffsetStep);
+ updatePage(current, xPage, xPages, finalXOffsetStep);
+ });
}
private void initWindowPages(EngineWindowPage[] windowPages, float step) {
@@ -1603,10 +1609,8 @@ public abstract class WallpaperService extends Service {
if (DEBUG) Log.d(TAG, "result of pixel copy is " + res);
if (res != PixelCopy.SUCCESS) {
Bitmap lastBitmap = currentPage.getBitmap();
- currentPage.execSync((p) -> {
- // assign the last bitmap taken for now
- p.setBitmap(mLastScreenshot);
- });
+ // assign the last bitmap taken for now
+ currentPage.setBitmap(mLastScreenshot);
Bitmap lastScreenshot = mLastScreenshot;
if (lastScreenshot != null && !lastScreenshot.isRecycled()
&& !Objects.equals(lastBitmap, lastScreenshot)) {
@@ -1615,10 +1619,8 @@ public abstract class WallpaperService extends Service {
} else {
mLastScreenshot = finalScreenShot;
// going to hold this lock for a while
- currentPage.execSync((p) -> {
- p.setBitmap(finalScreenShot);
- p.setLastUpdateTime(current);
- });
+ currentPage.setBitmap(finalScreenShot);
+ currentPage.setLastUpdateTime(current);
updatePageColors(currentPage, pageIndx, numPages, xOffsetStep);
}
}, mHandler);
@@ -1698,16 +1700,14 @@ public abstract class WallpaperService extends Service {
private void resetWindowPages() {
if (supportsLocalColorExtraction()) return;
mLastWindowPage = -1;
- synchronized (mLock) {
+ mHandler.post(() -> {
for (int i = 0; i < mWindowPages.length; i++) {
EngineWindowPage page = mWindowPages[i];
if (page != null) {
- page.execSync((p) -> {
- p.setLastUpdateTime(0L);
- });
+ page.setLastUpdateTime(0L);
}
}
- }
+ });
}
private int getRectFPage(RectF area, float step) {
@@ -1730,10 +1730,10 @@ public abstract class WallpaperService extends Service {
if (DEBUG) {
Log.d(TAG, "addLocalColorsAreas adding local color areas " + regions);
}
- float step = mPendingXOffsetStep;
List<WallpaperColors> colors = getLocalWallpaperColors(regions);
- synchronized (mLock) {
+ mHandler.post(() -> {
+ float step = mPendingXOffsetStep;
if (!validStep(step)) {
step = 0;
}
@@ -1749,26 +1749,25 @@ public abstract class WallpaperService extends Service {
page.addArea(area);
WallpaperColors color = colors.get(i);
if (color != null && !color.equals(page.getColors(area))) {
- page.execSync(p -> {
- p.addWallpaperColors(area, color);
- });
+ page.addWallpaperColors(area, color);
}
} else {
mLocalColorsToAdd.add(area);
}
}
- }
-
- for (int i = 0; i < colors.size() && colors.get(i) != null; i++) {
- try {
- mConnection.onLocalWallpaperColorsChanged(regions.get(i), colors.get(i),
- mDisplayContext.getDisplayId());
- } catch (RemoteException e) {
- Log.e(TAG, "Error calling Connection.onLocalWallpaperColorsChanged", e);
- return;
+ for (int i = 0; i < colors.size() && colors.get(i) != null; i++) {
+ try {
+ mConnection.onLocalWallpaperColorsChanged(regions.get(i), colors.get(i),
+ mDisplayContext.getDisplayId());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling Connection.onLocalWallpaperColorsChanged", e);
+ return;
+ }
}
- }
- processLocalColors(mPendingXOffset, mPendingYOffset);
+ processLocalColors(mPendingXOffset, mPendingYOffset);
+ });
+
+
}
/**
@@ -1778,7 +1777,7 @@ public abstract class WallpaperService extends Service {
*/
public void removeLocalColorsAreas(@NonNull List<RectF> regions) {
if (supportsLocalColorExtraction()) return;
- synchronized (mLock) {
+ mHandler.post(() -> {
float step = mPendingXOffsetStep;
mLocalColorsToAdd.removeAll(regions);
mLocalColorAreas.removeAll(regions);
@@ -1792,12 +1791,10 @@ public abstract class WallpaperService extends Service {
// no page should be null
EngineWindowPage page = mWindowPages[pageInx];
if (page != null) {
- page.execSync(p -> {
- p.removeArea(area);
- });
+ page.removeArea(area);
}
}
- }
+ });
}
private @NonNull List<WallpaperColors> getLocalWallpaperColors(@NonNull List<RectF> areas) {
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index f724285df9dc..3d4d9eca6b16 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -338,7 +338,10 @@ public class RemoteViews implements Parcelable, Filter {
* Maps bitmaps to unique indicies to avoid Bitmap duplication.
*/
@UnsupportedAppUsage
- private BitmapCache mBitmapCache;
+ private BitmapCache mBitmapCache = new BitmapCache();
+
+ /** Cache of ApplicationInfos used by collection items. */
+ private ApplicationInfoCache mApplicationInfoCache = new ApplicationInfoCache();
/**
* Indicates whether or not this RemoteViews object is contained as a child of any other
@@ -576,7 +579,7 @@ public class RemoteViews implements Parcelable, Filter {
return 0;
}
- public void setBitmapCache(BitmapCache bitmapCache) {
+ public void setHierarchyRootData(HierarchyRootData root) {
// Do nothing
}
@@ -605,14 +608,6 @@ public class RemoteViews implements Parcelable, Filter {
return false;
}
- /**
- * Overridden by subclasses which have (or inherit) an ApplicationInfo instance
- * as member variable
- */
- public boolean hasSameAppInfo(ApplicationInfo parentInfo) {
- return true;
- }
-
public void visitUris(@NonNull Consumer<Uri> visitor) {
// Nothing to visit by default
}
@@ -690,9 +685,8 @@ public class RemoteViews implements Parcelable, Filter {
}
}
- // Because pruning can remove the need for bitmaps, we reconstruct the bitmap cache
- mBitmapCache = new BitmapCache();
- setBitmapCache(mBitmapCache);
+ // Because pruning can remove the need for bitmaps, we reconstruct the caches.
+ reconstructCaches();
}
/**
@@ -938,23 +932,70 @@ public class RemoteViews implements Parcelable, Filter {
ArrayList<RemoteViews> list;
}
- private static class SetRemoteCollectionItemListAdapterAction extends Action {
+ /**
+ * Cache of {@link ApplicationInfo}s that can be used to ensure that the same
+ * {@link ApplicationInfo} instance is used throughout the RemoteViews.
+ */
+ private static class ApplicationInfoCache {
+ private final Map<Pair<String, Integer>, ApplicationInfo> mPackageUserToApplicationInfo;
+
+ ApplicationInfoCache() {
+ mPackageUserToApplicationInfo = new ArrayMap<>();
+ }
+
+ /**
+ * Adds the {@link ApplicationInfo} to the cache if it's not present. Returns either the
+ * provided {@code applicationInfo} or a previously added value with the same package name
+ * and uid.
+ */
+ @Nullable
+ ApplicationInfo getOrPut(@Nullable ApplicationInfo applicationInfo) {
+ Pair<String, Integer> key = getPackageUserKey(applicationInfo);
+ if (key == null) return null;
+ return mPackageUserToApplicationInfo.computeIfAbsent(key, ignored -> applicationInfo);
+ }
+
+ /** Puts the {@link ApplicationInfo} in the cache, replacing any previously stored value. */
+ void put(@Nullable ApplicationInfo applicationInfo) {
+ Pair<String, Integer> key = getPackageUserKey(applicationInfo);
+ if (key == null) return;
+ mPackageUserToApplicationInfo.put(key, applicationInfo);
+ }
+
+ /**
+ * Returns the currently stored {@link ApplicationInfo} from the cache matching
+ * {@code applicationInfo}, or null if there wasn't any.
+ */
+ @Nullable ApplicationInfo get(@Nullable ApplicationInfo applicationInfo) {
+ Pair<String, Integer> key = getPackageUserKey(applicationInfo);
+ if (key == null) return null;
+ return mPackageUserToApplicationInfo.get(key);
+ }
+ }
+
+ private class SetRemoteCollectionItemListAdapterAction extends Action {
private final RemoteCollectionItems mItems;
SetRemoteCollectionItemListAdapterAction(@IdRes int id, RemoteCollectionItems items) {
viewId = id;
mItems = items;
+ mItems.setHierarchyRootData(getHierarchyRootData());
}
SetRemoteCollectionItemListAdapterAction(Parcel parcel) {
viewId = parcel.readInt();
- mItems = parcel.readTypedObject(RemoteCollectionItems.CREATOR);
+ mItems = new RemoteCollectionItems(parcel, getHierarchyRootData());
+ }
+
+ @Override
+ public void setHierarchyRootData(HierarchyRootData rootData) {
+ mItems.setHierarchyRootData(rootData);
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(viewId);
- dest.writeTypedObject(mItems, flags);
+ mItems.writeToParcel(dest, flags, /* attached= */ true);
}
@Override
@@ -1602,8 +1643,8 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void setBitmapCache(BitmapCache bitmapCache) {
- bitmapId = bitmapCache.getBitmapId(bitmap);
+ public void setHierarchyRootData(HierarchyRootData rootData) {
+ bitmapId = rootData.mBitmapCache.getBitmapId(bitmap);
}
@Override
@@ -2220,15 +2261,6 @@ public class RemoteViews implements Parcelable, Filter {
}
}
- private void configureRemoteViewsAsChild(RemoteViews rv) {
- rv.setBitmapCache(mBitmapCache);
- rv.setNotRoot();
- }
-
- void setNotRoot() {
- mIsRoot = false;
- }
-
private static boolean hasStableId(View view) {
Object tag = view.getTag(com.android.internal.R.id.remote_views_stable_id);
return tag != null;
@@ -2302,17 +2334,14 @@ public class RemoteViews implements Parcelable, Filter {
mNestedViews = nestedViews;
mIndex = index;
mStableId = stableId;
- if (nestedViews != null) {
- configureRemoteViewsAsChild(nestedViews);
- }
+ nestedViews.configureAsChild(getHierarchyRootData());
}
- ViewGroupActionAdd(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info,
- int depth, Map<Class, Object> classCookies) {
+ ViewGroupActionAdd(Parcel parcel, ApplicationInfo info, int depth) {
viewId = parcel.readInt();
mIndex = parcel.readInt();
mStableId = parcel.readInt();
- mNestedViews = new RemoteViews(parcel, bitmapCache, info, depth, classCookies);
+ mNestedViews = new RemoteViews(parcel, getHierarchyRootData(), info, depth);
mNestedViews.addFlags(mApplyFlags);
}
@@ -2324,8 +2353,8 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public boolean hasSameAppInfo(ApplicationInfo parentInfo) {
- return mNestedViews.hasSameAppInfo(parentInfo);
+ public void setHierarchyRootData(HierarchyRootData root) {
+ mNestedViews.configureAsChild(root);
}
private int findViewIndexToRecycle(ViewGroup target, RemoteViews newContent) {
@@ -2494,11 +2523,6 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public void setBitmapCache(BitmapCache bitmapCache) {
- mNestedViews.setBitmapCache(bitmapCache);
- }
-
- @Override
public int mergeBehavior() {
return MERGE_APPEND;
}
@@ -3505,8 +3529,7 @@ public class RemoteViews implements Parcelable, Filter {
protected RemoteViews(ApplicationInfo application, @LayoutRes int layoutId) {
mApplication = application;
mLayoutId = layoutId;
- mBitmapCache = new BitmapCache();
- mClassCookies = null;
+ mApplicationInfoCache.put(application);
}
private boolean hasMultipleLayouts() {
@@ -3562,12 +3585,10 @@ public class RemoteViews implements Parcelable, Filter {
mLandscape = landscape;
mPortrait = portrait;
- mBitmapCache = new BitmapCache();
- configureRemoteViewsAsChild(landscape);
- configureRemoteViewsAsChild(portrait);
-
mClassCookies = (portrait.mClassCookies != null)
? portrait.mClassCookies : landscape.mClassCookies;
+
+ configureDescendantsAsChildren();
}
/**
@@ -3593,10 +3614,12 @@ public class RemoteViews implements Parcelable, Filter {
throw new IllegalArgumentException("Too many RemoteViews in constructor");
}
if (remoteViews.size() == 1) {
- initializeFrom(remoteViews.values().iterator().next());
+ // If the map only contains a single mapping, treat this as if that RemoteViews was
+ // passed as the top-level RemoteViews.
+ RemoteViews single = remoteViews.values().iterator().next();
+ initializeFrom(single, /* hierarchyRoot= */ single);
return;
}
- mBitmapCache = new BitmapCache();
mClassCookies = initializeSizedRemoteViews(
remoteViews.entrySet().stream().map(
entry -> {
@@ -3611,6 +3634,8 @@ public class RemoteViews implements Parcelable, Filter {
mLayoutId = smallestView.mLayoutId;
mViewId = smallestView.mViewId;
mLightBackgroundLayoutId = smallestView.mLightBackgroundLayoutId;
+
+ configureDescendantsAsChildren();
}
// Initialize mSizedRemoteViews and return the class cookies.
@@ -3639,7 +3664,6 @@ public class RemoteViews implements Parcelable, Filter {
} else {
sizedRemoteViews.add(view);
}
- configureRemoteViewsAsChild(view);
view.setIdealSize(size);
if (classCookies == null) {
classCookies = view.mClassCookies;
@@ -3654,13 +3678,38 @@ public class RemoteViews implements Parcelable, Filter {
* Creates a copy of another RemoteViews.
*/
public RemoteViews(RemoteViews src) {
- initializeFrom(src);
+ initializeFrom(src, /* hierarchyRoot= */ null);
}
- private void initializeFrom(RemoteViews src) {
- mBitmapCache = src.mBitmapCache;
+ /**
+ * No-arg constructor for use with {@link #initializeFrom(RemoteViews, RemoteViews)}. A
+ * constructor taking two RemoteViews parameters would clash with the landscape/portrait
+ * constructor.
+ */
+ private RemoteViews() {}
+
+ private static RemoteViews createInitializedFrom(@NonNull RemoteViews src,
+ @Nullable RemoteViews hierarchyRoot) {
+ RemoteViews child = new RemoteViews();
+ child.initializeFrom(src, hierarchyRoot);
+ return child;
+ }
+
+ private void initializeFrom(@NonNull RemoteViews src, @Nullable RemoteViews hierarchyRoot) {
+ if (hierarchyRoot == null || src.mIsRoot) {
+ // If there's no provided root, or if src was itself a root, then this RemoteViews is
+ // the root of the new hierarchy.
+ mIsRoot = true;
+ mBitmapCache = new BitmapCache();
+ mApplicationInfoCache = new ApplicationInfoCache();
+ hierarchyRoot = this;
+ } else {
+ // Otherwise, we're a descendant in the hierarchy.
+ mIsRoot = false;
+ mBitmapCache = hierarchyRoot.mBitmapCache;
+ mApplicationInfoCache = hierarchyRoot.mApplicationInfoCache;
+ }
mApplication = src.mApplication;
- mIsRoot = src.mIsRoot;
mLayoutId = src.mLayoutId;
mLightBackgroundLayoutId = src.mLightBackgroundLayoutId;
mApplyFlags = src.mApplyFlags;
@@ -3669,21 +3718,21 @@ public class RemoteViews implements Parcelable, Filter {
mProviderInstanceId = src.mProviderInstanceId;
if (src.hasLandscapeAndPortraitLayouts()) {
- mLandscape = new RemoteViews(src.mLandscape);
- mPortrait = new RemoteViews(src.mPortrait);
+ mLandscape = createInitializedFrom(src.mLandscape, hierarchyRoot);
+ mPortrait = createInitializedFrom(src.mPortrait, hierarchyRoot);
}
if (src.hasSizedRemoteViews()) {
mSizedRemoteViews = new ArrayList<>(src.mSizedRemoteViews.size());
for (RemoteViews srcView : src.mSizedRemoteViews) {
- mSizedRemoteViews.add(new RemoteViews(srcView));
+ mSizedRemoteViews.add(createInitializedFrom(srcView, hierarchyRoot));
}
}
if (src.mActions != null) {
Parcel p = Parcel.obtain();
p.putClassCookies(mClassCookies);
- src.writeActionsToParcel(p);
+ src.writeActionsToParcel(p, /* flags= */ 0);
p.setDataPosition(0);
// Since src is already in memory, we do not care about stack overflow as it has
// already been read once.
@@ -3691,9 +3740,11 @@ public class RemoteViews implements Parcelable, Filter {
p.recycle();
}
- // Now that everything is initialized and duplicated, setting a new BitmapCache will
- // re-initialize the cache.
- setBitmapCache(new BitmapCache());
+ // Now that everything is initialized and duplicated, create new caches for this
+ // RemoteViews and recursively set up all descendants.
+ if (mIsRoot) {
+ reconstructCaches();
+ }
}
/**
@@ -3702,11 +3753,11 @@ public class RemoteViews implements Parcelable, Filter {
* @param parcel
*/
public RemoteViews(Parcel parcel) {
- this(parcel, null, null, 0, null);
+ this(parcel, /* rootParent= */ null, /* info= */ null, /* depth= */ 0);
}
- private RemoteViews(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info, int depth,
- Map<Class, Object> classCookies) {
+ private RemoteViews(@NonNull Parcel parcel, @Nullable HierarchyRootData rootData,
+ @Nullable ApplicationInfo info, int depth) {
if (depth > MAX_NESTED_VIEWS
&& (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID)) {
throw new IllegalArgumentException("Too many nested views.");
@@ -3715,20 +3766,17 @@ public class RemoteViews implements Parcelable, Filter {
int mode = parcel.readInt();
- // We only store a bitmap cache in the root of the RemoteViews.
- if (bitmapCache == null) {
+ if (rootData == null) {
+ // We only store a bitmap cache in the root of the RemoteViews.
mBitmapCache = new BitmapCache(parcel);
// Store the class cookies such that they are available when we clone this RemoteView.
mClassCookies = parcel.copyClassCookies();
} else {
- setBitmapCache(bitmapCache);
- mClassCookies = classCookies;
- setNotRoot();
+ configureAsChild(rootData);
}
if (mode == MODE_NORMAL) {
- mApplication = parcel.readInt() == 0 ? info :
- ApplicationInfo.CREATOR.createFromParcel(parcel);
+ mApplication = ApplicationInfo.CREATOR.createFromParcel(parcel);
mIdealSize = parcel.readInt() == 0 ? null : SizeF.CREATOR.createFromParcel(parcel);
mLayoutId = parcel.readInt();
mViewId = parcel.readInt();
@@ -3743,8 +3791,7 @@ public class RemoteViews implements Parcelable, Filter {
}
List<RemoteViews> remoteViews = new ArrayList<>(numViews);
for (int i = 0; i < numViews; i++) {
- RemoteViews view = new RemoteViews(parcel, mBitmapCache, info, depth,
- mClassCookies);
+ RemoteViews view = new RemoteViews(parcel, getHierarchyRootData(), info, depth);
info = view.mApplication;
remoteViews.add(view);
}
@@ -3756,9 +3803,9 @@ public class RemoteViews implements Parcelable, Filter {
mLightBackgroundLayoutId = smallestView.mLightBackgroundLayoutId;
} else {
// MODE_HAS_LANDSCAPE_AND_PORTRAIT
- mLandscape = new RemoteViews(parcel, mBitmapCache, info, depth, mClassCookies);
- mPortrait = new RemoteViews(parcel, mBitmapCache, mLandscape.mApplication, depth,
- mClassCookies);
+ mLandscape = new RemoteViews(parcel, getHierarchyRootData(), info, depth);
+ mPortrait =
+ new RemoteViews(parcel, getHierarchyRootData(), mLandscape.mApplication, depth);
mApplication = mPortrait.mApplication;
mLayoutId = mPortrait.mLayoutId;
mViewId = mPortrait.mViewId;
@@ -3766,6 +3813,11 @@ public class RemoteViews implements Parcelable, Filter {
}
mApplyFlags = parcel.readInt();
mProviderInstanceId = parcel.readLong();
+
+ // Ensure that all descendants have their caches set up recursively.
+ if (mIsRoot) {
+ configureDescendantsAsChildren();
+ }
}
private void readActionsFromParcel(Parcel parcel, int depth) {
@@ -3788,8 +3840,7 @@ public class RemoteViews implements Parcelable, Filter {
case REFLECTION_ACTION_TAG:
return new ReflectionAction(parcel);
case VIEW_GROUP_ACTION_ADD_TAG:
- return new ViewGroupActionAdd(parcel, mBitmapCache, mApplication, depth,
- mClassCookies);
+ return new ViewGroupActionAdd(parcel, mApplication, depth);
case VIEW_GROUP_ACTION_REMOVE_TAG:
return new ViewGroupActionRemove(parcel);
case VIEW_CONTENT_NAVIGATION_TAG:
@@ -3879,28 +3930,56 @@ public class RemoteViews implements Parcelable, Filter {
}
/**
- * Recursively sets BitmapCache in the hierarchy and update the bitmap ids.
+ * Sets the root of the hierarchy and then recursively traverses the tree to update the root
+ * and populate caches for all descendants.
+ */
+ private void configureAsChild(@NonNull HierarchyRootData rootData) {
+ mIsRoot = false;
+ mBitmapCache = rootData.mBitmapCache;
+ mApplicationInfoCache = rootData.mApplicationInfoCache;
+ mClassCookies = rootData.mClassCookies;
+ configureDescendantsAsChildren();
+ }
+
+ /**
+ * Recursively traverses the tree to update the root and populate caches for all descendants.
*/
- private void setBitmapCache(BitmapCache bitmapCache) {
- mBitmapCache = bitmapCache;
+ private void configureDescendantsAsChildren() {
+ // Before propagating down the tree, replace our application from the root application info
+ // cache, to ensure the same instance is present throughout the hierarchy to allow for
+ // squashing.
+ mApplication = mApplicationInfoCache.getOrPut(mApplication);
+
+ HierarchyRootData rootData = getHierarchyRootData();
if (hasSizedRemoteViews()) {
for (RemoteViews remoteView : mSizedRemoteViews) {
- remoteView.setBitmapCache(bitmapCache);
+ remoteView.configureAsChild(rootData);
}
} else if (hasLandscapeAndPortraitLayouts()) {
- mLandscape.setBitmapCache(bitmapCache);
- mPortrait.setBitmapCache(bitmapCache);
+ mLandscape.configureAsChild(rootData);
+ mPortrait.configureAsChild(rootData);
} else {
if (mActions != null) {
- final int count = mActions.size();
- for (int i = 0; i < count; ++i) {
- mActions.get(i).setBitmapCache(bitmapCache);
+ for (Action action : mActions) {
+ action.setHierarchyRootData(rootData);
}
}
}
}
/**
+ * Recreates caches at the root level of the hierarchy, then recursively populates the caches
+ * down the hierarchy.
+ */
+ private void reconstructCaches() {
+ if (!mIsRoot) return;
+ mBitmapCache = new BitmapCache();
+ mApplicationInfoCache = new ApplicationInfoCache();
+ mApplication = mApplicationInfoCache.getOrPut(mApplication);
+ configureDescendantsAsChildren();
+ }
+
+ /**
* Returns an estimate of the bitmap heap memory usage for this RemoteViews.
*/
/** @hide */
@@ -5848,21 +5927,18 @@ public class RemoteViews implements Parcelable, Filter {
/** @hide */
public void updateAppInfo(@NonNull ApplicationInfo info) {
- if (mApplication != null && mApplication.sourceDir.equals(info.sourceDir)) {
+ ApplicationInfo existing = mApplicationInfoCache.get(info);
+ if (existing != null && !existing.sourceDir.equals(info.sourceDir)) {
// Overlay paths are generated against a particular version of an application.
// The overlays paths of a newly upgraded application are incompatible with the
// old version of the application.
- mApplication = info;
- }
- if (hasSizedRemoteViews()) {
- for (RemoteViews layout : mSizedRemoteViews) {
- layout.updateAppInfo(info);
- }
- }
- if (hasLandscapeAndPortraitLayouts()) {
- mLandscape.updateAppInfo(info);
- mPortrait.updateAppInfo(info);
+ return;
}
+
+ // If we can update to the new AppInfo, put it in the cache and propagate the change
+ // throughout the hierarchy.
+ mApplicationInfoCache.put(info);
+ configureDescendantsAsChildren();
}
private Context getContextForResourcesEnsuringCorrectCachedApkPaths(Context context) {
@@ -6028,6 +6104,8 @@ public class RemoteViews implements Parcelable, Filter {
}
public void writeToParcel(Parcel dest, int flags) {
+ boolean prevSquashingAllowed = dest.allowSquashing();
+
if (!hasMultipleLayouts()) {
dest.writeInt(MODE_NORMAL);
// We only write the bitmap cache if we are the root RemoteViews, as this cache
@@ -6035,12 +6113,7 @@ public class RemoteViews implements Parcelable, Filter {
if (mIsRoot) {
mBitmapCache.writeBitmapsToParcel(dest, flags);
}
- if (!mIsRoot && (flags & PARCELABLE_ELIDE_DUPLICATES) != 0) {
- dest.writeInt(0);
- } else {
- dest.writeInt(1);
- mApplication.writeToParcel(dest, flags);
- }
+ mApplication.writeToParcel(dest, flags);
if (mIsRoot || mIdealSize == null) {
dest.writeInt(0);
} else {
@@ -6050,17 +6123,15 @@ public class RemoteViews implements Parcelable, Filter {
dest.writeInt(mLayoutId);
dest.writeInt(mViewId);
dest.writeInt(mLightBackgroundLayoutId);
- writeActionsToParcel(dest);
+ writeActionsToParcel(dest, flags);
} else if (hasSizedRemoteViews()) {
dest.writeInt(MODE_HAS_SIZED_REMOTEVIEWS);
if (mIsRoot) {
mBitmapCache.writeBitmapsToParcel(dest, flags);
}
- int childFlags = flags;
dest.writeInt(mSizedRemoteViews.size());
for (RemoteViews view : mSizedRemoteViews) {
- view.writeToParcel(dest, childFlags);
- childFlags |= PARCELABLE_ELIDE_DUPLICATES;
+ view.writeToParcel(dest, flags);
}
} else {
dest.writeInt(MODE_HAS_LANDSCAPE_AND_PORTRAIT);
@@ -6071,13 +6142,15 @@ public class RemoteViews implements Parcelable, Filter {
}
mLandscape.writeToParcel(dest, flags);
// Both RemoteViews already share the same package and user
- mPortrait.writeToParcel(dest, flags | PARCELABLE_ELIDE_DUPLICATES);
+ mPortrait.writeToParcel(dest, flags);
}
dest.writeInt(mApplyFlags);
dest.writeLong(mProviderInstanceId);
+
+ dest.restoreAllowSquashing(prevSquashingAllowed);
}
- private void writeActionsToParcel(Parcel parcel) {
+ private void writeActionsToParcel(Parcel parcel, int flags) {
int count;
if (mActions != null) {
count = mActions.size();
@@ -6088,8 +6161,7 @@ public class RemoteViews implements Parcelable, Filter {
for (int i = 0; i < count; i++) {
Action a = mActions.get(i);
parcel.writeInt(a.getActionTag());
- a.writeToParcel(parcel, a.hasSameAppInfo(mApplication)
- ? PARCELABLE_ELIDE_DUPLICATES : 0);
+ a.writeToParcel(parcel, flags);
}
}
@@ -6568,6 +6640,8 @@ public class RemoteViews implements Parcelable, Filter {
private final boolean mHasStableIds;
private final int mViewTypeCount;
+ private HierarchyRootData mHierarchyRootData;
+
RemoteCollectionItems(
long[] ids, RemoteViews[] views, boolean hasStableIds, int viewTypeCount) {
mIds = ids;
@@ -6590,16 +6664,53 @@ public class RemoteViews implements Parcelable, Filter {
"View type count is set to " + viewTypeCount + ", but the collection "
+ "contains " + layoutIdCount + " different layout ids");
}
+
+ // Until the collection items are attached to a parent, we configure the first item
+ // to be the root of the others to share caches and save space during serialization.
+ if (views.length > 0) {
+ setHierarchyRootData(views[0].getHierarchyRootData());
+ views[0].mIsRoot = true;
+ }
}
- RemoteCollectionItems(Parcel in) {
+ RemoteCollectionItems(@NonNull Parcel in, @Nullable HierarchyRootData hierarchyRootData) {
+ mHasStableIds = in.readBoolean();
+ mViewTypeCount = in.readInt();
int length = in.readInt();
mIds = new long[length];
in.readLongArray(mIds);
+
+ boolean attached = in.readBoolean();
mViews = new RemoteViews[length];
- in.readTypedArray(mViews, RemoteViews.CREATOR);
- mHasStableIds = in.readBoolean();
- mViewTypeCount = in.readInt();
+ int firstChildIndex;
+ if (attached) {
+ if (hierarchyRootData == null) {
+ throw new IllegalStateException("Cannot unparcel a RemoteCollectionItems that "
+ + "was parceled as attached without providing data for a root "
+ + "RemoteViews");
+ }
+ mHierarchyRootData = hierarchyRootData;
+ firstChildIndex = 0;
+ } else {
+ mViews[0] = new RemoteViews(in);
+ mHierarchyRootData = mViews[0].getHierarchyRootData();
+ firstChildIndex = 1;
+ }
+
+ for (int i = firstChildIndex; i < length; i++) {
+ mViews[i] = new RemoteViews(
+ in,
+ mHierarchyRootData,
+ /* info= */ null,
+ /* depth= */ 0);
+ }
+ }
+
+ void setHierarchyRootData(@NonNull HierarchyRootData rootData) {
+ mHierarchyRootData = rootData;
+ for (RemoteViews view : mViews) {
+ view.configureAsChild(rootData);
+ }
}
@Override
@@ -6609,11 +6720,39 @@ public class RemoteViews implements Parcelable, Filter {
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeInt(mIds.length);
- dest.writeLongArray(mIds);
- dest.writeTypedArray(mViews, flags);
+ writeToParcel(dest, flags, /* attached= */ false);
+ }
+
+ private void writeToParcel(@NonNull Parcel dest, int flags, boolean attached) {
+ boolean prevAllowSquashing = dest.allowSquashing();
+
dest.writeBoolean(mHasStableIds);
dest.writeInt(mViewTypeCount);
+ dest.writeInt(mIds.length);
+ dest.writeLongArray(mIds);
+
+ if (attached && mHierarchyRootData == null) {
+ throw new IllegalStateException("Cannot call writeToParcelAttached for a "
+ + "RemoteCollectionItems without first calling setHierarchyRootData()");
+ }
+
+ // Write whether we parceled as attached or not. This allows cleaner validation and
+ // proper error messaging when unparceling later.
+ dest.writeBoolean(attached);
+ boolean restoreRoot = false;
+ if (!attached && mViews.length > 0 && !mViews[0].mIsRoot) {
+ // If we're writing unattached, temporarily set the first item as the root so that
+ // the bitmap cache is written to the parcel.
+ restoreRoot = true;
+ mViews[0].mIsRoot = true;
+ }
+
+ for (RemoteViews view : mViews) {
+ view.writeToParcel(dest, flags);
+ }
+
+ if (restoreRoot) mViews[0].mIsRoot = false;
+ dest.restoreAllowSquashing(prevAllowSquashing);
}
/**
@@ -6671,7 +6810,7 @@ public class RemoteViews implements Parcelable, Filter {
@NonNull
@Override
public RemoteCollectionItems createFromParcel(@NonNull Parcel source) {
- return new RemoteCollectionItems(source);
+ return new RemoteCollectionItems(source, /* hierarchyRoot= */ null);
}
@NonNull
@@ -6846,4 +6985,29 @@ public class RemoteViews implements Parcelable, Filter {
viewId |= childId;
return viewId;
}
+
+ @Nullable
+ private static Pair<String, Integer> getPackageUserKey(@Nullable ApplicationInfo info) {
+ if (info == null || info.packageName == null) return null;
+ return Pair.create(info.packageName, info.uid);
+ }
+
+ private HierarchyRootData getHierarchyRootData() {
+ return new HierarchyRootData(mBitmapCache, mApplicationInfoCache, mClassCookies);
+ }
+
+ private static final class HierarchyRootData {
+ final BitmapCache mBitmapCache;
+ final ApplicationInfoCache mApplicationInfoCache;
+ final Map<Class, Object> mClassCookies;
+
+ HierarchyRootData(
+ BitmapCache bitmapCache,
+ ApplicationInfoCache applicationInfoCache,
+ Map<Class, Object> classCookies) {
+ mBitmapCache = bitmapCache;
+ mApplicationInfoCache = applicationInfoCache;
+ mClassCookies = classCookies;
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 6f17ea994699..4a7c50d8b934 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -304,18 +304,22 @@ public class ActivityThreadTest {
final int numOfConfig = activity.mNumOfConfigChanges;
final Configuration processConfigLandscape = new Configuration();
+ processConfigLandscape.orientation = ORIENTATION_LANDSCAPE;
processConfigLandscape.windowConfiguration.setBounds(new Rect(0, 0, 100, 60));
processConfigLandscape.seq = BASE_SEQ + 1;
final Configuration activityConfigLandscape = new Configuration();
+ activityConfigLandscape.orientation = ORIENTATION_LANDSCAPE;
activityConfigLandscape.windowConfiguration.setBounds(new Rect(0, 0, 100, 50));
activityConfigLandscape.seq = BASE_SEQ + 2;
final Configuration processConfigPortrait = new Configuration();
+ processConfigPortrait.orientation = ORIENTATION_PORTRAIT;
processConfigPortrait.windowConfiguration.setBounds(new Rect(0, 0, 60, 100));
processConfigPortrait.seq = BASE_SEQ + 3;
final Configuration activityConfigPortrait = new Configuration();
+ activityConfigPortrait.orientation = ORIENTATION_PORTRAIT;
activityConfigPortrait.windowConfiguration.setBounds(new Rect(0, 0, 50, 100));
activityConfigPortrait.seq = BASE_SEQ + 4;
@@ -343,7 +347,8 @@ public class ActivityThreadTest {
assertEquals(activityConfigPortrait.windowConfiguration.getBounds(), bounds);
// Ensure that Activity#onConfigurationChanged() not be called because the changes in
- // WindowConfiguration shouldn't be reported.
+ // WindowConfiguration shouldn't be reported, and we only apply the latest Configuration
+ // update in transaction.
assertEquals(numOfConfig, activity.mNumOfConfigChanges);
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
index 194b6330d92c..89d7a407e459 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationAdapter.java
@@ -39,6 +39,8 @@ class TaskFragmentAnimationAdapter {
final Transformation mTransformation = new Transformation();
final float[] mMatrix = new float[9];
+ final float[] mVecs = new float[4];
+ final Rect mRect = new Rect();
private boolean mIsFirstFrame = true;
TaskFragmentAnimationAdapter(@NonNull Animation animation,
@@ -76,6 +78,22 @@ class TaskFragmentAnimationAdapter {
mTarget.localBounds.left, mTarget.localBounds.top);
t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
t.setAlpha(mLeash, mTransformation.getAlpha());
+
+ // Open/close animation may scale up the surface. Apply an inverse scale to the window crop
+ // so that it will not be covering other windows.
+ mVecs[1] = mVecs[2] = 0;
+ mVecs[0] = mVecs[3] = 1;
+ mTransformation.getMatrix().mapVectors(mVecs);
+ mVecs[0] = 1.f / mVecs[0];
+ mVecs[3] = 1.f / mVecs[3];
+ final Rect clipRect = mTarget.localBounds;
+ mRect.left = (int) (clipRect.left * mVecs[0] + 0.5f);
+ mRect.right = (int) (clipRect.right * mVecs[0] + 0.5f);
+ mRect.top = (int) (clipRect.top * mVecs[3] + 0.5f);
+ mRect.bottom = (int) (clipRect.bottom * mVecs[3] + 0.5f);
+ mRect.offsetTo(Math.round(mTarget.localBounds.width() * (1 - mVecs[0]) / 2.f),
+ Math.round(mTarget.localBounds.height() * (1 - mVecs[3]) / 2.f));
+ t.setWindowCrop(mLeash, mRect);
}
/** Called after animation finished. */
@@ -157,8 +175,6 @@ class TaskFragmentAnimationAdapter {
* Should be used for the animation of the {@link RemoteAnimationTarget} that has size change.
*/
static class BoundsChangeAdapter extends TaskFragmentAnimationAdapter {
- private final float[] mVecs = new float[4];
- private final Rect mRect = new Rect();
BoundsChangeAdapter(@NonNull Animation animation, @NonNull RemoteAnimationTarget target) {
super(animation, target);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index 32d447ef1586..fe9ce971d4d9 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -18,9 +18,12 @@ package androidx.window.extensions.layout;
import static android.view.Display.DEFAULT_DISPLAY;
+import static androidx.window.common.DisplayFeature.COMMON_STATE_FLAT;
+import static androidx.window.common.DisplayFeature.COMMON_STATE_HALF_OPENED;
import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation;
import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect;
+import android.annotation.Nullable;
import android.app.Activity;
import android.content.Context;
import android.graphics.Rect;
@@ -119,22 +122,45 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
return !mWindowLayoutChangeListeners.isEmpty();
}
- private int getFeatureState(DisplayFeature feature) {
+ /**
+ * Calculate the {@link DisplayFeature.State} from the feature or the device posture producer.
+ * If the given {@link DisplayFeature.State} is not valid then {@code null} will be returned.
+ * The {@link FoldingFeature} should be ignored in the case of an invalid
+ * {@link DisplayFeature.State}.
+ *
+ * @param feature a {@link DisplayFeature} to provide the feature state if present.
+ * @return {@link DisplayFeature.State} of the hinge if present or the state from the posture
+ * produce if present.
+ */
+ @Nullable
+ private Integer getFeatureState(DisplayFeature feature) {
Integer featureState = feature.getState();
Optional<Integer> posture = mDevicePostureProducer.getData();
- int fallbackPosture = posture.orElse(DisplayFeature.COMMON_STATE_FLAT);
- int displayFeatureState = featureState == null ? fallbackPosture : featureState;
- return convertToExtensionState(displayFeatureState);
+ Integer state = featureState == null ? posture.orElse(null) : featureState;
+ return convertToExtensionState(state);
}
- private int convertToExtensionState(int state) {
- switch (state) {
- case DisplayFeature.COMMON_STATE_FLAT:
- return FoldingFeature.STATE_FLAT;
- case DisplayFeature.COMMON_STATE_HALF_OPENED:
- return FoldingFeature.STATE_HALF_OPENED;
+ /**
+ * A convenience method to translate from the common feature state to the extensions feature
+ * state. More specifically, translates from {@link DisplayFeature.State} to
+ * {@link FoldingFeature.STATE_FLAT} or {@link FoldingFeature.STATE_HALF_OPENED}. If it is not
+ * possible to translate, then we will return a {@code null} value.
+ *
+ * @param state if it matches a value in {@link DisplayFeature.State}, {@code null} otherwise.
+ * @return a {@link FoldingFeature.STATE_FLAT} or {@link FoldingFeature.STATE_HALF_OPENED} if
+ * the given state matches a value in {@link DisplayFeature.State} and {@code null} otherwise.
+ */
+ @Nullable
+ private Integer convertToExtensionState(@Nullable Integer state) {
+ if (state == null) { // The null check avoids a NullPointerException.
+ return null;
+ } else if (state == COMMON_STATE_FLAT) {
+ return FoldingFeature.STATE_FLAT;
+ } else if (state == COMMON_STATE_HALF_OPENED) {
+ return FoldingFeature.STATE_HALF_OPENED;
+ } else {
+ return null;
}
- return FoldingFeature.STATE_FLAT;
}
private void onDisplayFeaturesChanged() {
@@ -151,6 +177,25 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
return new WindowLayoutInfo(displayFeatures);
}
+ /**
+ * Translate from the {@link DisplayFeature} to
+ * {@link androidx.window.extensions.layout.DisplayFeature} for a given {@link Activity}. If a
+ * {@link DisplayFeature} is not valid then it will be omitted.
+ *
+ * For a {@link FoldingFeature} the bounds are localized into the {@link Activity} window
+ * coordinate space and the state is calculated either from {@link DisplayFeature#getState()} or
+ * {@link #mDisplayFeatureProducer}. The state from {@link #mDisplayFeatureProducer} may not be
+ * valid since {@link #mDisplayFeatureProducer} is a general state controller. If the state is
+ * not valid, the {@link FoldingFeature} is omitted from the {@link List} of
+ * {@link androidx.window.extensions.layout.DisplayFeature}. If the bounds are not valid,
+ * constructing a {@link FoldingFeature} will throw an {@link IllegalArgumentException} since
+ * this can cause negative UI effects down stream.
+ *
+ * @param activity a proxy for the {@link android.view.Window} that contains the
+ * {@link androidx.window.extensions.layout.DisplayFeature}.
+ * @return a {@link List} of valid {@link androidx.window.extensions.layout.DisplayFeature} that
+ * are within the {@link android.view.Window} of the {@link Activity}
+ */
private List<androidx.window.extensions.layout.DisplayFeature> getDisplayFeatures(
@NonNull Activity activity) {
List<androidx.window.extensions.layout.DisplayFeature> features = new ArrayList<>();
@@ -170,6 +215,10 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
if (storedFeatures.isPresent()) {
for (DisplayFeature baseFeature : storedFeatures.get()) {
+ Integer state = getFeatureState(baseFeature);
+ if (state == null) {
+ continue;
+ }
Rect featureRect = baseFeature.getRect();
rotateRectToDisplayRotation(displayId, featureRect);
transformToWindowSpaceRect(activity, featureRect);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
index f5678776ed78..c3ce3627fb0b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
@@ -74,7 +74,7 @@ public class ShellInitImpl {
Optional<PipTouchHandler> pipTouchHandlerOptional,
FullscreenTaskListener fullscreenTaskListener,
Optional<FullscreenUnfoldController> fullscreenUnfoldTransitionController,
- Optional<Optional<FreeformTaskListener>> freeformTaskListenerOptional,
+ Optional<FreeformTaskListener> freeformTaskListenerOptional,
Optional<RecentTasksController> recentTasks,
Transitions transitions,
StartingWindowController startingWindow,
@@ -90,7 +90,7 @@ public class ShellInitImpl {
mFullscreenTaskListener = fullscreenTaskListener;
mPipTouchHandlerOptional = pipTouchHandlerOptional;
mFullscreenUnfoldController = fullscreenUnfoldTransitionController;
- mFreeformTaskListenerOptional = freeformTaskListenerOptional.flatMap(f -> f);
+ mFreeformTaskListenerOptional = freeformTaskListenerOptional;
mRecentTasks = recentTasks;
mTransitions = transitions;
mMainExecutor = mainExecutor;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java
new file mode 100644
index 000000000000..806f795d1015
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.dagger;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.inject.Qualifier;
+
+/**
+ * This is a qualifier that Shell uses to workaround an issue with providing nullable optionals
+ * which are by default unbound.
+ *
+ * For example, ideally we would have this scenario:
+ * BaseModule:
+ * @BindsOptionalOf
+ * abstract Optional<Interface> optionalInterface();
+ *
+ * SpecializedModule:
+ * @Provides
+ * static Interface providesInterface() {
+ * return new InterfaceImpl();
+ * }
+ *
+ * However, if the interface is supposed to be provided dynamically, then Dagger is not able to bind
+ * the optional interface to a null instance, and @BindsOptionalOf does not support @Nullable
+ * instances of the interface provided by the specialized module.
+ *
+ * For example, this does not work:
+ * BaseModule:
+ * @BindsOptionalOf
+ * abstract Optional<Interface> optionalInterface();
+ *
+ * SpecializedModule:
+ * @Provides
+ * static Interface providesInterface() {
+ * if (systemSupportsInterfaceFeature) {
+ * return new InterfaceImpl();
+ * } else {
+ * return null;
+ * }
+ * }
+ *
+ * To workaround this, we can instead upstream the check (assuming it can be upstreamed into the
+ * base module), and then always provide a non-null instance in the specialized module.
+ *
+ * For example:
+ * BaseModule:
+ * @BindsOptionalOf
+ * @DynamicOverride
+ * abstract Interface dynamicInterface();
+ *
+ * @Provides
+ * static Optional<Interface> providesOptionalInterface(
+ * @DynamicOverride Optional<Interface> interface) {
+ * if (systemSupportsInterfaceFeature) {
+ * return interface;
+ * }
+ * return Optional.empty();
+ * }
+ *
+ * SpecializedModule:
+ * @Provides
+ * @DynamicOverride
+ * static Interface providesInterface() {
+ * return new InterfaceImpl();
+ * }
+ *
+ * This is also useful in cases where there needs to be a default implementation in the base module
+ * which is also overridable in the specialized module. This isn't generally recommended, but
+ * due to the nature of Shell modules being referenced from a number of various projects, this
+ * can be useful for *required* components that
+ * 1) clearly identifies which are intended for overriding in the base module, and
+ * 2) allows us to declare a default implementation in the base module, without having to force
+ * every SysUI impl to explicitly provide it (if a large number of them share the default impl)
+ *
+ * For example, this uses the same setup as above, but the interface provided (if bound) is used
+ * otherwise the default is created:
+ * @BindsOptionalOf
+ * @DynamicOverride
+ * abstract Interface dynamicInterface();
+ *
+ * @Provides
+ * static Optional<Interface> providesOptionalInterface(
+ * @DynamicOverride Optional<Interface> overrideInterfaceImpl) {
+ * if (overrideInterfaceImpl.isPresent()) {
+ * return overrideInterfaceImpl.get();
+ * }
+ * return new DefaultImpl();
+ * }
+ *
+ * SpecializedModule:
+ * @Provides
+ * @DynamicOverride
+ * static Interface providesInterface() {
+ * return new SuperSpecialImpl();
+ * }
+ */
+@Documented
+@Inherited
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+public @interface DynamicOverride {} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
index 6997d60c75f1..15bfeb297b41 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
@@ -16,25 +16,16 @@
package com.android.wm.shell.dagger;
-import android.animation.AnimationHandler;
-import android.content.Context;
import android.view.IWindowManager;
-import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.SystemWindows;
-import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.common.annotations.ChoreographerSfVsync;
import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.startingsurface.StartingWindowTypeAlgorithm;
import com.android.wm.shell.startingsurface.tv.TvStartingWindowTypeAlgorithm;
-import com.android.wm.shell.transition.Transitions;
import dagger.Module;
import dagger.Provides;
@@ -51,42 +42,12 @@ import dagger.Provides;
public class TvWMShellModule {
//
- // Internal common - Components used internally by multiple shell features
- //
-
- @WMSingleton
- @Provides
- static DisplayImeController provideDisplayImeController(IWindowManager wmService,
- DisplayController displayController, DisplayInsetsController displayInsetsController,
- @ShellMainThread ShellExecutor mainExecutor, TransactionPool transactionPool) {
- return new DisplayImeController(wmService, displayController, displayInsetsController,
- mainExecutor, transactionPool);
- }
-
- //
- // Split/multiwindow
- //
-
- @WMSingleton
- @Provides
- static LegacySplitScreenController provideSplitScreen(Context context,
- DisplayController displayController, SystemWindows systemWindows,
- DisplayImeController displayImeController, TransactionPool transactionPool,
- ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue,
- TaskStackListenerImpl taskStackListener, Transitions transitions,
- @ShellMainThread ShellExecutor mainExecutor,
- @ChoreographerSfVsync AnimationHandler sfVsyncAnimationHandler) {
- return new LegacySplitScreenController(context, displayController, systemWindows,
- displayImeController, transactionPool, shellTaskOrganizer, syncQueue,
- taskStackListener, transitions, mainExecutor, sfVsyncAnimationHandler);
- }
-
- //
// Starting Windows (Splash Screen)
//
@WMSingleton
@Provides
+ @DynamicOverride
static StartingWindowTypeAlgorithm provideStartingWindowTypeAlgorithm() {
return new TvStartingWindowTypeAlgorithm();
};
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index ac2e448eab58..d239e56bfd69 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -16,16 +16,16 @@
package com.android.wm.shell.dagger;
+import static com.android.wm.shell.onehanded.OneHandedController.SUPPORT_ONE_HANDED_MODE;
+
import android.app.ActivityTaskManager;
import android.content.Context;
-import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.os.Handler;
+import android.os.SystemProperties;
import android.view.IWindowManager;
-import android.view.WindowManager;
import com.android.internal.logging.UiEventLogger;
-import com.android.internal.statusbar.IStatusBarService;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.RootDisplayAreaOrganizer;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
@@ -74,26 +74,23 @@ import com.android.wm.shell.pip.phone.PipAppOpsListener;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.recents.RecentTasks;
import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.sizecompatui.SizeCompatUI;
import com.android.wm.shell.sizecompatui.SizeCompatUIController;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreenController;
-import com.android.wm.shell.splitscreen.StageTaskUnfoldController;
import com.android.wm.shell.startingsurface.StartingSurface;
import com.android.wm.shell.startingsurface.StartingWindowController;
import com.android.wm.shell.startingsurface.StartingWindowTypeAlgorithm;
+import com.android.wm.shell.startingsurface.phone.PhoneStartingWindowTypeAlgorithm;
import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper;
import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelperController;
import com.android.wm.shell.transition.ShellTransitions;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
-import com.android.wm.shell.unfold.UnfoldBackgroundController;
import java.util.Optional;
-import javax.inject.Provider;
-
import dagger.BindsOptionalOf;
-import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
@@ -128,6 +125,28 @@ public abstract class WMShellBaseModule {
return new DisplayInsetsController(wmService, displayController, mainExecutor);
}
+ // Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
+ @BindsOptionalOf
+ @DynamicOverride
+ abstract DisplayImeController optionalDisplayImeController();
+
+ @WMSingleton
+ @Provides
+ static DisplayImeController provideDisplayImeController(
+ @DynamicOverride Optional<DisplayImeController> overrideDisplayImeController,
+ IWindowManager wmService,
+ DisplayController displayController,
+ DisplayInsetsController displayInsetsController,
+ @ShellMainThread ShellExecutor mainExecutor,
+ TransactionPool transactionPool
+ ) {
+ if (overrideDisplayImeController.isPresent()) {
+ return overrideDisplayImeController.get();
+ }
+ return new DisplayImeController(wmService, displayController, displayInsetsController,
+ mainExecutor, transactionPool);
+ }
+
@WMSingleton
@Provides
static DisplayLayout provideDisplayLayout() {
@@ -153,11 +172,18 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
+ static SizeCompatUI provideSizeCompatUI(SizeCompatUIController sizeCompatUIController) {
+ return sizeCompatUIController.asSizeCompatUI();
+ }
+
+ @WMSingleton
+ @Provides
static SizeCompatUIController provideSizeCompatUIController(Context context,
DisplayController displayController, DisplayInsetsController displayInsetsController,
- DisplayImeController imeController, SyncTransactionQueue syncQueue) {
+ DisplayImeController imeController, SyncTransactionQueue syncQueue,
+ @ShellMainThread ShellExecutor mainExecutor) {
return new SizeCompatUIController(context, displayController, displayInsetsController,
- imeController, syncQueue);
+ imeController, syncQueue, mainExecutor);
}
@WMSingleton
@@ -202,7 +228,7 @@ public abstract class WMShellBaseModule {
}
//
- // Bubbles
+ // Bubbles (optional feature)
//
@WMSingleton
@@ -211,27 +237,8 @@ public abstract class WMShellBaseModule {
return bubbleController.map((controller) -> controller.asBubbles());
}
- // Note: Handler needed for LauncherApps.register
- @WMSingleton
- @Provides
- static Optional<BubbleController> provideBubbleController(Context context,
- FloatingContentCoordinator floatingContentCoordinator,
- IStatusBarService statusBarService,
- WindowManager windowManager,
- WindowManagerShellWrapper windowManagerShellWrapper,
- LauncherApps launcherApps,
- TaskStackListenerImpl taskStackListener,
- UiEventLogger uiEventLogger,
- ShellTaskOrganizer organizer,
- DisplayController displayController,
- @ShellMainThread ShellExecutor mainExecutor,
- @ShellMainThread Handler mainHandler,
- SyncTransactionQueue syncQueue) {
- return Optional.of(BubbleController.create(context, null /* synchronizer */,
- floatingContentCoordinator, statusBarService, windowManager,
- windowManagerShellWrapper, launcherApps, taskStackListener,
- uiEventLogger, organizer, displayController, mainExecutor, mainHandler, syncQueue));
- }
+ @BindsOptionalOf
+ abstract BubbleController optionalBubblesController();
//
// Fullscreen
@@ -252,59 +259,45 @@ public abstract class WMShellBaseModule {
// Unfold transition
//
- @WMSingleton
- @Provides
- static Optional<FullscreenUnfoldController> provideFullscreenUnfoldController(
- Context context,
- Optional<ShellUnfoldProgressProvider> progressProvider,
- Lazy<UnfoldBackgroundController> unfoldBackgroundController,
- DisplayInsetsController displayInsetsController,
- @ShellMainThread ShellExecutor mainExecutor
- ) {
- return progressProvider.map(shellUnfoldTransitionProgressProvider ->
- new FullscreenUnfoldController(context, mainExecutor,
- unfoldBackgroundController.get(), shellUnfoldTransitionProgressProvider,
- displayInsetsController));
- }
+ @BindsOptionalOf
+ abstract ShellUnfoldProgressProvider optionalShellUnfoldProgressProvider();
- @Provides
- static Optional<StageTaskUnfoldController> provideStageTaskUnfoldController(
- Optional<ShellUnfoldProgressProvider> progressProvider,
- Context context,
- TransactionPool transactionPool,
- Lazy<UnfoldBackgroundController> unfoldBackgroundController,
- DisplayInsetsController displayInsetsController,
- @ShellMainThread ShellExecutor mainExecutor
- ) {
- return progressProvider.map(shellUnfoldTransitionProgressProvider ->
- new StageTaskUnfoldController(
- context,
- transactionPool,
- shellUnfoldTransitionProgressProvider,
- displayInsetsController,
- unfoldBackgroundController.get(),
- mainExecutor
- ));
- }
+ // Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
+ @BindsOptionalOf
+ @DynamicOverride
+ abstract FullscreenUnfoldController optionalFullscreenUnfoldController();
@WMSingleton
@Provides
- static UnfoldBackgroundController provideUnfoldBackgroundController(
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
- Context context
- ) {
- return new UnfoldBackgroundController(
- context,
- rootTaskDisplayAreaOrganizer
- );
+ static Optional<FullscreenUnfoldController> provideFullscreenUnfoldController(
+ @DynamicOverride Optional<FullscreenUnfoldController> fullscreenUnfoldController,
+ Optional<ShellUnfoldProgressProvider> progressProvider) {
+ if (progressProvider.isPresent()
+ && progressProvider.get() != ShellUnfoldProgressProvider.NO_PROVIDER) {
+ return fullscreenUnfoldController;
+ }
+ return Optional.empty();
}
//
// Freeform (optional feature)
//
+ // Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
@BindsOptionalOf
- abstract Optional<FreeformTaskListener> optionalFreeformTaskListener();
+ @DynamicOverride
+ abstract FreeformTaskListener optionalFreeformTaskListener();
+
+ @WMSingleton
+ @Provides
+ static Optional<FreeformTaskListener> provideFreeformTaskListener(
+ @DynamicOverride Optional<FreeformTaskListener> freeformTaskListener,
+ Context context) {
+ if (FreeformTaskListener.isFreeformEnabled(context)) {
+ return freeformTaskListener;
+ }
+ return Optional.empty();
+ }
//
// Hide display cutout
@@ -335,20 +328,22 @@ public abstract class WMShellBaseModule {
return oneHandedController.map((controller) -> controller.asOneHanded());
}
- // Needs the shell main handler for ContentObserver callbacks
+ // Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
+ @BindsOptionalOf
+ @DynamicOverride
+ abstract OneHandedController optionalOneHandedController();
+
@WMSingleton
@Provides
- static Optional<OneHandedController> provideOneHandedController(Context context,
- WindowManager windowManager, DisplayController displayController,
- DisplayLayout displayLayout, TaskStackListenerImpl taskStackListener,
- UiEventLogger uiEventLogger,
- @ShellMainThread ShellExecutor mainExecutor,
- @ShellMainThread Handler mainHandler) {
- return Optional.ofNullable(OneHandedController.create(context, windowManager,
- displayController, displayLayout, taskStackListener, uiEventLogger, mainExecutor,
- mainHandler));
+ static Optional<OneHandedController> providesOneHandedController(
+ @DynamicOverride Optional<OneHandedController> oneHandedController) {
+ if (!SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false)) {
+ return oneHandedController;
+ }
+ return Optional.empty();
}
+
//
// Task to Surface communication
//
@@ -366,15 +361,6 @@ public abstract class WMShellBaseModule {
return Optional.ofNullable(new TaskSurfaceHelperController(taskOrganizer, mainExecutor));
}
- @WMSingleton
- @Provides
- static Optional<DisplayAreaHelper> provideDisplayAreaHelper(
- @ShellMainThread ShellExecutor mainExecutor,
- RootDisplayAreaOrganizer rootDisplayAreaOrganizer) {
- return Optional.ofNullable(new DisplayAreaHelperController(mainExecutor,
- rootDisplayAreaOrganizer));
- }
-
//
// Pip (optional feature)
//
@@ -460,7 +446,7 @@ public abstract class WMShellBaseModule {
}
//
- // Split/multiwindow
+ // Display areas
//
@WMSingleton
@@ -479,31 +465,38 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
+ static Optional<DisplayAreaHelper> provideDisplayAreaHelper(
+ @ShellMainThread ShellExecutor mainExecutor,
+ RootDisplayAreaOrganizer rootDisplayAreaOrganizer) {
+ return Optional.of(new DisplayAreaHelperController(mainExecutor,
+ rootDisplayAreaOrganizer));
+ }
+
+ //
+ // Splitscreen (optional feature)
+ //
+
+ @WMSingleton
+ @Provides
static Optional<SplitScreen> provideSplitScreen(
Optional<SplitScreenController> splitScreenController) {
return splitScreenController.map((controller) -> controller.asSplitScreen());
}
+ // Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
+ @BindsOptionalOf
+ @DynamicOverride
+ abstract SplitScreenController optionalSplitScreenController();
+
@WMSingleton
@Provides
- static Optional<SplitScreenController> provideSplitScreenController(
- ShellTaskOrganizer shellTaskOrganizer,
- SyncTransactionQueue syncQueue, Context context,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
- @ShellMainThread ShellExecutor mainExecutor,
- DisplayImeController displayImeController,
- DisplayInsetsController displayInsetsController, Transitions transitions,
- TransactionPool transactionPool, IconProvider iconProvider,
- Optional<RecentTasksController> recentTasks,
- Provider<Optional<StageTaskUnfoldController>> stageTaskUnfoldControllerProvider) {
+ static Optional<SplitScreenController> providesSplitScreenController(
+ @DynamicOverride Optional<SplitScreenController> splitscreenController,
+ Context context) {
if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) {
- return Optional.of(new SplitScreenController(shellTaskOrganizer, syncQueue, context,
- rootTaskDisplayAreaOrganizer, mainExecutor, displayImeController,
- displayInsetsController, transitions, transactionPool, iconProvider,
- recentTasks, stageTaskUnfoldControllerProvider));
- } else {
- return Optional.empty();
+ return splitscreenController;
}
+ return Optional.empty();
}
// Legacy split (optional feature)
@@ -529,7 +522,9 @@ public abstract class WMShellBaseModule {
@BindsOptionalOf
abstract AppPairsController optionalAppPairs();
+ //
// Starting window
+ //
@WMSingleton
@Provides
@@ -548,6 +543,23 @@ public abstract class WMShellBaseModule {
startingWindowTypeAlgorithm, iconProvider, pool);
}
+ // Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
+ @BindsOptionalOf
+ @DynamicOverride
+ abstract StartingWindowTypeAlgorithm optionalStartingWindowTypeAlgorithm();
+
+ @WMSingleton
+ @Provides
+ static StartingWindowTypeAlgorithm provideStartingWindowTypeAlgorithm(
+ @DynamicOverride Optional<StartingWindowTypeAlgorithm> startingWindowTypeAlgorithm
+ ) {
+ if (startingWindowTypeAlgorithm.isPresent()) {
+ return startingWindowTypeAlgorithm.get();
+ }
+ // Default to phone starting window type
+ return new PhoneStartingWindowTypeAlgorithm();
+ }
+
//
// Task view factory
//
@@ -591,7 +603,7 @@ public abstract class WMShellBaseModule {
Optional<PipTouchHandler> pipTouchHandlerOptional,
FullscreenTaskListener fullscreenTaskListener,
Optional<FullscreenUnfoldController> appUnfoldTransitionController,
- Optional<Optional<FreeformTaskListener>> freeformTaskListener,
+ Optional<FreeformTaskListener> freeformTaskListener,
Optional<RecentTasksController> recentTasksOptional,
Transitions transitions,
StartingWindowController startingWindow,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index ec701470354c..46c7b508d6e8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -18,15 +18,22 @@ package com.android.wm.shell.dagger;
import android.animation.AnimationHandler;
import android.content.Context;
+import android.content.pm.LauncherApps;
import android.os.Handler;
-import android.view.IWindowManager;
+import android.view.WindowManager;
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.apppairs.AppPairsController;
+import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -36,6 +43,7 @@ import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ChoreographerSfVsync;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.freeform.FreeformTaskListener;
+import com.android.wm.shell.fullscreen.FullscreenUnfoldController;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
@@ -55,13 +63,18 @@ import com.android.wm.shell.pip.phone.PipAppOpsListener;
import com.android.wm.shell.pip.phone.PipController;
import com.android.wm.shell.pip.phone.PipMotionHelper;
import com.android.wm.shell.pip.phone.PipTouchHandler;
+import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreenController;
-import com.android.wm.shell.startingsurface.StartingWindowTypeAlgorithm;
-import com.android.wm.shell.startingsurface.phone.PhoneStartingWindowTypeAlgorithm;
+import com.android.wm.shell.splitscreen.StageTaskUnfoldController;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
+import com.android.wm.shell.unfold.UnfoldBackgroundController;
import java.util.Optional;
+import javax.inject.Provider;
+
+import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
@@ -77,17 +90,29 @@ import dagger.Provides;
public class WMShellModule {
//
- // Internal common - Components used internally by multiple shell features
+ // Bubbles
//
+ // Note: Handler needed for LauncherApps.register
@WMSingleton
@Provides
- static DisplayImeController provideDisplayImeController(IWindowManager wmService,
- DisplayController displayController, DisplayInsetsController displayInsetsController,
+ static BubbleController provideBubbleController(Context context,
+ FloatingContentCoordinator floatingContentCoordinator,
+ IStatusBarService statusBarService,
+ WindowManager windowManager,
+ WindowManagerShellWrapper windowManagerShellWrapper,
+ LauncherApps launcherApps,
+ TaskStackListenerImpl taskStackListener,
+ UiEventLogger uiEventLogger,
+ ShellTaskOrganizer organizer,
+ DisplayController displayController,
@ShellMainThread ShellExecutor mainExecutor,
- TransactionPool transactionPool) {
- return new DisplayImeController(wmService, displayController, displayInsetsController,
- mainExecutor, transactionPool);
+ @ShellMainThread Handler mainHandler,
+ SyncTransactionQueue syncQueue) {
+ return BubbleController.create(context, null /* synchronizer */,
+ floatingContentCoordinator, statusBarService, windowManager,
+ windowManagerShellWrapper, launcherApps, taskStackListener,
+ uiEventLogger, organizer, displayController, mainExecutor, mainHandler, syncQueue);
}
//
@@ -96,18 +121,57 @@ public class WMShellModule {
@WMSingleton
@Provides
- static Optional<FreeformTaskListener> provideFreeformTaskListener(
- Context context,
+ @DynamicOverride
+ static FreeformTaskListener provideFreeformTaskListener(
SyncTransactionQueue syncQueue) {
- return Optional.ofNullable(FreeformTaskListener.create(context, syncQueue));
+ return new FreeformTaskListener(syncQueue);
+ }
+
+ //
+ // One handed mode
+ //
+
+
+ // Needs the shell main handler for ContentObserver callbacks
+ @WMSingleton
+ @Provides
+ @DynamicOverride
+ static OneHandedController provideOneHandedController(Context context,
+ WindowManager windowManager, DisplayController displayController,
+ DisplayLayout displayLayout, TaskStackListenerImpl taskStackListener,
+ UiEventLogger uiEventLogger,
+ @ShellMainThread ShellExecutor mainExecutor,
+ @ShellMainThread Handler mainHandler) {
+ return OneHandedController.create(context, windowManager,
+ displayController, displayLayout, taskStackListener, uiEventLogger, mainExecutor,
+ mainHandler);
}
//
- // Split/multiwindow
+ // Splitscreen
//
@WMSingleton
@Provides
+ @DynamicOverride
+ static SplitScreenController provideSplitScreenController(
+ ShellTaskOrganizer shellTaskOrganizer,
+ SyncTransactionQueue syncQueue, Context context,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ @ShellMainThread ShellExecutor mainExecutor,
+ DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController, Transitions transitions,
+ TransactionPool transactionPool, IconProvider iconProvider,
+ Optional<RecentTasksController> recentTasks,
+ Provider<Optional<StageTaskUnfoldController>> stageTaskUnfoldControllerProvider) {
+ return new SplitScreenController(shellTaskOrganizer, syncQueue, context,
+ rootTaskDisplayAreaOrganizer, mainExecutor, displayImeController,
+ displayInsetsController, transitions, transactionPool, iconProvider,
+ recentTasks, stageTaskUnfoldControllerProvider);
+ }
+
+ @WMSingleton
+ @Provides
static LegacySplitScreenController provideLegacySplitScreen(Context context,
DisplayController displayController, SystemWindows systemWindows,
DisplayImeController displayImeController, TransactionPool transactionPool,
@@ -258,12 +322,53 @@ public class WMShellModule {
}
//
- // Starting Windows (Splash Screen)
+ // Unfold transition
//
@WMSingleton
@Provides
- static StartingWindowTypeAlgorithm provideStartingWindowTypeAlgorithm() {
- return new PhoneStartingWindowTypeAlgorithm();
+ @DynamicOverride
+ static FullscreenUnfoldController provideFullscreenUnfoldController(
+ Context context,
+ Optional<ShellUnfoldProgressProvider> progressProvider,
+ Lazy<UnfoldBackgroundController> unfoldBackgroundController,
+ DisplayInsetsController displayInsetsController,
+ @ShellMainThread ShellExecutor mainExecutor
+ ) {
+ return new FullscreenUnfoldController(context, mainExecutor,
+ unfoldBackgroundController.get(), progressProvider.get(),
+ displayInsetsController);
+ }
+
+ @Provides
+ static Optional<StageTaskUnfoldController> provideStageTaskUnfoldController(
+ Optional<ShellUnfoldProgressProvider> progressProvider,
+ Context context,
+ TransactionPool transactionPool,
+ Lazy<UnfoldBackgroundController> unfoldBackgroundController,
+ DisplayInsetsController displayInsetsController,
+ @ShellMainThread ShellExecutor mainExecutor
+ ) {
+ return progressProvider.map(shellUnfoldTransitionProgressProvider ->
+ new StageTaskUnfoldController(
+ context,
+ transactionPool,
+ shellUnfoldTransitionProgressProvider,
+ displayInsetsController,
+ unfoldBackgroundController.get(),
+ mainExecutor
+ ));
+ }
+
+ @WMSingleton
+ @Provides
+ static UnfoldBackgroundController provideUnfoldBackgroundController(
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ Context context
+ ) {
+ return new UnfoldBackgroundController(
+ context,
+ rootTaskDisplayAreaOrganizer
+ );
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index 8a8d7c68d9f6..5c8e7d03eb01 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -141,16 +141,4 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
|| Settings.Global.getInt(context.getContentResolver(),
DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0;
}
-
- /**
- * Creates {@link FreeformTaskListener} if freeform is enabled.
- */
- public static FreeformTaskListener create(Context context,
- SyncTransactionQueue syncQueue) {
- if (!isFreeformEnabled(context)) {
- return null;
- }
-
- return new FreeformTaskListener(syncQueue);
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
index 80ab166d0649..67e487de0993 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
@@ -172,6 +172,14 @@ public class LegacySplitScreenController implements DisplayController.OnDisplays
};
mWindowManager = new DividerWindowManager(mSystemWindows);
+
+ // No need to listen to display window container or create root tasks if the device is not
+ // using legacy split screen.
+ if (!context.getResources().getBoolean(com.android.internal.R.bool.config_useLegacySplit)) {
+ return;
+ }
+
+
mDisplayController.addDisplayWindowListener(this);
// Don't initialize the divider or anything until we get the default display.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 900743712227..e0686146e821 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -46,7 +46,6 @@ import android.view.accessibility.AccessibilityManager;
import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
@@ -76,7 +75,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
private static final int OVERLAY_ENABLED_DELAY_MS = 250;
private static final int DISPLAY_AREA_READY_RETRY_MS = 10;
- static final String SUPPORT_ONE_HANDED_MODE = "ro.support_one_handed_mode";
+ public static final String SUPPORT_ONE_HANDED_MODE = "ro.support_one_handed_mode";
private volatile boolean mIsOneHandedEnabled;
private volatile boolean mIsSwipeToNotificationEnabled;
@@ -198,16 +197,10 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
/**
* Creates {@link OneHandedController}, returns {@code null} if the feature is not supported.
*/
- @Nullable
public static OneHandedController create(
Context context, WindowManager windowManager, DisplayController displayController,
DisplayLayout displayLayout, TaskStackListenerImpl taskStackListener,
UiEventLogger uiEventLogger, ShellExecutor mainExecutor, Handler mainHandler) {
- if (!SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false)) {
- Slog.w(TAG, "Device doesn't support OneHanded feature");
- return null;
- }
-
OneHandedSettingsUtil settingsUtil = new OneHandedSettingsUtil();
OneHandedAccessibilityUtil accessibilityUtil = new OneHandedAccessibilityUtil(context);
OneHandedTimeoutHandler timeoutHandler = new OneHandedTimeoutHandler(mainExecutor);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 6ce43a041299..7cfba148a907 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -491,25 +491,12 @@ public class PipController implements PipTransitionController.PipTransitionCallb
if (mPipTaskOrganizer.isInPip() && saveRestoreSnapFraction) {
// Calculate the snap fraction of the current stack along the old movement bounds
final PipSnapAlgorithm pipSnapAlgorithm = mPipBoundsAlgorithm.getSnapAlgorithm();
- final float snapFraction = pipSnapAlgorithm.getSnapFraction(mPipBoundsState.getBounds(),
- mPipBoundsAlgorithm.getMovementBounds(mPipBoundsState.getBounds()),
+ final Rect postChangeStackBounds = new Rect(mPipBoundsState.getBounds());
+ final float snapFraction = pipSnapAlgorithm.getSnapFraction(postChangeStackBounds,
+ mPipBoundsAlgorithm.getMovementBounds(postChangeStackBounds),
mPipBoundsState.getStashedState());
updateDisplayLayout.run();
- final Rect postChangeStackBounds;
- if (mPipBoundsState.getBounds() != null
- && (mPipBoundsState.getBounds().width() > mPipBoundsState.getMaxSize().x
- || mPipBoundsState.getBounds().height() > mPipBoundsState.getMaxSize().y)) {
- postChangeStackBounds = new Rect(0, 0, mPipBoundsState.getMaxSize().x,
- mPipBoundsState.getMaxSize().y);
- } else if (mPipBoundsState.getBounds() != null
- && (mPipBoundsState.getBounds().width() < mPipBoundsState.getMinSize().x
- || mPipBoundsState.getBounds().height() < mPipBoundsState.getMinSize().y)) {
- postChangeStackBounds = new Rect(0, 0, mPipBoundsState.getMinSize().x,
- mPipBoundsState.getMinSize().y);
- } else {
- postChangeStackBounds = new Rect(mPipBoundsState.getBounds());
- }
// Calculate the stack bounds in the new orientation based on same fraction along the
// rotated movement bounds.
@@ -521,7 +508,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipBoundsState.getDisplayBounds(),
mPipBoundsState.getDisplayLayout().stableInsets());
- mTouchHandler.getMotionHelper().animateResizedBounds(postChangeStackBounds);
+ mTouchHandler.getMotionHelper().movePip(postChangeStackBounds);
} else {
updateDisplayLayout.run();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index c634b7f220b0..96fd59f0c911 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -69,7 +69,6 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
private static final int UNSTASH_DURATION = 250;
private static final int LEAVE_PIP_DURATION = 300;
private static final int SHIFT_DURATION = 300;
- private static final int ANIMATE_PIP_RESIZE_ANIMATION = 250;
/** Friction to use for PIP when it moves via physics fling animations. */
private static final float DEFAULT_FRICTION = 1.9f;
@@ -549,14 +548,6 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
}
/**
- * Animates the PiP from an old bound to a new bound. This is mostly used when display
- * has changed and PiP bounds needs to be changed.
- */
- void animateResizedBounds(Rect newBounds) {
- resizeAndAnimatePipUnchecked(newBounds, ANIMATE_PIP_RESIZE_ANIMATION);
- }
-
- /**
* Animates the PiP to offset it from the IME or shelf.
*/
void animateToOffset(Rect originalBounds, int offset) {
diff --git a/core/java/android/app/ForegroundServiceDidNotStartInTimeException.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java
index 364291e69ad6..a703114194a0 100644
--- a/core/java/android/app/ForegroundServiceDidNotStartInTimeException.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java
@@ -14,20 +14,19 @@
* limitations under the License.
*/
-package android.app;
+package com.android.wm.shell.sizecompatui;
+
+import com.android.wm.shell.common.annotations.ExternalThread;
/**
- * Exception used to crash an app process when it didn't call {@link Service#startForeground}
- * in time after the service was started with
- * {@link android.content.Context#startForegroundService}.
- *
- * @hide
+ * Interface to engage size compat UI.
*/
-public class ForegroundServiceDidNotStartInTimeException extends RemoteServiceException {
- /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */
- public static final int TYPE_ID = 1;
-
- public ForegroundServiceDidNotStartInTimeException(String msg) {
- super(msg);
- }
+@ExternalThread
+public interface SizeCompatUI {
+ /**
+ * Called when the keyguard occluded state changes. Removes all size compat UIs if the
+ * keyguard is now occluded.
+ * @param occluded indicates if the keyguard is now occluded.
+ */
+ void onKeyguardOccludedChanged(boolean occluded);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
index 04d974a09cd7..e06070ab12e5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
@@ -35,13 +35,16 @@ import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.annotations.ExternalThread;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
+import java.util.function.Predicate;
/**
* Controls to show/update restart-activity buttons on Tasks based on whether the foreground
@@ -78,26 +81,37 @@ public class SizeCompatUIController implements OnDisplaysChangedListener,
private final DisplayInsetsController mDisplayInsetsController;
private final DisplayImeController mImeController;
private final SyncTransactionQueue mSyncQueue;
+ private final ShellExecutor mMainExecutor;
+ private final SizeCompatUIImpl mImpl = new SizeCompatUIImpl();
private SizeCompatUICallback mCallback;
/** Only show once automatically in the process life. */
private boolean mHasShownHint;
+ /** Indicates if the keyguard is currently occluded, in which case size compat UIs shouldn't
+ * be shown. */
+ private boolean mKeyguardOccluded;
public SizeCompatUIController(Context context,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
DisplayImeController imeController,
- SyncTransactionQueue syncQueue) {
+ SyncTransactionQueue syncQueue,
+ ShellExecutor mainExecutor) {
mContext = context;
mDisplayController = displayController;
mDisplayInsetsController = displayInsetsController;
mImeController = imeController;
mSyncQueue = syncQueue;
+ mMainExecutor = mainExecutor;
mDisplayController.addDisplayWindowListener(this);
mImeController.addPositionProcessor(this);
}
+ public SizeCompatUI asSizeCompatUI() {
+ return mImpl;
+ }
+
/** Sets the callback for UI interactions. */
public void setSizeCompatUICallback(SizeCompatUICallback callback) {
mCallback = callback;
@@ -106,6 +120,7 @@ public class SizeCompatUIController implements OnDisplaysChangedListener,
/**
* Called when the Task info changed. Creates and updates the size compat UI if there is an
* activity in size compat, or removes the UI if there is no size compat activity.
+ *
* @param displayId display the task and activity are in.
* @param taskId task the activity is in.
* @param taskConfig task config to place the size compat UI with.
@@ -180,7 +195,19 @@ public class SizeCompatUIController implements OnDisplaysChangedListener,
}
// Hide the size compat UIs when input method is showing.
- forAllLayoutsOnDisplay(displayId, layout -> layout.updateImeVisibility(isShowing));
+ forAllLayoutsOnDisplay(displayId,
+ layout -> layout.updateVisibility(showOnDisplay(displayId)));
+ }
+
+ @VisibleForTesting
+ void onKeyguardOccludedChanged(boolean occluded) {
+ mKeyguardOccluded = occluded;
+ // Hide the size compat UIs when keyguard is occluded.
+ forAllLayouts(layout -> layout.updateVisibility(showOnDisplay(layout.getDisplayId())));
+ }
+
+ private boolean showOnDisplay(int displayId) {
+ return !mKeyguardOccluded && !isImeShowingOnDisplay(displayId);
}
private boolean isImeShowingOnDisplay(int displayId) {
@@ -198,7 +225,7 @@ public class SizeCompatUIController implements OnDisplaysChangedListener,
final SizeCompatUILayout layout = createLayout(context, displayId, taskId, taskConfig,
taskListener);
mActiveLayouts.put(taskId, layout);
- layout.createSizeCompatButton(isImeShowingOnDisplay(displayId));
+ layout.createSizeCompatButton(showOnDisplay(displayId));
}
@VisibleForTesting
@@ -218,8 +245,7 @@ public class SizeCompatUIController implements OnDisplaysChangedListener,
if (layout == null) {
return;
}
- layout.updateSizeCompatInfo(taskConfig, taskListener,
- isImeShowingOnDisplay(layout.getDisplayId()));
+ layout.updateSizeCompatInfo(taskConfig, taskListener, showOnDisplay(layout.getDisplayId()));
}
private void removeLayout(int taskId) {
@@ -250,15 +276,37 @@ public class SizeCompatUIController implements OnDisplaysChangedListener,
}
private void forAllLayoutsOnDisplay(int displayId, Consumer<SizeCompatUILayout> callback) {
+ forAllLayouts(layout -> layout.getDisplayId() == displayId, callback);
+ }
+
+ private void forAllLayouts(Consumer<SizeCompatUILayout> callback) {
+ forAllLayouts(layout -> true, callback);
+ }
+
+ private void forAllLayouts(Predicate<SizeCompatUILayout> condition,
+ Consumer<SizeCompatUILayout> callback) {
for (int i = 0; i < mActiveLayouts.size(); i++) {
final int taskId = mActiveLayouts.keyAt(i);
final SizeCompatUILayout layout = mActiveLayouts.get(taskId);
- if (layout != null && layout.getDisplayId() == displayId) {
+ if (layout != null && condition.test(layout)) {
callback.accept(layout);
}
}
}
+ /**
+ * The interface for calls from outside the Shell, within the host process.
+ */
+ @ExternalThread
+ private class SizeCompatUIImpl implements SizeCompatUI {
+ @Override
+ public void onKeyguardOccludedChanged(boolean occluded) {
+ mMainExecutor.execute(() -> {
+ SizeCompatUIController.this.onKeyguardOccludedChanged(occluded);
+ });
+ }
+ }
+
/** An implementation of {@link OnInsetsChangedListener} for a given display id. */
private class PerDisplayOnInsetsChangedListener implements OnInsetsChangedListener {
final int mDisplayId;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java
index 1a2c94fd4747..c35b89af6c1b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java
@@ -103,9 +103,9 @@ class SizeCompatUILayout {
}
/** Creates the activity restart button window. */
- void createSizeCompatButton(boolean isImeShowing) {
- if (isImeShowing || mButton != null) {
- // When ime is showing, wait until ime is dismiss to create UI.
+ void createSizeCompatButton(boolean show) {
+ if (!show || mButton != null) {
+ // Wait until button should be visible.
return;
}
mButton = mButtonWindowManager.createSizeCompatButton();
@@ -154,7 +154,7 @@ class SizeCompatUILayout {
/** Called when size compat info changed. */
void updateSizeCompatInfo(Configuration taskConfig,
- ShellTaskOrganizer.TaskListener taskListener, boolean isImeShowing) {
+ ShellTaskOrganizer.TaskListener taskListener, boolean show) {
final Configuration prevTaskConfig = mTaskConfig;
final ShellTaskOrganizer.TaskListener prevTaskListener = mTaskListener;
mTaskConfig = taskConfig;
@@ -170,7 +170,7 @@ class SizeCompatUILayout {
if (mButton == null || prevTaskListener != taskListener) {
// TaskListener changed, recreate the button for new surface parent.
release();
- createSizeCompatButton(isImeShowing);
+ createSizeCompatButton(show);
return;
}
@@ -204,16 +204,16 @@ class SizeCompatUILayout {
}
}
- /** Called when IME visibility changed. */
- void updateImeVisibility(boolean isImeShowing) {
+ /** Called when the visibility of the UI should change. */
+ void updateVisibility(boolean show) {
if (mButton == null) {
- // Button may not be created because ime is previous showing.
- createSizeCompatButton(isImeShowing);
+ // Button may not have been created because it was hidden previously.
+ createSizeCompatButton(show);
return;
}
// Hide size compat UIs when IME is showing.
- final int newVisibility = isImeShowing ? View.GONE : View.VISIBLE;
+ final int newVisibility = show ? View.VISIBLE : View.GONE;
if (mButton.getVisibility() != newVisibility) {
mButton.setVisibility(newVisibility);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 7457be2d0871..8af72a89a75f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -130,19 +130,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
private StageCoordinator mStageCoordinator;
- // TODO(b/205019015): Remove after we clean up downstream modules
- public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer,
- SyncTransactionQueue syncQueue, Context context,
- RootTaskDisplayAreaOrganizer rootTDAOrganizer,
- ShellExecutor mainExecutor, DisplayImeController displayImeController,
- DisplayInsetsController displayInsetsController,
- Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider,
- Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
- this(shellTaskOrganizer, syncQueue, context, rootTDAOrganizer, mainExecutor,
- displayImeController, displayInsetsController, transitions, transactionPool,
- iconProvider, Optional.empty(), unfoldControllerProvider);
- }
-
public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer,
SyncTransactionQueue syncQueue, Context context,
RootTaskDisplayAreaOrganizer rootTDAOrganizer,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 050d255bfd2d..d4941916850d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -78,7 +78,6 @@ import android.window.TransitionRequestInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
-import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
import com.android.internal.protolog.common.ProtoLog;
@@ -157,10 +156,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private boolean mExitSplitScreenOnHide;
private boolean mKeyguardOccluded;
- // TODO(b/187041611): remove this flag after totally deprecated legacy split
- /** Whether the device is supporting legacy split or not. */
- private boolean mUseLegacySplit;
-
@SplitScreen.StageType
private int mDismissTop = NO_DISMISS;
@@ -735,17 +730,9 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private void onStageRootTaskAppeared(StageListenerImpl stageListener) {
if (mMainStageListener.mHasRootTask && mSideStageListener.mHasRootTask) {
- mUseLegacySplit = mContext.getResources().getBoolean(R.bool.config_useLegacySplit);
final WindowContainerTransaction wct = new WindowContainerTransaction();
// Make the stages adjacent to each other so they occlude what's behind them.
wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
-
- // Only sets side stage as launch-adjacent-flag-root when the device is not using legacy
- // split to prevent new split behavior confusing users.
- if (!mUseLegacySplit) {
- wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
- }
-
mTaskOrganizer.applyTransaction(wct);
}
}
@@ -755,11 +742,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final WindowContainerTransaction wct = new WindowContainerTransaction();
// Deactivate the main stage if it no longer has a root task.
mMainStage.deactivate(wct);
-
- if (!mUseLegacySplit) {
- wct.clearLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
- }
-
mTaskOrganizer.applyTransaction(wct);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 190006ec0d9b..62b8638a2582 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -37,6 +37,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
+import com.android.internal.R;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SurfaceUtils;
@@ -102,7 +103,12 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
mSurfaceSession = surfaceSession;
mIconProvider = iconProvider;
mStageTaskUnfoldController = stageTaskUnfoldController;
- taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this);
+
+ // No need to create root task if the device is using legacy split screen.
+ // TODO(b/199236198): Remove this check after totally deprecated legacy split.
+ if (!context.getResources().getBoolean(R.bool.config_useLegacySplit)) {
+ taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this);
+ }
}
int getChildCount() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java
index 74e48120bf1a..367676f54aba 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java
@@ -26,10 +26,16 @@ import java.util.concurrent.Executor;
*/
public interface ShellUnfoldProgressProvider {
+ // This is a temporary workaround until we move the progress providers into the Shell or
+ // refactor the dependencies. TLDR, the base module depends on this provider to determine if the
+ // FullscreenUnfoldController is available, but this check can't rely on an optional component.
+ public static final ShellUnfoldProgressProvider NO_PROVIDER =
+ new ShellUnfoldProgressProvider() {};
+
/**
* Adds a transition listener
*/
- void addListener(Executor executor, UnfoldListener listener);
+ default void addListener(Executor executor, UnfoldListener listener) {}
/**
* Listener for receiving unfold updates
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index c2f58b8b6266..935f6695538d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -191,7 +191,7 @@ public class PipControllerTest extends ShellTestCase {
mPipController.mDisplaysChangedListener.onDisplayConfigurationChanged(
displayId, new Configuration());
- verify(mMockPipMotionHelper).animateResizedBounds(any(Rect.class));
+ verify(mMockPipMotionHelper).movePip(any(Rect.class));
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java
index d12cf5c3518a..877b19223bf6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java
@@ -26,6 +26,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.content.Context;
@@ -43,6 +44,7 @@ import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import org.junit.Before;
@@ -72,6 +74,7 @@ public class SizeCompatUIControllerTest extends ShellTestCase {
private @Mock DisplayImeController mMockImeController;
private @Mock ShellTaskOrganizer.TaskListener mMockTaskListener;
private @Mock SyncTransactionQueue mMockSyncQueue;
+ private @Mock ShellExecutor mMockExecutor;
private @Mock SizeCompatUILayout mMockLayout;
@Captor
@@ -85,7 +88,7 @@ public class SizeCompatUIControllerTest extends ShellTestCase {
doReturn(DISPLAY_ID).when(mMockLayout).getDisplayId();
doReturn(TASK_ID).when(mMockLayout).getTaskId();
mController = new SizeCompatUIController(mContext, mMockDisplayController,
- mMockDisplayInsetsController, mMockImeController, mMockSyncQueue) {
+ mMockDisplayInsetsController, mMockImeController, mMockSyncQueue, mMockExecutor) {
@Override
SizeCompatUILayout createLayout(Context context, int displayId, int taskId,
Configuration taskConfig, ShellTaskOrganizer.TaskListener taskListener) {
@@ -106,19 +109,17 @@ public class SizeCompatUIControllerTest extends ShellTestCase {
final Configuration taskConfig = new Configuration();
// Verify that the restart button is added with non-null size compat info.
- mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
- mMockTaskListener);
+ mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
verify(mController).createLayout(any(), eq(DISPLAY_ID), eq(TASK_ID), eq(taskConfig),
eq(mMockTaskListener));
// Verify that the restart button is updated with non-null new size compat info.
final Configuration newTaskConfig = new Configuration();
- mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, newTaskConfig,
- mMockTaskListener);
+ mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, newTaskConfig, mMockTaskListener);
verify(mMockLayout).updateSizeCompatInfo(taskConfig, mMockTaskListener,
- false /* isImeShowing */);
+ true /* show */);
// Verify that the restart button is removed with null size compat info.
mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, null, mMockTaskListener);
@@ -196,15 +197,90 @@ public class SizeCompatUIControllerTest extends ShellTestCase {
@Test
public void testChangeButtonVisibilityOnImeShowHide() {
final Configuration taskConfig = new Configuration();
- mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
- mMockTaskListener);
+ mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+ // Verify that the restart button is hidden after IME is showing.
mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */);
- verify(mMockLayout).updateImeVisibility(true);
+ verify(mMockLayout).updateVisibility(false);
+
+ // Verify button remains hidden while IME is showing.
+ mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+ verify(mMockLayout).updateSizeCompatInfo(taskConfig, mMockTaskListener,
+ false /* show */);
+
+ // Verify button is shown after IME is hidden.
mController.onImeVisibilityChanged(DISPLAY_ID, false /* isShowing */);
- verify(mMockLayout).updateImeVisibility(false);
+ verify(mMockLayout).updateVisibility(true);
+ }
+
+ @Test
+ public void testChangeButtonVisibilityOnKeyguardOccludedChanged() {
+ final Configuration taskConfig = new Configuration();
+ mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+
+ // Verify that the restart button is hidden after keyguard becomes occluded.
+ mController.onKeyguardOccludedChanged(true);
+
+ verify(mMockLayout).updateVisibility(false);
+
+ // Verify button remains hidden while keyguard is occluded.
+ mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+
+ verify(mMockLayout).updateSizeCompatInfo(taskConfig, mMockTaskListener,
+ false /* show */);
+
+ // Verify button is shown after keyguard becomes not occluded.
+ mController.onKeyguardOccludedChanged(false);
+
+ verify(mMockLayout).updateVisibility(true);
+ }
+
+ @Test
+ public void testButtonRemainsHiddenOnKeyguardOccludedFalseWhenImeIsShowing() {
+ final Configuration taskConfig = new Configuration();
+ mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+
+ mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */);
+ mController.onKeyguardOccludedChanged(true);
+
+ verify(mMockLayout, times(2)).updateVisibility(false);
+
+ clearInvocations(mMockLayout);
+
+ // Verify button remains hidden after keyguard becomes not occluded since IME is showing.
+ mController.onKeyguardOccludedChanged(false);
+
+ verify(mMockLayout).updateVisibility(false);
+
+ // Verify button is shown after IME is not showing.
+ mController.onImeVisibilityChanged(DISPLAY_ID, false /* isShowing */);
+
+ verify(mMockLayout).updateVisibility(true);
+ }
+
+ @Test
+ public void testButtonRemainsHiddenOnImeHideWhenKeyguardIsOccluded() {
+ final Configuration taskConfig = new Configuration();
+ mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+
+ mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */);
+ mController.onKeyguardOccludedChanged(true);
+
+ verify(mMockLayout, times(2)).updateVisibility(false);
+
+ clearInvocations(mMockLayout);
+
+ // Verify button remains hidden after IME is hidden since keyguard is occluded.
+ mController.onImeVisibilityChanged(DISPLAY_ID, false /* isShowing */);
+
+ verify(mMockLayout).updateVisibility(false);
+
+ // Verify button is shown after keyguard becomes not occluded.
+ mController.onKeyguardOccludedChanged(false);
+
+ verify(mMockLayout).updateVisibility(true);
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java
index 2ba603c8eda8..eb9305b2e995 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java
@@ -96,8 +96,8 @@ public class SizeCompatUILayoutTest extends ShellTestCase {
@Test
public void testCreateSizeCompatButton() {
- // Not create button if IME is showing.
- mLayout.createSizeCompatButton(true /* isImeShowing */);
+ // Not create button if show is false.
+ mLayout.createSizeCompatButton(false /* show */);
verify(mLayout.mButtonWindowManager, never()).createSizeCompatButton();
assertNull(mLayout.mButton);
@@ -106,7 +106,7 @@ public class SizeCompatUILayoutTest extends ShellTestCase {
// Not create hint popup.
mLayout.mShouldShowHint = false;
- mLayout.createSizeCompatButton(false /* isImeShowing */);
+ mLayout.createSizeCompatButton(true /* show */);
verify(mLayout.mButtonWindowManager).createSizeCompatButton();
assertNotNull(mLayout.mButton);
@@ -116,7 +116,7 @@ public class SizeCompatUILayoutTest extends ShellTestCase {
// Create hint popup.
mLayout.release();
mLayout.mShouldShowHint = true;
- mLayout.createSizeCompatButton(false /* isImeShowing */);
+ mLayout.createSizeCompatButton(true /* show */);
verify(mLayout.mButtonWindowManager, times(2)).createSizeCompatButton();
assertNotNull(mLayout.mButton);
@@ -128,7 +128,7 @@ public class SizeCompatUILayoutTest extends ShellTestCase {
@Test
public void testRelease() {
- mLayout.createSizeCompatButton(false /* isImeShowing */);
+ mLayout.createSizeCompatButton(true /* show */);
final SizeCompatUIWindowManager hintWindowManager = mLayout.mHintWindowManager;
mLayout.release();
@@ -142,12 +142,11 @@ public class SizeCompatUILayoutTest extends ShellTestCase {
@Test
public void testUpdateSizeCompatInfo() {
- mLayout.createSizeCompatButton(false /* isImeShowing */);
+ mLayout.createSizeCompatButton(true /* show */);
// No diff
clearInvocations(mLayout);
- mLayout.updateSizeCompatInfo(mTaskConfig, mTaskListener,
- false /* isImeShowing */);
+ mLayout.updateSizeCompatInfo(mTaskConfig, mTaskListener, true /* show */);
verify(mLayout, never()).updateButtonSurfacePosition();
verify(mLayout, never()).release();
@@ -158,7 +157,7 @@ public class SizeCompatUILayoutTest extends ShellTestCase {
final ShellTaskOrganizer.TaskListener newTaskListener = mock(
ShellTaskOrganizer.TaskListener.class);
mLayout.updateSizeCompatInfo(mTaskConfig, newTaskListener,
- false /* isImeShowing */);
+ true /* show */);
verify(mLayout).release();
verify(mLayout).createSizeCompatButton(anyBoolean());
@@ -168,7 +167,7 @@ public class SizeCompatUILayoutTest extends ShellTestCase {
final Configuration newTaskConfiguration = new Configuration();
newTaskConfiguration.windowConfiguration.setBounds(new Rect(0, 1000, 0, 2000));
mLayout.updateSizeCompatInfo(newTaskConfiguration, newTaskListener,
- false /* isImeShowing */);
+ true /* show */);
verify(mLayout).updateButtonSurfacePosition();
verify(mLayout).updateHintSurfacePosition();
@@ -220,24 +219,24 @@ public class SizeCompatUILayoutTest extends ShellTestCase {
}
@Test
- public void testUpdateImeVisibility() {
+ public void testUpdateVisibility() {
// Create button if it is not created.
mLayout.mButton = null;
- mLayout.updateImeVisibility(false /* isImeShowing */);
+ mLayout.updateVisibility(true /* show */);
- verify(mLayout).createSizeCompatButton(false /* isImeShowing */);
+ verify(mLayout).createSizeCompatButton(true /* show */);
- // Hide button if ime is shown.
+ // Hide button.
clearInvocations(mLayout);
doReturn(View.VISIBLE).when(mButton).getVisibility();
- mLayout.updateImeVisibility(true /* isImeShowing */);
+ mLayout.updateVisibility(false /* show */);
verify(mLayout, never()).createSizeCompatButton(anyBoolean());
verify(mButton).setVisibility(View.GONE);
- // Show button if ime is not shown.
+ // Show button.
doReturn(View.GONE).when(mButton).getVisibility();
- mLayout.updateImeVisibility(false /* isImeShowing */);
+ mLayout.updateVisibility(true /* show */);
verify(mLayout, never()).createSizeCompatButton(anyBoolean());
verify(mButton).setVisibility(View.VISIBLE);
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 96f127b6a611..77fff0f08d4c 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -191,5 +191,6 @@ public class SecureSettings {
Settings.Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED,
Settings.Secure.LOCKSCREEN_SHOW_CONTROLS,
Settings.Secure.LOCKSCREEN_SHOW_WALLET,
+ Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK,
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 7aeacdc0cf71..9f883960981b 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -150,6 +150,7 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.POWER_MENU_LOCKED_SHOW_CONTENT, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.LOCKSCREEN_SHOW_CONTROLS, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.LOCKSCREEN_SHOW_WALLET, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.DOZE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.DOZE_ALWAYS_ON, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.DOZE_PICK_UP_GESTURE, BOOLEAN_VALIDATOR);
diff --git a/packages/SystemUI/animation/res/values/ids.xml b/packages/SystemUI/animation/res/values/ids.xml
index ef60a248f79a..c4cb89fecccb 100644
--- a/packages/SystemUI/animation/res/values/ids.xml
+++ b/packages/SystemUI/animation/res/values/ids.xml
@@ -16,4 +16,5 @@
-->
<resources>
<item type="id" name="launch_animation_running"/>
+ <item type="id" name="dialog_content_parent" />
</resources> \ No newline at end of file
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index 413612ff9a76..9aad2783ba2d 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -40,6 +40,7 @@ import android.widget.FrameLayout
import kotlin.math.roundToInt
private const val TAG = "DialogLaunchAnimator"
+private val DIALOG_CONTENT_PARENT_ID = R.id.dialog_content_parent
/**
* A class that allows dialogs to be started in a seamless way from a view that is transforming
@@ -86,10 +87,10 @@ class DialogLaunchAnimator(
// If the parent of the view we are launching from is the background of some other animated
// dialog, then this means the caller intent is to launch a dialog from another dialog. In
// this case, we also animate the parent (which is the dialog background).
- val dialogContentParent = openedDialogs
+ val animatedParent = openedDialogs
.firstOrNull { it.dialogContentParent == view.parent }
- ?.dialogContentParent
- val animateFrom = dialogContentParent ?: view
+ val parentHostDialog = animatedParent?.hostDialog
+ val animateFrom = animatedParent?.dialogContentParent ?: view
// Make sure we don't run the launch animation from the same view twice at the same time.
if (animateFrom.getTag(TAG_LAUNCH_ANIMATION_RUNNING) != null) {
@@ -100,12 +101,18 @@ class DialogLaunchAnimator(
animateFrom.setTag(TAG_LAUNCH_ANIMATION_RUNNING, true)
- val launchAnimation = AnimatedDialog(
- context, launchAnimator, hostDialogProvider, animateFrom,
- onDialogDismissed = { openedDialogs.remove(it) }, originalDialog = dialog,
- animateBackgroundBoundsChange)
- val hostDialog = launchAnimation.hostDialog
- openedDialogs.add(launchAnimation)
+ val animatedDialog = AnimatedDialog(
+ context,
+ launchAnimator,
+ hostDialogProvider,
+ animateFrom,
+ onDialogDismissed = { openedDialogs.remove(it) },
+ originalDialog = dialog,
+ animateBackgroundBoundsChange,
+ openedDialogs.firstOrNull { it.hostDialog == parentHostDialog }
+ )
+ val hostDialog = animatedDialog.hostDialog
+ openedDialogs.add(animatedDialog)
// If the dialog is dismissed/hidden/shown, then we should actually dismiss/hide/show the
// host dialog.
@@ -119,15 +126,15 @@ class DialogLaunchAnimator(
// If AOD is disabled the screen will directly becomes black and we won't see
// the animation anyways.
if (reason == DialogListener.DismissReason.DEVICE_LOCKED) {
- launchAnimation.exitAnimationDisabled = true
+ animatedDialog.exitAnimationDisabled = true
}
hostDialog.dismiss()
}
override fun onHide() {
- if (launchAnimation.ignoreNextCallToHide) {
- launchAnimation.ignoreNextCallToHide = false
+ if (animatedDialog.ignoreNextCallToHide) {
+ animatedDialog.ignoreNextCallToHide = false
return
}
@@ -138,21 +145,44 @@ class DialogLaunchAnimator(
hostDialog.show()
// We don't actually want to show the original dialog, so hide it.
- launchAnimation.ignoreNextCallToHide = true
+ animatedDialog.ignoreNextCallToHide = true
dialog.hide()
}
override fun onSizeChanged() {
- launchAnimation.onOriginalDialogSizeChanged()
+ animatedDialog.onOriginalDialogSizeChanged()
+ }
+
+ override fun prepareForStackDismiss() {
+ animatedDialog.touchSurface = animatedDialog.prepareForStackDismiss()
}
})
}
- launchAnimation.start()
+ animatedDialog.start()
return hostDialog
}
/**
+ * Launch [dialog] from a [parentHostDialog] as returned by [showFromView]. This will allow
+ * for dismissing the whole stack.
+ *
+ * This will return a new host dialog, with the same caveat as [showFromView].
+ *
+ * @see DialogListener.prepareForStackDismiss
+ */
+ fun showFromDialog(
+ dialog: Dialog,
+ parentHostDialog: Dialog,
+ animateBackgroundBoundsChange: Boolean = false
+ ): Dialog {
+ val view = parentHostDialog.findViewById<ViewGroup>(DIALOG_CONTENT_PARENT_ID)
+ ?.getChildAt(0)
+ ?: throw IllegalStateException("No dialog content parent found in host dialog")
+ return showFromView(dialog, view, animateBackgroundBoundsChange)
+ }
+
+ /**
* Ensure that all dialogs currently shown won't animate into their touch surface when
* dismissed.
*
@@ -214,6 +244,12 @@ interface DialogListener {
/** Called when this dialog show() is called. */
fun onShow()
+ /**
+ * Call before dismissing a stack of dialogs (dialogs launched from dialogs), so the topmost
+ * can animate directly into the original `touchSurface`.
+ */
+ fun prepareForStackDismiss()
+
/** Called when this dialog size might have changed, e.g. because of configuration changes. */
fun onSizeChanged()
}
@@ -224,7 +260,7 @@ private class AnimatedDialog(
hostDialogProvider: HostDialogProvider,
/** The view that triggered the dialog after being tapped. */
- private val touchSurface: View,
+ var touchSurface: View,
/**
* A callback that will be called with this [AnimatedDialog] after the dialog was
@@ -236,7 +272,10 @@ private class AnimatedDialog(
private val originalDialog: Dialog,
/** Whether we should animate the dialog background when its bounds change. */
- private val animateBackgroundBoundsChange: Boolean
+ private val animateBackgroundBoundsChange: Boolean,
+
+ /** Launch animation corresponding to the parent [hostDialog]. */
+ private val parentAnimatedDialog: AnimatedDialog? = null
) {
/**
* The fullscreen dialog to which we will add the content view [originalDialogView] of
@@ -253,7 +292,9 @@ private class AnimatedDialog(
* the same size as the original dialog window and to which we will set the original dialog
* window background.
*/
- val dialogContentParent = FrameLayout(context)
+ val dialogContentParent = FrameLayout(context).apply {
+ id = DIALOG_CONTENT_PARENT_ID
+ }
/**
* The background color of [originalDialogView], taking into consideration the [originalDialog]
@@ -359,9 +400,7 @@ private class AnimatedDialog(
// Make the touch surface invisible and make sure that it stays invisible as long as the
// dialog is shown or animating.
touchSurface.visibility = View.INVISIBLE
- if (touchSurface is LaunchableView) {
- touchSurface.setShouldBlockVisibilityChanges(true)
- }
+ (touchSurface as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
// Add a pre draw listener to (maybe) start the animation once the touch surface is
// actually invisible.
@@ -576,9 +615,7 @@ private class AnimatedDialog(
Log.i(TAG, "Skipping animation of dialog into the touch surface")
// Make sure we allow the touch surface to change its visibility again.
- if (touchSurface is LaunchableView) {
- touchSurface.setShouldBlockVisibilityChanges(false)
- }
+ (touchSurface as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
// If the view is invisible it's probably because of us, so we make it visible again.
if (touchSurface.visibility == View.INVISIBLE) {
@@ -598,9 +635,7 @@ private class AnimatedDialog(
},
onLaunchAnimationEnd = {
// Make sure we allow the touch surface to change its visibility again.
- if (touchSurface is LaunchableView) {
- touchSurface.setShouldBlockVisibilityChanges(false)
- }
+ (touchSurface as? LaunchableView)?.setShouldBlockVisibilityChanges(false)
touchSurface.visibility = View.VISIBLE
dialogContentParent.visibility = View.INVISIBLE
@@ -796,4 +831,18 @@ private class AnimatedDialog(
animator.start()
}
}
+
+ fun prepareForStackDismiss(): View {
+ if (parentAnimatedDialog == null) {
+ return touchSurface
+ }
+ parentAnimatedDialog.exitAnimationDisabled = true
+ parentAnimatedDialog.originalDialog.hide()
+ val view = parentAnimatedDialog.prepareForStackDismiss()
+ parentAnimatedDialog.originalDialog.dismiss()
+ // Make the touch surface invisible, so we end up animating to it when we actually
+ // dismiss the stack
+ view.visibility = View.INVISIBLE
+ return view
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 3d4e896178f6..9238b8226bbc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -11,7 +11,6 @@ import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewTreeObserver.OnPreDrawListener;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
@@ -89,7 +88,6 @@ public class KeyguardClockSwitch extends RelativeLayout {
private int mClockSwitchYAmount;
@VisibleForTesting boolean mChildrenAreLaidOut = false;
- private OnPreDrawListener mPreDrawListener;
public KeyguardClockSwitch(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -284,30 +282,21 @@ public class KeyguardClockSwitch extends RelativeLayout {
// translate them properly
if (mChildrenAreLaidOut) {
animateClockChange(clockSize == LARGE);
- mDisplayedClockSize = clockSize;
- } else if (mPreDrawListener == null) {
- mPreDrawListener = () -> {
- switchToClock(clockSize);
- getViewTreeObserver().removeOnPreDrawListener(mPreDrawListener);
- mPreDrawListener = null;
- return true;
- };
- getViewTreeObserver().addOnPreDrawListener(mPreDrawListener);
}
+
+ mDisplayedClockSize = clockSize;
return true;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
- mChildrenAreLaidOut = true;
- }
- void onViewDetached() {
- if (mPreDrawListener != null) {
- getViewTreeObserver().removeOnPreDrawListener(mPreDrawListener);
- mPreDrawListener = null;
+ if (mDisplayedClockSize != null && !mChildrenAreLaidOut) {
+ animateClockChange(mDisplayedClockSize == LARGE);
}
+
+ mChildrenAreLaidOut = true;
}
public Paint getPaint() {
@@ -368,5 +357,6 @@ public class KeyguardClockSwitch extends RelativeLayout {
pw.println(" mDarkAmount: " + mDarkAmount);
pw.println(" mSupportsDarkText: " + mSupportsDarkText);
pw.println(" mColorPalette: " + Arrays.toString(mColorPalette));
+ pw.println(" mDisplayedClockSize: " + mDisplayedClockSize);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 905495d369a0..c628d4401bb1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -23,6 +23,8 @@ import static com.android.keyguard.KeyguardClockSwitch.LARGE;
import android.app.WallpaperManager;
import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.provider.Settings;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
@@ -49,11 +51,13 @@ import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.ViewController;
+import com.android.systemui.util.settings.SecureSettings;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.TimeZone;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -72,6 +76,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
private final BatteryController mBatteryController;
private final LockscreenSmartspaceController mSmartspaceController;
private final Resources mResources;
+ private final SecureSettings mSecureSettings;
/**
* Clock for both small and large sizes
@@ -109,6 +114,14 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
private SmartspaceTransitionController mSmartspaceTransitionController;
private boolean mOnlyClock = false;
+ private Executor mUiExecutor;
+ private boolean mCanShowDoubleLineClock = true;
+ private ContentObserver mDoubleLineClockObserver = new ContentObserver(null) {
+ @Override
+ public void onChange(boolean change) {
+ updateDoubleLineClock();
+ }
+ };
@Inject
public KeyguardClockSwitchController(
@@ -125,6 +138,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
LockscreenSmartspaceController smartspaceController,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
SmartspaceTransitionController smartspaceTransitionController,
+ SecureSettings secureSettings,
+ @Main Executor uiExecutor,
@Main Resources resources) {
super(keyguardClockSwitch);
mStatusBarStateController = statusBarStateController;
@@ -138,7 +153,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
mBypassController = bypassController;
mSmartspaceController = smartspaceController;
mResources = resources;
-
+ mSecureSettings = secureSettings;
+ mUiExecutor = uiExecutor;
mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
mSmartspaceTransitionController = smartspaceTransitionController;
}
@@ -223,6 +239,14 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
updateClockLayout();
mSmartspaceTransitionController.setLockscreenSmartspace(mSmartspaceView);
}
+
+ mSecureSettings.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK),
+ false, /* notifyForDescendants */
+ mDoubleLineClockObserver
+ );
+
+ updateDoubleLineClock();
}
int getNotificationIconAreaHeight() {
@@ -236,7 +260,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
}
mColorExtractor.removeOnColorsChangedListener(mColorsListener);
mView.setClockPlugin(null, mStatusBarStateController.getState());
- mView.onViewDetached();
+
+ mSecureSettings.unregisterContentObserver(mDoubleLineClockObserver);
}
/**
@@ -268,6 +293,10 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
* hidden.
*/
public void displayClock(@KeyguardClockSwitch.ClockSize int clockSize) {
+ if (!mCanShowDoubleLineClock && clockSize == KeyguardClockSwitch.LARGE) {
+ return;
+ }
+
boolean appeared = mView.switchToClock(clockSize);
if (appeared && clockSize == LARGE) {
mLargeClockViewController.animateAppear();
@@ -410,4 +439,13 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
private int getCurrentLayoutDirection() {
return TextUtils.getLayoutDirectionFromLocale(Locale.getDefault());
}
+
+ private void updateDoubleLineClock() {
+ mCanShowDoubleLineClock = mSecureSettings.getInt(
+ Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1) != 0;
+
+ if (!mCanShowDoubleLineClock) {
+ mUiExecutor.execute(() -> displayClock(KeyguardClockSwitch.SMALL));
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
index 9286175cc2ea..471bac16642b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -43,8 +43,7 @@ data class KeyguardFingerprintListenModel(
val shouldListenForFingerprintAssistant: Boolean,
val switchingUser: Boolean,
val udfps: Boolean,
- val userDoesNotHaveTrust: Boolean,
- val userNeedsStrongAuth: Boolean
+ val userDoesNotHaveTrust: Boolean
) : KeyguardListenModel() {
override val modality: Int = TYPE_FACE
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index d27bc675ecb8..e24f07c21076 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2222,11 +2222,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
!(mFingerprintLockedOut && mBouncer && mCredentialAttempted);
final boolean isEncryptedOrLockdownForUser = isEncryptedOrLockdown(user);
- final boolean userNeedsStrongAuth = userNeedsStrongAuth();
final boolean shouldListenUdfpsState = !isUdfps
|| (!userCanSkipBouncer
&& !isEncryptedOrLockdownForUser
- && !userNeedsStrongAuth
&& userDoesNotHaveTrust
&& !mFingerprintLockedOut);
@@ -2257,8 +2255,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
shouldListenForFingerprintAssistant,
mSwitchingUser,
isUdfps,
- userDoesNotHaveTrust,
- userNeedsStrongAuth));
+ userDoesNotHaveTrust));
}
return shouldListen;
@@ -2362,7 +2359,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
|| (DEBUG_FINGERPRINT
&& model instanceof KeyguardFingerprintListenModel
&& mFingerprintRunningState != BIOMETRIC_STATE_RUNNING);
- if (notYetRunning && model.getListening()) {
+ final boolean running =
+ (DEBUG_FACE
+ && model instanceof KeyguardFaceListenModel
+ && mFaceRunningState == BIOMETRIC_STATE_RUNNING)
+ || (DEBUG_FINGERPRINT
+ && model instanceof KeyguardFingerprintListenModel
+ && mFingerprintRunningState == BIOMETRIC_STATE_RUNNING);
+ if (notYetRunning && model.getListening()
+ || running && !model.getListening()) {
mListenModels.add(model);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index e96e924d557f..d37dcafde92a 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -31,8 +31,6 @@ import com.android.systemui.dagger.WMComponent;
import com.android.systemui.navigationbar.gestural.BackGestureTfClassifierProvider;
import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider;
import com.android.wm.shell.transition.ShellTransitions;
-import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.recents.RecentTasks;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
@@ -122,7 +120,8 @@ public class SystemUIFactory {
.setStartingSurface(mWMComponent.getStartingSurface())
.setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper())
.setTaskSurfaceHelper(mWMComponent.getTaskSurfaceHelper())
- .setRecentTasks(mWMComponent.getRecentTasks());
+ .setRecentTasks(mWMComponent.getRecentTasks())
+ .setSizeCompatUI(Optional.of(mWMComponent.getSizeCompatUI()));
} else {
// TODO: Call on prepareSysUIComponentBuilder but not with real components. Other option
// is separating this logic into newly creating SystemUITestsFactory.
@@ -140,7 +139,8 @@ public class SystemUIFactory {
.setDisplayAreaHelper(Optional.ofNullable(null))
.setStartingSurface(Optional.ofNullable(null))
.setTaskSurfaceHelper(Optional.ofNullable(null))
- .setRecentTasks(Optional.ofNullable(null));
+ .setRecentTasks(Optional.ofNullable(null))
+ .setSizeCompatUI(Optional.ofNullable(null));
}
mSysUIComponent = builder.build();
if (mInitializeComponents) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index a9fb743bff8d..5fdf026b86f3 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -37,6 +37,7 @@ import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.recents.RecentTasks;
+import com.android.wm.shell.sizecompatui.SizeCompatUI;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.startingsurface.StartingSurface;
import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper;
@@ -107,6 +108,9 @@ public interface SysUIComponent {
@BindsInstance
Builder setRecentTasks(Optional<RecentTasks> r);
+ @BindsInstance
+ Builder setSizeCompatUI(Optional<SizeCompatUI> s);
+
SysUIComponent build();
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index d8b77426703e..543ba8f9b854 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -20,13 +20,13 @@ import android.content.Context;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.tv.TvWMComponent;
-import com.android.wm.shell.dagger.TvWMShellModule;
-import com.android.wm.shell.dagger.WMShellModule;
import com.android.wm.shell.ShellCommandHandler;
import com.android.wm.shell.ShellInit;
import com.android.wm.shell.TaskViewFactory;
import com.android.wm.shell.apppairs.AppPairs;
import com.android.wm.shell.bubbles.Bubbles;
+import com.android.wm.shell.dagger.TvWMShellModule;
+import com.android.wm.shell.dagger.WMShellModule;
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
@@ -34,6 +34,7 @@ import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.recents.RecentTasks;
+import com.android.wm.shell.sizecompatui.SizeCompatUI;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.startingsurface.StartingSurface;
import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper;
@@ -115,4 +116,7 @@ public interface WMComponent {
@WMSingleton
Optional<RecentTasks> getRecentTasks();
+
+ @WMSingleton
+ SizeCompatUI getSizeCompatUI();
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 5c3e07fbaea1..dd94a75c55c6 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -16,6 +16,8 @@
package com.android.systemui.doze;
+import static com.android.systemui.doze.DozeLog.REASON_SENSOR_QUICK_PICKUP;
+import static com.android.systemui.doze.DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS;
import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_DISPLAY;
import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_LOCK_SCREEN;
@@ -98,6 +100,7 @@ public class DozeSensors {
private final DozeLog mDozeLog;
private final SecureSettings mSecureSettings;
private final DevicePostureController mDevicePostureController;
+ private final AuthController mAuthController;
private final boolean mScreenOffUdfpsEnabled;
// Sensors
@@ -115,6 +118,7 @@ public class DozeSensors {
private boolean mListening;
private boolean mListeningTouchScreenSensors;
private boolean mListeningProxSensors;
+ private boolean mUdfpsEnrolled;
@DevicePostureController.DevicePostureInt
private int mDevicePosture;
@@ -169,10 +173,11 @@ public class DozeSensors {
config.screenOffUdfpsEnabled(KeyguardUpdateMonitor.getCurrentUser());
mDevicePostureController = devicePostureController;
mDevicePosture = mDevicePostureController.getDevicePosture();
+ mAuthController = authController;
- boolean udfpsEnrolled =
- authController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser());
- boolean alwaysOn = mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT);
+ mUdfpsEnrolled =
+ mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser());
+ mAuthController.addCallback(mAuthControllerCallback);
mTriggerSensors = new TriggerSensor[] {
new TriggerSensor(
mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION),
@@ -221,7 +226,7 @@ public class DozeSensors {
findSensor(config.udfpsLongPressSensorType()),
"doze_pulse_on_auth",
true /* settingDef */,
- udfpsEnrolled && (alwaysOn || mScreenOffUdfpsEnabled),
+ udfpsLongPressConfigured(),
DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS,
true /* reports touch coordinates */,
true /* touchscreen */,
@@ -230,7 +235,8 @@ public class DozeSensors {
new PluginSensor(
new SensorManagerPlugin.Sensor(TYPE_WAKE_DISPLAY),
Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE,
- mConfig.wakeScreenGestureAvailable() && alwaysOn,
+ mConfig.wakeScreenGestureAvailable()
+ && mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT),
DozeLog.REASON_SENSOR_WAKE_UP_PRESENCE,
false /* reports touch coordinates */,
false /* touchscreen */),
@@ -246,8 +252,7 @@ public class DozeSensors {
findSensor(config.quickPickupSensorType()),
Settings.Secure.DOZE_QUICK_PICKUP_GESTURE,
true /* setting default */,
- config.quickPickupSensorEnabled(KeyguardUpdateMonitor.getCurrentUser())
- && udfpsEnrolled,
+ quickPickUpConfigured(),
DozeLog.REASON_SENSOR_QUICK_PICKUP,
false /* requiresTouchCoordinates */,
false /* requiresTouchscreen */,
@@ -265,6 +270,16 @@ public class DozeSensors {
mDevicePostureController.addCallback(mDevicePostureCallback);
}
+ private boolean udfpsLongPressConfigured() {
+ return mUdfpsEnrolled
+ && (mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT) || mScreenOffUdfpsEnabled);
+ }
+
+ private boolean quickPickUpConfigured() {
+ return mUdfpsEnrolled
+ && mConfig.quickPickupSensorEnabled(KeyguardUpdateMonitor.getCurrentUser());
+ }
+
/**
* Unregister all sensors and callbacks.
*/
@@ -276,6 +291,7 @@ public class DozeSensors {
mProximitySensor.pause();
mDevicePostureController.removeCallback(mDevicePostureCallback);
+ mAuthController.removeCallback(mAuthControllerCallback);
}
/**
@@ -450,6 +466,7 @@ public class DozeSensors {
pw.println("mSelectivelyRegisterProxSensors=" + mSelectivelyRegisterProxSensors);
pw.println("mListeningProxSensors=" + mListeningProxSensors);
pw.println("mScreenOffUdfpsEnabled=" + mScreenOffUdfpsEnabled);
+ pw.println("mUdfpsEnrolled=" + mUdfpsEnrolled);
IndentingPrintWriter idpw = new IndentingPrintWriter(pw);
idpw.increaseIndent();
for (TriggerSensor s : mTriggerSensors) {
@@ -468,7 +485,7 @@ public class DozeSensors {
@VisibleForTesting
class TriggerSensor extends TriggerEventListener {
@NonNull final Sensor[] mSensors; // index = posture, value = sensor
- final boolean mConfigured;
+ boolean mConfigured;
final int mPulseReason;
private final String mSetting;
private final boolean mReportsTouchCoordinates;
@@ -606,8 +623,18 @@ public class DozeSensors {
updateListening();
}
+ /**
+ * Update configured state.
+ */
+ public void setConfigured(boolean configured) {
+ if (mConfigured == configured) return;
+ mConfigured = configured;
+ updateListening();
+ }
+
public void updateListening() {
final Sensor sensor = mSensors[mPosture];
+
if (!mConfigured || sensor == null) return;
if (mRequested && !mDisabled && (enabledBySetting() || mIgnoresSetting)) {
if (!mRegistered) {
@@ -791,6 +818,30 @@ public class DozeSensors {
}
};
+ private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
+ @Override
+ public void onAllAuthenticatorsRegistered() {
+ updateUdfpsEnrolled();
+ }
+
+ @Override
+ public void onEnrollmentsChanged() {
+ updateUdfpsEnrolled();
+ }
+
+ private void updateUdfpsEnrolled() {
+ mUdfpsEnrolled = mAuthController.isUdfpsEnrolled(
+ KeyguardUpdateMonitor.getCurrentUser());
+ for (TriggerSensor sensor : mTriggerSensors) {
+ if (REASON_SENSOR_QUICK_PICKUP == sensor.mPulseReason) {
+ sensor.setConfigured(quickPickUpConfigured());
+ } else if (REASON_SENSOR_UDFPS_LONG_PRESS == sensor.mPulseReason) {
+ sensor.setConfigured(udfpsLongPressConfigured());
+ }
+ }
+ }
+ };
+
public interface Callback {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
index 821bd5117d18..be9aa0e98876 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -28,6 +28,8 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import androidx.annotation.Nullable;
+
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -37,10 +39,10 @@ import com.android.systemui.R;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.qs.PseudoGridView;
import com.android.systemui.qs.QSUserSwitcherEvent;
+import com.android.systemui.qs.user.UserSwitchDialogController;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.UserSwitcherController;
-import java.util.function.Consumer;
-
import javax.inject.Inject;
/**
@@ -77,7 +79,7 @@ public class UserDetailView extends PseudoGridView {
private View mCurrentUserView;
private final UiEventLogger mUiEventLogger;
private final FalsingManager mFalsingManager;
- private Consumer<UserSwitcherController.UserRecord> mClickCallback;
+ private @Nullable UserSwitchDialogController.DialogShower mDialogShower;
@Inject
public Adapter(Context context, UserSwitcherController controller,
@@ -95,8 +97,17 @@ public class UserDetailView extends PseudoGridView {
return createUserDetailItemView(convertView, parent, item);
}
- public void injectCallback(Consumer<UserSwitcherController.UserRecord> clickCallback) {
- mClickCallback = clickCallback;
+ /**
+ * If this adapter is inside a dialog, passing a
+ * {@link UserSwitchDialogController.DialogShower} will help animate to and from the parent
+ * dialog. This will also allow for dismissing the whole stack of dialogs in a single
+ * animation.
+ *
+ * @param shower
+ * @see SystemUIDialog#dismissStack()
+ */
+ public void injectDialogShower(UserSwitchDialogController.DialogShower shower) {
+ mDialogShower = shower;
}
public UserDetailItemView createUserDetailItemView(View convertView, ViewGroup parent,
@@ -172,10 +183,7 @@ public class UserDetailView extends PseudoGridView {
}
view.setActivated(true);
}
- onUserListItemClicked(tag);
- }
- if (mClickCallback != null) {
- mClickCallback.accept(tag);
+ onUserListItemClicked(tag, mDialogShower);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index 563c4cd628d2..77b9cc14fa6d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -306,12 +306,8 @@ public class InternetDialog extends SystemUIDialog implements
final boolean isWifiScanEnabled = mInternetDialogController.isWifiScanEnabled();
updateWifiToggle(isWifiEnabled, isDeviceLocked);
updateConnectedWifi(isWifiEnabled, isDeviceLocked);
+ updateWifiListAndSeeAll(isWifiEnabled, isDeviceLocked);
updateWifiScanNotify(isWifiEnabled, isWifiScanEnabled, isDeviceLocked);
-
- final int visibility = (isDeviceLocked || !isWifiEnabled || mWifiEntriesCount <= 0)
- ? View.GONE : View.VISIBLE;
- mWifiRecyclerView.setVisibility(visibility);
- mSeeAllLayout.setVisibility(visibility);
}
private void setOnClickListener() {
@@ -414,6 +410,18 @@ public class InternetDialog extends SystemUIDialog implements
}
@MainThread
+ private void updateWifiListAndSeeAll(boolean isWifiEnabled, boolean isDeviceLocked) {
+ if (!isWifiEnabled || isDeviceLocked) {
+ mWifiRecyclerView.setVisibility(View.GONE);
+ mSeeAllLayout.setVisibility(View.GONE);
+ return;
+ }
+ mWifiRecyclerView.setVisibility(mWifiEntriesCount > 0 ? View.VISIBLE : View.GONE);
+ mSeeAllLayout.setVisibility(
+ (mConnectedWifiEntry != null || mWifiEntriesCount > 0) ? View.VISIBLE : View.GONE);
+ }
+
+ @MainThread
private void updateWifiScanNotify(boolean isWifiEnabled, boolean isWifiScanEnabled,
boolean isDeviceLocked) {
if (isWifiEnabled || !isWifiScanEnabled || isDeviceLocked) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
index bae7996517c5..d74a50e24ed3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
@@ -16,7 +16,9 @@
package com.android.systemui.qs.user
+import android.app.Dialog
import android.content.Context
+import android.content.DialogInterface
import android.content.Intent
import android.provider.Settings
import android.view.View
@@ -84,12 +86,26 @@ class UserSwitchDialogController @VisibleForTesting constructor(
doneButton.setOnClickListener { dismiss() }
val adapter = userDetailViewAdapterProvider.get()
- adapter.injectCallback {
- dismiss()
- }
adapter.linkToViewGroup(grid)
- dialogLaunchAnimator.showFromView(this, view)
+ val hostDialog = dialogLaunchAnimator.showFromView(this, view)
+ adapter.injectDialogShower(DialogShowerImpl(hostDialog, dialogLaunchAnimator))
}
}
+
+ private class DialogShowerImpl(
+ private val hostDialog: Dialog,
+ private val dialogLaunchAnimator: DialogLaunchAnimator
+ ) : DialogInterface by hostDialog, DialogShower {
+ override fun showDialog(dialog: Dialog): Dialog {
+ return dialogLaunchAnimator.showFromDialog(
+ dialog,
+ parentHostDialog = hostDialog
+ )
+ }
+ }
+
+ interface DialogShower : DialogInterface {
+ fun showDialog(dialog: Dialog): Dialog
+ }
} \ No newline at end of file
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 1bbd45192bea..4479aa0468b7 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
@@ -3345,6 +3345,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
ipw.decreaseIndent();
ipw.println("}");
+ } else if (mPrivateLayout != null) {
+ mPrivateLayout.dumpSmartReplies(ipw);
}
});
}
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 df484dd8ed77..0ffca30ed409 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
@@ -27,6 +27,7 @@ import android.os.Build;
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.AttributeSet;
+import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Pair;
import android.view.LayoutInflater;
@@ -1955,6 +1956,22 @@ public class NotificationContentView extends FrameLayout {
pw.println();
}
+ /** Add any existing SmartReplyView to the dump */
+ public void dumpSmartReplies(IndentingPrintWriter pw) {
+ if (mHeadsUpSmartReplyView != null) {
+ pw.println("HeadsUp SmartReplyView:");
+ pw.increaseIndent();
+ mHeadsUpSmartReplyView.dump(pw);
+ pw.decreaseIndent();
+ }
+ if (mExpandedSmartReplyView != null) {
+ pw.println("Expanded SmartReplyView:");
+ pw.increaseIndent();
+ mExpandedSmartReplyView.dump(pw);
+ pw.decreaseIndent();
+ }
+ }
+
public RemoteInputView getExpandedRemoteInput() {
return mExpandedRemoteInput;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index cf4aaba107cf..1130ec24108a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -218,6 +218,19 @@ public class SystemUIDialog extends AlertDialog implements ListenableDialog,
}
}
+ /**
+ * Dismiss this dialog. If it was launched from another dialog using
+ * {@link com.android.systemui.animation.DialogLaunchAnimator#showFromView} with a
+ * non-{@code null} {@code parentHostDialog} parameter, also dismisses the stack of dialogs,
+ * animating back to the original touchSurface.
+ */
+ public void dismissStack() {
+ for (DialogListener listener : new LinkedHashSet<>(mDialogListeners)) {
+ listener.prepareForStackDismiss();
+ }
+ dismiss();
+ }
+
@Override
public void hide() {
super.hide();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index 4e33529f3c36..85add6c21b72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -1,7 +1,8 @@
package com.android.systemui.statusbar.policy;
+import static java.lang.Float.NaN;
+
import android.annotation.ColorInt;
-import android.annotation.NonNull;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.RemoteInput;
@@ -14,10 +15,12 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.InsetDrawable;
import android.graphics.drawable.RippleDrawable;
+import android.os.SystemClock;
import android.text.Layout;
import android.text.TextPaint;
import android.text.method.TransformationMethod;
import android.util.AttributeSet;
+import android.util.IndentingPrintWriter;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -25,6 +28,8 @@ import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
+import androidx.annotation.NonNull;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.R;
@@ -89,6 +94,13 @@ public class SmartReplyView extends ViewGroup {
private int mMaxNumActions;
private int mMinNumSystemGeneratedReplies;
+ // DEBUG variables tracked for the dump()
+ private long mLastDrawChildTime;
+ private long mLastDispatchDrawTime;
+ private long mLastMeasureTime;
+ private int mTotalSqueezeRemeasureAttempts;
+ private boolean mDidHideSystemReplies;
+
public SmartReplyView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -217,6 +229,7 @@ public class SmartReplyView extends ViewGroup {
// Mark all buttons as hidden and un-squeezed.
resetButtonsLayoutParams();
+ mTotalSqueezeRemeasureAttempts = 0;
if (!mCandidateButtonQueueForSqueezing.isEmpty()) {
Log.wtf(TAG, "Single line button queue leaked between onMeasure calls");
@@ -329,6 +342,7 @@ public class SmartReplyView extends ViewGroup {
}
}
+ mDidHideSystemReplies = false;
if (mSmartRepliesGeneratedByAssistant) {
if (!gotEnoughSmartReplies(smartReplies)) {
// We don't have enough smart replies - hide all of them.
@@ -339,6 +353,7 @@ public class SmartReplyView extends ViewGroup {
// Reset our measures back to when we had only added actions (before adding
// replies).
accumulatedMeasures = actionsMeasures;
+ mDidHideSystemReplies = true;
}
}
@@ -356,6 +371,7 @@ public class SmartReplyView extends ViewGroup {
accumulatedMeasures.mMeasuredWidth),
widthMeasureSpec),
resolveSize(buttonHeight, heightMeasureSpec));
+ mLastMeasureTime = SystemClock.elapsedRealtime();
}
// TODO: this should be replaced, and instead, setMinSystemGenerated... should be invoked
@@ -371,6 +387,53 @@ public class SmartReplyView extends ViewGroup {
}
}
+ /** Dump internal state for debugging */
+ public void dump(IndentingPrintWriter pw) {
+ pw.println(this);
+ pw.increaseIndent();
+ pw.print("mMaxSqueezeRemeasureAttempts=");
+ pw.println(mMaxSqueezeRemeasureAttempts);
+ pw.print("mTotalSqueezeRemeasureAttempts=");
+ pw.println(mTotalSqueezeRemeasureAttempts);
+ pw.print("mMaxNumActions=");
+ pw.println(mMaxNumActions);
+ pw.print("mSmartRepliesGeneratedByAssistant=");
+ pw.println(mSmartRepliesGeneratedByAssistant);
+ pw.print("mMinNumSystemGeneratedReplies=");
+ pw.println(mMinNumSystemGeneratedReplies);
+ pw.print("mHeightUpperLimit=");
+ pw.println(mHeightUpperLimit);
+ pw.print("mDidHideSystemReplies=");
+ pw.println(mDidHideSystemReplies);
+ long now = SystemClock.elapsedRealtime();
+ pw.print("lastMeasureAge (s)=");
+ pw.println(mLastMeasureTime == 0 ? NaN : (now - mLastMeasureTime) / 1000.0f);
+ pw.print("lastDrawChildAge (s)=");
+ pw.println(mLastDrawChildTime == 0 ? NaN : (now - mLastDrawChildTime) / 1000.0f);
+ pw.print("lastDispatchDrawAge (s)=");
+ pw.println(mLastDispatchDrawTime == 0 ? NaN : (now - mLastDispatchDrawTime) / 1000.0f);
+ int numChildren = getChildCount();
+ pw.print("children: num=");
+ pw.println(numChildren);
+ pw.increaseIndent();
+ for (int i = 0; i < numChildren; i++) {
+ View child = getChildAt(i);
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ pw.print("[");
+ pw.print(i);
+ pw.print("] type=");
+ pw.print(lp.mButtonType);
+ pw.print(" squeezeStatus=");
+ pw.print(lp.squeezeStatus);
+ pw.print(" show=");
+ pw.print(lp.show);
+ pw.print(" view=");
+ pw.println(child);
+ }
+ pw.decreaseIndent();
+ pw.decreaseIndent();
+ }
+
/**
* Fields we keep track of inside onMeasure() to correctly measure the SmartReplyView depending
* on which suggestions are added.
@@ -393,8 +456,11 @@ public class SmartReplyView extends ViewGroup {
* Returns whether our notification contains at least N smart replies (or 0) where N is
* determined by {@link SmartReplyConstants}.
*/
- // TODO: we probably sholdn't make this deliberation in the View
private boolean gotEnoughSmartReplies(List<View> smartReplies) {
+ if (mMinNumSystemGeneratedReplies <= 1) {
+ // Count is irrelevant, do not bother.
+ return true;
+ }
int numShownReplies = 0;
for (View smartReplyButton : smartReplies) {
final LayoutParams lp = (LayoutParams) smartReplyButton.getLayoutParams();
@@ -474,6 +540,7 @@ public class SmartReplyView extends ViewGroup {
final boolean moveLeft = initialLeftTextWidth > initialRightTextWidth;
final int maxSqueezeRemeasureAttempts = mMaxSqueezeRemeasureAttempts;
for (int i = 0; i < maxSqueezeRemeasureAttempts; i++) {
+ mTotalSqueezeRemeasureAttempts++;
final int newPosition =
moveLeft ? mBreakIterator.previous() : mBreakIterator.next();
if (newPosition == BreakIterator.DONE) {
@@ -613,7 +680,17 @@ public class SmartReplyView extends ViewGroup {
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- return lp.show && super.drawChild(canvas, child, drawingTime);
+ if (!lp.show) {
+ return false;
+ }
+ mLastDrawChildTime = SystemClock.elapsedRealtime();
+ return super.drawChild(canvas, child, drawingTime);
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+ mLastDispatchDrawTime = SystemClock.elapsedRealtime();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index b630689567ce..fd387ae0a82e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -55,6 +55,8 @@ import android.view.ViewGroup;
import android.view.WindowManagerGlobal;
import android.widget.BaseAdapter;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEventLogger;
@@ -77,6 +79,7 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.qs.QSUserSwitcherEvent;
import com.android.systemui.qs.tiles.UserDetailView;
+import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -460,7 +463,7 @@ public class UserSwitcherController implements Dumpable {
}
@VisibleForTesting
- void onUserListItemClicked(UserRecord record) {
+ void onUserListItemClicked(UserRecord record, DialogShower dialogShower) {
int id;
if (record.isGuest && record.info == null) {
// No guest user. Create one.
@@ -472,7 +475,7 @@ public class UserSwitcherController implements Dumpable {
mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_ADD);
id = guestId;
} else if (record.isAddUser) {
- showAddUserDialog();
+ showAddUserDialog(dialogShower);
return;
} else {
id = record.info.id;
@@ -481,7 +484,7 @@ public class UserSwitcherController implements Dumpable {
int currUserId = mUserTracker.getUserId();
if (currUserId == id) {
if (record.isGuest) {
- showExitGuestDialog(id);
+ showExitGuestDialog(id, dialogShower);
}
return;
}
@@ -490,11 +493,15 @@ public class UserSwitcherController implements Dumpable {
// If switching from guest, we want to bring up the guest exit dialog instead of switching
UserInfo currUserInfo = mUserManager.getUserInfo(currUserId);
if (currUserInfo != null && currUserInfo.isGuest()) {
- showExitGuestDialog(currUserId, record.resolveId());
+ showExitGuestDialog(currUserId, record.resolveId(), dialogShower);
return;
}
}
-
+ if (dialogShower != null) {
+ // If we haven't morphed into another dialog, it means we have just switched users.
+ // Then, dismiss the dialog.
+ dialogShower.dismiss();
+ }
switchToUserId(id);
}
@@ -511,7 +518,7 @@ public class UserSwitcherController implements Dumpable {
}
}
- protected void showExitGuestDialog(int id) {
+ private void showExitGuestDialog(int id, DialogShower dialogShower) {
int newId = UserHandle.USER_SYSTEM;
if (mResumeUserOnGuestLogout && mLastNonGuestUser != UserHandle.USER_SYSTEM) {
UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser);
@@ -519,23 +526,31 @@ public class UserSwitcherController implements Dumpable {
newId = info.id;
}
}
- showExitGuestDialog(id, newId);
+ showExitGuestDialog(id, newId, dialogShower);
}
- protected void showExitGuestDialog(int id, int targetId) {
+ private void showExitGuestDialog(int id, int targetId, DialogShower dialogShower) {
if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
mExitGuestDialog.cancel();
}
mExitGuestDialog = new ExitGuestDialog(mContext, id, targetId);
- mExitGuestDialog.show();
+ if (dialogShower != null) {
+ dialogShower.showDialog(mExitGuestDialog);
+ } else {
+ mExitGuestDialog.show();
+ }
}
- public void showAddUserDialog() {
+ private void showAddUserDialog(DialogShower dialogShower) {
if (mAddUserDialog != null && mAddUserDialog.isShowing()) {
mAddUserDialog.cancel();
}
mAddUserDialog = new AddUserDialog(mContext);
- mAddUserDialog.show();
+ if (dialogShower != null) {
+ dialogShower.showDialog(mAddUserDialog);
+ } else {
+ mAddUserDialog.show();
+ }
}
private void listenForCallState() {
@@ -868,9 +883,17 @@ public class UserSwitcherController implements Dumpable {
/**
* It handles click events on user list items.
+ *
+ * If the user switcher is hosted in a dialog, passing a non-null {@link DialogShower}
+ * will allow animation to and from the parent dialog.
+ *
*/
+ public void onUserListItemClicked(UserRecord record, @Nullable DialogShower dialogShower) {
+ mController.onUserListItemClicked(record, dialogShower);
+ }
+
public void onUserListItemClicked(UserRecord record) {
- mController.onUserListItemClicked(record);
+ onUserListItemClicked(record, null);
}
public String getName(Context context, UserRecord item) {
@@ -1156,7 +1179,7 @@ public class UserSwitcherController implements Dumpable {
cancel();
} else {
mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE);
- dismiss();
+ dismissStack();
removeGuestUser(mGuestId, mTargetId);
}
}
@@ -1187,7 +1210,7 @@ public class UserSwitcherController implements Dumpable {
if (which == BUTTON_NEGATIVE) {
cancel();
} else {
- dismiss();
+ dismissStack();
if (ActivityManager.isUserAMonkey()) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 426bc91a606d..592fa152b406 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -176,6 +176,34 @@ public class ThemeOverlayController extends SystemUI implements Dumpable {
? WallpaperManager.FLAG_LOCK : WallpaperManager.FLAG_SYSTEM;
}
+ private boolean isSeedColorSet(JSONObject jsonObject, WallpaperColors newWallpaperColors) {
+ if (newWallpaperColors == null) {
+ return false;
+ }
+ // Gets the color that was overridden in the theme setting if any.
+ String sysPaletteColor = (String) jsonObject.opt(OVERLAY_CATEGORY_SYSTEM_PALETTE);
+ if (sysPaletteColor == null) {
+ return false;
+ }
+ if (!sysPaletteColor.startsWith("#")) {
+ sysPaletteColor = "#" + sysPaletteColor;
+ }
+ final int systemPaletteColorArgb = Color.parseColor(sysPaletteColor);
+ // Gets seed colors from incoming {@link WallpaperColors} instance.
+ List<Integer> seedColors = ColorScheme.getSeedColors(newWallpaperColors);
+ for (int seedColor : seedColors) {
+ // The seed color from incoming {@link WallpaperColors} instance
+ // was set as color override.
+ if (seedColor == systemPaletteColorArgb) {
+ if (DEBUG) {
+ Log.d(TAG, "Same as previous set system palette: " + sysPaletteColor);
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
private void handleWallpaperColors(WallpaperColors wallpaperColors, int flags) {
final boolean hadWallpaperColors = mCurrentColors != null;
int latestWallpaperType = getLatestWallpaperType();
@@ -213,8 +241,11 @@ public class ThemeOverlayController extends SystemUI implements Dumpable {
try {
JSONObject jsonObject = (overlayPackageJson == null) ? new JSONObject()
: new JSONObject(overlayPackageJson);
+ // The latest applied wallpaper should be the source of system colors when:
+ // There is not preset color applied and the incoming wallpaper color is not applied
if (!COLOR_SOURCE_PRESET.equals(jsonObject.optString(OVERLAY_COLOR_SOURCE))
- && ((flags & latestWallpaperType) != 0)) {
+ && ((flags & latestWallpaperType) != 0 && !isSeedColorSet(jsonObject,
+ wallpaperColors))) {
mSkipSettingChange = true;
if (jsonObject.has(OVERLAY_CATEGORY_ACCENT_COLOR) || jsonObject.has(
OVERLAY_CATEGORY_SYSTEM_PALETTE)) {
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
index cebc93182f1e..cd3e2d335f0b 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
@@ -100,11 +100,11 @@ class UnfoldTransitionModule {
fun provideShellProgressProvider(
config: UnfoldTransitionConfig,
provider: Optional<UnfoldTransitionProgressProvider>
- ): Optional<ShellUnfoldProgressProvider> =
+ ): ShellUnfoldProgressProvider =
if (config.isEnabled && provider.isPresent()) {
- Optional.of(UnfoldProgressProvider(provider.get()))
+ UnfoldProgressProvider(provider.get())
} else {
- Optional.empty()
+ ShellUnfoldProgressProvider.NO_PROVIDER
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 74611cce6f87..c3f9c9f7aacd 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -65,6 +65,7 @@ import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
import com.android.wm.shell.onehanded.OneHandedUiEventLogger;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.protolog.ShellProtoLogImpl;
+import com.android.wm.shell.sizecompatui.SizeCompatUI;
import com.android.wm.shell.splitscreen.SplitScreen;
import java.io.FileDescriptor;
@@ -112,6 +113,7 @@ public final class WMShell extends SystemUI
private final Optional<OneHanded> mOneHandedOptional;
private final Optional<HideDisplayCutout> mHideDisplayCutoutOptional;
private final Optional<ShellCommandHandler> mShellCommandHandler;
+ private final Optional<SizeCompatUI> mSizeCompatUIOptional;
private final CommandQueue mCommandQueue;
private final ConfigurationController mConfigurationController;
@@ -128,6 +130,7 @@ public final class WMShell extends SystemUI
private KeyguardUpdateMonitorCallback mSplitScreenKeyguardCallback;
private KeyguardUpdateMonitorCallback mPipKeyguardCallback;
private KeyguardUpdateMonitorCallback mOneHandedKeyguardCallback;
+ private KeyguardUpdateMonitorCallback mSizeCompatUIKeyguardCallback;
private WakefulnessLifecycle.Observer mWakefulnessObserver;
@Inject
@@ -138,6 +141,7 @@ public final class WMShell extends SystemUI
Optional<OneHanded> oneHandedOptional,
Optional<HideDisplayCutout> hideDisplayCutoutOptional,
Optional<ShellCommandHandler> shellCommandHandler,
+ Optional<SizeCompatUI> sizeCompatUIOptional,
CommandQueue commandQueue,
ConfigurationController configurationController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -162,6 +166,7 @@ public final class WMShell extends SystemUI
mWakefulnessLifecycle = wakefulnessLifecycle;
mProtoTracer = protoTracer;
mShellCommandHandler = shellCommandHandler;
+ mSizeCompatUIOptional = sizeCompatUIOptional;
mSysUiMainExecutor = sysUiMainExecutor;
}
@@ -176,6 +181,7 @@ public final class WMShell extends SystemUI
mSplitScreenOptional.ifPresent(this::initSplitScreen);
mOneHandedOptional.ifPresent(this::initOneHanded);
mHideDisplayCutoutOptional.ifPresent(this::initHideDisplayCutout);
+ mSizeCompatUIOptional.ifPresent(this::initSizeCompatUi);
}
@VisibleForTesting
@@ -367,6 +373,17 @@ public final class WMShell extends SystemUI
});
}
+ @VisibleForTesting
+ void initSizeCompatUi(SizeCompatUI sizeCompatUI) {
+ mSizeCompatUIKeyguardCallback = new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onKeyguardOccludedChanged(boolean occluded) {
+ sizeCompatUI.onKeyguardOccludedChanged(occluded);
+ }
+ };
+ mKeyguardUpdateMonitor.registerCallback(mSizeCompatUIKeyguardCallback);
+ }
+
@Override
public void writeToProto(SystemUiTraceProto proto) {
if (proto.wmShell == null) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 5e0f427800fc..e967033b69a2 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -18,14 +18,19 @@ package com.android.keyguard;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.view.View;
import android.widget.FrameLayout;
@@ -50,6 +55,9 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
@@ -104,11 +112,14 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
private AnimatableClockView mLargeClockView;
@Mock
private FrameLayout mLargeClockFrame;
+ @Mock
+ private SecureSettings mSecureSettings;
private final View mFakeSmartspaceView = new View(mContext);
private KeyguardClockSwitchController mController;
private View mSliceView;
+ private FakeExecutor mExecutor;
@Before
public void setup() {
@@ -129,6 +140,7 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
when(mView.isAttachedToWindow()).thenReturn(true);
when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
+ mExecutor = new FakeExecutor(new FakeSystemClock());
mController = new KeyguardClockSwitchController(
mView,
mStatusBarStateController,
@@ -143,6 +155,8 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
mSmartspaceController,
mKeyguardUnlockAnimationController,
mSmartSpaceTransitionController,
+ mSecureSettings,
+ mExecutor,
mResources
);
@@ -194,7 +208,6 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
verifyAttachment(times(1));
listenerArgumentCaptor.getValue().onViewDetachedFromWindow(mView);
- verify(mView).onViewDetached();
verify(mColorExtractor).removeOnColorsChangedListener(
any(ColorExtractor.OnColorsChangedListener.class));
}
@@ -235,6 +248,25 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
verify(mSmartspaceController).requestSmartspaceUpdate();
}
+ @Test
+ public void testChangeToDoubleLineClockSetsSmallClock() {
+ when(mSecureSettings.getInt(Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1))
+ .thenReturn(0);
+ ArgumentCaptor<ContentObserver> observerCaptor =
+ ArgumentCaptor.forClass(ContentObserver.class);
+ mController.init();
+ verify(mSecureSettings).registerContentObserver(any(Uri.class),
+ anyBoolean(), observerCaptor.capture());
+ ContentObserver observer = observerCaptor.getValue();
+ mExecutor.runAllReady();
+
+ // When a settings change has occurred to the small clock, make sure the view is adjusted
+ reset(mView);
+ observer.onChange(true);
+ mExecutor.runAllReady();
+ verify(mView).switchToClock(KeyguardClockSwitch.SMALL);
+ }
+
private void verifyAttachment(VerificationMode times) {
verify(mClockManager, times).addOnClockChangedListener(
any(ClockManager.ClockChangedListener.class));
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
index db87c5df16e1..4bdab7658a06 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
@@ -75,8 +75,7 @@ private fun fingerprintModel(user: Int) = KeyguardFingerprintListenModel(
shouldListenForFingerprintAssistant = false,
switchingUser = false,
udfps = false,
- userDoesNotHaveTrust = false,
- userNeedsStrongAuth = false
+ userDoesNotHaveTrust = false
)
private fun faceModel(user: Int) = KeyguardFaceListenModel(
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index ff5960bc33ce..de8cc8992da0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -19,6 +19,7 @@ package com.android.keyguard;
import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
import static com.google.common.truth.Truth.assertThat;
@@ -961,6 +962,19 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
}
@Test
+ public void testStartUdfpsServiceStrongAuthRequiredAfterTimeout() {
+ // GIVEN status bar state is on the keyguard
+ mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD);
+
+ // WHEN user loses smart unlock trust
+ when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
+ .thenReturn(SOME_AUTH_REQUIRED_AFTER_USER_REQUEST);
+
+ // THEN we should still listen for udfps
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(true);
+ }
+
+ @Test
public void testShouldNotListenForUdfps_whenTrustEnabled() {
// GIVEN a "we should listen for udfps" state
mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
index d4c3840356d1..9bd33eb8db6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
@@ -16,6 +16,7 @@ import com.android.systemui.animation.DialogListener.DismissReason
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
+import org.junit.After
import org.junit.Test
import org.junit.runner.RunWith
@@ -28,24 +29,22 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
private val dialogLaunchAnimator =
DialogLaunchAnimator(context, launchAnimator, hostDialogprovider)
+ private val attachedViews = mutableSetOf<View>()
+
+ @After
+ fun tearDown() {
+ runOnMainThreadAndWaitForIdleSync {
+ attachedViews.forEach {
+ ViewUtils.detachView(it)
+ }
+ }
+ }
+
@Test
fun testShowDialogFromView() {
// Show the dialog. showFromView() must be called on the main thread with a dialog created
// on the main thread too.
- val (dialog, hostDialog) = runOnMainThreadAndWaitForIdleSync {
- val touchSurfaceRoot = LinearLayout(context)
- val touchSurface = View(context)
- touchSurfaceRoot.addView(touchSurface)
-
- // We need to attach the root to the window manager otherwise the exit animation will
- // be skipped
- ViewUtils.attachView(touchSurfaceRoot)
-
- val dialog = TestDialog(context)
- val hostDialog =
- dialogLaunchAnimator.showFromView(dialog, touchSurface) as TestHostDialog
- dialog to hostDialog
- }
+ val (dialog, hostDialog) = createDialogAndHostDialog()
// Only the host dialog is actually showing.
assertTrue(hostDialog.isShowing)
@@ -100,6 +99,51 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
assertTrue(dialog.onStopCalled)
}
+ @Test
+ fun testStackedDialogsDismissesAll() {
+ val (_, hostDialogFirst) = createDialogAndHostDialog()
+ val (dialogSecond, hostDialogSecond) = createDialogAndHostDialogFromDialog(hostDialogFirst)
+
+ runOnMainThreadAndWaitForIdleSync {
+ dialogLaunchAnimator.disableAllCurrentDialogsExitAnimations()
+ dialogSecond.dismissStack()
+ }
+
+ assertTrue(hostDialogSecond.wasDismissed)
+ assertTrue(hostDialogFirst.wasDismissed)
+ }
+
+ private fun createDialogAndHostDialog(): Pair<TestDialog, TestHostDialog> {
+ return runOnMainThreadAndWaitForIdleSync {
+ val touchSurfaceRoot = LinearLayout(context)
+ val touchSurface = View(context)
+ touchSurfaceRoot.addView(touchSurface)
+
+ // We need to attach the root to the window manager otherwise the exit animation will
+ // be skipped
+ ViewUtils.attachView(touchSurfaceRoot)
+ attachedViews.add(touchSurfaceRoot)
+
+ val dialog = TestDialog(context)
+ val hostDialog =
+ dialogLaunchAnimator.showFromView(dialog, touchSurface) as TestHostDialog
+ dialog to hostDialog
+ }
+ }
+
+ private fun createDialogAndHostDialogFromDialog(
+ hostParent: Dialog
+ ): Pair<TestDialog, TestHostDialog> {
+ return runOnMainThreadAndWaitForIdleSync {
+ val dialog = TestDialog(context)
+ val hostDialog = dialogLaunchAnimator.showFromDialog(
+ dialog,
+ hostParent
+ ) as TestHostDialog
+ dialog to hostDialog
+ }
+ }
+
private fun <T : Any> runOnMainThreadAndWaitForIdleSync(f: () -> T): T {
lateinit var result: T
context.mainExecutor.execute {
@@ -198,6 +242,11 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
notifyListeners { onShow() }
}
+ fun dismissStack() {
+ notifyListeners { prepareForStackDismiss() }
+ dismiss()
+ }
+
private fun notifyListeners(notify: DialogListener.() -> Unit) {
for (listener in HashSet(listeners)) {
listener.notify()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index f525fee27e20..4ccb92609c77 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -17,6 +17,7 @@
package com.android.systemui.doze;
import static com.android.systemui.doze.DozeLog.REASON_SENSOR_TAP;
+import static com.android.systemui.doze.DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS;
import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_LOCK_SCREEN;
import static org.junit.Assert.assertEquals;
@@ -37,6 +38,7 @@ import static org.mockito.Mockito.when;
import android.database.ContentObserver;
import android.hardware.Sensor;
import android.hardware.display.AmbientDisplayConfiguration;
+import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -57,6 +59,8 @@ import com.android.systemui.util.wakelock.WakeLock;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -94,6 +98,13 @@ public class DozeSensorsTest extends SysuiTestCase {
private DevicePostureController mDevicePostureController;
@Mock
private ProximitySensor mProximitySensor;
+
+ // Capture listeners so that they can be used to send events
+ @Captor
+ private ArgumentCaptor<AuthController.Callback> mAuthControllerCallbackCaptor =
+ ArgumentCaptor.forClass(AuthController.Callback.class);
+ private AuthController.Callback mAuthControllerCallback;
+
private FakeSettings mFakeSettings = new FakeSettings();
private SensorManagerPlugin.SensorEventListener mWakeLockScreenListener;
private TestableLooper mTestableLooper;
@@ -105,14 +116,18 @@ public class DozeSensorsTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this);
mTestableLooper = TestableLooper.get(this);
when(mAmbientDisplayConfiguration.tapSensorTypeMapping())
- .thenReturn(new String[]{"tapSEnsor"});
+ .thenReturn(new String[]{"tapSensor"});
when(mAmbientDisplayConfiguration.getWakeLockScreenDebounce()).thenReturn(5000L);
when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
+ when(mAmbientDisplayConfiguration.enabled(UserHandle.USER_CURRENT)).thenReturn(true);
doAnswer(invocation -> {
((Runnable) invocation.getArgument(0)).run();
return null;
}).when(mWakeLock).wrap(any(Runnable.class));
mDozeSensors = new TestableDozeSensors();
+
+ verify(mAuthController).addCallback(mAuthControllerCallbackCaptor.capture());
+ mAuthControllerCallback = mAuthControllerCallbackCaptor.getValue();
}
@Test
@@ -375,6 +390,35 @@ public class DozeSensorsTest extends SysuiTestCase {
"some other name"));
}
+ @Test
+ public void testUdfpsEnrollmentChanged() throws Exception {
+ // GIVEN a UDFPS_LONG_PRESS trigger sensor that's not configured
+ Sensor mockSensor = mock(Sensor.class);
+ TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+ mockSensor,
+ REASON_SENSOR_UDFPS_LONG_PRESS,
+ /* configured */ false);
+ mDozeSensors.addSensor(triggerSensor);
+ when(mSensorManager.requestTriggerSensor(eq(triggerSensor), eq(mockSensor)))
+ .thenReturn(true);
+
+ // WHEN listening state is set to TRUE
+ mDozeSensors.setListening(true, true);
+
+ // THEN mRegistered is still false b/c !mConfigured
+ assertFalse(triggerSensor.mConfigured);
+ assertFalse(triggerSensor.mRegistered);
+
+ // WHEN enrollment changes to TRUE
+ when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true);
+ mAuthControllerCallback.onEnrollmentsChanged();
+
+ // THEN mConfigured = TRUE
+ assertTrue(triggerSensor.mConfigured);
+
+ // THEN mRegistered = TRUE
+ assertTrue(triggerSensor.mRegistered);
+ }
private class TestableDozeSensors extends DozeSensors {
TestableDozeSensors() {
@@ -407,6 +451,22 @@ public class DozeSensorsTest extends SysuiTestCase {
requiresTouchScreen);
}
+ public TriggerSensor createDozeSensor(
+ Sensor sensor,
+ int pulseReason,
+ boolean configured
+ ) {
+ return new TriggerSensor(/* sensor */ sensor,
+ /* setting name */ "test_setting",
+ /* settingDefault */ true,
+ /* configured */ configured,
+ /* pulseReason*/ pulseReason,
+ /* reportsTouchCoordinate*/ false,
+ /* requiresTouchscreen */ false,
+ /* ignoresSetting */ false,
+ /* requiresTouchScreen */false);
+ }
+
/**
* create a doze sensor that supports postures and is enabled
*/
@@ -422,6 +482,15 @@ public class DozeSensorsTest extends SysuiTestCase {
/* requiresProx */false,
posture);
}
+
+ public void addSensor(TriggerSensor sensor) {
+ TriggerSensor[] newArray = new TriggerSensor[mTriggerSensors.length + 1];
+ for (int i = 0; i < mTriggerSensors.length; i++) {
+ newArray[i] = mTriggerSensors[i];
+ }
+ newArray[mTriggerSensors.length] = sensor;
+ mTriggerSensors = newArray;
+ }
}
public static void setSensorType(Sensor sensor, int type, String strType) throws Exception {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
index b6e8979db189..b32b4d4f3810 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
@@ -219,61 +219,89 @@ public class InternetDialogTest extends SysuiTestCase {
}
@Test
- public void updateDialog_wifiOnAndNoWifiList_hideWifiListAndSeeAll() {
+ public void updateDialog_wifiOnAndNoWifiEntry_hideWifiEntryAndSeeAll() {
// The precondition WiFi ON is already in setUp()
+ mInternetDialog.mConnectedWifiEntry = null;
mInternetDialog.mWifiEntriesCount = 0;
mInternetDialog.updateDialog(false);
+ assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE);
assertThat(mSeeAll.getVisibility()).isEqualTo(View.GONE);
}
@Test
+ public void updateDialog_wifiOnAndHasConnectedWifi_showConnectedWifiAndSeeAll() {
+ // The preconditions WiFi ON and WiFi entries are already in setUp()
+ mInternetDialog.mWifiEntriesCount = 0;
+
+ mInternetDialog.updateDialog(false);
+
+ assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mSeeAll.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
public void updateDialog_wifiOnAndHasWifiList_showWifiListAndSeeAll() {
// The preconditions WiFi ON and WiFi entries are already in setUp()
+ mInternetDialog.mConnectedWifiEntry = null;
mInternetDialog.updateDialog(false);
+ assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mSeeAll.getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
- public void updateDialog_deviceLockedAndHasInternetWifi_showHighlightWifiToggle() {
- // The preconditions WiFi ON and Internet WiFi are already in setUp()
- when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
+ public void updateDialog_wifiOnAndHasBothWifiEntry_showBothWifiEntryAndSeeAll() {
+ // The preconditions WiFi ON and WiFi entries are already in setUp()
mInternetDialog.updateDialog(false);
- assertThat(mWifiToggle.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(mWifiToggle.getBackground()).isNotNull();
+ assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mSeeAll.getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
- public void updateDialog_deviceLockedAndHasInternetWifi_hideConnectedWifi() {
- // The preconditions WiFi ON and Internet WiFi are already in setUp()
+ public void updateDialog_deviceLockedAndNoConnectedWifi_showWifiToggle() {
+ // The preconditions WiFi entries are already in setUp()
when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
+ mInternetDialog.mConnectedWifiEntry = null;
mInternetDialog.updateDialog(false);
+ // Show WiFi Toggle without background
+ assertThat(mWifiToggle.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mWifiToggle.getBackground()).isNull();
+ // Hide Wi-Fi networks and See all
assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mSeeAll.getVisibility()).isEqualTo(View.GONE);
}
@Test
- public void updateDialog_deviceLockedAndHasWifiList_hideWifiListAndSeeAll() {
- // The preconditions WiFi entries are already in setUp()
+ public void updateDialog_deviceLockedAndHasConnectedWifi_showWifiToggleWithBackground() {
+ // The preconditions WiFi ON and WiFi entries are already in setUp()
when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
mInternetDialog.updateDialog(false);
+ // Show WiFi Toggle with highlight background
+ assertThat(mWifiToggle.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mWifiToggle.getBackground()).isNotNull();
+ // Hide Wi-Fi networks and See all
+ assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE);
assertThat(mSeeAll.getVisibility()).isEqualTo(View.GONE);
}
@Test
public void updateDialog_wifiOn_hideWifiScanNotify() {
- // The preconditions WiFi ON and Internet WiFi are already in setUp()
+ // The preconditions WiFi ON and WiFi entries are already in setUp()
mInternetDialog.updateDialog(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
index 7e900c843cc3..ea3a42ce501c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.user
+import android.app.Dialog
import android.content.Intent
import android.provider.Settings
import android.testing.AndroidTestingRunner
@@ -27,7 +28,7 @@ import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.PseudoGridView
import com.android.systemui.qs.tiles.UserDetailView
-import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
@@ -39,15 +40,13 @@ import org.mockito.ArgumentMatcher
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.`when`
-import org.mockito.Mockito.any
+import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.argThat
import org.mockito.Mockito.inOrder
-import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-import java.util.function.Consumer
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -71,6 +70,8 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
private lateinit var gridView: PseudoGridView
@Mock
private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+ @Mock
+ private lateinit var hostDialog: Dialog
@Captor
private lateinit var clickCaptor: ArgumentCaptor<View.OnClickListener>
@@ -85,6 +86,8 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
`when`(dialog.grid).thenReturn(gridView)
`when`(launchView.context).thenReturn(mContext)
+ `when`(dialogLaunchAnimator.showFromView(any(), any(), anyBoolean()))
+ .thenReturn(hostDialog)
controller = UserSwitchDialogController(
{ userDetailViewAdapter },
@@ -188,15 +191,15 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
}
@Test
- fun callbackFromDetailView_dismissesDialog() {
- val captor = argumentCaptor<Consumer<UserSwitcherController.UserRecord>>()
+ fun callbackFromDialogShower_dismissesDialog() {
+ val captor = argumentCaptor<UserSwitchDialogController.DialogShower>()
controller.showDialog(launchView)
- verify(userDetailViewAdapter).injectCallback(capture(captor))
+ verify(userDetailViewAdapter).injectDialogShower(capture(captor))
- captor.value.accept(mock(UserSwitcherController.UserRecord::class.java))
+ captor.value.dismiss()
- verify(dialog).dismiss()
+ verify(hostDialog).dismiss()
}
private class IntentMatcher(private val action: String) : ArgumentMatcher<Intent> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
index 69ab9c51db81..bdd189a9215b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
@@ -44,13 +44,16 @@ import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.QSUserSwitcherEvent
+import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.phone.NotificationShadeWindowView
import com.android.systemui.telephony.TelephonyListenerManager
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.time.FakeSystemClock
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertFalse
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -85,6 +88,8 @@ class UserSwitcherControllerTest : SysuiTestCase() {
@Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var interactionJankMonitor: InteractionJankMonitor
@Mock private lateinit var latencyTracker: LatencyTracker
+ @Mock private lateinit var dialogShower: UserSwitchDialogController.DialogShower
+ @Mock private lateinit var notificationShadeWindowView: NotificationShadeWindowView
private lateinit var testableLooper: TestableLooper
private lateinit var uiBgExecutor: FakeExecutor
private lateinit var uiEventLogger: UiEventLoggerFake
@@ -98,6 +103,8 @@ class UserSwitcherControllerTest : SysuiTestCase() {
private val guestId = 1234
private val guestInfo = UserInfo(guestId, "Guest", null,
UserInfo.FLAG_FULL or UserInfo.FLAG_GUEST, UserManager.USER_TYPE_FULL_GUEST)
+ private val secondaryUser =
+ UserInfo(10, "Secondary", null, 0, UserManager.USER_TYPE_FULL_SECONDARY)
@Before
fun setUp() {
@@ -114,6 +121,7 @@ class UserSwitcherControllerTest : SysuiTestCase() {
mock(FingerprintManager::class.java))
`when`(userManager.canAddMoreUsers()).thenReturn(true)
+ `when`(notificationShadeWindowView.context).thenReturn(context)
userSwitcherController = UserSwitcherController(
context,
@@ -139,6 +147,26 @@ class UserSwitcherControllerTest : SysuiTestCase() {
userSwitcherController.mPauseRefreshUsers = true
picture = UserIcons.convertToBitmap(context.getDrawable(R.drawable.ic_avatar_user))
+ userSwitcherController.init(notificationShadeWindowView)
+ }
+
+ @Test
+ fun testSwitchUser_parentDialogDismissed() {
+ val otherUserRecord = UserSwitcherController.UserRecord(
+ secondaryUser,
+ picture,
+ false /* guest */,
+ false /* current */,
+ false /* isAddUser */,
+ false /* isRestricted */,
+ true /* isSwitchToEnabled */)
+ `when`(userTracker.userId).thenReturn(ownerId)
+ `when`(userTracker.userInfo).thenReturn(ownerInfo)
+
+ userSwitcherController.onUserListItemClicked(otherUserRecord, dialogShower)
+ testableLooper.processAllMessages()
+
+ verify(dialogShower).dismiss()
}
@Test
@@ -156,7 +184,7 @@ class UserSwitcherControllerTest : SysuiTestCase() {
`when`(userManager.createGuest(any(), anyString())).thenReturn(guestInfo)
- userSwitcherController.onUserListItemClicked(emptyGuestUserRecord)
+ userSwitcherController.onUserListItemClicked(emptyGuestUserRecord, null)
testableLooper.processAllMessages()
verify(interactionJankMonitor).begin(any())
verify(latencyTracker).onActionStart(LatencyTracker.ACTION_USER_SWITCH)
@@ -166,6 +194,26 @@ class UserSwitcherControllerTest : SysuiTestCase() {
}
@Test
+ fun testAddGuest_parentDialogDismissed() {
+ val emptyGuestUserRecord = UserSwitcherController.UserRecord(
+ null,
+ null,
+ true /* guest */,
+ false /* current */,
+ false /* isAddUser */,
+ false /* isRestricted */,
+ true /* isSwitchToEnabled */)
+ `when`(userTracker.userId).thenReturn(ownerId)
+ `when`(userTracker.userInfo).thenReturn(ownerInfo)
+
+ `when`(userManager.createGuest(any(), anyString())).thenReturn(guestInfo)
+
+ userSwitcherController.onUserListItemClicked(emptyGuestUserRecord, dialogShower)
+ testableLooper.processAllMessages()
+ verify(dialogShower).dismiss()
+ }
+
+ @Test
fun testRemoveGuest_removeButtonPressed_isLogged() {
val currentGuestUserRecord = UserSwitcherController.UserRecord(
guestInfo,
@@ -178,7 +226,7 @@ class UserSwitcherControllerTest : SysuiTestCase() {
`when`(userTracker.userId).thenReturn(guestInfo.id)
`when`(userTracker.userInfo).thenReturn(guestInfo)
- userSwitcherController.onUserListItemClicked(currentGuestUserRecord)
+ userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
assertNotNull(userSwitcherController.mExitGuestDialog)
userSwitcherController.mExitGuestDialog
.getButton(DialogInterface.BUTTON_POSITIVE).performClick()
@@ -188,6 +236,46 @@ class UserSwitcherControllerTest : SysuiTestCase() {
}
@Test
+ fun testRemoveGuest_removeButtonPressed_dialogDismissed() {
+ val currentGuestUserRecord = UserSwitcherController.UserRecord(
+ guestInfo,
+ picture,
+ true /* guest */,
+ true /* current */,
+ false /* isAddUser */,
+ false /* isRestricted */,
+ true /* isSwitchToEnabled */)
+ `when`(userTracker.userId).thenReturn(guestInfo.id)
+ `when`(userTracker.userInfo).thenReturn(guestInfo)
+
+ userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
+ assertNotNull(userSwitcherController.mExitGuestDialog)
+ userSwitcherController.mExitGuestDialog
+ .getButton(DialogInterface.BUTTON_POSITIVE).performClick()
+ testableLooper.processAllMessages()
+ assertFalse(userSwitcherController.mExitGuestDialog.isShowing)
+ }
+
+ @Test
+ fun testRemoveGuest_dialogShowerUsed() {
+ val currentGuestUserRecord = UserSwitcherController.UserRecord(
+ guestInfo,
+ picture,
+ true /* guest */,
+ true /* current */,
+ false /* isAddUser */,
+ false /* isRestricted */,
+ true /* isSwitchToEnabled */)
+ `when`(userTracker.userId).thenReturn(guestInfo.id)
+ `when`(userTracker.userInfo).thenReturn(guestInfo)
+
+ userSwitcherController.onUserListItemClicked(currentGuestUserRecord, dialogShower)
+ assertNotNull(userSwitcherController.mExitGuestDialog)
+ testableLooper.processAllMessages()
+ verify(dialogShower).showDialog(userSwitcherController.mExitGuestDialog)
+ }
+
+ @Test
fun testRemoveGuest_cancelButtonPressed_isNotLogged() {
val currentGuestUserRecord = UserSwitcherController.UserRecord(
guestInfo,
@@ -200,7 +288,7 @@ class UserSwitcherControllerTest : SysuiTestCase() {
`when`(userTracker.userId).thenReturn(guestId)
`when`(userTracker.userInfo).thenReturn(guestInfo)
- userSwitcherController.onUserListItemClicked(currentGuestUserRecord)
+ userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
assertNotNull(userSwitcherController.mExitGuestDialog)
userSwitcherController.mExitGuestDialog
.getButton(DialogInterface.BUTTON_NEGATIVE).performClick()
@@ -226,7 +314,7 @@ class UserSwitcherControllerTest : SysuiTestCase() {
eq(GuestResumeSessionReceiver.SETTING_GUEST_HAS_LOGGED_IN), anyInt(), anyInt()))
.thenReturn(1)
- userSwitcherController.onUserListItemClicked(currentGuestUserRecord)
+ userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
// Simulate a user switch event
val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, guestId)
@@ -260,7 +348,7 @@ class UserSwitcherControllerTest : SysuiTestCase() {
eq(GuestResumeSessionReceiver.SETTING_GUEST_HAS_LOGGED_IN), anyInt(), anyInt()))
.thenReturn(1)
- userSwitcherController.onUserListItemClicked(currentGuestUserRecord)
+ userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
// Simulate a user switch event
val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, guestId)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 766471b9a695..3ff5666271bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -248,8 +248,9 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
Color.valueOf(Color.BLUE), null);
String jsonString =
- "{\"android.theme.customization.system_palette\":\"override.package.name\","
- + "\"android.theme.customization.color_source\":\"home_wallpaper\","
+ "{\"android.theme.customization.color_source\":\"home_wallpaper\","
+ + "\"android.theme.customization.system_palette\":\"A16B00\","
+ + "\"android.theme.customization.accent_color\":\"A16B00\","
+ "\"android.theme.customization.color_index\":\"2\"}";
when(mSecureSettings.getStringForUser(
@@ -274,14 +275,15 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
}
@Test
- public void onWallpaperColorsChanged_ResetThemeWithDifferentWallpapers() {
+ public void onWallpaperColorsChanged_ResetThemeWithNewHomeWallpapers() {
// Should ask for a new theme when wallpaper colors change
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
String jsonString =
- "{\"android.theme.customization.system_palette\":\"override.package.name\","
- + "\"android.theme.customization.color_source\":\"home_wallpaper\","
+ "{\"android.theme.customization.color_source\":\"home_wallpaper\","
+ + "\"android.theme.customization.system_palette\":\"A16B00\","
+ + "\"android.theme.customization.accent_color\":\"A16B00\","
+ "\"android.theme.customization.color_index\":\"2\"}";
when(mSecureSettings.getStringForUser(
@@ -304,14 +306,15 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
}
@Test
- public void onWallpaperColorsChanged_ResetThemeWithSameWallpaper() {
+ public void onWallpaperColorsChanged_ResetThemeWithNewHomeAndLockWallpaper() {
// Should ask for a new theme when wallpaper colors change
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
String jsonString =
- "{\"android.theme.customization.system_palette\":\"override.package.name\","
- + "\"android.theme.customization.color_source\":\"home_wallpaper\","
+ "{\"android.theme.customization.color_source\":\"home_wallpaper\","
+ + "\"android.theme.customization.system_palette\":\"A16B00\","
+ + "\"android.theme.customization.accent_color\":\"A16B00\","
+ "\"android.theme.customization.color_index\":\"2\"}";
when(mSecureSettings.getStringForUser(
@@ -339,8 +342,9 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
String jsonString =
- "{\"android.theme.customization.system_palette\":\"override.package.name\","
- + "\"android.theme.customization.color_source\":\"home_wallpaper\","
+ "{\"android.theme.customization.color_source\":\"home_wallpaper\","
+ + "\"android.theme.customization.system_palette\":\"A16B00\","
+ + "\"android.theme.customization.accent_color\":\"A16B00\","
+ "\"android.theme.customization.color_index\":\"2\"}";
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
@@ -366,8 +370,9 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
String jsonString =
- "{\"android.theme.customization.system_palette\":\"override.package.name\","
- + "\"android.theme.customization.color_source\":\"lock_wallpaper\","
+ "{\"android.theme.customization.color_source\":\"lock_wallpaper\","
+ + "\"android.theme.customization.system_palette\":\"A16B00\","
+ + "\"android.theme.customization.accent_color\":\"A16B00\","
+ "\"android.theme.customization.color_index\":\"2\"}";
when(mSecureSettings.getStringForUser(
eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
@@ -394,8 +399,9 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
Color.valueOf(Color.BLUE), null);
String jsonString =
- "{\"android.theme.customization.system_palette\":\"override.package.name\","
- + "\"android.theme.customization.color_source\":\"home_wallpaper\","
+ "{\"android.theme.customization.color_source\":\"home_wallpaper\","
+ + "\"android.theme.customization.system_palette\":\"A16B00\","
+ + "\"android.theme.customization.accent_color\":\"A16B00\","
+ "\"android.theme.customization.color_index\":\"2\"}";
when(mSecureSettings.getStringForUser(
@@ -416,6 +422,36 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
}
@Test
+ public void onWallpaperColorsChanged_keepThemeWhenFromLatestWallpaperAndSpecifiedColor() {
+ // Shouldn't ask for a new theme when the colors of the last applied wallpaper change
+ // with the same specified system palette one.
+ WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+ Color.valueOf(0xffa16b00), null);
+
+ String jsonString =
+ "{\"android.theme.customization.color_source\":\"home_wallpaper\","
+ + "\"android.theme.customization.system_palette\":\"A16B00\","
+ + "\"android.theme.customization.accent_color\":\"A16B00\","
+ + "\"android.theme.customization.color_index\":\"2\"}";
+
+ when(mSecureSettings.getStringForUser(
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
+ .thenReturn(jsonString);
+ when(mWallpaperManager.getWallpaperId(WallpaperManager.FLAG_LOCK)).thenReturn(1);
+ // SYSTEM wallpaper is the last applied one
+ when(mWallpaperManager.getWallpaperId(WallpaperManager.FLAG_SYSTEM)).thenReturn(2);
+
+ mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
+
+ ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
+ verify(mSecureSettings, never()).putString(
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture());
+
+ // Apply overlay by existing theme from secure setting
+ verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
+ }
+
+ @Test
public void onWallpaperColorsChanged_keepThemeIfNotLatestWallpaper() {
// Shouldn't ask for a new theme when the colors of the wallpaper that is not the last
// applied one change
@@ -423,8 +459,9 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
Color.valueOf(Color.BLUE), null);
String jsonString =
- "{\"android.theme.customization.system_palette\":\"override.package.name\","
- + "\"android.theme.customization.color_source\":\"home_wallpaper\","
+ "{\"android.theme.customization.color_source\":\"home_wallpaper\","
+ + "\"android.theme.customization.system_palette\":\"A16B00\","
+ + "\"android.theme.customization.accent_color\":\"A16B00\","
+ "\"android.theme.customization.color_index\":\"2\"}";
when(mSecureSettings.getStringForUser(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index 8480702c57c0..ae7afcef57a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -41,6 +41,7 @@ import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedEventCallback;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.sizecompatui.SizeCompatUI;
import com.android.wm.shell.splitscreen.SplitScreen;
import org.junit.Before;
@@ -76,6 +77,7 @@ public class WMShellTest extends SysuiTestCase {
@Mock WakefulnessLifecycle mWakefulnessLifecycle;
@Mock ProtoTracer mProtoTracer;
@Mock ShellCommandHandler mShellCommandHandler;
+ @Mock SizeCompatUI mSizeCompatUI;
@Mock ShellExecutor mSysUiMainExecutor;
@Before
@@ -84,10 +86,10 @@ public class WMShellTest extends SysuiTestCase {
mWMShell = new WMShell(mContext, Optional.of(mPip), Optional.of(mLegacySplitScreen),
Optional.of(mSplitScreen), Optional.of(mOneHanded), Optional.of(mHideDisplayCutout),
- Optional.of(mShellCommandHandler), mCommandQueue, mConfigurationController,
- mKeyguardUpdateMonitor, mNavigationModeController,
- mScreenLifecycle, mSysUiState, mProtoTracer, mWakefulnessLifecycle,
- mSysUiMainExecutor);
+ Optional.of(mShellCommandHandler), Optional.of(mSizeCompatUI),
+ mCommandQueue, mConfigurationController, mKeyguardUpdateMonitor,
+ mNavigationModeController, mScreenLifecycle, mSysUiState, mProtoTracer,
+ mWakefulnessLifecycle, mSysUiMainExecutor);
}
@Test
@@ -129,4 +131,11 @@ public class WMShellTest extends SysuiTestCase {
verify(mConfigurationController).addCallback(
any(ConfigurationController.ConfigurationListener.class));
}
+
+ @Test
+ public void initSizeCompatUI_registersCallbacks() {
+ mWMShell.initSizeCompatUi(mSizeCompatUI);
+
+ verify(mKeyguardUpdateMonitor).registerCallback(any(KeyguardUpdateMonitorCallback.class));
+ }
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index a2fec2753340..df2ae987f8cb 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -87,13 +87,13 @@ import android.app.ActivityManagerInternal.ServiceNotificationPolicy;
import android.app.ActivityThread;
import android.app.AppGlobals;
import android.app.AppOpsManager;
-import android.app.ForegroundServiceDidNotStartInTimeException;
import android.app.ForegroundServiceStartNotAllowedException;
import android.app.IApplicationThread;
import android.app.IServiceConnection;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.app.RemoteServiceException.ForegroundServiceDidNotStartInTimeException;
import android.app.Service;
import android.app.ServiceStartArgs;
import android.app.admin.DevicePolicyEventLogger;
@@ -1243,7 +1243,7 @@ public final class ActiveServices {
}
void killMisbehavingService(ServiceRecord r,
- int appUid, int appPid, String localPackageName) {
+ int appUid, int appPid, String localPackageName, int exceptionTypeId) {
synchronized (mAm) {
if (!r.destroying) {
// This service is still alive, stop it.
@@ -1257,8 +1257,8 @@ public final class ActiveServices {
stopServiceLocked(found, false);
}
}
- mAm.crashApplication(appUid, appPid, localPackageName, -1,
- "Bad notification for startForeground", true /*force*/);
+ mAm.crashApplicationWithType(appUid, appPid, localPackageName, -1,
+ "Bad notification for startForeground", true /*force*/, exceptionTypeId);
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b5f3389c50e0..5cd66aabbf2e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -183,7 +183,6 @@ import android.app.PendingIntent;
import android.app.ProcessMemoryState;
import android.app.ProfilerInfo;
import android.app.PropertyInvalidatedCache;
-import android.app.RemoteServiceException;
import android.app.SyncNotedAppOp;
import android.app.WaitResult;
import android.app.backup.BackupManager.OperationType;
@@ -3000,13 +2999,6 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
- public void crashApplication(int uid, int initialPid, String packageName, int userId,
- String message, boolean force) {
- crashApplicationWithType(uid, initialPid, packageName, userId, message, force,
- RemoteServiceException.TYPE_ID);
- }
-
- @Override
public void crashApplicationWithType(int uid, int initialPid, String packageName, int userId,
String message, boolean force, int exceptionTypeId) {
if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 31e48fb0837f..ea28117a6a3d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -46,6 +46,7 @@ import android.app.IStopUserCallback;
import android.app.IUidObserver;
import android.app.KeyguardManager;
import android.app.ProfilerInfo;
+import android.app.RemoteServiceException.CrashedByAdbException;
import android.app.UserSwitchObserver;
import android.app.WaitResult;
import android.app.usage.AppStandbyInfo;
@@ -1154,7 +1155,8 @@ final class ActivityManagerShellCommand extends ShellCommand {
} catch (NumberFormatException e) {
packageName = arg;
}
- mInterface.crashApplication(-1, pid, packageName, userId, "shell-induced crash", false);
+ mInterface.crashApplicationWithType(-1, pid, packageName, userId, "shell-induced crash",
+ false, CrashedByAdbException.TYPE_ID);
return 0;
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 94bf62f8b9b7..487101b7eac4 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -20,7 +20,13 @@ import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
import static android.text.TextUtils.formatSimple;
-import static com.android.server.am.ActivityManagerDebugConfig.*;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_DEFERRAL;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -29,6 +35,7 @@ import android.app.AppOpsManager;
import android.app.BroadcastOptions;
import android.app.IApplicationThread;
import android.app.PendingIntent;
+import android.app.RemoteServiceException.CannotDeliverBroadcastException;
import android.app.usage.UsageEvents.Event;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -603,7 +610,8 @@ public final class BroadcastQueue {
synchronized (mService) {
Slog.w(TAG, "Can't deliver broadcast to " + app.processName
+ " (pid " + app.getPid() + "). Crashing it.");
- app.scheduleCrashLocked("can't deliver broadcast");
+ app.scheduleCrashLocked("can't deliver broadcast",
+ CannotDeliverBroadcastException.TYPE_ID);
}
throw ex;
}
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 9e94d4aa2c0f..14ba7167f529 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -26,7 +26,6 @@ import android.app.ApplicationExitInfo;
import android.app.ApplicationExitInfo.Reason;
import android.app.ApplicationExitInfo.SubReason;
import android.app.IApplicationThread;
-import android.app.RemoteServiceException;
import android.content.pm.ApplicationInfo;
import android.content.pm.ProcessInfo;
import android.content.pm.VersionedPackage;
@@ -948,11 +947,6 @@ class ProcessRecord implements WindowProcessListener {
return mServices.hasForegroundServices();
}
- @GuardedBy("mService")
- void scheduleCrashLocked(String message) {
- scheduleCrashLocked(message, RemoteServiceException.TYPE_ID);
- }
-
/**
* Let an app process throw an exception on a binder thread, which typically crashes the
* process, unless it has an unhandled exception handler.
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 17930ea9c93c..e36898fee387 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -28,6 +28,7 @@ import android.annotation.Nullable;
import android.app.IApplicationThread;
import android.app.Notification;
import android.app.PendingIntent;
+import android.app.RemoteServiceException.CannotPostForegroundServiceNotificationException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -1039,7 +1040,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
// If it gave us a garbage notification, it doesn't
// get to be foreground.
ams.mServices.killMisbehavingService(record,
- appUid, appPid, localPackageName);
+ appUid, appPid, localPackageName,
+ CannotPostForegroundServiceNotificationException.TYPE_ID);
}
}
});
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index aa4fa7c6f470..4a41cb6d81e2 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -917,7 +917,7 @@ public final class MultiClientInputMethodManagerService {
.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
context, 0,
new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS),
- PendingIntent.FLAG_MUTABLE));
+ PendingIntent.FLAG_IMMUTABLE));
// Note: Instead of re-dispatching callback from the main thread to the worker thread
// where OnWorkerThreadCallback is running, we pass the Handler object here so that
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 47d8022a5acc..7ed897dfdbf9 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -149,6 +149,7 @@ import android.app.NotificationHistory.HistoricalNotification;
import android.app.NotificationManager;
import android.app.NotificationManager.Policy;
import android.app.PendingIntent;
+import android.app.RemoteServiceException.BadForegroundServiceNotificationException;
import android.app.StatsManager;
import android.app.StatusBarManager;
import android.app.UriGrantsManager;
@@ -1256,10 +1257,11 @@ public class NotificationManagerService extends SystemService {
// Still crash for foreground services, preventing the not-crash behaviour abused
// by apps to give us a garbage notification and silently start a fg service.
Binder.withCleanCallingIdentity(
- () -> mAm.crashApplication(uid, initialPid, pkg, -1,
+ () -> mAm.crashApplicationWithType(uid, initialPid, pkg, -1,
"Bad notification(tag=" + tag + ", id=" + id + ") posted from package "
+ pkg + ", crashing app(uid=" + uid + ", pid=" + initialPid + "): "
- + message, true /* force */));
+ + message, true /* force */,
+ BadForegroundServiceNotificationException.TYPE_ID));
}
}
@@ -7065,7 +7067,9 @@ public class NotificationManagerService extends SystemService {
if (index < 0) {
mNotificationList.add(r);
mUsageStats.registerPostedByApp(r);
- r.setInterruptive(isVisuallyInterruptive(null, r));
+ final boolean isInterruptive = isVisuallyInterruptive(null, r);
+ r.setInterruptive(isInterruptive);
+ r.setTextChanged(isInterruptive);
} else {
old = mNotificationList.get(index); // Potentially *changes* old
mNotificationList.set(index, r);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index c493639fc6b1..0ad119df6b55 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -3872,6 +3872,19 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void testTextChangedSet_forNewNotifs() throws Exception {
+ NotificationRecord original = generateNotificationRecord(mTestNotificationChannel);
+ mService.addEnqueuedNotification(original);
+
+ NotificationManagerService.PostNotificationRunnable runnable =
+ mService.new PostNotificationRunnable(original.getKey());
+ runnable.run();
+ waitForIdle();
+
+ assertTrue(original.isTextChanged());
+ }
+
+ @Test
public void testVisuallyInterruptive_notSeen() throws Exception {
NotificationRecord original = generateNotificationRecord(mTestNotificationChannel);
mService.addNotification(original);
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index f05dd636aa22..e19ea47df3bb 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -841,7 +841,7 @@ final class HotwordDetectionConnection {
try {
return mContext.bindIsolatedService(
mIntent,
- Context.BIND_AUTO_CREATE | mBindingFlags,
+ Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE | mBindingFlags,
"hotword_detector_" + mInstanceNumber,
mExecutor,
serviceConnection);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 4d2e00785d49..9c9e41b64892 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -5865,7 +5865,7 @@ public class CarrierConfigManager {
sDefaults.putString(KEY_CARRIER_PROVISIONING_APP_STRING, "");
sDefaults.putBoolean(KEY_DISPLAY_NO_DATA_NOTIFICATION_ON_PERMANENT_FAILURE_BOOL, false);
sDefaults.putBoolean(KEY_UNTHROTTLE_DATA_RETRY_WHEN_TAC_CHANGES_BOOL, false);
- sDefaults.putBoolean(KEY_VONR_SETTING_VISIBILITY_BOOL, false);
+ sDefaults.putBoolean(KEY_VONR_SETTING_VISIBILITY_BOOL, true);
sDefaults.putBoolean(KEY_VONR_ENABLED_BOOL, false);
}