summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/test-current.txt1
-rw-r--r--core/java/android/app/Notification.java1
-rw-r--r--core/java/android/database/sqlite/SQLiteQueryBuilder.java1
-rw-r--r--core/java/android/hardware/fingerprint/FingerprintManager.java81
-rw-r--r--core/java/android/hardware/fingerprint/IFingerprintService.aidl8
-rw-r--r--core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl1
-rw-r--r--core/java/android/provider/CallLog.java11
-rw-r--r--core/java/android/service/notification/NotificationListenerService.java11
-rw-r--r--core/java/android/view/IDisplayWindowInsetsController.aidl7
-rw-r--r--core/java/android/view/IWindowManager.aidl10
-rw-r--r--core/java/android/view/WindowManagerPolicyConstants.java7
-rw-r--r--core/java/android/widget/TextView.java97
-rw-r--r--core/java/com/android/internal/app/ChooserActivity.java10
-rw-r--r--core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java4
-rw-r--r--core/java/com/android/internal/widget/ResolverDrawerLayout.java17
-rw-r--r--core/res/res/layout/notification_material_action_list.xml1
-rw-r--r--core/res/res/values/attrs.xml7
-rw-r--r--core/res/res/values/config.xml15
-rw-r--r--core/res/res/values/public.xml2
-rw-r--r--core/res/res/values/strings.xml2
-rw-r--r--core/res/res/values/symbols.xml2
-rw-r--r--data/etc/privapp-permissions-platform.xml7
-rw-r--r--diff25
-rw-r--r--packages/CarSystemUI/TEST_MAPPING11
-rw-r--r--packages/CarSystemUI/res/layout/headsup_container_bottom.xml7
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java6
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/CarSystemUiTest.java32
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBar.java9
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarView.java22
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/navigationbar/NavigationBarViewFactory.java7
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullScreenUserSwitcherViewController.java36
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java107
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/wm/BarControlPolicy.java250
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java168
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsInsetsControllerHost.java173
-rw-r--r--packages/CarSystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java2
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/car/hvac/HvacControllerTest.java2
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/car/keyguard/CarKeyguardViewControllerTest.java2
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/ButtonRoleHolderControllerTest.java2
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/ButtonSelectionStateControllerTest.java2
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarControllerTest.java2
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarTest.java2
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarViewTest.java2
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationButtonTest.java2
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java2
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/car/notification/NotificationVisibilityLoggerTest.java2
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/SideLoadedAppDetectorTest.java2
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/SideLoadedAppListenerTest.java2
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewControllerTest.java2
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewMediatorTest.java2
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifierTest.java29
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayPanelViewControllerTest.java2
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewControllerTest.java2
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java2
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/wm/BarControlPolicyTest.java195
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/wm/DisplaySystemBarsControllerTest.java111
-rw-r--r--packages/Shell/AndroidManifest.xml5
-rw-r--r--packages/SystemUI/res-keyguard/drawable/kg_emergency_button_background.xml32
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_emergency_carrier_area.xml9
-rw-r--r--packages/SystemUI/res-keyguard/values/styles.xml8
-rw-r--r--packages/SystemUI/res/layout/qs_footer_impl.xml2
-rw-r--r--packages/SystemUI/res/values/ids.xml2
-rw-r--r--packages/SystemUI/res/values/strings.xml21
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java85
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java41
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java43
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java438
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java39
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java72
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java2
-rw-r--r--packages/WAPPushManager/AndroidManifest.xml2
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java7
-rw-r--r--services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java25
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java12
-rw-r--r--services/core/java/com/android/server/biometrics/AuthService.java25
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricServiceBase.java30
-rw-r--r--services/core/java/com/android/server/biometrics/Utils.java64
-rw-r--r--services/core/java/com/android/server/biometrics/face/FaceService.java5
-rw-r--r--services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java101
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerService.java14
-rw-r--r--services/core/java/com/android/server/policy/WindowManagerPolicy.java4
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java13
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java214
-rw-r--r--services/core/java/com/android/server/wm/InsetsPolicy.java58
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java8
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java25
-rw-r--r--services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java59
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java40
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java25
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java71
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java18
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ScreenDecorWindowTests.java8
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java18
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java32
-rwxr-xr-xtelephony/java/android/telephony/CarrierConfigManager.java11
120 files changed, 2821 insertions, 624 deletions
diff --git a/api/test-current.txt b/api/test-current.txt
index 5172e592f750..519bbbb9bc39 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -34,6 +34,7 @@ package android {
public static final class R.bool {
field public static final int config_assistantOnTopOfDream = 17891333; // 0x1110005
field public static final int config_perDisplayFocusEnabled = 17891332; // 0x1110004
+ field public static final int config_remoteInsetsControllerControlsSystemBars = 17891334; // 0x1110006
}
public static final class R.string {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 79d2a8102358..76226888bf1a 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -4801,7 +4801,6 @@ public class Notification implements Parcelable
contentView.setViewVisibility(R.id.time, View.GONE);
contentView.setImageViewIcon(R.id.profile_badge, null);
contentView.setViewVisibility(R.id.profile_badge, View.GONE);
- contentView.setViewVisibility(R.id.alerted_icon, View.GONE);
mN.mUsesStandardHeader = false;
}
diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
index 669d0466fdf2..e9c59f55a418 100644
--- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java
+++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
@@ -798,6 +798,7 @@ public class SQLiteQueryBuilder {
}
private void enforceStrictToken(@NonNull String token) {
+ if (TextUtils.isEmpty(token)) return;
if (isTableOrColumn(token)) return;
if (SQLiteTokenizer.isFunction(token)) return;
if (SQLiteTokenizer.isType(token)) return;
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index d57a7e4b97f0..6900105c4d4c 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -19,6 +19,7 @@ package android.hardware.fingerprint;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.MANAGE_FINGERPRINT;
import static android.Manifest.permission.USE_BIOMETRIC;
+import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.Manifest.permission.USE_FINGERPRINT;
import android.annotation.NonNull;
@@ -75,11 +76,13 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
private static final int MSG_ERROR = 104;
private static final int MSG_REMOVED = 105;
private static final int MSG_ENUMERATED = 106;
+ private static final int MSG_FINGERPRINT_DETECTED = 107;
private IFingerprintService mService;
private Context mContext;
private IBinder mToken = new Binder();
private AuthenticationCallback mAuthenticationCallback;
+ private FingerprintDetectionCallback mFingerprintDetectionCallback;
private EnrollmentCallback mEnrollmentCallback;
private RemovalCallback mRemovalCallback;
private EnumerateCallback mEnumerateCallback;
@@ -107,6 +110,13 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
}
}
+ private class OnFingerprintDetectionCancelListener implements OnCancelListener {
+ @Override
+ public void onCancel() {
+ cancelFingerprintDetect();
+ }
+ }
+
/**
* A wrapper class for the crypto objects supported by FingerprintManager. Currently the
* framework supports {@link Signature}, {@link Cipher} and {@link Mac} objects.
@@ -272,6 +282,18 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
};
/**
+ * Callback structure provided for {@link #detectFingerprint(CancellationSignal,
+ * FingerprintDetectionCallback, int)}.
+ * @hide
+ */
+ public interface FingerprintDetectionCallback {
+ /**
+ * Invoked when a fingerprint has been detected.
+ */
+ void onFingerprintDetected(int userId, boolean isStrongBiometric);
+ }
+
+ /**
* Callback structure provided to {@link FingerprintManager#enroll(byte[], CancellationSignal,
* int, int, EnrollmentCallback)} must provide an implementation of this for listening to
* fingerprint events.
@@ -454,6 +476,35 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
}
/**
+ * Uses the fingerprint hardware to detect for the presence of a finger, without giving details
+ * about accept/reject/lockout.
+ * @hide
+ */
+ @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+ public void detectFingerprint(@NonNull CancellationSignal cancel,
+ @NonNull FingerprintDetectionCallback callback, int userId) {
+ if (mService == null) {
+ return;
+ }
+
+ if (cancel.isCanceled()) {
+ Slog.w(TAG, "Detection already cancelled");
+ return;
+ } else {
+ cancel.setOnCancelListener(new OnFingerprintDetectionCancelListener());
+ }
+
+ mFingerprintDetectionCallback = callback;
+
+ try {
+ mService.detectFingerprint(mToken, userId, mServiceReceiver,
+ mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Remote exception when requesting finger detect", e);
+ }
+ }
+
+ /**
* Request fingerprint enrollment. This call warms up the fingerprint hardware
* and starts scanning for fingerprints. Progress will be indicated by callbacks to the
* {@link EnrollmentCallback} object. It terminates when
@@ -797,6 +848,10 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
sendEnumeratedResult((Long) msg.obj /* deviceId */, msg.arg1 /* fingerId */,
msg.arg2 /* groupId */);
break;
+ case MSG_FINGERPRINT_DETECTED:
+ sendFingerprintDetected(msg.arg1 /* userId */,
+ (boolean) msg.obj /* isStrongBiometric */);
+ break;
}
}
};
@@ -891,6 +946,14 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
}
}
+ private void sendFingerprintDetected(int userId, boolean isStrongBiometric) {
+ if (mFingerprintDetectionCallback == null) {
+ Slog.e(TAG, "sendFingerprintDetected, callback null");
+ return;
+ }
+ mFingerprintDetectionCallback.onFingerprintDetected(userId, isStrongBiometric);
+ }
+
/**
* @hide
*/
@@ -927,6 +990,18 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
}
}
+ private void cancelFingerprintDetect() {
+ if (mService == null) {
+ return;
+ }
+
+ try {
+ mService.cancelFingerprintDetect(mToken, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/**
* @hide
*/
@@ -1032,6 +1107,12 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
fp).sendToTarget();
}
+ @Override
+ public void onFingerprintDetected(long deviceId, int userId, boolean isStrongBiometric) {
+ mHandler.obtainMessage(MSG_FINGERPRINT_DETECTED, userId, 0, isStrongBiometric)
+ .sendToTarget();
+ }
+
@Override // binder call
public void onAuthenticationFailed(long deviceId) {
mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index c5c375543adc..8aa36d7d5d76 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -33,6 +33,11 @@ interface IFingerprintService {
void authenticate(IBinder token, long sessionId, int userId,
IFingerprintServiceReceiver receiver, int flags, String opPackageName);
+ // Uses the fingerprint hardware to detect for the presence of a finger, without giving details
+ // about accept/reject/lockout.
+ void detectFingerprint(IBinder token, int userId, IFingerprintServiceReceiver receiver,
+ String opPackageName);
+
// This method prepares the service to start authenticating, but doesn't start authentication.
// This is protected by the MANAGE_BIOMETRIC signatuer permission. This method should only be
// called from BiometricService. The additional uid, pid, userId arguments should be determined
@@ -48,6 +53,9 @@ interface IFingerprintService {
// Cancel authentication for the given sessionId
void cancelAuthentication(IBinder token, String opPackageName);
+ // Cancel finger detection
+ void cancelFingerprintDetect(IBinder token, String opPackageName);
+
// Same as above, except this is protected by the MANAGE_BIOMETRIC signature permission. Takes
// an additional uid, pid, userid.
void cancelAuthenticationFromService(IBinder token, String opPackageName,
diff --git a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
index 4412cee31bb0..a84b81e1eb81 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
@@ -26,6 +26,7 @@ oneway interface IFingerprintServiceReceiver {
void onAcquired(long deviceId, int acquiredInfo, int vendorCode);
void onAuthenticationSucceeded(long deviceId, in Fingerprint fp, int userId,
boolean isStrongBiometric);
+ void onFingerprintDetected(long deviceId, int userId, boolean isStrongBiometric);
void onAuthenticationFailed(long deviceId);
void onError(long deviceId, int error, int vendorCode);
void onRemoved(long deviceId, int fingerId, int groupId, int remaining);
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index a0e92b398c4e..276f16216b4d 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -99,6 +99,13 @@ public class CallLog {
public static final String LIMIT_PARAM_KEY = "limit";
/**
+ * Form of {@link #CONTENT_URI} which limits the query results to a single result.
+ */
+ private static final Uri CONTENT_URI_LIMIT_1 = CONTENT_URI.buildUpon()
+ .appendQueryParameter(LIMIT_PARAM_KEY, "1")
+ .build();
+
+ /**
* Query parameter used to specify the starting record to return.
* <p>
* TYPE: integer
@@ -932,11 +939,11 @@ public class CallLog {
Cursor c = null;
try {
c = resolver.query(
- CONTENT_URI,
+ CONTENT_URI_LIMIT_1,
new String[] {NUMBER},
TYPE + " = " + OUTGOING_TYPE,
null,
- DEFAULT_SORT_ORDER + " LIMIT 1");
+ DEFAULT_SORT_ORDER);
if (c == null || !c.moveToFirst()) {
return "";
}
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index c52b02bb6a3c..dfd305330b14 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1903,6 +1903,17 @@ public abstract class NotificationListenerService extends Service {
/**
* @hide
*/
+ public @NonNull Ranking withAudiblyAlertedInfo(@Nullable Ranking previous) {
+ if (previous != null && previous.mLastAudiblyAlertedMs > 0
+ && this.mLastAudiblyAlertedMs <= 0) {
+ this.mLastAudiblyAlertedMs = previous.mLastAudiblyAlertedMs;
+ }
+ return this;
+ }
+
+ /**
+ * @hide
+ */
public void populate(Ranking other) {
populate(other.mKey,
other.mRank,
diff --git a/core/java/android/view/IDisplayWindowInsetsController.aidl b/core/java/android/view/IDisplayWindowInsetsController.aidl
index 429c3aeba9b3..a0d4a6587bcf 100644
--- a/core/java/android/view/IDisplayWindowInsetsController.aidl
+++ b/core/java/android/view/IDisplayWindowInsetsController.aidl
@@ -27,6 +27,13 @@ import android.view.InsetsState;
oneway interface IDisplayWindowInsetsController {
/**
+ * Called when top focused window changes to determine whether or not to take over insets
+ * control. Won't be called if config_remoteInsetsControllerControlsSystemBars is false.
+ * @param packageName: Passes the top package name
+ */
+ void topFocusedWindowChanged(String packageName);
+
+ /**
* @see IWindow#insetsChanged
*/
void insetsChanged(in InsetsState insetsState);
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 58597cf3fb6d..00fc67214f75 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -388,16 +388,6 @@ interface IWindowManager
oneway void hideTransientBars(int displayId);
/**
- * When set to {@code true} the system bars will always be shown. This is true even if an app
- * requests to be fullscreen by setting the system ui visibility flags. The
- * functionality was added for the automotive case as a way to guarantee required content stays
- * on screen at all times.
- *
- * @hide
- */
- oneway void setForceShowSystemBars(boolean show);
-
- /**
* Called by System UI to notify of changes to the visibility of Recents.
*/
oneway void setRecentsVisibility(boolean visible);
diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java
index 492ab6f8a3d5..8c355202b63f 100644
--- a/core/java/android/view/WindowManagerPolicyConstants.java
+++ b/core/java/android/view/WindowManagerPolicyConstants.java
@@ -49,6 +49,13 @@ public interface WindowManagerPolicyConstants {
int PRESENCE_INTERNAL = 1 << 0;
int PRESENCE_EXTERNAL = 1 << 1;
+ // Alternate bars position values
+ int ALT_BAR_UNKNOWN = -1;
+ int ALT_BAR_LEFT = 1 << 0;
+ int ALT_BAR_RIGHT = 1 << 1;
+ int ALT_BAR_BOTTOM = 1 << 2;
+ int ALT_BAR_TOP = 1 << 3;
+
// Navigation bar position values
int NAV_BAR_INVALID = -1;
int NAV_BAR_LEFT = 1 << 0;
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index f2bc89e9deb6..e16bf5a18808 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -395,6 +395,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private static final int EMS = LINES;
private static final int PIXELS = 2;
+ // Maximum text length for single line input.
+ private static final int MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT = 5000;
+ private InputFilter.LengthFilter mSingleLineLengthFilter = null;
+
private static final RectF TEMP_RECTF = new RectF();
/** @hide */
@@ -1589,7 +1593,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// Same as setSingleLine(), but make sure the transformation method and the maximum number
// of lines of height are unchanged for multi-line TextViews.
setInputTypeSingleLine(singleLine);
- applySingleLine(singleLine, singleLine, singleLine);
+ applySingleLine(singleLine, singleLine, singleLine,
+ // Does not apply automated max length filter since length filter will be resolved
+ // later in this function.
+ false
+ );
if (singleLine && getKeyListener() == null && ellipsize == ELLIPSIZE_NOT_SET) {
ellipsize = ELLIPSIZE_END;
@@ -1633,7 +1641,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
setTransformationMethod(PasswordTransformationMethod.getInstance());
}
- if (maxlength >= 0) {
+ // For addressing b/145128646
+ // For the performance reason, we limit characters for single line text field.
+ if (bufferType == BufferType.EDITABLE && singleLine && maxlength == -1) {
+ mSingleLineLengthFilter = new InputFilter.LengthFilter(
+ MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
+ }
+
+ if (mSingleLineLengthFilter != null) {
+ setFilters(new InputFilter[] { mSingleLineLengthFilter });
+ } else if (maxlength >= 0) {
setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
} else {
setFilters(NO_FILTERS);
@@ -6590,7 +6607,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (mSingleLine != singleLine || forceUpdate) {
// Change single line mode, but only change the transformation if
// we are not in password mode.
- applySingleLine(singleLine, !isPassword, true);
+ applySingleLine(singleLine, !isPassword, true, true);
}
if (!isSuggestionsEnabled()) {
@@ -10229,6 +10246,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* Note that the default conditions are not necessarily those that were in effect prior this
* method, and you may want to reset these properties to your custom values.
*
+ * Note that due to performance reasons, by setting single line for the EditText, the maximum
+ * text length is set to 5000 if no other character limitation are applied.
+ *
* @attr ref android.R.styleable#TextView_singleLine
*/
@android.view.RemotableViewMethod
@@ -10236,7 +10256,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// Could be used, but may break backward compatibility.
// if (mSingleLine == singleLine) return;
setInputTypeSingleLine(singleLine);
- applySingleLine(singleLine, true, true);
+ applySingleLine(singleLine, true, true, true);
}
/**
@@ -10256,14 +10276,40 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
private void applySingleLine(boolean singleLine, boolean applyTransformation,
- boolean changeMaxLines) {
+ boolean changeMaxLines, boolean changeMaxLength) {
mSingleLine = singleLine;
+
if (singleLine) {
setLines(1);
setHorizontallyScrolling(true);
if (applyTransformation) {
setTransformationMethod(SingleLineTransformationMethod.getInstance());
}
+
+ if (!changeMaxLength) return;
+
+ // Single line length filter is only applicable editable text.
+ if (mBufferType != BufferType.EDITABLE) return;
+
+ final InputFilter[] prevFilters = getFilters();
+ for (InputFilter filter: getFilters()) {
+ // We don't add LengthFilter if already there.
+ if (filter instanceof InputFilter.LengthFilter) return;
+ }
+
+ if (mSingleLineLengthFilter == null) {
+ mSingleLineLengthFilter = new InputFilter.LengthFilter(
+ MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT);
+ }
+
+ final InputFilter[] newFilters = new InputFilter[prevFilters.length + 1];
+ System.arraycopy(prevFilters, 0, newFilters, 0, prevFilters.length);
+ newFilters[prevFilters.length] = mSingleLineLengthFilter;
+
+ setFilters(newFilters);
+
+ // Since filter doesn't apply to existing text, trigger filter by setting text.
+ setText(getText());
} else {
if (changeMaxLines) {
setMaxLines(Integer.MAX_VALUE);
@@ -10272,6 +10318,47 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (applyTransformation) {
setTransformationMethod(null);
}
+
+ if (!changeMaxLength) return;
+
+ // Single line length filter is only applicable editable text.
+ if (mBufferType != BufferType.EDITABLE) return;
+
+ final InputFilter[] prevFilters = getFilters();
+ if (prevFilters.length == 0) return;
+
+ // Short Circuit: if mSingleLineLengthFilter is not allocated, nobody sets automated
+ // single line char limit filter.
+ if (mSingleLineLengthFilter == null) return;
+
+ // If we need to remove mSingleLineLengthFilter, we need to allocate another array.
+ // Since filter list is expected to be small and want to avoid unnecessary array
+ // allocation, check if there is mSingleLengthFilter first.
+ int targetIndex = -1;
+ for (int i = 0; i < prevFilters.length; ++i) {
+ if (prevFilters[i] == mSingleLineLengthFilter) {
+ targetIndex = i;
+ break;
+ }
+ }
+ if (targetIndex == -1) return; // not found. Do nothing.
+
+ if (prevFilters.length == 1) {
+ setFilters(NO_FILTERS);
+ return;
+ }
+
+ // Create new array which doesn't include mSingleLengthFilter.
+ final InputFilter[] newFilters = new InputFilter[prevFilters.length - 1];
+ System.arraycopy(prevFilters, 0, newFilters, 0, targetIndex);
+ System.arraycopy(
+ prevFilters,
+ targetIndex + 1,
+ newFilters,
+ targetIndex,
+ prevFilters.length - targetIndex - 1);
+ setFilters(newFilters);
+ mSingleLineLengthFilter = null;
}
}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 14cf258f18ab..3a89dcd96487 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -2640,7 +2640,10 @@ public class ChooserActivity extends ResolverActivity implements
}
RecyclerView recyclerView = mChooserMultiProfilePagerAdapter.getActiveAdapterView();
ChooserGridAdapter gridAdapter = mChooserMultiProfilePagerAdapter.getCurrentRootAdapter();
- if (gridAdapter == null || recyclerView == null) {
+ // Skip height calculation if recycler view was scrolled to prevent it inaccurately
+ // calculating the height, as the logic below does not account for the scrolled offset.
+ if (gridAdapter == null || recyclerView == null
+ || recyclerView.computeVerticalScrollOffset() != 0) {
return;
}
@@ -3127,6 +3130,11 @@ public class ChooserActivity extends ResolverActivity implements
ChooserGridAdapter currentRootAdapter =
mChooserMultiProfilePagerAdapter.getCurrentRootAdapter();
currentRootAdapter.updateDirectShareExpansion();
+ // This fixes an edge case where after performing a variety of gestures, vertical scrolling
+ // ends up disabled. That's because at some point the old tab's vertical scrolling is
+ // disabled and the new tab's is enabled. For context, see b/159997845
+ setVerticalScrollEnabled(true);
+ mResolverDrawerLayout.scrollNestedScrollableChildBackToTop();
}
@Override
diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
index ffa6041721c6..3a65a324f9d6 100644
--- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
@@ -252,8 +252,10 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd
@Override
protected void setupContainerPadding(View container) {
+ int initialBottomPadding = getContext().getResources().getDimensionPixelSize(
+ R.dimen.resolver_empty_state_container_padding_bottom);
container.setPadding(container.getPaddingLeft(), container.getPaddingTop(),
- container.getPaddingRight(), container.getPaddingBottom() + mBottomOffset);
+ container.getPaddingRight(), initialBottomPadding + mBottomOffset);
}
class ChooserProfileDescriptor extends ProfileDescriptor {
diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
index 3f708f84750c..90eeabb47e9a 100644
--- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java
+++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
@@ -464,11 +464,7 @@ public class ResolverDrawerLayout extends ViewGroup {
smoothScrollTo(mCollapsibleHeight + mUncollapsibleHeight, yvel);
mDismissOnScrollerFinished = true;
} else {
- if (isNestedListChildScrolled()) {
- mNestedListChild.smoothScrollToPosition(0);
- } else if (isNestedRecyclerChildScrolled()) {
- mNestedRecyclerChild.smoothScrollToPosition(0);
- }
+ scrollNestedScrollableChildBackToTop();
smoothScrollTo(yvel < 0 ? 0 : mCollapsibleHeight, yvel);
}
}
@@ -493,6 +489,17 @@ public class ResolverDrawerLayout extends ViewGroup {
return handled;
}
+ /**
+ * Scroll nested scrollable child back to top if it has been scrolled.
+ */
+ public void scrollNestedScrollableChildBackToTop() {
+ if (isNestedListChildScrolled()) {
+ mNestedListChild.smoothScrollToPosition(0);
+ } else if (isNestedRecyclerChildScrolled()) {
+ mNestedRecyclerChild.smoothScrollToPosition(0);
+ }
+ }
+
private void onSecondaryPointerUp(MotionEvent ev) {
final int pointerIndex = ev.getActionIndex();
final int pointerId = ev.getPointerId(pointerIndex);
diff --git a/core/res/res/layout/notification_material_action_list.xml b/core/res/res/layout/notification_material_action_list.xml
index 3615b9e2f9cb..df271f0f2942 100644
--- a/core/res/res/layout/notification_material_action_list.xml
+++ b/core/res/res/layout/notification_material_action_list.xml
@@ -25,6 +25,7 @@
android:id="@+id/actions_container_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:gravity="end"
android:orientation="horizontal"
android:paddingEnd="@dimen/bubble_gone_padding_end"
>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 8a4676dec6b3..2f1bcdc76afb 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1259,7 +1259,12 @@
<!-- Can be combined with <var>text</var> and its variations to
allow multiple lines of text in the field. If this flag is not set,
the text field will be constrained to a single line. Corresponds to
- {@link android.text.InputType#TYPE_TEXT_FLAG_MULTI_LINE}. -->
+ {@link android.text.InputType#TYPE_TEXT_FLAG_MULTI_LINE}.
+
+ Note: If this flag is not set and the text field doesn't have max length limit, the
+ framework automatically set maximum length of the characters to 5000 for the
+ performance reasons.
+ -->
<flag name="textMultiLine" value="0x00020001" />
<!-- Can be combined with <var>text</var> and its variations to
indicate that though the regular text view should not be multiple
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 9f4b6f2142c0..8bbb2cec8a9d 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -712,10 +712,17 @@
case, this can be disabled (set to false). -->
<bool name="config_enableCarDockHomeLaunch">true</bool>
- <!-- Control whether to force the display of System UI Bars at all times regardless of
- System Ui Flags. This can be useful in the Automotive case if there's a requirement for
- a UI element to be on screen at all times. -->
- <bool name="config_forceShowSystemBars">false</bool>
+ <!-- Control whether to force apps to give up control over the display of system bars at all
+ times regardless of System Ui Flags.
+ In the Automotive case, this is helpful if there's a requirement for an UI element to be on
+ screen at all times. Setting this to true also gives System UI the ability to override the
+ visibility controls for the system through the usage of the
+ "SYSTEM_BAR_VISIBILITY_OVERRIDE" setting.
+ Ex: Only setting the config to true will force show system bars for the entire system.
+ Ex: Setting the config to true and the "SYSTEM_BAR_VISIBILITY_OVERRIDE" setting to
+ "immersive.status=apps" will force show navigation bar for all apps and force hide status
+ bar for all apps. -->
+ <bool name="config_remoteInsetsControllerControlsSystemBars">false</bool>
<!-- HDMI behavior -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index e2fbbf46608f..e00aff1af37b 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3024,6 +3024,8 @@
<!-- @hide @TestApi -->
<public type="bool" name="config_assistantOnTopOfDream" id="0x01110005" />
+ <!-- @hide @TestApi -->
+ <public type="bool" name="config_remoteInsetsControllerControlsSystemBars" id="0x01110006" />
<!-- ===============================================================
Resources added in version S of the platform
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 4f9911fbe387..d6ee28b93f92 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2122,7 +2122,7 @@
<!-- On the unlock pattern screen, shown at the top of the unlock screen to tell the user what to do. Below this text is the place for theu ser to draw the pattern. -->
<string name="lockscreen_pattern_instructions">Draw pattern to unlock</string>
<!-- Button at the bottom of the unlock screen to make an emergency call or access other emergency assistance functions. -->
- <string name="lockscreen_emergency_call">Emergency</string>
+ <string name="lockscreen_emergency_call">Emergency call</string>
<!-- Button at the bottom of the unlock screen that lets the user return to a call -->
<string name="lockscreen_return_to_call">Return to call</string>
<!-- Shown to confirm that the user entered their lock pattern correctly. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6901eb21d9fe..dcd0b51772a7 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1670,7 +1670,7 @@
<java-symbol type="bool" name="config_enableCarDockHomeLaunch" />
<java-symbol type="bool" name="config_enableLockBeforeUnlockScreen" />
<java-symbol type="bool" name="config_enableLockScreenRotation" />
- <java-symbol type="bool" name="config_forceShowSystemBars" />
+ <java-symbol type="bool" name="config_remoteInsetsControllerControlsSystemBars" />
<java-symbol type="bool" name="config_lidControlsScreenLock" />
<java-symbol type="bool" name="config_lidControlsSleep" />
<java-symbol type="bool" name="config_lockDayNightMode" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index e6f4e27100de..a67161c1a4d5 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -143,6 +143,9 @@ applications that come with the platform
<permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
<permission name="android.permission.PACKAGE_USAGE_STATS" />
<permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
+
+ <!-- For permission hub 2 debugging only -->
+ <permission name="android.permission.GET_ACCOUNTS_PRIVILEGED"/>
</privapp-permissions>
<privapp-permissions package="com.android.phone">
@@ -427,6 +430,10 @@ applications that come with the platform
<permission name="android.permission.CAPTURE_AUDIO_OUTPUT" />
<!-- Permissions required for CTS test - AdbManagerTest -->
<permission name="android.permission.MANAGE_DEBUGGING" />
+ <!-- Permissions required for ATS tests - AtsCarHostTestCases, AtsCarDeviceApp -->
+ <permission name="android.car.permission.CAR_DRIVING_STATE" />
+ <!-- Permissions required for ATS tests - AtsDeviceInfo, AtsAudioDeviceTestCases -->
+ <permission name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME" />
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/diff b/diff
new file mode 100644
index 000000000000..5c75d88e6789
--- /dev/null
+++ b/diff
@@ -0,0 +1,25 @@
+diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+index fc43882..832dc91 100644
+--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
++++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+@@ -67,6 +67,7 @@ import java.util.Arrays;
+ import java.util.HashSet;
+ import java.util.List;
+ import java.util.Set;
++import java.util.NoSuchElementException;
+
+ /**
+ * This class represents an accessibility client - either an AccessibilityService or a UiAutomation.
+@@ -978,7 +979,11 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
+ /* ignore */
+ }
+ if (mService != null) {
+- mService.unlinkToDeath(this, 0);
++ try {
++ mService.unlinkToDeath(this, 0);
++ }catch(NoSuchElementException e) {
++ Slog.e(LOG_TAG, "Failed unregistering death link");
++ }
+ mService = null;
+ }
+
diff --git a/packages/CarSystemUI/TEST_MAPPING b/packages/CarSystemUI/TEST_MAPPING
index 6056ddfc0bc7..c18a12aeb2aa 100644
--- a/packages/CarSystemUI/TEST_MAPPING
+++ b/packages/CarSystemUI/TEST_MAPPING
@@ -8,5 +8,16 @@
}
]
}
+ ],
+
+ "carsysui-presubmit": [
+ {
+ "name": "CarSystemUITests",
+ "options" : [
+ {
+ "include-annotation": "com.android.systemui.car.CarSystemUiTest"
+ }
+ ]
+ }
]
}
diff --git a/packages/CarSystemUI/res/layout/headsup_container_bottom.xml b/packages/CarSystemUI/res/layout/headsup_container_bottom.xml
index 1782d2536035..5aab0a172b99 100644
--- a/packages/CarSystemUI/res/layout/headsup_container_bottom.xml
+++ b/packages/CarSystemUI/res/layout/headsup_container_bottom.xml
@@ -29,13 +29,12 @@
android:orientation="horizontal"
app:layout_constraintGuide_begin="@dimen/headsup_scrim_height"/>
- <!-- Include a FocusParkingView at the beginning or end. The rotary controller "parks" the
- focus here when the user navigates to another window. This is also used to prevent
- wrap-around which is why it must be first or last in Tab order. -->
+ <!-- Include a FocusParkingView at the beginning. The rotary controller "parks" the focus here
+ when the user navigates to another window. This is also used to prevent wrap-around. -->
<com.android.car.ui.FocusParkingView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<View
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
index 5bf989a971b9..496742680893 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
@@ -67,6 +67,8 @@ import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.volume.VolumeDialogComponent;
+import com.android.systemui.wm.DisplayImeController;
+import com.android.systemui.wm.DisplaySystemBarsController;
import javax.inject.Named;
import javax.inject.Singleton;
@@ -97,6 +99,10 @@ public abstract class CarSystemUIModule {
groupManager, configurationController);
}
+ @Binds
+ abstract DisplayImeController bindDisplayImeController(
+ DisplaySystemBarsController displaySystemBarsController);
+
@Singleton
@Provides
@Named(LEAK_REPORT_EMAIL_NAME)
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarSystemUiTest.java b/packages/CarSystemUI/src/com/android/systemui/car/CarSystemUiTest.java
new file mode 100644
index 000000000000..5f593b06c511
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/CarSystemUiTest.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.car;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotates that a test class should be run as part of CarSystemUI presubmit
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Documented
+public @interface CarSystemUiTest {
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBar.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBar.java
index 37dfce4e16ce..35b2080dddf9 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBar.java
@@ -16,10 +16,12 @@
package com.android.systemui.car.navigationbar;
+import static android.view.InsetsState.ITYPE_BOTTOM_GESTURES;
import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.InsetsState.ITYPE_TOP_GESTURES;
import static android.view.InsetsState.containsType;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
@@ -368,13 +370,15 @@ public class CarNavigationBar extends SystemUI implements CommandQueue.Callbacks
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
height,
- WindowManager.LayoutParams.TYPE_STATUS_BAR,
+ WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
PixelFormat.TRANSLUCENT);
lp.setTitle("TopCarNavigationBar");
+ lp.providesInsetsTypes = new int[]{ITYPE_STATUS_BAR, ITYPE_TOP_GESTURES};
+ lp.setFitInsetsTypes(0);
lp.windowAnimations = 0;
lp.gravity = Gravity.TOP;
mWindowManager.addView(mTopNavigationBarWindow, lp);
@@ -388,13 +392,14 @@ public class CarNavigationBar extends SystemUI implements CommandQueue.Callbacks
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
height,
- WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
+ WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
PixelFormat.TRANSLUCENT);
lp.setTitle("BottomCarNavigationBar");
+ lp.providesInsetsTypes = new int[]{ITYPE_NAVIGATION_BAR, ITYPE_BOTTOM_GESTURES};
lp.windowAnimations = 0;
lp.gravity = Gravity.BOTTOM;
mWindowManager.addView(mBottomNavigationBarWindow, lp);
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarView.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarView.java
index 029d4c7fa2fb..0ced4021ce38 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarView.java
@@ -16,7 +16,10 @@
package com.android.systemui.car.navigationbar;
+import static android.view.WindowInsets.Type.systemBars;
+
import android.content.Context;
+import android.graphics.Insets;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
@@ -79,9 +82,28 @@ public class CarNavigationBarView extends LinearLayout {
@Override
public WindowInsets onApplyWindowInsets(WindowInsets windowInsets) {
+ applyMargins(windowInsets.getInsets(systemBars()));
return windowInsets;
}
+ private void applyMargins(Insets insets) {
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ if (child.getLayoutParams() instanceof LayoutParams) {
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (lp.rightMargin != insets.right || lp.leftMargin != insets.left
+ || lp.topMargin != insets.top || lp.bottomMargin != insets.bottom) {
+ lp.rightMargin = insets.right;
+ lp.leftMargin = insets.left;
+ lp.topMargin = insets.top;
+ lp.bottomMargin = insets.bottom;
+ child.requestLayout();
+ }
+ }
+ }
+ }
+
// Used to forward touch events even if the touch was initiated from a child component
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/NavigationBarViewFactory.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/NavigationBarViewFactory.java
index d60bc418ece2..adf8d4d5acf8 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/NavigationBarViewFactory.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/NavigationBarViewFactory.java
@@ -148,10 +148,9 @@ public class NavigationBarViewFactory {
CarNavigationBarView view = (CarNavigationBarView) View.inflate(mContext, barLayout,
/* root= */ null);
- // Include a FocusParkingView at the end. The rotary controller "parks" the focus here when
- // the user navigates to another window. This is also used to prevent wrap-around which is
- // why it must be first or last in Tab order.
- view.addView(new FocusParkingView(mContext));
+ // Include a FocusParkingView at the beginning. The rotary controller "parks" the focus here
+ // when the user navigates to another window. This is also used to prevent wrap-around.
+ view.addView(new FocusParkingView(mContext), 0);
mCachedViewMap.put(type, view);
return mCachedViewMap.get(type);
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullScreenUserSwitcherViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullScreenUserSwitcherViewController.java
index 10b2b973071a..1a8f19e46798 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullScreenUserSwitcherViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullScreenUserSwitcherViewController.java
@@ -18,6 +18,8 @@ package com.android.systemui.car.userswitcher;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.car.Car;
+import android.car.user.CarUserManager;
import android.content.Context;
import android.content.res.Resources;
import android.view.View;
@@ -25,6 +27,7 @@ import android.view.View;
import androidx.recyclerview.widget.GridLayoutManager;
import com.android.systemui.R;
+import com.android.systemui.car.CarServiceProvider;
import com.android.systemui.car.window.OverlayViewController;
import com.android.systemui.car.window.OverlayViewGlobalStateController;
import com.android.systemui.dagger.qualifiers.Main;
@@ -39,7 +42,9 @@ import javax.inject.Singleton;
public class FullScreenUserSwitcherViewController extends OverlayViewController {
private final Context mContext;
private final Resources mResources;
+ private final CarServiceProvider mCarServiceProvider;
private final int mShortAnimationDuration;
+ private CarUserManager mCarUserManager;
private UserGridRecyclerView mUserGridView;
private UserGridRecyclerView.UserSelectionListener mUserSelectionListener;
@@ -47,10 +52,16 @@ public class FullScreenUserSwitcherViewController extends OverlayViewController
public FullScreenUserSwitcherViewController(
Context context,
@Main Resources resources,
+ CarServiceProvider carServiceProvider,
OverlayViewGlobalStateController overlayViewGlobalStateController) {
super(R.id.fullscreen_user_switcher_stub, overlayViewGlobalStateController);
mContext = context;
mResources = resources;
+ mCarServiceProvider = carServiceProvider;
+ mCarServiceProvider.addListener(car -> {
+ mCarUserManager = (CarUserManager) car.getCarManager(Car.CAR_USER_SERVICE);
+ registerCarUserManagerIfPossible();
+ });
mShortAnimationDuration = mResources.getInteger(android.R.integer.config_shortAnimTime);
}
@@ -63,6 +74,7 @@ public class FullScreenUserSwitcherViewController extends OverlayViewController
mUserGridView.setLayoutManager(layoutManager);
mUserGridView.buildAdapter();
mUserGridView.setUserSelectionListener(mUserSelectionListener);
+ registerCarUserManagerIfPossible();
}
@Override
@@ -91,18 +103,6 @@ public class FullScreenUserSwitcherViewController extends OverlayViewController
}
/**
- * Invalidate underlying view.
- */
- void invalidate() {
- if (getLayout() == null) {
- // layout hasn't been inflated.
- return;
- }
-
- getLayout().invalidate();
- }
-
- /**
* Set {@link UserGridRecyclerView.UserSelectionListener}.
*/
void setUserGridSelectionListener(
@@ -110,15 +110,9 @@ public class FullScreenUserSwitcherViewController extends OverlayViewController
mUserSelectionListener = userGridSelectionListener;
}
- /**
- * Returns {@code true} when layout is visible.
- */
- boolean isVisible() {
- if (getLayout() == null) {
- // layout hasn't been inflated.
- return false;
+ private void registerCarUserManagerIfPossible() {
+ if (mUserGridView != null && mCarUserManager != null) {
+ mUserGridView.setCarUserManager(mCarUserManager);
}
-
- return getLayout().getVisibility() == View.VISIBLE;
}
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java
index 2ff667093e58..d0a2aebdb80a 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java
@@ -24,11 +24,15 @@ import static android.view.WindowInsets.Type.statusBars;
import android.annotation.IntDef;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.app.Dialog;
-import android.car.userlib.CarUserManagerHelper;
+import android.car.user.CarUserManager;
+import android.car.user.UserCreationResult;
+import android.car.user.UserSwitchResult;
+import android.car.userlib.UserHelper;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
@@ -40,7 +44,9 @@ import android.graphics.Rect;
import android.os.AsyncTask;
import android.os.UserHandle;
import android.os.UserManager;
+import android.sysprop.CarProperties;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -54,6 +60,7 @@ import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.internal.infra.AndroidFuture;
import com.android.internal.util.UserIcons;
import com.android.systemui.R;
@@ -61,6 +68,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
@@ -68,9 +76,12 @@ import java.util.stream.Collectors;
* One of the uses of this is for the lock screen in auto.
*/
public class UserGridRecyclerView extends RecyclerView {
+ private static final String TAG = UserGridRecyclerView.class.getSimpleName();
+ private static final int TIMEOUT_MS = CarProperties.user_hal_timeout().orElse(5_000) + 500;
+
private UserSelectionListener mUserSelectionListener;
private UserAdapter mAdapter;
- private CarUserManagerHelper mCarUserManagerHelper;
+ private CarUserManager mCarUserManager;
private UserManager mUserManager;
private Context mContext;
private UserIconProvider mUserIconProvider;
@@ -85,7 +96,6 @@ public class UserGridRecyclerView extends RecyclerView {
public UserGridRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
- mCarUserManagerHelper = new CarUserManagerHelper(mContext);
mUserManager = UserManager.get(mContext);
mUserIconProvider = new UserIconProvider();
@@ -184,6 +194,11 @@ public class UserGridRecyclerView extends RecyclerView {
mUserSelectionListener = userSelectionListener;
}
+ /** Sets a {@link CarUserManager}. */
+ public void setCarUserManager(CarUserManager carUserManager) {
+ mCarUserManager = carUserManager;
+ }
+
private void onUsersUpdate() {
mAdapter.clearUsers();
mAdapter.updateUsers(createUserRecords(getUsersForUserGrid()));
@@ -273,7 +288,9 @@ public class UserGridRecyclerView extends RecyclerView {
notifyUserSelected(userRecord);
UserInfo guest = createNewOrFindExistingGuest(mContext);
if (guest != null) {
- mCarUserManagerHelper.switchToUser(guest);
+ if (!switchUser(guest.id)) {
+ Log.e(TAG, "Failed to switch to guest user: " + guest.id);
+ }
}
break;
case UserRecord.ADD_USER:
@@ -289,7 +306,9 @@ public class UserGridRecyclerView extends RecyclerView {
// If the user doesn't want to be a guest or add a user, switch to the user
// selected
notifyUserSelected(userRecord);
- mCarUserManagerHelper.switchToUser(userRecord.mInfo);
+ if (!switchUser(userRecord.mInfo.id)) {
+ Log.e(TAG, "Failed to switch users: " + userRecord.mInfo.id);
+ }
}
});
@@ -430,8 +449,9 @@ public class UserGridRecyclerView extends RecyclerView {
*/
@Nullable
public UserInfo createNewOrFindExistingGuest(Context context) {
+ AndroidFuture<UserCreationResult> future = mCarUserManager.createGuest(mGuestName);
// CreateGuest will return null if a guest already exists.
- UserInfo newGuest = mUserManager.createGuest(context, mGuestName);
+ UserInfo newGuest = getUserInfo(future);
if (newGuest != null) {
new UserIconProvider().assignDefaultIcon(
mUserManager, context.getResources(), newGuest);
@@ -444,7 +464,6 @@ public class UserGridRecyclerView extends RecyclerView {
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == BUTTON_POSITIVE) {
- notifyUserSelected(mAddUserRecord);
new AddNewUserTask().execute(mNewUserName);
} else if (which == BUTTON_NEGATIVE) {
// Enable the add button only if cancel
@@ -462,11 +481,77 @@ public class UserGridRecyclerView extends RecyclerView {
}
}
+ @Nullable
+ private UserInfo getUserInfo(AndroidFuture<UserCreationResult> future) {
+ UserCreationResult userCreationResult;
+ try {
+ userCreationResult = future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ } catch (Exception e) {
+ Log.w(TAG, "Could not create user.", e);
+ return null;
+ }
+
+ if (userCreationResult == null) {
+ Log.w(TAG, "Timed out while creating user: " + TIMEOUT_MS + "ms");
+ return null;
+ }
+ if (!userCreationResult.isSuccess() || userCreationResult.getUser() == null) {
+ Log.w(TAG, "Could not create user: " + userCreationResult);
+ return null;
+ }
+
+ return userCreationResult.getUser();
+ }
+
+ private boolean switchUser(@UserIdInt int userId) {
+ AndroidFuture<UserSwitchResult> userSwitchResultFuture =
+ mCarUserManager.switchUser(userId);
+ UserSwitchResult userSwitchResult;
+ try {
+ userSwitchResult = userSwitchResultFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ } catch (Exception e) {
+ Log.w(TAG, "Could not switch user.", e);
+ return false;
+ }
+
+ if (userSwitchResult == null) {
+ Log.w(TAG, "Timed out while switching user: " + TIMEOUT_MS + "ms");
+ return false;
+ }
+ if (!userSwitchResult.isSuccess()) {
+ Log.w(TAG, "Could not switch user: " + userSwitchResult);
+ return false;
+ }
+
+ return true;
+ }
+
+ // TODO(b/161539497): Replace AsyncTask with standard {@link java.util.concurrent} code.
private class AddNewUserTask extends AsyncTask<String, Void, UserInfo> {
@Override
protected UserInfo doInBackground(String... userNames) {
- return mCarUserManagerHelper.createNewNonAdminUser(userNames[0]);
+ AndroidFuture<UserCreationResult> future = mCarUserManager.createUser(userNames[0],
+ /* flags= */ 0);
+ try {
+ UserInfo user = getUserInfo(future);
+ if (user != null) {
+ UserHelper.setDefaultNonAdminRestrictions(mContext, user,
+ /* enable= */ true);
+ UserHelper.assignDefaultIcon(mContext, user);
+ mAddUserRecord = new UserRecord(user, UserRecord.ADD_USER);
+ return user;
+ } else {
+ Log.e(TAG, "Failed to create user in the background");
+ return user;
+ }
+ } catch (Exception e) {
+ if (e instanceof InterruptedException) {
+ Thread.currentThread().interrupt();
+ }
+ Log.e(TAG, "Error creating new user: ", e);
+ }
+ return null;
}
@Override
@@ -476,7 +561,11 @@ public class UserGridRecyclerView extends RecyclerView {
@Override
protected void onPostExecute(UserInfo user) {
if (user != null) {
- mCarUserManagerHelper.switchToUser(user);
+ notifyUserSelected(mAddUserRecord);
+ mAddUserView.setEnabled(true);
+ if (!switchUser(user.id)) {
+ Log.e(TAG, "Failed to switch to new user: " + user.id);
+ }
}
}
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/wm/BarControlPolicy.java b/packages/CarSystemUI/src/com/android/systemui/wm/BarControlPolicy.java
new file mode 100644
index 000000000000..5f9665ff7632
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/wm/BarControlPolicy.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wm;
+
+import android.car.settings.CarSettings;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.view.WindowInsets;
+
+import androidx.annotation.VisibleForTesting;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * Util class to load PolicyControl and allow for querying if a package matches immersive filters.
+ * Similar to {@link com.android.server.wm.PolicyControl}, but separate due to CarSystemUI needing
+ * to set its own policies for system bar visibilities.
+ *
+ * This forces immersive mode behavior for one or both system bars (based on a package
+ * list).
+ *
+ * Control by setting {@link Settings.Global#POLICY_CONTROL_AUTO} to one or more name-value pairs.
+ * e.g.
+ * to force immersive mode everywhere:
+ * "immersive.full=*"
+ * to force hide status bars for com.package1 but not com.package2:
+ * "immersive.status=com.package1,-com.package2"
+ *
+ * Separate multiple name-value pairs with ':'
+ * e.g. "immersive.status=com.package:immersive.navigation=*"
+ */
+public class BarControlPolicy {
+
+ private static final String TAG = "BarControlPolicy";
+ private static final boolean DEBUG = false;
+
+ private static final String NAME_IMMERSIVE_FULL = "immersive.full";
+ private static final String NAME_IMMERSIVE_STATUS = "immersive.status";
+ private static final String NAME_IMMERSIVE_NAVIGATION = "immersive.navigation";
+
+ @VisibleForTesting
+ static String sSettingValue;
+ @VisibleForTesting
+ static Filter sImmersiveStatusFilter;
+ private static Filter sImmersiveNavigationFilter;
+
+ /** Loads values from the POLICY_CONTROL setting to set filters. */
+ static boolean reloadFromSetting(Context context) {
+ if (DEBUG) Slog.d(TAG, "reloadFromSetting()");
+ String value = null;
+ try {
+ value = Settings.Global.getStringForUser(context.getContentResolver(),
+ CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE,
+ UserHandle.USER_CURRENT);
+ if (sSettingValue == value || sSettingValue != null && sSettingValue.equals(value)) {
+ return false;
+ }
+ setFilters(value);
+ sSettingValue = value;
+ } catch (Throwable t) {
+ Slog.w(TAG, "Error loading policy control, value=" + value, t);
+ return false;
+ }
+ return true;
+ }
+
+ /** Used in testing to reset BarControlPolicy. */
+ @VisibleForTesting
+ static void reset() {
+ sSettingValue = null;
+ sImmersiveStatusFilter = null;
+ sImmersiveNavigationFilter = null;
+ }
+
+ /**
+ * Registers a content observer to listen to updates to the SYSTEM_BAR_VISIBILITY_OVERRIDE flag.
+ */
+ static void registerContentObserver(Context context, Handler handler, FilterListener listener) {
+ context.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE), false,
+ new ContentObserver(handler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ if (reloadFromSetting(context)) {
+ listener.onFilterUpdated();
+ }
+ }
+ }, UserHandle.USER_ALL);
+ }
+
+ /**
+ * Returns bar visibilities based on POLICY_CONTROL_AUTO filters and window policies.
+ * @return int[], where the first value is the inset types that should be shown, and the second
+ * is the inset types that should be hidden.
+ */
+ @WindowInsets.Type.InsetsType
+ static int[] getBarVisibilities(String packageName) {
+ int hideTypes = 0;
+ int showTypes = 0;
+ if (matchesStatusFilter(packageName)) {
+ hideTypes |= WindowInsets.Type.statusBars();
+ } else {
+ showTypes |= WindowInsets.Type.statusBars();
+ }
+ if (matchesNavigationFilter(packageName)) {
+ hideTypes |= WindowInsets.Type.navigationBars();
+ } else {
+ showTypes |= WindowInsets.Type.navigationBars();
+ }
+
+ return new int[] {showTypes, hideTypes};
+ }
+
+ private static boolean matchesStatusFilter(String packageName) {
+ return sImmersiveStatusFilter != null && sImmersiveStatusFilter.matches(packageName);
+ }
+
+ private static boolean matchesNavigationFilter(String packageName) {
+ return sImmersiveNavigationFilter != null
+ && sImmersiveNavigationFilter.matches(packageName);
+ }
+
+ private static void setFilters(String value) {
+ if (DEBUG) Slog.d(TAG, "setFilters: " + value);
+ sImmersiveStatusFilter = null;
+ sImmersiveNavigationFilter = null;
+ if (value != null) {
+ String[] nvps = value.split(":");
+ for (String nvp : nvps) {
+ int i = nvp.indexOf('=');
+ if (i == -1) continue;
+ String n = nvp.substring(0, i);
+ String v = nvp.substring(i + 1);
+ if (n.equals(NAME_IMMERSIVE_FULL)) {
+ Filter f = Filter.parse(v);
+ sImmersiveStatusFilter = sImmersiveNavigationFilter = f;
+ } else if (n.equals(NAME_IMMERSIVE_STATUS)) {
+ Filter f = Filter.parse(v);
+ sImmersiveStatusFilter = f;
+ } else if (n.equals(NAME_IMMERSIVE_NAVIGATION)) {
+ Filter f = Filter.parse(v);
+ sImmersiveNavigationFilter = f;
+ }
+ }
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "immersiveStatusFilter: " + sImmersiveStatusFilter);
+ Slog.d(TAG, "immersiveNavigationFilter: " + sImmersiveNavigationFilter);
+ }
+ }
+
+ private static class Filter {
+ private static final String ALL = "*";
+
+ private final ArraySet<String> mWhitelist;
+ private final ArraySet<String> mBlacklist;
+
+ private Filter(ArraySet<String> whitelist, ArraySet<String> blacklist) {
+ mWhitelist = whitelist;
+ mBlacklist = blacklist;
+ }
+
+ boolean matches(String packageName) {
+ if (packageName == null) return false;
+ if (onBlacklist(packageName)) return false;
+ return onWhitelist(packageName);
+ }
+
+ private boolean onBlacklist(String packageName) {
+ return mBlacklist.contains(packageName) || mBlacklist.contains(ALL);
+ }
+
+ private boolean onWhitelist(String packageName) {
+ return mWhitelist.contains(ALL) || mWhitelist.contains(packageName);
+ }
+
+ void dump(PrintWriter pw) {
+ pw.print("Filter[");
+ dump("whitelist", mWhitelist, pw); pw.print(',');
+ dump("blacklist", mBlacklist, pw); pw.print(']');
+ }
+
+ private void dump(String name, ArraySet<String> set, PrintWriter pw) {
+ pw.print(name); pw.print("=(");
+ int n = set.size();
+ for (int i = 0; i < n; i++) {
+ if (i > 0) pw.print(',');
+ pw.print(set.valueAt(i));
+ }
+ pw.print(')');
+ }
+
+ @Override
+ public String toString() {
+ StringWriter sw = new StringWriter();
+ dump(new PrintWriter(sw, true));
+ return sw.toString();
+ }
+
+ // value = comma-delimited list of tokens, where token = (package name|*)
+ // e.g. "com.package1", or "com.android.systemui, com.android.keyguard" or "*"
+ static Filter parse(String value) {
+ if (value == null) return null;
+ ArraySet<String> whitelist = new ArraySet<String>();
+ ArraySet<String> blacklist = new ArraySet<String>();
+ for (String token : value.split(",")) {
+ token = token.trim();
+ if (token.startsWith("-") && token.length() > 1) {
+ token = token.substring(1);
+ blacklist.add(token);
+ } else {
+ whitelist.add(token);
+ }
+ }
+ return new Filter(whitelist, blacklist);
+ }
+ }
+
+ /**
+ * Interface to listen for updates to the filter triggered by the content observer listening to
+ * the SYSTEM_BAR_VISIBILITY_OVERRIDE flag.
+ */
+ interface FilterListener {
+
+ /** Callback triggered when the content observer updates the filter. */
+ void onFilterUpdated();
+ }
+
+ private BarControlPolicy() {}
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java b/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java
new file mode 100644
index 000000000000..a831464e7987
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wm;
+
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.IDisplayWindowInsetsController;
+import android.view.InsetsController;
+import android.view.InsetsSourceControl;
+import android.view.InsetsState;
+import android.view.WindowInsets;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.systemui.TransactionPool;
+import com.android.systemui.dagger.qualifiers.Main;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Controller that maps between displays and {@link IDisplayWindowInsetsController} in order to
+ * give system bar control to SystemUI.
+ * {@link R.bool#config_remoteInsetsControllerControlsSystemBars} determines whether this controller
+ * takes control or not.
+ */
+@Singleton
+public class DisplaySystemBarsController extends DisplayImeController {
+
+ private static final String TAG = "DisplaySystemBarsController";
+
+ private SparseArray<PerDisplay> mPerDisplaySparseArray;
+
+ @Inject
+ public DisplaySystemBarsController(
+ SystemWindows syswin,
+ DisplayController displayController,
+ @Main Handler mainHandler,
+ TransactionPool transactionPool) {
+ super(syswin, displayController, mainHandler, transactionPool);
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) {
+ PerDisplay pd = new PerDisplay(displayId);
+ try {
+ mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, pd);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Unable to set insets controller on display " + displayId);
+ }
+ // Lazy loading policy control filters instead of during boot.
+ if (mPerDisplaySparseArray == null) {
+ mPerDisplaySparseArray = new SparseArray<>();
+ BarControlPolicy.reloadFromSetting(mSystemWindows.mContext);
+ BarControlPolicy.registerContentObserver(mSystemWindows.mContext, mHandler, () -> {
+ int size = mPerDisplaySparseArray.size();
+ for (int i = 0; i < size; i++) {
+ mPerDisplaySparseArray.valueAt(i).modifyDisplayWindowInsets();
+ }
+ });
+ }
+ mPerDisplaySparseArray.put(displayId, pd);
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ try {
+ mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, null);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Unable to remove insets controller on display " + displayId);
+ }
+ mPerDisplaySparseArray.remove(displayId);
+ }
+
+ @VisibleForTesting
+ class PerDisplay extends IDisplayWindowInsetsController.Stub {
+
+ int mDisplayId;
+ InsetsController mInsetsController;
+ InsetsState mInsetsState = new InsetsState();
+ String mPackageName;
+
+ PerDisplay(int displayId) {
+ mDisplayId = displayId;
+ mInsetsController = new InsetsController(
+ new DisplaySystemBarsInsetsControllerHost(mHandler, this));
+ }
+
+ @Override
+ public void insetsChanged(InsetsState insetsState) {
+ if (mInsetsState.equals(insetsState)) {
+ return;
+ }
+ mInsetsState.set(insetsState, true /* copySources */);
+ mInsetsController.onStateChanged(insetsState);
+ if (mPackageName != null) {
+ modifyDisplayWindowInsets();
+ }
+ }
+
+ @Override
+ public void insetsControlChanged(InsetsState insetsState,
+ InsetsSourceControl[] activeControls) {
+ mInsetsController.onControlsChanged(activeControls);
+ }
+
+ @Override
+ public void hideInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme) {
+ mInsetsController.hide(types);
+ }
+
+ @Override
+ public void showInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme) {
+ mInsetsController.show(types);
+ }
+
+ @Override
+ public void topFocusedWindowChanged(String packageName) {
+ // If both package names are null or both package names are equal, return.
+ if (mPackageName == packageName
+ || (mPackageName != null && mPackageName.equals(packageName))) {
+ return;
+ }
+ mPackageName = packageName;
+ modifyDisplayWindowInsets();
+ }
+
+ private void modifyDisplayWindowInsets() {
+ if (mPackageName == null) {
+ return;
+ }
+ int[] barVisibilities = BarControlPolicy.getBarVisibilities(mPackageName);
+ updateInsetsState(barVisibilities[0], /* visible= */ true);
+ updateInsetsState(barVisibilities[1], /* visible= */ false);
+ showInsets(barVisibilities[0], /* fromIme= */ false);
+ hideInsets(barVisibilities[1], /* fromIme= */ false);
+ try {
+ mSystemWindows.mWmService.modifyDisplayWindowInsets(mDisplayId, mInsetsState);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Unable to update window manager service.");
+ }
+ }
+
+ private void updateInsetsState(@WindowInsets.Type.InsetsType int types, boolean visible) {
+ ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
+ for (int i = internalTypes.size() - 1; i >= 0; i--) {
+ mInsetsState.getSource(internalTypes.valueAt(i)).setVisible(visible);
+ }
+ }
+ }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsInsetsControllerHost.java b/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsInsetsControllerHost.java
new file mode 100644
index 000000000000..2f8da44ba851
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsInsetsControllerHost.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wm;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.IDisplayWindowInsetsController;
+import android.view.InsetsController;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+import android.view.SyncRtSurfaceTransactionApplier;
+import android.view.WindowInsets;
+import android.view.WindowInsetsAnimation;
+import android.view.WindowInsetsController;
+import android.view.inputmethod.InputMethodManager;
+
+import java.util.List;
+
+/**
+ * Implements {@link InsetsController.Host} for usage by
+ * {@link DisplaySystemBarsController.PerDisplay} instances in {@link DisplaySystemBarsController}.
+ * @hide
+ */
+public class DisplaySystemBarsInsetsControllerHost implements InsetsController.Host {
+
+ private static final String TAG = DisplaySystemBarsInsetsControllerHost.class.getSimpleName();
+
+ private final Handler mHandler;
+ private final IDisplayWindowInsetsController mController;
+ private final float[] mTmpFloat9 = new float[9];
+
+ public DisplaySystemBarsInsetsControllerHost(
+ Handler handler, IDisplayWindowInsetsController controller) {
+ mHandler = handler;
+ mController = controller;
+ }
+
+ @Override
+ public Handler getHandler() {
+ return mHandler;
+ }
+
+ @Override
+ public void notifyInsetsChanged() {
+ // no-op
+ }
+
+ @Override
+ public void dispatchWindowInsetsAnimationPrepare(@NonNull WindowInsetsAnimation animation) {
+ // no-op
+ }
+
+ @Override
+ public WindowInsetsAnimation.Bounds dispatchWindowInsetsAnimationStart(
+ @NonNull WindowInsetsAnimation animation,
+ @NonNull WindowInsetsAnimation.Bounds bounds) {
+ return null;
+ }
+
+ @Override
+ public WindowInsets dispatchWindowInsetsAnimationProgress(@NonNull WindowInsets insets,
+ @NonNull List<WindowInsetsAnimation> runningAnimations) {
+ return null;
+ }
+
+ @Override
+ public void dispatchWindowInsetsAnimationEnd(@NonNull WindowInsetsAnimation animation) {
+ // no-op
+ }
+
+ @Override
+ public void applySurfaceParams(final SyncRtSurfaceTransactionApplier.SurfaceParams... params) {
+ for (int i = params.length - 1; i >= 0; i--) {
+ SyncRtSurfaceTransactionApplier.applyParams(
+ new SurfaceControl.Transaction(), params[i], mTmpFloat9);
+ }
+
+ }
+
+ @Override
+ public void updateCompatSysUiVisibility(
+ @InsetsState.InternalInsetsType int type, boolean visible, boolean hasControl) {
+ // no-op
+ }
+
+ @Override
+ public void onInsetsModified(InsetsState insetsState) {
+ try {
+ mController.insetsChanged(insetsState);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to send insets to controller");
+ }
+ }
+
+ @Override
+ public boolean hasAnimationCallbacks() {
+ return false;
+ }
+
+ @Override
+ public void setSystemBarsAppearance(
+ @WindowInsetsController.Appearance int appearance,
+ @WindowInsetsController.Appearance int mask) {
+ // no-op
+ }
+
+ @Override
+ public @WindowInsetsController.Appearance int getSystemBarsAppearance() {
+ return 0;
+ }
+
+ @Override
+ public void setSystemBarsBehavior(@WindowInsetsController.Behavior int behavior) {
+ // no-op
+ }
+
+ @Override
+ public @WindowInsetsController.Behavior int getSystemBarsBehavior() {
+ return 0;
+ }
+
+ @Override
+ public void releaseSurfaceControlFromRt(SurfaceControl surfaceControl) {
+ surfaceControl.release();
+ }
+
+ @Override
+ public void addOnPreDrawRunnable(Runnable r) {
+ mHandler.post(r);
+ }
+
+ @Override
+ public void postInsetsAnimationCallback(Runnable r) {
+ mHandler.post(r);
+ }
+
+ @Override
+ public InputMethodManager getInputMethodManager() {
+ return null;
+ }
+
+ @Override
+ public String getRootViewTitle() {
+ return null;
+ }
+
+ @Override
+ public int dipToPx(int dips) {
+ return 0;
+ }
+
+ @Override
+ public IBinder getWindowToken() {
+ return null;
+ }
+}
diff --git a/packages/CarSystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java b/packages/CarSystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
index fe59cbf20a13..d769cacadf1d 100644
--- a/packages/CarSystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
@@ -33,6 +33,7 @@ import androidx.test.internal.runner.ClassPathScanner.ExternalClassNameFilter;
import com.android.systemui.SysuiBaseFragmentTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.car.CarSystemUiTest;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -55,6 +56,7 @@ import java.util.Collections;
* test suite causes errors, such as the incorrect settings provider being cached.
* For an example, see {@link com.android.systemui.DependencyTest}.
*/
+@CarSystemUiTest
@RunWith(AndroidTestingRunner.class)
@SmallTest
public class AAAPlusPlusVerifySysuiRequiredTestPropertiesTest extends SysuiTestCase {
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/hvac/HvacControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/hvac/HvacControllerTest.java
index 7996170ba7d6..e179ef1ce2a4 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/hvac/HvacControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/hvac/HvacControllerTest.java
@@ -32,6 +32,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.car.CarServiceProvider;
+import com.android.systemui.car.CarSystemUiTest;
import org.junit.Before;
import org.junit.Test;
@@ -39,6 +40,7 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+@CarSystemUiTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/keyguard/CarKeyguardViewControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/keyguard/CarKeyguardViewControllerTest.java
index 189e240169c3..62dc23624520 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/keyguard/CarKeyguardViewControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/keyguard/CarKeyguardViewControllerTest.java
@@ -41,6 +41,7 @@ import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.car.CarServiceProvider;
+import com.android.systemui.car.CarSystemUiTest;
import com.android.systemui.car.navigationbar.CarNavigationBarController;
import com.android.systemui.car.window.OverlayViewGlobalStateController;
import com.android.systemui.keyguard.DismissCallbackRegistry;
@@ -59,6 +60,7 @@ import org.mockito.MockitoAnnotations;
import dagger.Lazy;
+@CarSystemUiTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/ButtonRoleHolderControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/ButtonRoleHolderControllerTest.java
index a57736bb3502..4b8268052324 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/ButtonRoleHolderControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/ButtonRoleHolderControllerTest.java
@@ -39,6 +39,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.car.CarDeviceProvisionedController;
+import com.android.systemui.car.CarSystemUiTest;
import com.android.systemui.tests.R;
import org.junit.Before;
@@ -49,6 +50,7 @@ import org.mockito.MockitoAnnotations;
import java.util.List;
+@CarSystemUiTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/ButtonSelectionStateControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/ButtonSelectionStateControllerTest.java
index 893057e222a9..f623c26d12b6 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/ButtonSelectionStateControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/ButtonSelectionStateControllerTest.java
@@ -28,6 +28,7 @@ import android.widget.LinearLayout;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.car.CarSystemUiTest;
import com.android.systemui.tests.R;
import org.junit.Before;
@@ -38,6 +39,7 @@ import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.List;
+@CarSystemUiTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarControllerTest.java
index e84e42c77245..dec8b8ecdfb4 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarControllerTest.java
@@ -31,6 +31,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.car.CarSystemUiTest;
import com.android.systemui.car.hvac.HvacController;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -41,6 +42,7 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+@CarSystemUiTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarTest.java
index 0caa86f0eab2..d9edfa960858 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarTest.java
@@ -45,6 +45,7 @@ import com.android.internal.view.AppearanceRegion;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.car.CarDeviceProvisionedController;
+import com.android.systemui.car.CarSystemUiTest;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.LightBarController;
@@ -63,6 +64,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+@CarSystemUiTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarViewTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarViewTest.java
index 19e394f69af4..47fd8201d197 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarViewTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarViewTest.java
@@ -30,6 +30,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.car.CarSystemUiTest;
import org.junit.After;
import org.junit.Before;
@@ -38,6 +39,7 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+@CarSystemUiTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationButtonTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationButtonTest.java
index bcaa5e9a03ee..173f5487c728 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationButtonTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationButtonTest.java
@@ -37,6 +37,7 @@ import android.widget.LinearLayout;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.car.CarSystemUiTest;
import com.android.systemui.statusbar.AlphaOptimizedImageView;
import com.android.systemui.tests.R;
@@ -45,6 +46,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatcher;
+@CarSystemUiTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java
index ccaeb458fe54..384888ab42c3 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java
@@ -30,6 +30,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.car.CarDeviceProvisionedController;
+import com.android.systemui.car.CarSystemUiTest;
import com.android.systemui.car.window.OverlayViewGlobalStateController;
import org.junit.Before;
@@ -38,6 +39,7 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+@CarSystemUiTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/NotificationVisibilityLoggerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/NotificationVisibilityLoggerTest.java
index 89dac58cd2a7..d51aeb18135d 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/NotificationVisibilityLoggerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/NotificationVisibilityLoggerTest.java
@@ -37,6 +37,7 @@ import com.android.car.notification.NotificationDataManager;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.car.CarSystemUiTest;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -49,6 +50,7 @@ import org.mockito.MockitoAnnotations;
import java.util.Collections;
+@CarSystemUiTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/SideLoadedAppDetectorTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/SideLoadedAppDetectorTest.java
index 77620f3fb345..421e2109356d 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/SideLoadedAppDetectorTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/SideLoadedAppDetectorTest.java
@@ -37,6 +37,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.car.CarDeviceProvisionedController;
+import com.android.systemui.car.CarSystemUiTest;
import org.junit.Before;
import org.junit.Test;
@@ -44,6 +45,7 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+@CarSystemUiTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/SideLoadedAppListenerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/SideLoadedAppListenerTest.java
index 73f9f6a55afc..20576e9ec11f 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/SideLoadedAppListenerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/SideLoadedAppListenerTest.java
@@ -35,6 +35,7 @@ import android.view.DisplayInfo;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.car.CarSystemUiTest;
import org.junit.Before;
import org.junit.Test;
@@ -46,6 +47,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+@CarSystemUiTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewControllerTest.java
index 797dbf515b7e..2e9d43b595a1 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewControllerTest.java
@@ -36,6 +36,7 @@ import android.view.ViewGroup;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.car.CarSystemUiTest;
import com.android.systemui.car.window.OverlayViewGlobalStateController;
import org.junit.Before;
@@ -44,6 +45,7 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+@CarSystemUiTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewMediatorTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewMediatorTest.java
index a808e2d40e26..de6feb64391f 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewMediatorTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewMediatorTest.java
@@ -25,6 +25,7 @@ import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import com.android.systemui.car.CarServiceProvider;
+import com.android.systemui.car.CarSystemUiTest;
import org.junit.Before;
import org.junit.Test;
@@ -32,6 +33,7 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+@CarSystemUiTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifierTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifierTest.java
index eca51e34995c..31f1170c9603 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifierTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifierTest.java
@@ -24,6 +24,8 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadsetClient;
import android.content.Intent;
import android.os.Handler;
@@ -33,25 +35,32 @@ import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.car.CarSystemUiTest;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+@CarSystemUiTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
public class ConnectedDeviceVoiceRecognitionNotifierTest extends SysuiTestCase {
private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
+ private static final String BLUETOOTH_REMOTE_ADDRESS = "00:11:22:33:44:55";
private ConnectedDeviceVoiceRecognitionNotifier mVoiceRecognitionNotifier;
+ private TestableLooper mTestableLooper;
private Handler mTestHandler;
+ private BluetoothDevice mBluetoothDevice;
@Before
public void setUp() throws Exception {
- TestableLooper testableLooper = TestableLooper.get(this);
- mTestHandler = spy(new Handler(testableLooper.getLooper()));
+ mTestableLooper = TestableLooper.get(this);
+ mTestHandler = spy(new Handler(mTestableLooper.getLooper()));
+ mBluetoothDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(
+ BLUETOOTH_REMOTE_ADDRESS);
mVoiceRecognitionNotifier = new ConnectedDeviceVoiceRecognitionNotifier(
mContext, mTestHandler);
mVoiceRecognitionNotifier.onBootCompleted();
@@ -61,8 +70,10 @@ public class ConnectedDeviceVoiceRecognitionNotifierTest extends SysuiTestCase {
public void testReceiveIntent_started_showToast() {
Intent intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
intent.putExtra(BluetoothHeadsetClient.EXTRA_VOICE_RECOGNITION, VOICE_RECOGNITION_STARTED);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
+
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
- waitForIdleSync();
+ mTestableLooper.processAllMessages();
verify(mTestHandler).post(any());
}
@@ -71,8 +82,10 @@ public class ConnectedDeviceVoiceRecognitionNotifierTest extends SysuiTestCase {
public void testReceiveIntent_invalidExtra_noToast() {
Intent intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
intent.putExtra(BluetoothHeadsetClient.EXTRA_VOICE_RECOGNITION, INVALID_VALUE);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
+
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
- waitForIdleSync();
+ mTestableLooper.processAllMessages();
verify(mTestHandler, never()).post(any());
}
@@ -80,8 +93,10 @@ public class ConnectedDeviceVoiceRecognitionNotifierTest extends SysuiTestCase {
@Test
public void testReceiveIntent_noExtra_noToast() {
Intent intent = new Intent(BluetoothHeadsetClient.ACTION_AG_EVENT);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
+
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
- waitForIdleSync();
+ mTestableLooper.processAllMessages();
verify(mTestHandler, never()).post(any());
}
@@ -89,8 +104,10 @@ public class ConnectedDeviceVoiceRecognitionNotifierTest extends SysuiTestCase {
@Test
public void testReceiveIntent_invalidIntent_noToast() {
Intent intent = new Intent(BluetoothHeadsetClient.ACTION_AUDIO_STATE_CHANGED);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
+
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
- waitForIdleSync();
+ mTestableLooper.processAllMessages();
verify(mTestHandler, never()).post(any());
}
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayPanelViewControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayPanelViewControllerTest.java
index 45a05ac69bd7..7311cdb68a3c 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayPanelViewControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayPanelViewControllerTest.java
@@ -39,6 +39,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.car.CarDeviceProvisionedController;
+import com.android.systemui.car.CarSystemUiTest;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.tests.R;
@@ -52,6 +53,7 @@ import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.List;
+@CarSystemUiTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewControllerTest.java
index c24a3b52e348..e784761f6d5d 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewControllerTest.java
@@ -29,6 +29,7 @@ import android.view.ViewGroup;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.car.CarSystemUiTest;
import com.android.systemui.tests.R;
import org.junit.Before;
@@ -39,6 +40,7 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+@CarSystemUiTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java
index cba42e5a9be4..20f9bc8ec1cb 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java
@@ -32,6 +32,7 @@ import android.view.ViewStub;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.car.CarSystemUiTest;
import com.android.systemui.car.navigationbar.CarNavigationBarController;
import com.android.systemui.tests.R;
@@ -44,6 +45,7 @@ import org.mockito.MockitoAnnotations;
import java.util.Arrays;
+@CarSystemUiTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/wm/BarControlPolicyTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/wm/BarControlPolicyTest.java
new file mode 100644
index 000000000000..da7cb8e4f6ac
--- /dev/null
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/wm/BarControlPolicyTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wm;
+
+import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowInsets.Type.statusBars;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.settings.CarSettings;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class BarControlPolicyTest extends SysuiTestCase {
+
+ private static final String PACKAGE_NAME = "sample.app";
+
+ @Before
+ public void setUp() {
+ BarControlPolicy.reset();
+ }
+
+ @After
+ public void tearDown() {
+ Settings.Global.clearProviderForTest();
+ }
+
+ @Test
+ public void reloadFromSetting_notSet_doesNotSetFilters() {
+ BarControlPolicy.reloadFromSetting(mContext);
+
+ assertThat(BarControlPolicy.sImmersiveStatusFilter).isNull();
+ }
+
+ @Test
+ public void reloadFromSetting_invalidPolicyControlString_doesNotSetFilters() {
+ String text = "sample text";
+ Settings.Global.putString(
+ mContext.getContentResolver(),
+ CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE,
+ text
+ );
+
+ BarControlPolicy.reloadFromSetting(mContext);
+
+ assertThat(BarControlPolicy.sImmersiveStatusFilter).isNull();
+ }
+
+ @Test
+ public void reloadFromSetting_validPolicyControlString_setsFilters() {
+ String text = "immersive.status=" + PACKAGE_NAME;
+ Settings.Global.putString(
+ mContext.getContentResolver(),
+ CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE,
+ text
+ );
+
+ BarControlPolicy.reloadFromSetting(mContext);
+
+ assertThat(BarControlPolicy.sImmersiveStatusFilter).isNotNull();
+ }
+
+ @Test
+ public void reloadFromSetting_filtersSet_doesNotSetFiltersAgain() {
+ String text = "immersive.status=" + PACKAGE_NAME;
+ Settings.Global.putString(
+ mContext.getContentResolver(),
+ CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE,
+ text
+ );
+
+ BarControlPolicy.reloadFromSetting(mContext);
+
+ assertThat(BarControlPolicy.reloadFromSetting(mContext)).isFalse();
+ }
+
+ @Test
+ public void getBarVisibilities_policyControlNotSet_showsSystemBars() {
+ int[] visibilities = BarControlPolicy.getBarVisibilities(PACKAGE_NAME);
+
+ assertThat(visibilities[0]).isEqualTo(statusBars() | navigationBars());
+ assertThat(visibilities[1]).isEqualTo(0);
+ }
+
+ @Test
+ public void getBarVisibilities_immersiveStatusForAppAndMatchingApp_hidesStatusBar() {
+ Settings.Global.putString(
+ mContext.getContentResolver(),
+ CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE,
+ "immersive.status=" + PACKAGE_NAME);
+ BarControlPolicy.reloadFromSetting(mContext);
+
+ int[] visibilities = BarControlPolicy.getBarVisibilities(PACKAGE_NAME);
+
+ assertThat(visibilities[0]).isEqualTo(navigationBars());
+ assertThat(visibilities[1]).isEqualTo(statusBars());
+ }
+
+ @Test
+ public void getBarVisibilities_immersiveStatusForAppAndNonMatchingApp_showsSystemBars() {
+ Settings.Global.putString(
+ mContext.getContentResolver(),
+ CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE,
+ "immersive.status=" + PACKAGE_NAME);
+ BarControlPolicy.reloadFromSetting(mContext);
+
+ int[] visibilities = BarControlPolicy.getBarVisibilities("sample2.app");
+
+ assertThat(visibilities[0]).isEqualTo(statusBars() | navigationBars());
+ assertThat(visibilities[1]).isEqualTo(0);
+ }
+
+ @Test
+ public void getBarVisibilities_immersiveStatusForAppsAndNonApp_showsSystemBars() {
+ Settings.Global.putString(
+ mContext.getContentResolver(),
+ CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE,
+ "immersive.status=apps");
+ BarControlPolicy.reloadFromSetting(mContext);
+
+ int[] visibilities = BarControlPolicy.getBarVisibilities(PACKAGE_NAME);
+
+ assertThat(visibilities[0]).isEqualTo(statusBars() | navigationBars());
+ assertThat(visibilities[1]).isEqualTo(0);
+ }
+
+ @Test
+ public void getBarVisibilities_immersiveFullForAppAndMatchingApp_hidesSystemBars() {
+ Settings.Global.putString(
+ mContext.getContentResolver(),
+ CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE,
+ "immersive.full=" + PACKAGE_NAME);
+ BarControlPolicy.reloadFromSetting(mContext);
+
+ int[] visibilities = BarControlPolicy.getBarVisibilities(PACKAGE_NAME);
+
+ assertThat(visibilities[0]).isEqualTo(0);
+ assertThat(visibilities[1]).isEqualTo(statusBars() | navigationBars());
+ }
+
+ @Test
+ public void getBarVisibilities_immersiveFullForAppAndNonMatchingApp_showsSystemBars() {
+ Settings.Global.putString(
+ mContext.getContentResolver(),
+ CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE,
+ "immersive.full=" + PACKAGE_NAME);
+ BarControlPolicy.reloadFromSetting(mContext);
+
+ int[] visibilities = BarControlPolicy.getBarVisibilities("sample2.app");
+
+ assertThat(visibilities[0]).isEqualTo(statusBars() | navigationBars());
+ assertThat(visibilities[1]).isEqualTo(0);
+ }
+
+ @Test
+ public void getBarVisibilities_immersiveFullForAppsAndNonApp_showsSystemBars() {
+ Settings.Global.putString(
+ mContext.getContentResolver(),
+ CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE,
+ "immersive.full=apps");
+ BarControlPolicy.reloadFromSetting(mContext);
+
+ int[] visibilities = BarControlPolicy.getBarVisibilities(PACKAGE_NAME);
+
+ assertThat(visibilities[0]).isEqualTo(statusBars() | navigationBars());
+ assertThat(visibilities[1]).isEqualTo(0);
+ }
+}
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/wm/DisplaySystemBarsControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/wm/DisplaySystemBarsControllerTest.java
new file mode 100644
index 000000000000..29cc8eec4bc3
--- /dev/null
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/wm/DisplaySystemBarsControllerTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
+
+import android.car.settings.CarSettings;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.IWindowManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.TransactionPool;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class DisplaySystemBarsControllerTest extends SysuiTestCase {
+
+ private DisplaySystemBarsController mController;
+
+ private static final int DISPLAY_ID = 1;
+
+ @Mock
+ private SystemWindows mSystemWindows;
+ @Mock
+ private IWindowManager mIWindowManager;
+ @Mock
+ private DisplayController mDisplayController;
+ @Mock
+ private Handler mHandler;
+ @Mock
+ private TransactionPool mTransactionPool;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mSystemWindows.mContext = mContext;
+ mSystemWindows.mWmService = mIWindowManager;
+
+ mController = new DisplaySystemBarsController(
+ mSystemWindows,
+ mDisplayController,
+ mHandler,
+ mTransactionPool
+ );
+ }
+
+ @Test
+ public void onDisplayAdded_setsDisplayWindowInsetsControllerOnWMService()
+ throws RemoteException {
+ mController.onDisplayAdded(DISPLAY_ID);
+
+ verify(mIWindowManager).setDisplayWindowInsetsController(
+ eq(DISPLAY_ID), any(DisplaySystemBarsController.PerDisplay.class));
+ }
+
+ @Test
+ public void onDisplayAdded_loadsBarControlPolicyFilters() {
+ String text = "sample text";
+ Settings.Global.putString(
+ mContext.getContentResolver(),
+ CarSettings.Global.SYSTEM_BAR_VISIBILITY_OVERRIDE,
+ text
+ );
+
+ mController.onDisplayAdded(DISPLAY_ID);
+
+ assertThat(BarControlPolicy.sSettingValue).isEqualTo(text);
+ }
+
+ @Test
+ public void onDisplayRemoved_unsetsDisplayWindowInsetsControllerInWMService()
+ throws RemoteException {
+ mController.onDisplayAdded(DISPLAY_ID);
+
+ mController.onDisplayRemoved(DISPLAY_ID);
+
+ verify(mIWindowManager).setDisplayWindowInsetsController(
+ DISPLAY_ID, /* displayWindowInsetsController= */ null);
+ }
+}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 319b44ce216f..8253c5e642f3 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -320,6 +320,11 @@
<!-- Permissions required for CTS test - AdbManagerTest -->
<uses-permission android:name="android.permission.MANAGE_DEBUGGING" />
+ <!-- Permissions required for ATS tests - AtsCarHostTestCases, AtsCarDeviceApp -->
+ <uses-permission android:name="android.car.permission.CAR_DRIVING_STATE" />
+ <!-- Permissions required for ATS tests - AtsDeviceInfo, AtsAudioDeviceTestCases -->
+ <uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME" />
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/res-keyguard/drawable/kg_emergency_button_background.xml b/packages/SystemUI/res-keyguard/drawable/kg_emergency_button_background.xml
new file mode 100644
index 000000000000..cc2089f69287
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/kg_emergency_button_background.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?attr/wallpaperTextColorSecondary">
+ <item android:id="@android:id/background">
+ <shape
+ android:color="@android:color/transparent">
+ <stroke android:width="1dp" android:color="?attr/wallpaperTextColorSecondary"/>
+ <corners android:radius="24dp"/>
+ </shape>
+ </item>
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle">
+ <solid android:color="?attr/wallpaperTextColorSecondary"/>
+ <corners android:radius="24dp"/>
+ </shape>
+ </item>
+</ripple> \ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_emergency_carrier_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_emergency_carrier_area.xml
index 3018a022e763..370576b43463 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_emergency_carrier_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_emergency_carrier_area.xml
@@ -33,6 +33,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/Keyguard.TextView"
+ android:layout_marginBottom="8dp"
android:singleLine="true"
android:ellipsize="marquee"
android:visibility="gone"
@@ -42,11 +43,9 @@
<com.android.keyguard.EmergencyButton
android:id="@+id/emergency_call_button"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:layout_marginTop="@dimen/eca_overlap"
+ android:layout_height="32dp"
+ android:layout_marginBottom="12dp"
android:text="@*android:string/lockscreen_emergency_call"
- style="@style/Keyguard.TextView.EmergencyButton"
- android:textAllCaps="@bool/kg_use_all_caps" />
+ style="@style/Keyguard.TextView.EmergencyButton" />
</com.android.keyguard.EmergencyCarrierArea>
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 5f2a946a1b6d..53eb2343d26a 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -23,10 +23,12 @@
<item name="android:textColor">?attr/wallpaperTextColorSecondary</item>
<item name="android:textSize">@dimen/kg_status_line_font_size</item>
</style>
- <style name="Keyguard.TextView.EmergencyButton" parent="@android:style/DeviceDefault.ButtonBar">
+ <style name="Keyguard.TextView.EmergencyButton" parent="Theme.SystemUI">
<item name="android:textColor">?attr/wallpaperTextColorSecondary</item>
- <item name="android:textSize">@dimen/kg_status_line_font_size</item>
- <item name="android:background">@null</item>
+ <item name="android:textSize">14dp</item>
+ <item name="android:background">@drawable/kg_emergency_button_background</item>
+ <item name="android:paddingLeft">12dp</item>
+ <item name="android:paddingRight">12dp</item>
</style>
<style name="Widget.TextView.NumPadKey" parent="@android:style/Widget.TextView">
<item name="android:singleLine">true</item>
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index 5c00af5705e9..436188a83d4f 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -62,7 +62,7 @@
android:gravity="center_vertical"
android:focusable="true"
android:singleLine="true"
- android:ellipsize="end"
+ android:ellipsize="marquee"
android:textAppearance="@style/TextAppearance.QS.Status"
android:layout_marginEnd="4dp"
android:visibility="gone"/>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 8212d6159737..a56f6f56836a 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -92,6 +92,8 @@
<item type="id" name="requires_remeasuring"/>
+ <item type="id" name="secondary_home_handle" />
+
<!-- Whether the icon is from a notification for which targetSdk < L -->
<item type="id" name="icon_is_pre_L"/>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index db45a60ab7c0..f51cffcdfb5c 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2404,17 +2404,26 @@
<!-- Title for notification & dialog that the user's phone last shut down because it got too hot. [CHAR LIMIT=40] -->
<string name="thermal_shutdown_title">Phone turned off due to heat</string>
- <!-- Message body for notification that user's phone last shut down because it got too hot. [CHAR LIMIT=100] -->
- <string name="thermal_shutdown_message">Your phone is now running normally</string>
- <!-- Text body for dialog alerting user that their phone last shut down because it got too hot. [CHAR LIMIT=450] -->
+ <!-- Message body for notification that user's phone last shut down because it got too hot. [CHAR LIMIT=120] -->
+ <string name="thermal_shutdown_message">Your phone is now running normally.\nTap for more info</string>
+ <!-- Text body for dialog alerting user that their phone last shut down because it got too hot. [CHAR LIMIT=500] -->
<string name="thermal_shutdown_dialog_message">Your phone was too hot, so it turned off to cool down. Your phone is now running normally.\n\nYour phone may get too hot if you:\n\t&#8226; Use resource-intensive apps (such as gaming, video, or navigation apps)\n\t&#8226; Download or upload large files\n\t&#8226; Use your phone in high temperatures</string>
+ <!-- Text help link for care instructions for overheating devices [CHAR LIMIT=40] -->
+ <string name="thermal_shutdown_dialog_help_text">See care steps</string>
+ <!-- URL for care instructions for overheating devices -->
+ <string name="thermal_shutdown_dialog_help_url" translatable="false"></string>
<!-- Title for notification (and dialog) that user's phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=30] -->
<string name="high_temp_title">Phone is getting warm</string>
- <!-- Message body for notification that user's phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=100] -->
- <string name="high_temp_notif_message">Some features limited while phone cools down</string>
- <!-- Text body for dialog alerting user that their phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=300] -->
+ <!-- Message body for notification that user's phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=120] -->
+ <string name="high_temp_notif_message">Some features limited while phone cools down.\nTap for more info</string>
+ <!-- Text body for dialog alerting user that their phone has reached a certain temperature and may start to slow down in order to cool down. [CHAR LIMIT=350] -->
<string name="high_temp_dialog_message">Your phone will automatically try to cool down. You can still use your phone, but it may run slower.\n\nOnce your phone has cooled down, it will run normally.</string>
+ <!-- Text help link for care instructions for overheating devices [CHAR LIMIT=40] -->
+ <string name="high_temp_dialog_help_text">See care steps</string>
+ <!-- URL for care instructions for overheating devices -->
+ <string name="high_temp_dialog_help_url" translatable="false"></string>
+
<!-- Title for alarm dialog alerting user the usb adapter has reached a certain temperature that should disconnect charging cable immediately. [CHAR LIMIT=30] -->
<string name="high_temp_alarm_title">Unplug charger</string>
<!-- Text body for dialog alerting user the usb adapter has reached a certain temperature that should disconnect charging cable immediately. [CHAR LIMIT=300] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 3acbfb87c3f4..c07c982b7451 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1072,6 +1072,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
}
+ private boolean isEncryptedOrLockdown(int userId) {
+ final int strongAuth = mStrongAuthTracker.getStrongAuthForUser(userId);
+ final boolean isLockDown =
+ containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW)
+ || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+ final boolean isEncrypted = containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT);
+ return isEncrypted || isLockDown;
+ }
+
public boolean userNeedsStrongAuth() {
return mStrongAuthTracker.getStrongAuthForUser(getCurrentUser())
!= LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
@@ -1248,6 +1257,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
};
+ // Trigger the fingerprint success path so the bouncer can be shown
+ private final FingerprintManager.FingerprintDetectionCallback mFingerprintDetectionCallback
+ = this::handleFingerprintAuthenticated;
+
private FingerprintManager.AuthenticationCallback mFingerprintAuthenticationCallback
= new AuthenticationCallback() {
@@ -2050,8 +2063,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mFingerprintCancelSignal.cancel();
}
mFingerprintCancelSignal = new CancellationSignal();
- mFpm.authenticate(null, mFingerprintCancelSignal, 0, mFingerprintAuthenticationCallback,
- null, userId);
+
+ if (isEncryptedOrLockdown(userId)) {
+ mFpm.detectFingerprint(mFingerprintCancelSignal, mFingerprintDetectionCallback,
+ userId);
+ } else {
+ mFpm.authenticate(null, mFingerprintCancelSignal, 0,
+ mFingerprintAuthenticationCallback, null, userId);
+ }
+
setFingerprintRunningState(BIOMETRIC_STATE_RUNNING);
}
}
@@ -2087,7 +2107,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private boolean isUnlockWithFingerprintPossible(int userId) {
return mFpm != null && mFpm.isHardwareDetected() && !isFingerprintDisabled(userId)
- && mFpm.getEnrolledFingerprints(userId).size() > 0;
+ && mFpm.hasEnrolledTemplates(userId);
}
private boolean isUnlockWithFacePossible(int userId) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
index 40a93e1cdc47..d017bc0e31c2 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
@@ -24,7 +24,8 @@ import android.content.pm.ShortcutInfo;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
-import android.graphics.Rect;
+import android.graphics.Path;
+import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
@@ -41,15 +42,15 @@ import com.android.systemui.R;
*/
public class BubbleIconFactory extends BaseIconFactory {
+ private int mBadgeSize;
+
protected BubbleIconFactory(Context context) {
super(context, context.getResources().getConfiguration().densityDpi,
context.getResources().getDimensionPixelSize(R.dimen.individual_bubble_size));
- }
-
- int getBadgeSize() {
- return mContext.getResources().getDimensionPixelSize(
+ mBadgeSize = mContext.getResources().getDimensionPixelSize(
com.android.launcher3.icons.R.dimen.profile_badge_size);
}
+
/**
* Returns the drawable that the developer has provided to display in the bubble.
*/
@@ -79,25 +80,34 @@ public class BubbleIconFactory extends BaseIconFactory {
* will include the workprofile indicator on the badge if appropriate.
*/
BitmapInfo getBadgeBitmap(Drawable userBadgedAppIcon, boolean isImportantConversation) {
- Bitmap userBadgedBitmap = createIconBitmap(
- userBadgedAppIcon, 1f, getBadgeSize());
- ShadowGenerator shadowGenerator = new ShadowGenerator(getBadgeSize());
- if (!isImportantConversation) {
- Canvas c = new Canvas();
- c.setBitmap(userBadgedBitmap);
- shadowGenerator.recreateIcon(Bitmap.createBitmap(userBadgedBitmap), c);
- return createIconBitmap(userBadgedBitmap);
- } else {
- float ringStrokeWidth = mContext.getResources().getDimensionPixelSize(
+ ShadowGenerator shadowGenerator = new ShadowGenerator(mBadgeSize);
+ Bitmap userBadgedBitmap = createIconBitmap(userBadgedAppIcon, 1f, mBadgeSize);
+
+ if (userBadgedAppIcon instanceof AdaptiveIconDrawable) {
+ userBadgedBitmap = Bitmap.createScaledBitmap(
+ getCircleBitmap((AdaptiveIconDrawable) userBadgedAppIcon, /* size */
+ userBadgedAppIcon.getIntrinsicWidth()),
+ mBadgeSize, mBadgeSize, /* filter */ true);
+ }
+
+ if (isImportantConversation) {
+ final float ringStrokeWidth = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.importance_ring_stroke_width);
- int importantConversationColor = mContext.getResources().getColor(
+ final int importantConversationColor = mContext.getResources().getColor(
com.android.settingslib.R.color.important_conversation, null);
Bitmap badgeAndRing = Bitmap.createBitmap(userBadgedBitmap.getWidth(),
userBadgedBitmap.getHeight(), userBadgedBitmap.getConfig());
Canvas c = new Canvas(badgeAndRing);
- Rect dest = new Rect((int) ringStrokeWidth, (int) ringStrokeWidth,
- c.getHeight() - (int) ringStrokeWidth, c.getWidth() - (int) ringStrokeWidth);
- c.drawBitmap(userBadgedBitmap, null, dest, null);
+
+ final int bitmapTop = (int) ringStrokeWidth;
+ final int bitmapLeft = (int) ringStrokeWidth;
+ final int bitmapWidth = c.getWidth() - 2 * (int) ringStrokeWidth;
+ final int bitmapHeight = c.getHeight() - 2 * (int) ringStrokeWidth;
+
+ Bitmap scaledBitmap = Bitmap.createScaledBitmap(userBadgedBitmap, bitmapWidth,
+ bitmapHeight, /* filter */ true);
+ c.drawBitmap(scaledBitmap, bitmapTop, bitmapLeft, /* paint */null);
+
Paint ringPaint = new Paint();
ringPaint.setStyle(Paint.Style.STROKE);
ringPaint.setColor(importantConversationColor);
@@ -105,11 +115,48 @@ public class BubbleIconFactory extends BaseIconFactory {
ringPaint.setStrokeWidth(ringStrokeWidth);
c.drawCircle(c.getWidth() / 2, c.getHeight() / 2, c.getWidth() / 2 - ringStrokeWidth,
ringPaint);
+
shadowGenerator.recreateIcon(Bitmap.createBitmap(badgeAndRing), c);
return createIconBitmap(badgeAndRing);
+ } else {
+ Canvas c = new Canvas();
+ c.setBitmap(userBadgedBitmap);
+ shadowGenerator.recreateIcon(Bitmap.createBitmap(userBadgedBitmap), c);
+ return createIconBitmap(userBadgedBitmap);
}
}
+ public Bitmap getCircleBitmap(AdaptiveIconDrawable icon, int size) {
+ Drawable foreground = icon.getForeground();
+ Drawable background = icon.getBackground();
+ Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas();
+ canvas.setBitmap(bitmap);
+
+ // Clip canvas to circle.
+ Path circlePath = new Path();
+ circlePath.addCircle(/* x */ size / 2f,
+ /* y */ size / 2f,
+ /* radius */ size / 2f,
+ Path.Direction.CW);
+ canvas.clipPath(circlePath);
+
+ // Draw background.
+ background.setBounds(0, 0, size, size);
+ background.draw(canvas);
+
+ // Draw foreground. The foreground and background drawables are derived from adaptive icons
+ // Some icon shapes fill more space than others, so adaptive icons are normalized to about
+ // the same size. This size is smaller than the original bounds, so we estimate
+ // the difference in this offset.
+ int offset = size / 5;
+ foreground.setBounds(-offset, -offset, size + offset, size + offset);
+ foreground.draw(canvas);
+
+ canvas.setBitmap(null);
+ return bitmap;
+ }
+
/**
* Returns a {@link BitmapInfo} for the entire bubble icon including the badge.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 749b537ea364..c61a36c12059 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -1586,6 +1586,11 @@ public class BubbleStackView extends FrameLayout
Log.d(TAG, "setSelectedBubble: " + bubbleToSelect);
}
+ if (bubbleToSelect == null) {
+ mBubbleData.setShowingOverflow(false);
+ return;
+ }
+
// Ignore this new bubble only if it is the exact same bubble object. Otherwise, we'll want
// to re-render it even if it has the same key (equals() returns true). If the currently
// expanded bubble is removed and instantly re-added, we'll get back a new Bubble instance
@@ -1594,10 +1599,11 @@ public class BubbleStackView extends FrameLayout
if (mExpandedBubble == bubbleToSelect) {
return;
}
- if (bubbleToSelect == null || bubbleToSelect.getKey() != BubbleOverflow.KEY) {
- mBubbleData.setShowingOverflow(false);
- } else {
+
+ if (bubbleToSelect.getKey() == BubbleOverflow.KEY) {
mBubbleData.setShowingOverflow(true);
+ } else {
+ mBubbleData.setShowingOverflow(false);
}
if (mIsExpanded && mIsExpansionAnimating) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
index e8f0e069c98d..d0642ccf9714 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
@@ -33,23 +33,31 @@ class MediaDataCombineLatest @Inject constructor(
init {
dataSource.addListener(object : MediaDataManager.Listener {
override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
- if (oldKey != null && !oldKey.equals(key)) {
- val s = entries[oldKey]?.second
- entries[key] = data to entries[oldKey]?.second
- entries.remove(oldKey)
+ if (oldKey != null && oldKey != key && entries.contains(oldKey)) {
+ entries[key] = data to entries.remove(oldKey)?.second
+ update(key, oldKey)
} else {
entries[key] = data to entries[key]?.second
+ update(key, key)
}
- update(key, oldKey)
}
override fun onMediaDataRemoved(key: String) {
remove(key)
}
})
deviceSource.addListener(object : MediaDeviceManager.Listener {
- override fun onMediaDeviceChanged(key: String, data: MediaDeviceData?) {
- entries[key] = entries[key]?.first to data
- update(key, key)
+ override fun onMediaDeviceChanged(
+ key: String,
+ oldKey: String?,
+ data: MediaDeviceData?
+ ) {
+ if (oldKey != null && oldKey != key && entries.contains(oldKey)) {
+ entries[key] = entries.remove(oldKey)?.first to data
+ update(key, oldKey)
+ } else {
+ entries[key] = entries[key]?.first to data
+ update(key, key)
+ }
}
override fun onKeyRemoved(key: String) {
remove(key)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 299ae5b50aa9..b3277737f397 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -316,18 +316,13 @@ class MediaDataManager(
as MediaSession.Token?
val metadata = mediaControllerFactory.create(token).metadata
- if (metadata == null) {
- // TODO: handle this better, removing media notification
- return
- }
-
// Foreground and Background colors computed from album art
val notif: Notification = sbn.notification
- var artworkBitmap = metadata.getBitmap(MediaMetadata.METADATA_KEY_ART)
+ var artworkBitmap = metadata?.getBitmap(MediaMetadata.METADATA_KEY_ART)
if (artworkBitmap == null) {
- artworkBitmap = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART)
+ artworkBitmap = metadata?.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART)
}
- if (artworkBitmap == null) {
+ if (artworkBitmap == null && metadata != null) {
artworkBitmap = loadBitmapFromUri(metadata)
}
val artWorkIcon = if (artworkBitmap == null) {
@@ -363,16 +358,16 @@ class MediaDataManager(
val smallIconDrawable: Drawable = sbn.notification.smallIcon.loadDrawable(context)
// Song name
- var song: CharSequence? = metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE)
+ var song: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE)
if (song == null) {
- song = metadata.getString(MediaMetadata.METADATA_KEY_TITLE)
+ song = metadata?.getString(MediaMetadata.METADATA_KEY_TITLE)
}
if (song == null) {
song = HybridGroupManager.resolveTitle(notif)
}
// Artist name
- var artist: CharSequence? = metadata.getString(MediaMetadata.METADATA_KEY_ARTIST)
+ var artist: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_ARTIST)
if (artist == null) {
artist = HybridGroupManager.resolveText(notif)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
index 7ae2dc5c0941..143f8496e7aa 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
@@ -71,7 +71,8 @@ class MediaDeviceManager @Inject constructor(
val controller = data.token?.let {
MediaController(context, it)
}
- entry = Token(key, controller, localMediaManagerFactory.create(data.packageName))
+ entry = Token(key, oldKey, controller,
+ localMediaManagerFactory.create(data.packageName))
entries[key] = entry
entry.start()
}
@@ -98,23 +99,24 @@ class MediaDeviceManager @Inject constructor(
}
}
- private fun processDevice(key: String, device: MediaDevice?) {
+ private fun processDevice(key: String, oldKey: String?, device: MediaDevice?) {
val enabled = device != null
val data = MediaDeviceData(enabled, device?.iconWithoutBackground, device?.name)
listeners.forEach {
- it.onMediaDeviceChanged(key, data)
+ it.onMediaDeviceChanged(key, oldKey, data)
}
}
interface Listener {
/** Called when the route has changed for a given notification. */
- fun onMediaDeviceChanged(key: String, data: MediaDeviceData?)
+ fun onMediaDeviceChanged(key: String, oldKey: String?, data: MediaDeviceData?)
/** Called when the notification was removed. */
fun onKeyRemoved(key: String)
}
private inner class Token(
val key: String,
+ val oldKey: String?,
val controller: MediaController?,
val localMediaManager: LocalMediaManager
) : LocalMediaManager.DeviceCallback {
@@ -125,7 +127,7 @@ class MediaDeviceManager @Inject constructor(
set(value) {
if (!started || value != field) {
field = value
- processDevice(key, value)
+ processDevice(key, oldKey, value)
}
}
fun start() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
index 1dca3f1297b1..9e326aaec3c1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
@@ -71,7 +71,7 @@ private fun PlaybackState.computePosition(duration: Long): Long {
/** ViewModel for seek bar in QS media player. */
class SeekBarViewModel @Inject constructor(@Background private val bgExecutor: RepeatableExecutor) {
- private var _data = Progress(false, false, null, null)
+ private var _data = Progress(false, false, null, 0)
set(value) {
field = value
_progress.postValue(value)
@@ -186,10 +186,10 @@ class SeekBarViewModel @Inject constructor(@Background private val bgExecutor: R
val mediaMetadata = controller?.metadata
val seekAvailable = ((playbackState?.actions ?: 0L) and PlaybackState.ACTION_SEEK_TO) != 0L
val position = playbackState?.position?.toInt()
- val duration = mediaMetadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)?.toInt()
+ val duration = mediaMetadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)?.toInt() ?: 0
val enabled = if (playbackState == null ||
playbackState?.getState() == PlaybackState.STATE_NONE ||
- (duration != null && duration <= 0)) false else true
+ (duration <= 0)) false else true
_data = Progress(enabled, seekAvailable, position, duration)
checkIfPollingNeeded()
}
@@ -408,6 +408,6 @@ class SeekBarViewModel @Inject constructor(@Background private val bgExecutor: R
val enabled: Boolean,
val seekAvailable: Boolean,
val elapsedTime: Int?,
- val duration: Int?
+ val duration: Int
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
index ead17867844a..72019315139b 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
@@ -21,7 +21,6 @@ import android.animation.Animator;
import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
-import android.content.Context;
import android.graphics.Rect;
import android.view.SurfaceControl;
@@ -56,13 +55,15 @@ public class PipAnimationController {
public static final int TRANSITION_DIRECTION_TO_PIP = 2;
public static final int TRANSITION_DIRECTION_TO_FULLSCREEN = 3;
public static final int TRANSITION_DIRECTION_TO_SPLIT_SCREEN = 4;
+ public static final int TRANSITION_DIRECTION_REMOVE_STACK = 5;
@IntDef(prefix = { "TRANSITION_DIRECTION_" }, value = {
TRANSITION_DIRECTION_NONE,
TRANSITION_DIRECTION_SAME,
TRANSITION_DIRECTION_TO_PIP,
TRANSITION_DIRECTION_TO_FULLSCREEN,
- TRANSITION_DIRECTION_TO_SPLIT_SCREEN
+ TRANSITION_DIRECTION_TO_SPLIT_SCREEN,
+ TRANSITION_DIRECTION_REMOVE_STACK
})
@Retention(RetentionPolicy.SOURCE)
public @interface TransitionDirection {}
@@ -88,7 +89,7 @@ public class PipAnimationController {
});
@Inject
- PipAnimationController(Context context, PipSurfaceTransactionHelper helper) {
+ PipAnimationController(PipSurfaceTransactionHelper helper) {
mSurfaceTransactionHelper = helper;
}
@@ -338,6 +339,10 @@ public class PipAnimationController {
@Override
void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {
+ if (getTransitionDirection() == TRANSITION_DIRECTION_REMOVE_STACK) {
+ // while removing the pip stack, no extra work needs to be done here.
+ return;
+ }
getSurfaceTransactionHelper()
.resetScale(tx, leash, getDestinationBounds())
.crop(tx, leash, getDestinationBounds())
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index 0141dee04086..c4152fa9a4e7 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -24,6 +24,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static com.android.systemui.pip.PipAnimationController.ANIM_TYPE_ALPHA;
import static com.android.systemui.pip.PipAnimationController.ANIM_TYPE_BOUNDS;
import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_NONE;
+import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK;
import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_SAME;
import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_TO_FULLSCREEN;
import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
@@ -337,23 +338,32 @@ public class PipTaskOrganizer extends TaskOrganizer implements
+ " mInPip=" + mInPip + " mExitingPip=" + mExitingPip + " mToken=" + mToken);
return;
}
- getUpdateHandler().post(() -> {
- try {
- // Reset the task bounds first to ensure the activity configuration is reset as well
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setBounds(mToken, null);
- WindowOrganizer.applyTransaction(wct);
-
- ActivityTaskManager.getService().removeStacksInWindowingModes(
- new int[]{ WINDOWING_MODE_PINNED });
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to remove PiP", e);
- }
- });
+
+ // removePipImmediately is expected when the following animation finishes.
+ mUpdateHandler.post(() -> mPipAnimationController
+ .getAnimator(mLeash, mLastReportedBounds, 1f, 0f)
+ .setTransitionDirection(TRANSITION_DIRECTION_REMOVE_STACK)
+ .setPipAnimationCallback(mPipAnimationCallback)
+ .setDuration(mEnterExitAnimationDuration)
+ .start());
mInitialState.remove(mToken.asBinder());
mExitingPip = true;
}
+ private void removePipImmediately() {
+ try {
+ // Reset the task bounds first to ensure the activity configuration is reset as well
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setBounds(mToken, null);
+ WindowOrganizer.applyTransaction(wct);
+
+ ActivityTaskManager.getService().removeStacksInWindowingModes(
+ new int[]{ WINDOWING_MODE_PINNED });
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to remove PiP", e);
+ }
+ }
+
@Override
public void onTaskAppeared(ActivityManager.RunningTaskInfo info, SurfaceControl leash) {
Objects.requireNonNull(info, "Requires RunningTaskInfo");
@@ -803,7 +813,10 @@ public class PipTaskOrganizer extends TaskOrganizer implements
+ "directly");
}
mLastReportedBounds.set(destinationBounds);
- if (isInPipDirection(direction) && type == ANIM_TYPE_ALPHA) {
+ if (direction == TRANSITION_DIRECTION_REMOVE_STACK) {
+ removePipImmediately();
+ return;
+ } else if (isInPipDirection(direction) && type == ANIM_TYPE_ALPHA) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index 10b04c0129e4..6abbbbeaa397 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -24,6 +24,7 @@ import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioAttributes;
@@ -376,13 +377,15 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
return;
}
mHighTempWarning = true;
+ final String message = mContext.getString(R.string.high_temp_notif_message);
final Notification.Builder nb =
new Notification.Builder(mContext, NotificationChannels.ALERTS)
.setSmallIcon(R.drawable.ic_device_thermostat_24)
.setWhen(0)
.setShowWhen(false)
.setContentTitle(mContext.getString(R.string.high_temp_title))
- .setContentText(mContext.getString(R.string.high_temp_notif_message))
+ .setContentText(message)
+ .setStyle(new Notification.BigTextStyle().bigText(message))
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setContentIntent(pendingBroadcast(ACTION_CLICKED_TEMP_WARNING))
.setDeleteIntent(pendingBroadcast(ACTION_DISMISSED_TEMP_WARNING))
@@ -402,6 +405,23 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
d.setPositiveButton(com.android.internal.R.string.ok, null);
d.setShowForAllUsers(true);
d.setOnDismissListener(dialog -> mHighTempDialog = null);
+ final String url = mContext.getString(R.string.high_temp_dialog_help_url);
+ if (!url.isEmpty()) {
+ d.setNeutralButton(R.string.high_temp_dialog_help_text,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final Intent helpIntent =
+ new Intent(Intent.ACTION_VIEW)
+ .setData(Uri.parse(url))
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ Dependency.get(ActivityStarter.class).startActivity(helpIntent,
+ true /* dismissShade */, resultCode -> {
+ mHighTempDialog = null;
+ });
+ }
+ });
+ }
d.show();
mHighTempDialog = d;
}
@@ -420,19 +440,38 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
d.setPositiveButton(com.android.internal.R.string.ok, null);
d.setShowForAllUsers(true);
d.setOnDismissListener(dialog -> mThermalShutdownDialog = null);
+ final String url = mContext.getString(R.string.thermal_shutdown_dialog_help_url);
+ if (!url.isEmpty()) {
+ d.setNeutralButton(R.string.thermal_shutdown_dialog_help_text,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final Intent helpIntent =
+ new Intent(Intent.ACTION_VIEW)
+ .setData(Uri.parse(url))
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ Dependency.get(ActivityStarter.class).startActivity(helpIntent,
+ true /* dismissShade */, resultCode -> {
+ mThermalShutdownDialog = null;
+ });
+ }
+ });
+ }
d.show();
mThermalShutdownDialog = d;
}
@Override
public void showThermalShutdownWarning() {
+ final String message = mContext.getString(R.string.thermal_shutdown_message);
final Notification.Builder nb =
new Notification.Builder(mContext, NotificationChannels.ALERTS)
.setSmallIcon(R.drawable.ic_device_thermostat_24)
.setWhen(0)
.setShowWhen(false)
.setContentTitle(mContext.getString(R.string.thermal_shutdown_title))
- .setContentText(mContext.getString(R.string.thermal_shutdown_message))
+ .setContentText(message)
+ .setStyle(new Notification.BigTextStyle().bigText(message))
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setContentIntent(pendingBroadcast(ACTION_CLICKED_THERMAL_SHUTDOWN_WARNING))
.setDeleteIntent(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index c4bb4e86e41e..6e4ab9a3323a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -78,6 +78,8 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
private SettingsButton mSettingsButton;
protected View mSettingsContainer;
private PageIndicator mPageIndicator;
+ private TextView mBuildText;
+ private boolean mShouldShowBuildText;
private boolean mQsDisabled;
private QSPanel mQsPanel;
@@ -147,6 +149,7 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
mActionsContainer = findViewById(R.id.qs_footer_actions_container);
mEditContainer = findViewById(R.id.qs_footer_actions_edit_container);
+ mBuildText = findViewById(R.id.build);
// RenderThread is doing more harm than good when touching the header (to expand quick
// settings), so disable it for this view
@@ -162,16 +165,19 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
}
private void setBuildText() {
- TextView v = findViewById(R.id.build);
- if (v == null) return;
+ if (mBuildText == null) return;
if (DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext)) {
- v.setText(mContext.getString(
+ mBuildText.setText(mContext.getString(
com.android.internal.R.string.bugreport_status,
Build.VERSION.RELEASE_OR_CODENAME,
Build.ID));
- v.setVisibility(View.VISIBLE);
+ // Set as selected for marquee before its made visible, then it won't be announced when
+ // it's made visible.
+ mBuildText.setSelected(true);
+ mShouldShowBuildText = true;
} else {
- v.setVisibility(View.GONE);
+ mShouldShowBuildText = false;
+ mBuildText.setSelected(false);
}
}
@@ -321,6 +327,8 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
mMultiUserSwitch.setVisibility(showUserSwitcher() ? View.VISIBLE : View.INVISIBLE);
mEditContainer.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE);
mSettingsButton.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE);
+
+ mBuildText.setVisibility(mExpanded && mShouldShowBuildText ? View.VISIBLE : View.GONE);
}
private boolean showUserSwitcher() {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index d6e1a16bc69e..4a4aa79aadfa 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -57,7 +57,6 @@ import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.os.PowerManager;
import android.os.RemoteException;
import android.provider.Settings;
import android.util.DisplayMetrics;
@@ -214,9 +213,9 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
private Animator mScreenshotAnimation;
private Runnable mOnCompleteRunnable;
private Animator mDismissAnimation;
- private boolean mInDarkMode = false;
- private boolean mDirectionLTR = true;
- private boolean mOrientationPortrait = true;
+ private boolean mInDarkMode;
+ private boolean mDirectionLTR;
+ private boolean mOrientationPortrait;
private float mCornerSizeX;
private float mDismissDeltaY;
@@ -245,9 +244,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
}
};
- /**
- * @param context everything needs a context :(
- */
@Inject
public GlobalScreenshot(
Context context, @Main Resources resources,
@@ -320,6 +316,104 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
inoutInfo.touchableRegion.set(touchRegion);
}
+ void takeScreenshotFullscreen(Consumer<Uri> finisher, Runnable onComplete) {
+ mOnCompleteRunnable = onComplete;
+
+ mDisplay.getRealMetrics(mDisplayMetrics);
+ takeScreenshotInternal(
+ finisher,
+ new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
+ }
+
+ void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds,
+ Insets visibleInsets, int taskId, int userId, ComponentName topComponent,
+ Consumer<Uri> finisher, Runnable onComplete) {
+ // TODO: use task Id, userId, topComponent for smart handler
+
+ mOnCompleteRunnable = onComplete;
+ if (aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) {
+ saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, false);
+ } else {
+ saveScreenshot(screenshot, finisher,
+ new Rect(0, 0, screenshot.getWidth(), screenshot.getHeight()), Insets.NONE,
+ true);
+ }
+ }
+
+ /**
+ * Displays a screenshot selector
+ */
+ @SuppressLint("ClickableViewAccessibility")
+ void takeScreenshotPartial(final Consumer<Uri> finisher, Runnable onComplete) {
+ dismissScreenshot("new screenshot requested", true);
+ mOnCompleteRunnable = onComplete;
+
+ mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
+ mScreenshotSelectorView.setOnTouchListener((v, event) -> {
+ ScreenshotSelectorView view = (ScreenshotSelectorView) v;
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ view.startSelection((int) event.getX(), (int) event.getY());
+ return true;
+ case MotionEvent.ACTION_MOVE:
+ view.updateSelection((int) event.getX(), (int) event.getY());
+ return true;
+ case MotionEvent.ACTION_UP:
+ view.setVisibility(View.GONE);
+ mWindowManager.removeView(mScreenshotLayout);
+ final Rect rect = view.getSelectionRect();
+ if (rect != null) {
+ if (rect.width() != 0 && rect.height() != 0) {
+ // Need mScreenshotLayout to handle it after the view disappears
+ mScreenshotLayout.post(() -> takeScreenshotInternal(finisher, rect));
+ }
+ }
+
+ view.stopSelection();
+ return true;
+ }
+
+ return false;
+ });
+ mScreenshotLayout.post(() -> {
+ mScreenshotSelectorView.setVisibility(View.VISIBLE);
+ mScreenshotSelectorView.requestFocus();
+ });
+ }
+
+ /**
+ * Cancels screenshot request
+ */
+ void stopScreenshot() {
+ // If the selector layer still presents on screen, we remove it and resets its state.
+ if (mScreenshotSelectorView.getSelectionRect() != null) {
+ mWindowManager.removeView(mScreenshotLayout);
+ mScreenshotSelectorView.stopSelection();
+ }
+ }
+
+ /**
+ * Clears current screenshot
+ */
+ void dismissScreenshot(String reason, boolean immediate) {
+ Log.v(TAG, "clearing screenshot: " + reason);
+ mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
+ mScreenshotLayout.getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
+ if (!immediate) {
+ mDismissAnimation = createScreenshotDismissAnimation();
+ mDismissAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ clearScreenshot();
+ }
+ });
+ mDismissAnimation.start();
+ } else {
+ clearScreenshot();
+ }
+ }
+
private void onConfigChanged(Configuration newConfig) {
boolean needsUpdate = false;
// dark mode
@@ -408,15 +502,12 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
}
return mScreenshotLayout.onApplyWindowInsets(insets);
});
- mScreenshotLayout.setOnKeyListener(new View.OnKeyListener() {
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_BACK) {
- dismissScreenshot("back pressed", true);
- return true;
- }
- return false;
+ mScreenshotLayout.setOnKeyListener((v, keyCode, event) -> {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ dismissScreenshot("back pressed", true);
+ return true;
}
+ return false;
});
// Get focus so that the key events go to the layout.
mScreenshotLayout.setFocusableInTouchMode(true);
@@ -471,59 +562,19 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
}
/**
- * Updates the window focusability. If the window is already showing, then it updates the
- * window immediately, otherwise the layout params will be applied when the window is next
- * shown.
- */
- private void setWindowFocusable(boolean focusable) {
- if (focusable) {
- mWindowLayoutParams.flags &= ~FLAG_NOT_FOCUSABLE;
- } else {
- mWindowLayoutParams.flags |= FLAG_NOT_FOCUSABLE;
- }
- if (mScreenshotLayout.isAttachedToWindow()) {
- mWindowManager.updateViewLayout(mScreenshotLayout, mWindowLayoutParams);
- }
- }
-
- /**
- * Creates a new worker thread and saves the screenshot to the media store.
- */
- private void saveScreenshotInWorkerThread(
- Consumer<Uri> finisher, @Nullable ActionsReadyListener actionsReadyListener) {
- SaveImageInBackgroundData data = new SaveImageInBackgroundData();
- data.image = mScreenBitmap;
- data.finisher = finisher;
- data.mActionsReadyListener = actionsReadyListener;
-
- if (mSaveInBgTask != null) {
- // just log success/failure for the pre-existing screenshot
- mSaveInBgTask.setActionsReadyListener(new ActionsReadyListener() {
- @Override
- void onActionsReady(SavedImageData imageData) {
- logSuccessOnActionsReady(imageData);
- }
- });
- }
-
- mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data);
- mSaveInBgTask.execute();
- }
-
- /**
* Takes a screenshot of the current display and shows an animation.
*/
- private void takeScreenshot(Consumer<Uri> finisher, Rect crop) {
+ private void takeScreenshotInternal(Consumer<Uri> finisher, Rect crop) {
// copy the input Rect, since SurfaceControl.screenshot can mutate it
Rect screenRect = new Rect(crop);
int rot = mDisplay.getRotation();
int width = crop.width();
int height = crop.height();
- takeScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher, screenRect,
+ saveScreenshot(SurfaceControl.screenshot(crop, width, height, rot), finisher, screenRect,
Insets.NONE, true);
}
- private void takeScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,
+ private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,
Insets screenInsets, boolean showFlash) {
dismissScreenshot("new screenshot requested", true);
@@ -561,85 +612,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
startAnimation(finisher, screenRect, screenInsets, showFlash);
}
- void takeScreenshot(Consumer<Uri> finisher, Runnable onComplete) {
- mOnCompleteRunnable = onComplete;
-
- mDisplay.getRealMetrics(mDisplayMetrics);
- takeScreenshot(
- finisher,
- new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
- }
-
- void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds,
- Insets visibleInsets, int taskId, int userId, ComponentName topComponent,
- Consumer<Uri> finisher, Runnable onComplete) {
- // TODO: use task Id, userId, topComponent for smart handler
-
- mOnCompleteRunnable = onComplete;
- if (aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) {
- takeScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, false);
- } else {
- takeScreenshot(screenshot, finisher,
- new Rect(0, 0, screenshot.getWidth(), screenshot.getHeight()), Insets.NONE,
- true);
- }
- }
-
- /**
- * Displays a screenshot selector
- */
- @SuppressLint("ClickableViewAccessibility")
- void takeScreenshotPartial(final Consumer<Uri> finisher, Runnable onComplete) {
- dismissScreenshot("new screenshot requested", true);
- mOnCompleteRunnable = onComplete;
-
- mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
- mScreenshotSelectorView.setOnTouchListener(new View.OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- ScreenshotSelectorView view = (ScreenshotSelectorView) v;
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- view.startSelection((int) event.getX(), (int) event.getY());
- return true;
- case MotionEvent.ACTION_MOVE:
- view.updateSelection((int) event.getX(), (int) event.getY());
- return true;
- case MotionEvent.ACTION_UP:
- view.setVisibility(View.GONE);
- mWindowManager.removeView(mScreenshotLayout);
- final Rect rect = view.getSelectionRect();
- if (rect != null) {
- if (rect.width() != 0 && rect.height() != 0) {
- // Need mScreenshotLayout to handle it after the view disappears
- mScreenshotLayout.post(() -> takeScreenshot(finisher, rect));
- }
- }
-
- view.stopSelection();
- return true;
- }
-
- return false;
- }
- });
- mScreenshotLayout.post(() -> {
- mScreenshotSelectorView.setVisibility(View.VISIBLE);
- mScreenshotSelectorView.requestFocus();
- });
- }
-
- /**
- * Cancels screenshot request
- */
- void stopScreenshot() {
- // If the selector layer still presents on screen, we remove it and resets its state.
- if (mScreenshotSelectorView.getSelectionRect() != null) {
- mWindowManager.removeView(mScreenshotLayout);
- mScreenshotSelectorView.stopSelection();
- }
- }
-
/**
* Save the bitmap but don't show the normal screenshot UI.. just a toast (or notification on
* failure).
@@ -670,55 +642,70 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
});
}
- private boolean isUserSetupComplete() {
- return Settings.Secure.getInt(mContext.getContentResolver(),
- SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
+ /**
+ * Starts the animation after taking the screenshot
+ */
+ private void startAnimation(final Consumer<Uri> finisher, Rect screenRect, Insets screenInsets,
+ boolean showFlash) {
+ mScreenshotHandler.post(() -> {
+ if (!mScreenshotLayout.isAttachedToWindow()) {
+ mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
+ }
+ mScreenshotAnimatedView.setImageDrawable(
+ createScreenDrawable(mScreenBitmap, screenInsets));
+ setAnimatedViewSize(screenRect.width(), screenRect.height());
+ // Show when the animation starts
+ mScreenshotAnimatedView.setVisibility(View.GONE);
+
+ mScreenshotPreview.setImageDrawable(createScreenDrawable(mScreenBitmap, screenInsets));
+ // make static preview invisible (from gone) so we can query its location on screen
+ mScreenshotPreview.setVisibility(View.INVISIBLE);
+
+ mScreenshotHandler.post(() -> {
+ mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
+
+ mScreenshotAnimation =
+ createScreenshotDropInAnimation(screenRect, showFlash);
+
+ saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
+ @Override
+ void onActionsReady(SavedImageData imageData) {
+ showUiOnActionsReady(imageData);
+ }
+ });
+
+ // Play the shutter sound to notify that we've taken a screenshot
+ mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
+
+ mScreenshotPreview.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ mScreenshotPreview.buildLayer();
+ mScreenshotAnimation.start();
+ });
+ });
}
/**
- * Clears current screenshot
+ * Creates a new worker thread and saves the screenshot to the media store.
*/
- void dismissScreenshot(String reason, boolean immediate) {
- Log.v(TAG, "clearing screenshot: " + reason);
- mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
- mScreenshotLayout.getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
- if (!immediate) {
- mDismissAnimation = createScreenshotDismissAnimation();
- mDismissAnimation.addListener(new AnimatorListenerAdapter() {
+ private void saveScreenshotInWorkerThread(
+ Consumer<Uri> finisher, @Nullable ActionsReadyListener actionsReadyListener) {
+ SaveImageInBackgroundData data = new SaveImageInBackgroundData();
+ data.image = mScreenBitmap;
+ data.finisher = finisher;
+ data.mActionsReadyListener = actionsReadyListener;
+
+ if (mSaveInBgTask != null) {
+ // just log success/failure for the pre-existing screenshot
+ mSaveInBgTask.setActionsReadyListener(new ActionsReadyListener() {
@Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- clearScreenshot();
+ void onActionsReady(SavedImageData imageData) {
+ logSuccessOnActionsReady(imageData);
}
});
- mDismissAnimation.start();
- } else {
- clearScreenshot();
}
- }
- private void clearScreenshot() {
- if (mScreenshotLayout.isAttachedToWindow()) {
- mWindowManager.removeView(mScreenshotLayout);
- }
-
- // Clear any references to the bitmap
- mScreenshotPreview.setImageDrawable(null);
- mScreenshotAnimatedView.setImageDrawable(null);
- mScreenshotAnimatedView.setVisibility(View.GONE);
- mActionsContainerBackground.setVisibility(View.GONE);
- mActionsContainer.setVisibility(View.GONE);
- mBackgroundProtection.setAlpha(0f);
- mDismissButton.setVisibility(View.GONE);
- mScreenshotPreview.setVisibility(View.GONE);
- mScreenshotPreview.setLayerType(View.LAYER_TYPE_NONE, null);
- mScreenshotPreview.setContentDescription(
- mContext.getResources().getString(R.string.screenshot_preview_description));
- mScreenshotLayout.setAlpha(1);
- mDismissButton.setTranslationY(0);
- mActionsContainer.setTranslationY(0);
- mActionsContainerBackground.setTranslationY(0);
- mScreenshotPreview.setTranslationY(0);
+ mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data);
+ mSaveInBgTask.execute();
}
/**
@@ -768,56 +755,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
}
}
- /**
- * Starts the animation after taking the screenshot
- */
- private void startAnimation(final Consumer<Uri> finisher, Rect screenRect, Insets screenInsets,
- boolean showFlash) {
-
- // If power save is on, show a toast so there is some visual indication that a
- // screenshot has been taken.
- PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
- if (powerManager.isPowerSaveMode()) {
- Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show();
- }
-
- mScreenshotHandler.post(() -> {
- if (!mScreenshotLayout.isAttachedToWindow()) {
- mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
- }
- mScreenshotAnimatedView.setImageDrawable(
- createScreenDrawable(mScreenBitmap, screenInsets));
- setAnimatedViewSize(screenRect.width(), screenRect.height());
- // Show when the animation starts
- mScreenshotAnimatedView.setVisibility(View.GONE);
-
- mScreenshotPreview.setImageDrawable(createScreenDrawable(mScreenBitmap, screenInsets));
- // make static preview invisible (from gone) so we can query its location on screen
- mScreenshotPreview.setVisibility(View.INVISIBLE);
-
- mScreenshotHandler.post(() -> {
- mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
-
- mScreenshotAnimation =
- createScreenshotDropInAnimation(screenRect, showFlash);
-
- saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
- @Override
- void onActionsReady(SavedImageData imageData) {
- showUiOnActionsReady(imageData);
- }
- });
-
- // Play the shutter sound to notify that we've taken a screenshot
- mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
-
- mScreenshotPreview.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- mScreenshotPreview.buildLayer();
- mScreenshotAnimation.start();
- });
- });
- }
-
private AnimatorSet createScreenshotDropInAnimation(Rect bounds, boolean showFlash) {
Rect previewBounds = new Rect();
mScreenshotPreview.getBoundsOnScreen(previewBounds);
@@ -1070,6 +1007,30 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
return animSet;
}
+ private void clearScreenshot() {
+ if (mScreenshotLayout.isAttachedToWindow()) {
+ mWindowManager.removeView(mScreenshotLayout);
+ }
+
+ // Clear any references to the bitmap
+ mScreenshotPreview.setImageDrawable(null);
+ mScreenshotAnimatedView.setImageDrawable(null);
+ mScreenshotAnimatedView.setVisibility(View.GONE);
+ mActionsContainerBackground.setVisibility(View.GONE);
+ mActionsContainer.setVisibility(View.GONE);
+ mBackgroundProtection.setAlpha(0f);
+ mDismissButton.setVisibility(View.GONE);
+ mScreenshotPreview.setVisibility(View.GONE);
+ mScreenshotPreview.setLayerType(View.LAYER_TYPE_NONE, null);
+ mScreenshotPreview.setContentDescription(
+ mContext.getResources().getString(R.string.screenshot_preview_description));
+ mScreenshotLayout.setAlpha(1);
+ mDismissButton.setTranslationY(0);
+ mActionsContainer.setTranslationY(0);
+ mActionsContainerBackground.setTranslationY(0);
+ mScreenshotPreview.setTranslationY(0);
+ }
+
private void setAnimatedViewSize(int width, int height) {
ViewGroup.LayoutParams layoutParams = mScreenshotAnimatedView.getLayoutParams();
layoutParams.width = width;
@@ -1077,6 +1038,27 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
mScreenshotAnimatedView.setLayoutParams(layoutParams);
}
+ /**
+ * Updates the window focusability. If the window is already showing, then it updates the
+ * window immediately, otherwise the layout params will be applied when the window is next
+ * shown.
+ */
+ private void setWindowFocusable(boolean focusable) {
+ if (focusable) {
+ mWindowLayoutParams.flags &= ~FLAG_NOT_FOCUSABLE;
+ } else {
+ mWindowLayoutParams.flags |= FLAG_NOT_FOCUSABLE;
+ }
+ if (mScreenshotLayout.isAttachedToWindow()) {
+ mWindowManager.updateViewLayout(mScreenshotLayout, mWindowLayoutParams);
+ }
+ }
+
+ private boolean isUserSetupComplete() {
+ return Settings.Secure.getInt(mContext.getContentResolver(),
+ SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
+ }
+
/** Does the aspect ratio of the bitmap with insets removed match the bounds. */
private boolean aspectRatiosMatch(Bitmap bitmap, Insets bitmapInsets, Rect screenBounds) {
int insettedWidth = bitmap.getWidth() - bitmapInsets.left - bitmapInsets.right;
@@ -1128,7 +1110,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
if (insets.left < 0 || insets.top < 0 || insets.right < 0 || insets.bottom < 0) {
// Are any of the insets negative, meaning the bitmap is smaller than the bounds so need
// to fill in the background of the drawable.
- return new LayerDrawable(new Drawable[] {
+ return new LayerDrawable(new Drawable[]{
new ColorDrawable(Color.BLACK), insetDrawable});
} else {
return insetDrawable;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 9f8a9bb4a432..6f143da5405a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -102,7 +102,7 @@ public class TakeScreenshotService extends Service {
switch (msg.what) {
case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
- mScreenshot.takeScreenshot(uriConsumer, onComplete);
+ mScreenshot.takeScreenshotFullscreen(uriConsumer, onComplete);
break;
case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:
mScreenshot.takeScreenshotPartial(uriConsumer, onComplete);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 423f85f2ddd0..bd65ef06f3a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -282,7 +282,7 @@ public final class NotificationEntry extends ListEntry {
+ " doesn't match existing key " + mKey);
}
- mRanking = ranking;
+ mRanking = ranking.withAudiblyAlertedInfo(mRanking);
}
/*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 582e3e5b6c34..a7d83b3b2774 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -37,6 +37,8 @@ import android.widget.RemoteViews;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.ImageMessageConsumer;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.media.MediaDataManagerKt;
+import com.android.systemui.media.MediaFeatureFlag;
import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.SmartReplyController;
@@ -71,6 +73,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
public static final String TAG = "NotifContentInflater";
private boolean mInflateSynchronously = false;
+ private final boolean mIsMediaInQS;
private final NotificationRemoteInputManager mRemoteInputManager;
private final NotifRemoteViewCache mRemoteViewCache;
private final Lazy<SmartReplyConstants> mSmartReplyConstants;
@@ -85,12 +88,14 @@ public class NotificationContentInflater implements NotificationRowContentBinder
Lazy<SmartReplyConstants> smartReplyConstants,
Lazy<SmartReplyController> smartReplyController,
ConversationNotificationProcessor conversationProcessor,
+ MediaFeatureFlag mediaFeatureFlag,
@Background Executor bgExecutor) {
mRemoteViewCache = remoteViewCache;
mRemoteInputManager = remoteInputManager;
mSmartReplyConstants = smartReplyConstants;
mSmartReplyController = smartReplyController;
mConversationProcessor = conversationProcessor;
+ mIsMediaInQS = mediaFeatureFlag.getEnabled();
mBgExecutor = bgExecutor;
}
@@ -135,7 +140,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder
bindParams.usesIncreasedHeight,
bindParams.usesIncreasedHeadsUpHeight,
callback,
- mRemoteInputManager.getRemoteViewsOnClickHandler());
+ mRemoteInputManager.getRemoteViewsOnClickHandler(),
+ mIsMediaInQS);
if (mInflateSynchronously) {
task.onPostExecute(task.doInBackground());
} else {
@@ -711,6 +717,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
private RemoteViews.OnClickHandler mRemoteViewClickHandler;
private CancellationSignal mCancellationSignal;
private final ConversationNotificationProcessor mConversationProcessor;
+ private final boolean mIsMediaInQS;
private AsyncInflationTask(
Executor bgExecutor,
@@ -726,7 +733,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder
boolean usesIncreasedHeight,
boolean usesIncreasedHeadsUpHeight,
InflationCallback callback,
- RemoteViews.OnClickHandler remoteViewClickHandler) {
+ RemoteViews.OnClickHandler remoteViewClickHandler,
+ boolean isMediaFlagEnabled) {
mEntry = entry;
mRow = row;
mSmartReplyConstants = smartReplyConstants;
@@ -742,6 +750,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
mRemoteViewClickHandler = remoteViewClickHandler;
mCallback = callback;
mConversationProcessor = conversationProcessor;
+ mIsMediaInQS = isMediaFlagEnabled;
entry.setInflationTask(this);
}
@@ -765,7 +774,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder
packageContext = new RtlEnabledContext(packageContext);
}
Notification notification = sbn.getNotification();
- if (notification.isMediaNotification()) {
+ if (notification.isMediaNotification() && !(mIsMediaInQS
+ && MediaDataManagerKt.isMediaNotification(sbn))) {
MediaNotificationProcessor processor = new MediaNotificationProcessor(mContext,
packageContext);
processor.processNotification(notification, recoveredBuilder);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
index c87b9986ca55..91cc8963b947 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
@@ -41,8 +41,8 @@ import com.android.systemui.statusbar.notification.row.StackScrollerDecorView
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.children
-import com.android.systemui.util.takeUntil
import com.android.systemui.util.foldToSparseArray
+import com.android.systemui.util.takeUntil
import javax.inject.Inject
/**
@@ -166,6 +166,9 @@ class NotificationSectionsManager @Inject internal constructor(
peopleHubSubscription?.unsubscribe()
peopleHubSubscription = null
peopleHeaderView = reinflateView(peopleHeaderView, layoutInflater, R.layout.people_strip)
+ .apply {
+ setOnHeaderClickListener(View.OnClickListener { onGentleHeaderClick() })
+ }
if (ENABLE_SNOOZED_CONVERSATION_HUB) {
peopleHubSubscription = peopleHubViewAdapter.bindView(peopleHubViewBoundary)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt
index 8f77a1d776e4..b13e7fb839ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt
@@ -93,6 +93,8 @@ class PeopleHubView(context: Context, attrs: AttributeSet) :
}
}
+ fun setOnHeaderClickListener(listener: OnClickListener) = label.setOnClickListener(listener)
+
private inner class PersonDataListenerImpl(val avatarView: ImageView) :
DataListener<PersonViewModel?> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 27daf8615a31..837543c9bdae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -591,6 +591,7 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback
.registerDisplayListener(this, new Handler(Looper.getMainLooper()));
mOrientationHandle = new QuickswitchOrientedNavHandle(getContext());
+ mOrientationHandle.setId(R.id.secondary_home_handle);
getBarTransitions().addDarkIntensityListener(mOrientationHandleIntensityListener);
mOrientationParams = new WindowManager.LayoutParams(0, 0,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index 812ce1c62677..6dd96f92b344 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -82,6 +82,7 @@ public class Clock extends TextView implements DemoMode, Tunable, CommandQueue.C
private boolean mClockVisibleByUser = true;
private boolean mAttached;
+ private boolean mScreenReceiverRegistered;
private Calendar mCalendar;
private String mClockFormatString;
private SimpleDateFormat mClockFormat;
@@ -213,6 +214,14 @@ public class Clock extends TextView implements DemoMode, Tunable, CommandQueue.C
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
+ if (mScreenReceiverRegistered) {
+ mScreenReceiverRegistered = false;
+ mBroadcastDispatcher.unregisterReceiver(mScreenReceiver);
+ if (mSecondsHandler != null) {
+ mSecondsHandler.removeCallbacks(mSecondTick);
+ mSecondsHandler = null;
+ }
+ }
if (mAttached) {
mBroadcastDispatcher.unregisterReceiver(mIntentReceiver);
mAttached = false;
@@ -363,12 +372,14 @@ public class Clock extends TextView implements DemoMode, Tunable, CommandQueue.C
mSecondsHandler.postAtTime(mSecondTick,
SystemClock.uptimeMillis() / 1000 * 1000 + 1000);
}
+ mScreenReceiverRegistered = true;
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
mBroadcastDispatcher.registerReceiver(mScreenReceiver, filter);
}
} else {
if (mSecondsHandler != null) {
+ mScreenReceiverRegistered = false;
mBroadcastDispatcher.unregisterReceiver(mScreenReceiver);
mSecondsHandler.removeCallbacks(mSecondTick);
mSecondsHandler = null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index 07de388598b7..c43ad36d4462 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -106,9 +106,9 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
public void updateNotification(@NonNull String key, boolean alert) {
super.updateNotification(key, alert);
- AlertEntry alertEntry = getHeadsUpEntry(key);
- if (alert && alertEntry != null) {
- setEntryPinned((HeadsUpEntry) alertEntry, shouldHeadsUpBecomePinned(alertEntry.mEntry));
+ HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
+ if (alert && headsUpEntry != null) {
+ setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUpEntry.mEntry));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index 18a7adda3f7d..cf83603997c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -284,6 +284,9 @@ public class MobileSignalController extends SignalController<
mNetworkToIconLookup.put(toDisplayIconKey(
TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE),
TelephonyIcons.NR_5G_PLUS);
+ mNetworkToIconLookup.put(toIconKey(
+ TelephonyManager.NETWORK_TYPE_NR),
+ TelephonyIcons.NR_5G);
}
private String getIconKey() {
@@ -306,9 +309,9 @@ public class MobileSignalController extends SignalController<
case TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO:
return toIconKey(TelephonyManager.NETWORK_TYPE_LTE) + "_CA_Plus";
case TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA:
- return "5G";
+ return toIconKey(TelephonyManager.NETWORK_TYPE_NR);
case TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE:
- return "5G_Plus";
+ return toIconKey(TelephonyManager.NETWORK_TYPE_NR) + "_Plus";
default:
return "unsupported";
}
diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
index 926f653153ee..89f469a438a9 100644
--- a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
+++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
@@ -259,6 +259,11 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
mHandler.post(() -> startAnimation(false /* show */, false /* forceRestart */));
}
+ @Override
+ public void topFocusedWindowChanged(String packageName) {
+ // no-op
+ }
+
/**
* Sends the local visibility state back to window manager. Needed for legacy adjustForIme.
*/
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 9e056cf16ec7..6362812d4fed 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -174,6 +174,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
when(mFaceManager.isHardwareDetected()).thenReturn(true);
when(mFaceManager.hasEnrolledTemplates()).thenReturn(true);
when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+ when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+ when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
when(mUserManager.isUserUnlocked(anyInt())).thenReturn(true);
when(mUserManager.isPrimaryUser()).thenReturn(true);
when(mStrongAuthTracker.getStub()).thenReturn(mock(IStrongAuthTracker.Stub.class));
@@ -419,6 +421,43 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
}
@Test
+ public void testTriesToAuthenticateFingerprint_whenKeyguard() {
+ mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
+ mTestableLooper.processAllMessages();
+
+ verify(mFingerprintManager).authenticate(any(), any(), anyInt(), any(), any(), anyInt());
+ verify(mFingerprintManager, never()).detectFingerprint(any(), any(), anyInt());
+ }
+
+ @Test
+ public void testFingerprintDoesNotAuth_whenEncrypted() {
+ testFingerprintWhenStrongAuth(
+ KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT);
+ }
+
+ @Test
+ public void testFingerprintDoesNotAuth_whenDpmLocked() {
+ testFingerprintWhenStrongAuth(
+ KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW);
+ }
+
+ @Test
+ public void testFingerprintDoesNotAuth_whenUserLockdown() {
+ testFingerprintWhenStrongAuth(
+ KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+ }
+
+ private void testFingerprintWhenStrongAuth(int strongAuth) {
+ when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(strongAuth);
+ mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
+ mTestableLooper.processAllMessages();
+
+ verify(mFingerprintManager, never())
+ .authenticate(any(), any(), anyInt(), any(), any(), anyInt());
+ verify(mFingerprintManager).detectFingerprint(any(), any(), anyInt());
+ }
+
+ @Test
public void testTriesToAuthenticate_whenBouncer() {
mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(true);
mTestableLooper.processAllMessages();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
index dbc5596d9f4e..492b33e3c4a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
@@ -22,6 +22,7 @@ import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import android.graphics.Color;
@@ -47,6 +48,7 @@ import java.util.Map;
public class MediaDataCombineLatestTest extends SysuiTestCase {
private static final String KEY = "TEST_KEY";
+ private static final String OLD_KEY = "TEST_KEY_OLD";
private static final String APP = "APP";
private static final String PACKAGE = "PKG";
private static final int BG_COLOR = Color.RED;
@@ -97,7 +99,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase {
@Test
public void eventNotEmittedWithoutMedia() {
// WHEN device source emits an event without media data
- mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData);
+ mDeviceListener.onMediaDeviceChanged(KEY, null, mDeviceData);
// THEN an event isn't emitted
verify(mListener, never()).onMediaDataLoaded(eq(KEY), any(), any());
}
@@ -105,7 +107,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase {
@Test
public void emitEventAfterDeviceFirst() {
// GIVEN that a device event has already been received
- mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData);
+ mDeviceListener.onMediaDeviceChanged(KEY, null, mDeviceData);
// WHEN media event is received
mDataListener.onMediaDataLoaded(KEY, null, mMediaData);
// THEN the listener receives a combined event
@@ -119,7 +121,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase {
// GIVEN that media event has already been received
mDataListener.onMediaDataLoaded(KEY, null, mMediaData);
// WHEN device event is received
- mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData);
+ mDeviceListener.onMediaDeviceChanged(KEY, null, mDeviceData);
// THEN the listener receives a combined event
ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
verify(mListener).onMediaDataLoaded(eq(KEY), any(), captor.capture());
@@ -127,6 +129,64 @@ public class MediaDataCombineLatestTest extends SysuiTestCase {
}
@Test
+ public void migrateKeyMediaFirst() {
+ // GIVEN that media and device info has already been received
+ mDataListener.onMediaDataLoaded(OLD_KEY, null, mMediaData);
+ mDeviceListener.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
+ reset(mListener);
+ // WHEN a key migration event is received
+ mDataListener.onMediaDataLoaded(KEY, OLD_KEY, mMediaData);
+ // THEN the listener receives a combined event
+ ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
+ verify(mListener).onMediaDataLoaded(eq(KEY), eq(OLD_KEY), captor.capture());
+ assertThat(captor.getValue().getDevice()).isNotNull();
+ }
+
+ @Test
+ public void migrateKeyDeviceFirst() {
+ // GIVEN that media and device info has already been received
+ mDataListener.onMediaDataLoaded(OLD_KEY, null, mMediaData);
+ mDeviceListener.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
+ reset(mListener);
+ // WHEN a key migration event is received
+ mDeviceListener.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData);
+ // THEN the listener receives a combined event
+ ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
+ verify(mListener).onMediaDataLoaded(eq(KEY), eq(OLD_KEY), captor.capture());
+ assertThat(captor.getValue().getDevice()).isNotNull();
+ }
+
+ @Test
+ public void migrateKeyMediaAfter() {
+ // GIVEN that media and device info has already been received
+ mDataListener.onMediaDataLoaded(OLD_KEY, null, mMediaData);
+ mDeviceListener.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
+ mDeviceListener.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData);
+ reset(mListener);
+ // WHEN a second key migration event is received for media
+ mDataListener.onMediaDataLoaded(KEY, OLD_KEY, mMediaData);
+ // THEN the key has already been migrated
+ ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
+ verify(mListener).onMediaDataLoaded(eq(KEY), eq(KEY), captor.capture());
+ assertThat(captor.getValue().getDevice()).isNotNull();
+ }
+
+ @Test
+ public void migrateKeyDeviceAfter() {
+ // GIVEN that media and device info has already been received
+ mDataListener.onMediaDataLoaded(OLD_KEY, null, mMediaData);
+ mDeviceListener.onMediaDeviceChanged(OLD_KEY, null, mDeviceData);
+ mDataListener.onMediaDataLoaded(KEY, OLD_KEY, mMediaData);
+ reset(mListener);
+ // WHEN a second key migration event is received for the device
+ mDeviceListener.onMediaDeviceChanged(KEY, OLD_KEY, mDeviceData);
+ // THEN the key has already be migrated
+ ArgumentCaptor<MediaData> captor = ArgumentCaptor.forClass(MediaData.class);
+ verify(mListener).onMediaDataLoaded(eq(KEY), eq(KEY), captor.capture());
+ assertThat(captor.getValue().getDevice()).isNotNull();
+ }
+
+ @Test
public void mediaDataRemoved() {
// WHEN media data is removed without first receiving device or data
mDataListener.onMediaDataRemoved(KEY);
@@ -143,7 +203,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase {
@Test
public void mediaDataRemovedAfterDeviceEvent() {
- mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData);
+ mDeviceListener.onMediaDeviceChanged(KEY, null, mDeviceData);
mDataListener.onMediaDataRemoved(KEY);
verify(mListener).onMediaDataRemoved(eq(KEY));
}
@@ -152,7 +212,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase {
public void mediaDataKeyUpdated() {
// GIVEN that device and media events have already been received
mDataListener.onMediaDataLoaded(KEY, null, mMediaData);
- mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData);
+ mDeviceListener.onMediaDeviceChanged(KEY, null, mDeviceData);
// WHEN the key is changed
mDataListener.onMediaDataLoaded("NEW_KEY", KEY, mMediaData);
// THEN the listener gets a load event with the correct keys
@@ -163,7 +223,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase {
@Test
public void getDataIncludesDevice() {
// GIVEN that device and media events have been received
- mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData);
+ mDeviceListener.onMediaDeviceChanged(KEY, null, mDeviceData);
mDataListener.onMediaDataLoaded(KEY, null, mMediaData);
// THEN the result of getData includes device info
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
index fc22eeb3ea68..3c6e19f0ec6f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
@@ -166,7 +166,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
// THEN the listener for the old key should removed.
verify(lmm).unregisterCallback(any())
// AND a new device event emitted
- val data = captureDeviceData(KEY)
+ val data = captureDeviceData(KEY, KEY_OLD)
assertThat(data.enabled).isTrue()
assertThat(data.name).isEqualTo(DEVICE_NAME)
}
@@ -179,13 +179,14 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
// WHEN the new key is the same as the old key
manager.onMediaDataLoaded(KEY, KEY, mediaData)
// THEN no event should be emitted
- verify(listener, never()).onMediaDeviceChanged(eq(KEY), any())
+ verify(listener, never()).onMediaDeviceChanged(eq(KEY), eq(null), any())
}
@Test
fun unknownOldKey() {
- manager.onMediaDataLoaded(KEY, "unknown", mediaData)
- verify(listener).onMediaDeviceChanged(eq(KEY), any())
+ val oldKey = "unknown"
+ manager.onMediaDataLoaded(KEY, oldKey, mediaData)
+ verify(listener).onMediaDeviceChanged(eq(KEY), eq(oldKey), any())
}
@Test
@@ -223,7 +224,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
manager.removeListener(listener)
// THEN it doesn't receive device events
manager.onMediaDataLoaded(KEY, null, mediaData)
- verify(listener, never()).onMediaDeviceChanged(eq(KEY), any())
+ verify(listener, never()).onMediaDeviceChanged(eq(KEY), eq(null), any())
}
@Test
@@ -318,9 +319,9 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
return captor.getValue()
}
- fun captureDeviceData(key: String): MediaDeviceData {
+ fun captureDeviceData(key: String, oldKey: String? = null): MediaDeviceData {
val captor = ArgumentCaptor.forClass(MediaDeviceData::class.java)
- verify(listener).onMediaDeviceChanged(eq(key), captor.capture())
+ verify(listener).onMediaDeviceChanged(eq(key), eq(oldKey), captor.capture())
return captor.getValue()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
index e9a0a40fe8ae..71554608f04b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
@@ -69,7 +69,7 @@ public class SeekBarObserverTest : SysuiTestCase() {
fun seekBarGone() {
// WHEN seek bar is disabled
val isEnabled = false
- val data = SeekBarViewModel.Progress(isEnabled, false, null, null)
+ val data = SeekBarViewModel.Progress(isEnabled, false, null, 0)
observer.onChanged(data)
// THEN seek bar shows just a thin line with no text
assertThat(seekBarView.isEnabled()).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
index c8ef9fbf06e5..b81ab74458ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt
@@ -204,6 +204,22 @@ public class SeekBarViewModelTest : SysuiTestCase() {
}
@Test
+ fun updateDurationNoMetadata() {
+ // GIVEN that the metadata is null
+ whenever(mockController.getMetadata()).thenReturn(null)
+ // AND a valid playback state (ie. media session is not destroyed)
+ val state = PlaybackState.Builder().run {
+ setState(PlaybackState.STATE_PLAYING, 200L, 1f)
+ build()
+ }
+ whenever(mockController.getPlaybackState()).thenReturn(state)
+ // WHEN the controller is updated
+ viewModel.updateController(mockController)
+ // THEN the seek bar is disabled
+ assertThat(viewModel.progress.value!!.enabled).isFalse()
+ }
+
+ @Test
fun updateElapsedTime() {
// GIVEN that the PlaybackState contains the current position
val position = 200L
diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
index 536cae4380c4..c9c111198f4c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
@@ -61,8 +61,7 @@ public class PipAnimationControllerTest extends SysuiTestCase {
@Before
public void setUp() throws Exception {
mPipAnimationController = new PipAnimationController(
- mContext, new PipSurfaceTransactionHelper(mContext,
- mock(ConfigurationController.class)));
+ new PipSurfaceTransactionHelper(mContext, mock(ConfigurationController.class)));
mLeash = new SurfaceControl.Builder()
.setContainerLayer()
.setName("FakeLeash")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index 25da74137a90..3718cd720659 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -51,6 +51,7 @@ import androidx.test.filters.SmallTest;
import androidx.test.filters.Suppress;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.media.MediaFeatureFlag;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
@@ -110,6 +111,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
() -> smartReplyConstants,
() -> smartReplyController,
mConversationNotificationProcessor,
+ mock(MediaFeatureFlag.class),
mock(Executor.class));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
index bedbec6aadce..2198edd0d8bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
@@ -43,6 +43,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.util.NotificationMessagingUtil;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.media.MediaFeatureFlag;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.plugins.PluginManager;
@@ -198,6 +199,7 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase {
() -> mock(SmartReplyConstants.class),
() -> mock(SmartReplyController.class),
mock(ConversationNotificationProcessor.class),
+ mock(MediaFeatureFlag.class),
mBgExecutor);
mRowContentBindStage = new RowContentBindStage(
binder,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index b9eb4d1e29c2..0c6409b38d21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -46,6 +46,7 @@ import android.widget.RemoteViews;
import com.android.systemui.TestableDependency;
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.bubbles.BubblesTestActivity;
+import com.android.systemui.media.MediaFeatureFlag;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationMediaManager;
@@ -133,6 +134,7 @@ public class NotificationTestHelper {
() -> mock(SmartReplyConstants.class),
() -> mock(SmartReplyController.class),
mock(ConversationNotificationProcessor.class),
+ mock(MediaFeatureFlag.class),
mock(Executor.class));
contentBinder.setInflateSynchronously(true);
mBindStage = new RowContentBindStage(contentBinder,
diff --git a/packages/WAPPushManager/AndroidManifest.xml b/packages/WAPPushManager/AndroidManifest.xml
index 14e6e91e3a25..a75fb2d47bbd 100644
--- a/packages/WAPPushManager/AndroidManifest.xml
+++ b/packages/WAPPushManager/AndroidManifest.xml
@@ -23,6 +23,8 @@
<permission android:name="com.android.smspush.WAPPUSH_MANAGER_BIND"
android:protectionLevel="signatureOrSystem" />
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+
<original-package android:name="com.android.smspush" />
<application
android:allowClearUserData="false">
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 6b852adce0f1..40a2816ee7de 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -90,6 +90,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.NoSuchElementException;
import java.util.Set;
/**
@@ -1179,7 +1180,11 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
/* ignore */
}
if (mService != null) {
- mService.unlinkToDeath(this, 0);
+ try {
+ mService.unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ Slog.e(LOG_TAG, "Failed unregistering death link");
+ }
mService = null;
}
diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
index 5e10916c4491..a4e58a1faeac 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
@@ -649,16 +649,33 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
mReporter.onStartPackageBackup(PM_PACKAGE);
mCurrentPackage = new PackageInfo();
mCurrentPackage.packageName = PM_PACKAGE;
-
try {
- extractPmAgentData(mCurrentPackage);
+ // If we can't even extractPmAgentData(), then we treat the local state as
+ // compromised, just in case. This means that we will clear data and will
+ // start from a clean slate in the next attempt. It's not clear whether that's
+ // the right thing to do, but matches what we have historically done.
+ try {
+ extractPmAgentData(mCurrentPackage);
+ } catch (TaskException e) {
+ throw TaskException.stateCompromised(e); // force stateCompromised
+ }
+ // During sendDataToTransport, we generally trust any thrown TaskException
+ // about whether stateCompromised because those are likely transient;
+ // clearing state for those would have the potential to lead to cascading
+ // failures, as discussed in http://b/144030477.
+ // For specific status codes (e.g. TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED),
+ // cleanUpAgentForTransportStatus() or theoretically handleTransportStatus()
+ // still have the opportunity to perform additional clean-up tasks.
int status = sendDataToTransport(mCurrentPackage);
cleanUpAgentForTransportStatus(status);
} catch (AgentException | TaskException e) {
mReporter.onExtractPmAgentDataError(e);
cleanUpAgentForError(e);
- // PM agent failure is task failure.
- throw TaskException.stateCompromised(e);
+ if (e instanceof TaskException) {
+ throw (TaskException) e;
+ } else {
+ throw TaskException.stateCompromised(e); // PM agent failure is task failure.
+ }
}
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 6eab0221b7ab..c5bdb9edd069 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -2052,6 +2052,8 @@ public class AppOpsService extends IAppOpsService.Stub {
public void getHistoricalOps(int uid, String packageName, String attributionTag,
List<String> opNames, int filter, long beginTimeMillis, long endTimeMillis,
int flags, RemoteCallback callback) {
+ PackageManager pm = mContext.getPackageManager();
+
ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
beginTimeMillis, endTimeMillis, flags);
Objects.requireNonNull(callback, "callback cannot be null");
@@ -2059,8 +2061,16 @@ public class AppOpsService extends IAppOpsService.Stub {
ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(Binder.getCallingUid());
boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
+ boolean isCallerPermissionController;
+ try {
+ isCallerPermissionController = pm.getPackageUid(
+ mContext.getPackageManager().getPermissionControllerPackageName(), 0)
+ == Binder.getCallingUid();
+ } catch (PackageManager.NameNotFoundException doesNotHappen) {
+ return;
+ }
- if (!isCallerSystem && !isCallerInstrumented) {
+ if (!isCallerSystem && !isCallerInstrumented && !isCallerPermissionController) {
mHandler.post(() -> callback.sendResult(new Bundle()));
return;
}
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 061972c5723a..131267924179 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -28,6 +28,7 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRIN
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_IRIS;
import static android.hardware.biometrics.BiometricManager.Authenticators;
+import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.biometrics.BiometricPrompt;
@@ -128,6 +129,11 @@ public class AuthService extends SystemService {
return IIrisService.Stub.asInterface(
ServiceManager.getService(Context.IRIS_SERVICE));
}
+
+ @VisibleForTesting
+ public AppOpsManager getAppOps(Context context) {
+ return context.getSystemService(AppOpsManager.class);
+ }
}
private final class AuthServiceImpl extends IAuthService.Stub {
@@ -138,6 +144,8 @@ public class AuthService extends SystemService {
// Only allow internal clients to authenticate with a different userId.
final int callingUserId = UserHandle.getCallingUserId();
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
if (userId == callingUserId) {
checkPermission();
} else {
@@ -146,6 +154,16 @@ public class AuthService extends SystemService {
checkInternalPermission();
}
+ if (!checkAppOps(callingUid, opPackageName, "authenticate()")) {
+ Slog.e(TAG, "Denied by app ops: " + opPackageName);
+ return;
+ }
+
+ if (!Utils.isForeground(callingUid, callingPid)) {
+ Slog.e(TAG, "Caller is not foreground: " + opPackageName);
+ return;
+ }
+
if (token == null || receiver == null || opPackageName == null || bundle == null) {
Slog.e(TAG, "Unable to authenticate, one or more null arguments");
return;
@@ -163,8 +181,6 @@ public class AuthService extends SystemService {
checkInternalPermission();
}
- final int callingUid = Binder.getCallingUid();
- final int callingPid = Binder.getCallingPid();
final long identity = Binder.clearCallingIdentity();
try {
mBiometricService.authenticate(
@@ -392,4 +408,9 @@ public class AuthService extends SystemService {
"Must have USE_BIOMETRIC permission");
}
}
+
+ private boolean checkAppOps(int uid, String opPackageName, String reason) {
+ return mInjector.getAppOps(getContext()).noteOp(AppOpsManager.OP_USE_BIOMETRIC, uid,
+ opPackageName, null /* attributionTag */, reason) == AppOpsManager.MODE_ALLOWED;
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
index 75452ea5fb61..20c004db4c9f 100644
--- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
+++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
@@ -722,10 +722,9 @@ public abstract class BiometricServiceBase extends SystemService
}
}
- protected void handleAuthenticated(BiometricAuthenticator.Identifier identifier,
- ArrayList<Byte> token) {
+ protected void handleAuthenticated(boolean authenticated,
+ BiometricAuthenticator.Identifier identifier, ArrayList<Byte> token) {
ClientMonitor client = mCurrentClient;
- final boolean authenticated = identifier.getBiometricId() != 0;
if (client != null && client.onAuthenticated(identifier, authenticated, token)) {
removeClient(client);
@@ -1019,7 +1018,7 @@ public abstract class BiometricServiceBase extends SystemService
return false;
}
- if (requireForeground && !(isForegroundActivity(uid, pid) || isCurrentClient(
+ if (requireForeground && !(Utils.isForeground(uid, pid) || isCurrentClient(
opPackageName))) {
Slog.w(getTag(), "Rejecting " + opPackageName + "; not in foreground");
return false;
@@ -1042,29 +1041,6 @@ public abstract class BiometricServiceBase extends SystemService
return mKeyguardPackage.equals(clientPackage);
}
- private boolean isForegroundActivity(int uid, int pid) {
- try {
- final List<ActivityManager.RunningAppProcessInfo> procs =
- ActivityManager.getService().getRunningAppProcesses();
- if (procs == null) {
- Slog.e(getTag(), "Processes null, defaulting to true");
- return true;
- }
-
- int N = procs.size();
- for (int i = 0; i < N; i++) {
- ActivityManager.RunningAppProcessInfo proc = procs.get(i);
- if (proc.pid == pid && proc.uid == uid
- && proc.importance <= IMPORTANCE_FOREGROUND_SERVICE) {
- return true;
- }
- }
- } catch (RemoteException e) {
- Slog.w(getTag(), "am.getRunningAppProcesses() failed");
- }
- return false;
- }
-
/**
* Calls the HAL to switch states to the new task. If there's already a current task,
* it calls cancel() and sets mPendingClient to begin when the current task finishes
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index 14378da0a90b..c661f452820d 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -16,20 +16,38 @@
package com.android.server.biometrics;
+import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
import static android.hardware.biometrics.BiometricManager.Authenticators;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricPrompt.AuthenticationResultType;
import android.os.Build;
import android.os.Bundle;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
+import com.android.internal.R;
+import com.android.internal.widget.LockPatternUtils;
+
+import java.util.List;
+
public class Utils {
+ private static final String TAG = "BiometricUtils";
+
public static boolean isDebugEnabled(Context context, int targetUserId) {
if (targetUserId == UserHandle.USER_NULL) {
return false;
@@ -256,4 +274,50 @@ public class Utils {
throw new IllegalArgumentException("Unsupported dismissal reason: " + reason);
}
}
+
+ public static boolean isForeground(int callingUid, int callingPid) {
+ try {
+ final List<ActivityManager.RunningAppProcessInfo> procs =
+ ActivityManager.getService().getRunningAppProcesses();
+ if (procs == null) {
+ Slog.e(TAG, "No running app processes found, defaulting to true");
+ return true;
+ }
+
+ for (int i = 0; i < procs.size(); i++) {
+ ActivityManager.RunningAppProcessInfo proc = procs.get(i);
+ if (proc.pid == callingPid && proc.uid == callingUid
+ && proc.importance <= IMPORTANCE_FOREGROUND_SERVICE) {
+ return true;
+ }
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "am.getRunningAppProcesses() failed");
+ }
+ return false;
+ }
+
+ public static boolean isKeyguard(Context context, String clientPackage) {
+ final boolean hasPermission = context.checkCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL)
+ == PackageManager.PERMISSION_GRANTED;
+
+ final ComponentName keyguardComponent = ComponentName.unflattenFromString(
+ context.getResources().getString(R.string.config_keyguardComponent));
+ final String keyguardPackage = keyguardComponent != null
+ ? keyguardComponent.getPackageName() : null;
+ return hasPermission && keyguardPackage != null && keyguardPackage.equals(clientPackage);
+ }
+
+ private static boolean containsFlag(int haystack, int needle) {
+ return (haystack & needle) != 0;
+ }
+
+ public static boolean isUserEncryptedOrLockdown(@NonNull LockPatternUtils lpu, int user) {
+ final int strongAuth = lpu.getStrongAuthForUser(user);
+ final boolean isEncrypted = containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT);
+ final boolean isLockDown = containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW)
+ || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+ Slog.d(TAG, "isEncrypted: " + isEncrypted + " isLockdown: " + isLockDown);
+ return isEncrypted || isLockDown;
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java
index 72e1bbbcba60..e5a1898459a2 100644
--- a/services/core/java/com/android/server/biometrics/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/face/FaceService.java
@@ -896,8 +896,9 @@ public class FaceService extends BiometricServiceBase {
public void onAuthenticated(final long deviceId, final int faceId, final int userId,
ArrayList<Byte> token) {
mHandler.post(() -> {
- Face face = new Face("", faceId, deviceId);
- FaceService.super.handleAuthenticated(face, token);
+ final Face face = new Face("", faceId, deviceId);
+ final boolean authenticated = faceId != 0;
+ FaceService.super.handleAuthenticated(authenticated, face, token);
});
}
diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
index 6b7ba6a56d82..a90fee6788a8 100644
--- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
@@ -56,6 +56,7 @@ import android.os.SELinux;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
+import android.util.EventLog;
import android.util.Slog;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
@@ -64,6 +65,7 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.DumpUtils;
+import com.android.internal.widget.LockPatternUtils;
import com.android.server.SystemServerInitThreadPool;
import com.android.server.biometrics.AuthenticationClient;
import com.android.server.biometrics.BiometricServiceBase;
@@ -72,6 +74,7 @@ import com.android.server.biometrics.ClientMonitor;
import com.android.server.biometrics.Constants;
import com.android.server.biometrics.EnumerateClient;
import com.android.server.biometrics.RemovalClient;
+import com.android.server.biometrics.Utils;
import org.json.JSONArray;
import org.json.JSONException;
@@ -124,6 +127,8 @@ public class FingerprintService extends BiometricServiceBase {
}
private final class FingerprintAuthClient extends AuthenticationClientImpl {
+ private final boolean mDetectOnly;
+
@Override
protected boolean isFingerprint() {
return true;
@@ -133,9 +138,10 @@ public class FingerprintService extends BiometricServiceBase {
DaemonWrapper daemon, long halDeviceId, IBinder token,
ServiceListener listener, int targetUserId, int groupId, long opId,
boolean restricted, String owner, int cookie,
- boolean requireConfirmation) {
+ boolean requireConfirmation, boolean detectOnly) {
super(context, daemon, halDeviceId, token, listener, targetUserId, groupId, opId,
restricted, owner, cookie, requireConfirmation);
+ mDetectOnly = detectOnly;
}
@Override
@@ -177,6 +183,10 @@ public class FingerprintService extends BiometricServiceBase {
return super.handleFailedAttempt();
}
+
+ boolean isDetectOnly() {
+ return mDetectOnly;
+ }
}
/**
@@ -234,18 +244,64 @@ public class FingerprintService extends BiometricServiceBase {
}
@Override // Binder call
- public void authenticate(final IBinder token, final long opId, final int groupId,
+ public void authenticate(final IBinder token, final long opId, final int userId,
final IFingerprintServiceReceiver receiver, final int flags,
final String opPackageName) {
- updateActiveGroup(groupId, opPackageName);
+ // Keyguard check must be done on the caller's binder identity, since it also checks
+ // permission.
+ final boolean isKeyguard = Utils.isKeyguard(getContext(), opPackageName);
+
+ // Clear calling identity when checking LockPatternUtils for StrongAuth flags.
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (isKeyguard && Utils.isUserEncryptedOrLockdown(mLockPatternUtils, userId)) {
+ // If this happens, something in KeyguardUpdateMonitor is wrong.
+ // SafetyNet for b/79776455
+ EventLog.writeEvent(0x534e4554, "79776455");
+ Slog.e(TAG, "Authenticate invoked when user is encrypted or lockdown");
+ return;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+
+ updateActiveGroup(userId, opPackageName);
final boolean restricted = isRestricted();
final AuthenticationClientImpl client = new FingerprintAuthClient(getContext(),
mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver),
- mCurrentUserId, groupId, opId, restricted, opPackageName,
- 0 /* cookie */, false /* requireConfirmation */);
+ mCurrentUserId, userId, opId, restricted, opPackageName,
+ 0 /* cookie */, false /* requireConfirmation */, false /* detectOnly */);
authenticateInternal(client, opId, opPackageName);
}
+ @Override
+ public void detectFingerprint(final IBinder token, final int userId,
+ final IFingerprintServiceReceiver receiver, final String opPackageName) {
+ checkPermission(USE_BIOMETRIC_INTERNAL);
+ if (!Utils.isKeyguard(getContext(), opPackageName)) {
+ Slog.w(TAG, "detectFingerprint called from non-sysui package: " + opPackageName);
+ return;
+ }
+
+ if (!Utils.isUserEncryptedOrLockdown(mLockPatternUtils, userId)) {
+ // If this happens, something in KeyguardUpdateMonitor is wrong. This should only
+ // ever be invoked when the user is encrypted or lockdown.
+ Slog.e(TAG, "detectFingerprint invoked when user is not encrypted or lockdown");
+ return;
+ }
+
+ Slog.d(TAG, "detectFingerprint, owner: " + opPackageName + ", user: " + userId);
+
+ updateActiveGroup(userId, opPackageName);
+ final boolean restricted = isRestricted();
+ final int operationId = 0;
+ final AuthenticationClientImpl client = new FingerprintAuthClient(getContext(),
+ mDaemonWrapper, mHalDeviceId, token, new ServiceListenerImpl(receiver),
+ mCurrentUserId, userId, operationId, restricted, opPackageName,
+ 0 /* cookie */, false /* requireConfirmation */, true /* detectOnly */);
+ authenticateInternal(client, operationId, opPackageName);
+ }
+
@Override // Binder call
public void prepareForAuthentication(IBinder token, long opId, int groupId,
IBiometricServiceReceiverInternal wrapperReceiver, String opPackageName,
@@ -257,7 +313,7 @@ public class FingerprintService extends BiometricServiceBase {
mDaemonWrapper, mHalDeviceId, token,
new BiometricPromptServiceListenerImpl(wrapperReceiver),
mCurrentUserId, groupId, opId, restricted, opPackageName, cookie,
- false /* requireConfirmation */);
+ false /* requireConfirmation */, false /* detectOnly */);
authenticateInternal(client, opId, opPackageName, callingUid, callingPid,
callingUserId);
}
@@ -275,6 +331,17 @@ public class FingerprintService extends BiometricServiceBase {
}
@Override // Binder call
+ public void cancelFingerprintDetect(final IBinder token, final String opPackageName) {
+ checkPermission(USE_BIOMETRIC_INTERNAL);
+ if (!Utils.isKeyguard(getContext(), opPackageName)) {
+ Slog.w(TAG, "cancelFingerprintDetect called from non-sysui package: "
+ + opPackageName);
+ return;
+ }
+ cancelAuthenticationInternal(token, opPackageName);
+ }
+
+ @Override // Binder call
public void cancelAuthenticationFromService(final IBinder token, final String opPackageName,
int callingUid, int callingPid, int callingUserId, boolean fromClient) {
checkPermission(MANAGE_BIOMETRIC);
@@ -518,7 +585,12 @@ public class FingerprintService extends BiometricServiceBase {
BiometricAuthenticator.Identifier biometric, int userId)
throws RemoteException {
if (mFingerprintServiceReceiver != null) {
- if (biometric == null || biometric instanceof Fingerprint) {
+ final ClientMonitor client = getCurrentClient();
+ if (client instanceof FingerprintAuthClient
+ && ((FingerprintAuthClient) client).isDetectOnly()) {
+ mFingerprintServiceReceiver
+ .onFingerprintDetected(deviceId, userId, isStrongBiometric());
+ } else if (biometric == null || biometric instanceof Fingerprint) {
mFingerprintServiceReceiver.onAuthenticationSucceeded(deviceId,
(Fingerprint) biometric, userId, isStrongBiometric());
} else {
@@ -575,6 +647,7 @@ public class FingerprintService extends BiometricServiceBase {
private final LockoutReceiver mLockoutReceiver = new LockoutReceiver();
protected final ResetFailedAttemptsForUserRunnable mResetFailedAttemptsForCurrentUserRunnable =
new ResetFailedAttemptsForUserRunnable();
+ private final LockPatternUtils mLockPatternUtils;
/**
* Receives callbacks from the HAL.
@@ -608,8 +681,17 @@ public class FingerprintService extends BiometricServiceBase {
public void onAuthenticated(final long deviceId, final int fingerId, final int groupId,
ArrayList<Byte> token) {
mHandler.post(() -> {
- Fingerprint fp = new Fingerprint("", groupId, fingerId, deviceId);
- FingerprintService.super.handleAuthenticated(fp, token);
+ boolean authenticated = fingerId != 0;
+ final ClientMonitor client = getCurrentClient();
+ if (client instanceof FingerprintAuthClient) {
+ if (((FingerprintAuthClient) client).isDetectOnly()) {
+ Slog.w(TAG, "Detect-only. Device is encrypted or locked down");
+ authenticated = true;
+ }
+ }
+
+ final Fingerprint fp = new Fingerprint("", groupId, fingerId, deviceId);
+ FingerprintService.super.handleAuthenticated(authenticated, fp, token);
});
}
@@ -722,6 +804,7 @@ public class FingerprintService extends BiometricServiceBase {
mAlarmManager = context.getSystemService(AlarmManager.class);
context.registerReceiver(mLockoutReceiver, new IntentFilter(getLockoutResetIntent()),
getLockoutBroadcastPermission(), null /* handler */);
+ mLockPatternUtils = new LockPatternUtils(context);
}
@Override
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 9963cf7e212d..bc7554c54eb0 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -4224,6 +4224,20 @@ public class PermissionManagerService extends IPermissionManager.Stub {
revokePermissionFromPackageForUser(p.getPackageName(),
bp.getName(), true, userId, callback));
}
+ } else {
+ mPackageManagerInt.forEachPackage(p -> {
+ PackageSetting ps = mPackageManagerInt.getPackageSetting(
+ p.getPackageName());
+ if (ps == null) {
+ return;
+ }
+ PermissionsState permissionsState = ps.getPermissionsState();
+ if (permissionsState.getInstallPermissionState(bp.getName()) != null) {
+ permissionsState.revokeInstallPermission(bp);
+ permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL,
+ MASK_PERMISSION_FLAGS_ALL, 0);
+ }
+ });
}
it.remove();
}
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index b3e162d473db..b9431a6968f9 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -141,6 +141,10 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
@IntDef({NAV_BAR_LEFT, NAV_BAR_RIGHT, NAV_BAR_BOTTOM})
@interface NavigationBarPosition {}
+ @Retention(SOURCE)
+ @IntDef({ALT_BAR_UNKNOWN, ALT_BAR_LEFT, ALT_BAR_RIGHT, ALT_BAR_BOTTOM, ALT_BAR_TOP})
+ @interface AltBarPosition {}
+
/**
* Pass this event to the user / app. To be returned from
* {@link #interceptKeyBeforeQueueing}.
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 06c0c4656332..e1693bab3f7d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5762,6 +5762,19 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
mRemoteInsetsController = controller;
}
+ /**
+ * Notifies the remote insets controller that the top focused window has changed.
+ *
+ * @param packageName The name of the package that is open in the top focused window.
+ */
+ void topFocusedWindowChanged(String packageName) {
+ try {
+ mRemoteInsetsController.topFocusedWindowChanged(packageName);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to deliver package in top focused window change", e);
+ }
+ }
+
void notifyInsetsChanged() {
try {
mRemoteInsetsController.insetsChanged(
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 0d467c50aa11..6a90f2994238 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -102,6 +102,11 @@ import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManagerGlobal.ADD_OKAY;
import static android.view.WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED;
+import static android.view.WindowManagerPolicyConstants.ALT_BAR_BOTTOM;
+import static android.view.WindowManagerPolicyConstants.ALT_BAR_LEFT;
+import static android.view.WindowManagerPolicyConstants.ALT_BAR_RIGHT;
+import static android.view.WindowManagerPolicyConstants.ALT_BAR_TOP;
+import static android.view.WindowManagerPolicyConstants.ALT_BAR_UNKNOWN;
import static android.view.WindowManagerPolicyConstants.EXTRA_HDMI_PLUGGED_STATE;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT;
@@ -314,6 +319,17 @@ public class DisplayPolicy {
private int[] mNavigationBarHeightForRotationInCarMode = new int[4];
private int[] mNavigationBarWidthForRotationInCarMode = new int[4];
+ // Alternative status bar for when flexible insets mapping is used to place the status bar on
+ // another side of the screen.
+ private WindowState mStatusBarAlt = null;
+ @WindowManagerPolicy.AltBarPosition
+ private int mStatusBarAltPosition = ALT_BAR_UNKNOWN;
+ // Alternative navigation bar for when flexible insets mapping is used to place the navigation
+ // bar elsewhere on the screen.
+ private WindowState mNavigationBarAlt = null;
+ @WindowManagerPolicy.AltBarPosition
+ private int mNavigationBarAltPosition = ALT_BAR_UNKNOWN;
+
/** See {@link #getNavigationBarFrameHeight} */
private int[] mNavigationBarFrameHeightForRotationDefault = new int[4];
@@ -386,11 +402,6 @@ public class DisplayPolicy {
private int mForcingShowNavBarLayer;
private boolean mForceShowSystemBars;
- /**
- * Force the display of system bars regardless of other settings.
- */
- private boolean mForceShowSystemBarsFromExternal;
-
private boolean mShowingDream;
private boolean mLastShowingDream;
private boolean mDreamingLockscreen;
@@ -436,7 +447,7 @@ public class DisplayPolicy {
case MSG_REQUEST_TRANSIENT_BARS:
synchronized (mLock) {
WindowState targetBar = (msg.arg1 == MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS)
- ? mStatusBar : mNavigationBar;
+ ? getStatusBar() : getNavigationBar();
if (targetBar != null) {
requestTransientBars(targetBar);
}
@@ -480,7 +491,6 @@ public class DisplayPolicy {
final Resources r = mContext.getResources();
mCarDockEnablesAccelerometer = r.getBoolean(R.bool.config_carDockEnablesAccelerometer);
mDeskDockEnablesAccelerometer = r.getBoolean(R.bool.config_deskDockEnablesAccelerometer);
- mForceShowSystemBarsFromExternal = r.getBoolean(R.bool.config_forceShowSystemBars);
mAccessibilityManager = (AccessibilityManager) mContext.getSystemService(
Context.ACCESSIBILITY_SERVICE);
@@ -500,6 +510,7 @@ public class DisplayPolicy {
if (mStatusBar != null) {
requestTransientBars(mStatusBar);
}
+ checkAltBarSwipeForTransientBars(ALT_BAR_TOP);
}
}
@@ -510,6 +521,7 @@ public class DisplayPolicy {
&& mNavigationBarPosition == NAV_BAR_BOTTOM) {
requestTransientBars(mNavigationBar);
}
+ checkAltBarSwipeForTransientBars(ALT_BAR_BOTTOM);
}
}
@@ -526,6 +538,7 @@ public class DisplayPolicy {
excludedRegion)) {
requestTransientBars(mNavigationBar);
}
+ checkAltBarSwipeForTransientBars(ALT_BAR_RIGHT);
}
excludedRegion.recycle();
}
@@ -543,6 +556,7 @@ public class DisplayPolicy {
excludedRegion)) {
requestTransientBars(mNavigationBar);
}
+ checkAltBarSwipeForTransientBars(ALT_BAR_LEFT);
}
excludedRegion.recycle();
}
@@ -644,6 +658,15 @@ public class DisplayPolicy {
mHandler.post(mGestureNavigationSettingsObserver::register);
}
+ private void checkAltBarSwipeForTransientBars(@WindowManagerPolicy.AltBarPosition int pos) {
+ if (mStatusBarAlt != null && mStatusBarAltPosition == pos) {
+ requestTransientBars(mStatusBarAlt);
+ }
+ if (mNavigationBarAlt != null && mNavigationBarAltPosition == pos) {
+ requestTransientBars(mNavigationBarAlt);
+ }
+ }
+
void systemReady() {
mSystemGestures.systemReady();
if (mService.mPointerLocationEnabled) {
@@ -698,17 +721,6 @@ public class DisplayPolicy {
return mDockMode;
}
- /**
- * @see WindowManagerService.setForceShowSystemBars
- */
- void setForceShowSystemBars(boolean forceShowSystemBars) {
- mForceShowSystemBarsFromExternal = forceShowSystemBars;
- }
-
- boolean getForceShowSystemBars() {
- return mForceShowSystemBarsFromExternal;
- }
-
public boolean hasNavigationBar() {
return mHasNavigationBar;
}
@@ -916,6 +928,14 @@ public class DisplayPolicy {
}
break;
}
+
+ // Check if alternate bars positions were updated.
+ if (mStatusBarAlt == win) {
+ mStatusBarAltPosition = getAltBarPosition(attrs);
+ }
+ if (mNavigationBarAlt == win) {
+ mNavigationBarAltPosition = getAltBarPosition(attrs);
+ }
}
/**
@@ -955,10 +975,9 @@ public class DisplayPolicy {
mContext.enforcePermission(
android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid,
"DisplayPolicy");
- if (mStatusBar != null) {
- if (mStatusBar.isAlive()) {
- return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
- }
+ if ((mStatusBar != null && mStatusBar.isAlive())
+ || (mStatusBarAlt != null && mStatusBarAlt.isAlive())) {
+ return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
}
break;
case TYPE_NOTIFICATION_SHADE:
@@ -975,10 +994,9 @@ public class DisplayPolicy {
mContext.enforcePermission(
android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid,
"DisplayPolicy");
- if (mNavigationBar != null) {
- if (mNavigationBar.isAlive()) {
- return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
- }
+ if ((mNavigationBar != null && mNavigationBar.isAlive())
+ || (mNavigationBarAlt != null && mNavigationBarAlt.isAlive())) {
+ return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
}
break;
case TYPE_NAVIGATION_BAR_PANEL:
@@ -1010,6 +1028,23 @@ public class DisplayPolicy {
android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid,
"DisplayPolicy");
enforceSingleInsetsTypeCorrespondingToWindowType(attrs.providesInsetsTypes);
+
+ for (@InternalInsetsType int insetType : attrs.providesInsetsTypes) {
+ switch (insetType) {
+ case ITYPE_STATUS_BAR:
+ if ((mStatusBar != null && mStatusBar.isAlive())
+ || (mStatusBarAlt != null && mStatusBarAlt.isAlive())) {
+ return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
+ }
+ break;
+ case ITYPE_NAVIGATION_BAR:
+ if ((mNavigationBar != null && mNavigationBar.isAlive())
+ || (mNavigationBarAlt != null && mNavigationBarAlt.isAlive())) {
+ return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
+ }
+ break;
+ }
+ }
}
return ADD_OKAY;
}
@@ -1101,7 +1136,19 @@ public class DisplayPolicy {
break;
default:
if (attrs.providesInsetsTypes != null) {
- for (int insetsType : attrs.providesInsetsTypes) {
+ for (@InternalInsetsType int insetsType : attrs.providesInsetsTypes) {
+ switch (insetsType) {
+ case ITYPE_STATUS_BAR:
+ mStatusBarAlt = win;
+ mStatusBarController.setWindow(mStatusBarAlt);
+ mStatusBarAltPosition = getAltBarPosition(attrs);
+ break;
+ case ITYPE_NAVIGATION_BAR:
+ mNavigationBarAlt = win;
+ mNavigationBarController.setWindow(mNavigationBarAlt);
+ mNavigationBarAltPosition = getAltBarPosition(attrs);
+ break;
+ }
mDisplayContent.setInsetProvider(insetsType, win, null);
}
}
@@ -1109,6 +1156,22 @@ public class DisplayPolicy {
}
}
+ @WindowManagerPolicy.AltBarPosition
+ private int getAltBarPosition(WindowManager.LayoutParams params) {
+ switch (params.gravity) {
+ case Gravity.LEFT:
+ return ALT_BAR_LEFT;
+ case Gravity.RIGHT:
+ return ALT_BAR_RIGHT;
+ case Gravity.BOTTOM:
+ return ALT_BAR_BOTTOM;
+ case Gravity.TOP:
+ return ALT_BAR_TOP;
+ default:
+ return ALT_BAR_UNKNOWN;
+ }
+ }
+
TriConsumer<DisplayFrames, WindowState, Rect> getImeSourceFrameProvider() {
return (displayFrames, windowState, inOutFrame) -> {
if (mNavigationBar != null && navigationBarPosition(displayFrames.mDisplayWidth,
@@ -1149,12 +1212,14 @@ public class DisplayPolicy {
* @param win The window being removed.
*/
void removeWindowLw(WindowState win) {
- if (mStatusBar == win) {
+ if (mStatusBar == win || mStatusBarAlt == win) {
mStatusBar = null;
+ mStatusBarAlt = null;
mStatusBarController.setWindow(null);
mDisplayContent.setInsetProvider(ITYPE_STATUS_BAR, null, null);
- } else if (mNavigationBar == win) {
+ } else if (mNavigationBar == win || mNavigationBarAlt == win) {
mNavigationBar = null;
+ mNavigationBarAlt = null;
mNavigationBarController.setWindow(null);
mDisplayContent.setInsetProvider(ITYPE_NAVIGATION_BAR, null, null);
} else if (mNotificationShade == win) {
@@ -1180,7 +1245,7 @@ public class DisplayPolicy {
}
WindowState getStatusBar() {
- return mStatusBar;
+ return mStatusBar != null ? mStatusBar : mStatusBarAlt;
}
WindowState getNotificationShade() {
@@ -1188,7 +1253,7 @@ public class DisplayPolicy {
}
WindowState getNavigationBar() {
- return mNavigationBar;
+ return mNavigationBar != null ? mNavigationBar : mNavigationBarAlt;
}
/**
@@ -1250,6 +1315,46 @@ public class DisplayPolicy {
return R.anim.dock_left_enter;
}
}
+ } else if (win == mStatusBarAlt || win == mNavigationBarAlt) {
+ if (win.getAttrs().windowAnimations != 0) {
+ return ANIMATION_STYLEABLE;
+ }
+
+ int pos = (win == mStatusBarAlt) ? mStatusBarAltPosition : mNavigationBarAltPosition;
+
+ boolean isExitOrHide = transit == TRANSIT_EXIT || transit == TRANSIT_HIDE;
+ boolean isEnterOrShow = transit == TRANSIT_ENTER || transit == TRANSIT_SHOW;
+
+ switch (pos) {
+ case ALT_BAR_LEFT:
+ if (isExitOrHide) {
+ return R.anim.dock_left_exit;
+ } else if (isEnterOrShow) {
+ return R.anim.dock_left_enter;
+ }
+ break;
+ case ALT_BAR_RIGHT:
+ if (isExitOrHide) {
+ return R.anim.dock_right_exit;
+ } else if (isEnterOrShow) {
+ return R.anim.dock_right_enter;
+ }
+ break;
+ case ALT_BAR_BOTTOM:
+ if (isExitOrHide) {
+ return R.anim.dock_bottom_exit;
+ } else if (isEnterOrShow) {
+ return R.anim.dock_bottom_enter;
+ }
+ break;
+ case ALT_BAR_TOP:
+ if (isExitOrHide) {
+ return R.anim.dock_top_exit;
+ } else if (isEnterOrShow) {
+ return R.anim.dock_top_enter;
+ }
+ break;
+ }
}
if (transit == TRANSIT_PREVIEW_DONE) {
@@ -1608,7 +1713,7 @@ public class DisplayPolicy {
mInputConsumer = null;
Slog.v(TAG, INPUT_CONSUMER_NAVIGATION + " dismissed.");
}
- } else if (mInputConsumer == null && mStatusBar != null && canHideNavigationBar()) {
+ } else if (mInputConsumer == null && getStatusBar() != null && canHideNavigationBar()) {
mInputConsumer = mDisplayContent.getInputMonitor().createInputConsumer(
mHandler.getLooper(),
INPUT_CONSUMER_NAVIGATION,
@@ -2694,10 +2799,10 @@ public class DisplayPolicy {
mDreamingLockscreen = mService.mPolicy.isKeyguardShowingAndNotOccluded();
}
- if (mStatusBar != null) {
+ if (getStatusBar() != null) {
if (DEBUG_LAYOUT) Slog.i(TAG, "force=" + mForceStatusBar
+ " top=" + mTopFullscreenOpaqueWindowState);
- final boolean forceShowStatusBar = (mStatusBar.getAttrs().privateFlags
+ final boolean forceShowStatusBar = (getStatusBar().getAttrs().privateFlags
& PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR) != 0;
final boolean notificationShadeForcesShowingNavigation =
mNotificationShade != null
@@ -3183,6 +3288,16 @@ public class DisplayPolicy {
return mNavigationBarPosition;
}
+ @WindowManagerPolicy.AltBarPosition
+ int getAlternateStatusBarPosition() {
+ return mStatusBarAltPosition;
+ }
+
+ @WindowManagerPolicy.AltBarPosition
+ int getAlternateNavBarPosition() {
+ return mNavigationBarAltPosition;
+ }
+
/**
* A new window has been focused.
*/
@@ -3344,8 +3459,8 @@ public class DisplayPolicy {
final boolean isFullscreen = (visibility & (View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)) != 0
|| (PolicyControl.getWindowFlags(win, win.mAttrs) & FLAG_FULLSCREEN) != 0
- || (mStatusBar != null && insetsPolicy.isHidden(ITYPE_STATUS_BAR))
- || (mNavigationBar != null && insetsPolicy.isHidden(
+ || (getStatusBar() != null && insetsPolicy.isHidden(ITYPE_STATUS_BAR))
+ || (getNavigationBar() != null && insetsPolicy.isHidden(
ITYPE_NAVIGATION_BAR));
final int behavior = win.mAttrs.insetsFlags.behavior;
final boolean isImmersive = (visibility & (View.SYSTEM_UI_FLAG_IMMERSIVE
@@ -3547,8 +3662,7 @@ public class DisplayPolicy {
// We need to force system bars when the docked stack is visible, when the freeform stack
// is focused but also when we are resizing for the transitions when docked stack
// visibility changes.
- mForceShowSystemBars = dockedStackVisible || win.inFreeformWindowingMode() || resizing
- || mForceShowSystemBarsFromExternal;
+ mForceShowSystemBars = dockedStackVisible || win.inFreeformWindowingMode() || resizing;
final boolean forceOpaqueStatusBar = mForceShowSystemBars && !isKeyguardShowing();
// apply translucent bar vis flags
@@ -3608,7 +3722,7 @@ public class DisplayPolicy {
final boolean hideNavBarSysui =
(vis & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0;
- final boolean transientStatusBarAllowed = mStatusBar != null
+ final boolean transientStatusBarAllowed = getStatusBar() != null
&& (notificationShadeHasFocus || (!mForceShowSystemBars
&& (hideStatusBarWM || (hideStatusBarSysui && immersiveSticky))));
@@ -3766,7 +3880,7 @@ public class DisplayPolicy {
// TODO(b/118118435): Remove this after migration
private boolean isImmersiveMode(int vis) {
final int flags = View.SYSTEM_UI_FLAG_IMMERSIVE | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
- return mNavigationBar != null
+ return getNavigationBar() != null
&& (vis & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0
&& (vis & flags) != 0
&& canHideNavigationBar();
@@ -3774,7 +3888,7 @@ public class DisplayPolicy {
private boolean isImmersiveMode(WindowState win) {
final int behavior = win.mAttrs.insetsFlags.behavior;
- return mNavigationBar != null
+ return getNavigationBar() != null
&& canHideNavigationBar()
&& (behavior == BEHAVIOR_SHOW_BARS_BY_SWIPE
|| behavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE)
@@ -3855,8 +3969,8 @@ public class DisplayPolicy {
public void takeScreenshot(int screenshotType, int source) {
if (mScreenshotHelper != null) {
mScreenshotHelper.takeScreenshot(screenshotType,
- mStatusBar != null && mStatusBar.isVisibleLw(),
- mNavigationBar != null && mNavigationBar.isVisibleLw(),
+ getStatusBar() != null && getStatusBar().isVisibleLw(),
+ getNavigationBar() != null && getNavigationBar().isVisibleLw(),
source, mHandler, null /* completionConsumer */);
}
}
@@ -3894,6 +4008,11 @@ public class DisplayPolicy {
if (mStatusBar != null) {
pw.print(prefix); pw.print("mStatusBar="); pw.print(mStatusBar);
}
+ if (mStatusBarAlt != null) {
+ pw.print(prefix); pw.print("mStatusBarAlt="); pw.print(mStatusBarAlt);
+ pw.print(prefix); pw.print("mStatusBarAltPosition=");
+ pw.println(mStatusBarAltPosition);
+ }
if (mNotificationShade != null) {
pw.print(prefix); pw.print("mExpandedPanel="); pw.print(mNotificationShade);
}
@@ -3905,6 +4024,11 @@ public class DisplayPolicy {
pw.print(prefix); pw.print("mNavigationBarPosition=");
pw.println(mNavigationBarPosition);
}
+ if (mNavigationBarAlt != null) {
+ pw.print(prefix); pw.print("mNavigationBarAlt="); pw.println(mNavigationBarAlt);
+ pw.print(prefix); pw.print("mNavigationBarAltPosition=");
+ pw.println(mNavigationBarAltPosition);
+ }
if (mFocusedWindow != null) {
pw.print(prefix); pw.print("mFocusedWindow="); pw.println(mFocusedWindow);
}
@@ -3923,8 +4047,8 @@ public class DisplayPolicy {
}
pw.print(prefix); pw.print("mTopIsFullscreen="); pw.println(mTopIsFullscreen);
pw.print(prefix); pw.print("mForceStatusBar="); pw.print(mForceStatusBar);
- pw.print(prefix); pw.print("mForceShowSystemBarsFromExternal=");
- pw.print(mForceShowSystemBarsFromExternal);
+ pw.print(prefix); pw.print("mRemoteInsetsControllerControlsSystemBars");
+ pw.print(mDisplayContent.getInsetsPolicy().getRemoteInsetsControllerControlsSystemBars());
pw.print(" mAllowLockscreenWhenOn="); pw.println(mAllowLockscreenWhenOn);
mStatusBarController.dump(pw, prefix);
mNavigationBarController.dump(pw, prefix);
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 254356d673e3..82e7555c62ac 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -46,7 +46,9 @@ import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimation;
import android.view.WindowInsetsAnimation.Bounds;
import android.view.WindowInsetsAnimationControlListener;
+import android.view.WindowManager;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.DisplayThread;
@@ -97,12 +99,31 @@ class InsetsPolicy {
private BarWindow mStatusBar = new BarWindow(StatusBarManager.WINDOW_STATUS_BAR);
private BarWindow mNavBar = new BarWindow(StatusBarManager.WINDOW_NAVIGATION_BAR);
private boolean mAnimatingShown;
+
+ /**
+ * Let remote insets controller control system bars regardless of other settings.
+ */
+ private boolean mRemoteInsetsControllerControlsSystemBars;
private final float[] mTmpFloat9 = new float[9];
InsetsPolicy(InsetsStateController stateController, DisplayContent displayContent) {
mStateController = stateController;
mDisplayContent = displayContent;
mPolicy = displayContent.getDisplayPolicy();
+ mRemoteInsetsControllerControlsSystemBars = mPolicy.getContext().getResources().getBoolean(
+ R.bool.config_remoteInsetsControllerControlsSystemBars);
+ }
+
+ boolean getRemoteInsetsControllerControlsSystemBars() {
+ return mRemoteInsetsControllerControlsSystemBars;
+ }
+
+ /**
+ * Used only for testing.
+ */
+ @VisibleForTesting
+ void setRemoteInsetsControllerControlsSystemBars(boolean controlsSystemBars) {
+ mRemoteInsetsControllerControlsSystemBars = controlsSystemBars;
}
/** Updates the target which can control system bars. */
@@ -256,6 +277,11 @@ class InsetsPolicy {
// Notification shade has control anyways, no reason to force anything.
return focusedWin;
}
+ if (remoteInsetsControllerControlsSystemBars(focusedWin)) {
+ mDisplayContent.mRemoteInsetsControlTarget.topFocusedWindowChanged(
+ focusedWin.mAttrs.packageName);
+ return mDisplayContent.mRemoteInsetsControlTarget;
+ }
if (forceShowsSystemBarsForWindowingMode) {
// Status bar is forcibly shown for the windowing mode which is a steady state.
// We don't want the client to control the status bar, and we will dispatch the real
@@ -285,6 +311,11 @@ class InsetsPolicy {
// Notification shade has control anyways, no reason to force anything.
return focusedWin;
}
+ if (remoteInsetsControllerControlsSystemBars(focusedWin)) {
+ mDisplayContent.mRemoteInsetsControlTarget.topFocusedWindowChanged(
+ focusedWin.mAttrs.packageName);
+ return mDisplayContent.mRemoteInsetsControlTarget;
+ }
if (forceShowsSystemBarsForWindowingMode) {
// Navigation bar is forcibly shown for the windowing mode which is a steady state.
// We don't want the client to control the navigation bar, and we will dispatch the real
@@ -300,6 +331,28 @@ class InsetsPolicy {
return focusedWin;
}
+ /**
+ * Determines whether the remote insets controller should take control of system bars for all
+ * windows.
+ */
+ boolean remoteInsetsControllerControlsSystemBars(@Nullable WindowState focusedWin) {
+ if (focusedWin == null) {
+ return false;
+ }
+ if (!mRemoteInsetsControllerControlsSystemBars) {
+ return false;
+ }
+ if (mDisplayContent == null || mDisplayContent.mRemoteInsetsControlTarget == null) {
+ // No remote insets control target to take control of insets.
+ return false;
+ }
+ // If necessary, auto can control application windows when
+ // config_remoteInsetsControllerControlsSystemBars is set to true. This is useful in cases
+ // where we want to dictate system bar inset state for applications.
+ return focusedWin.getAttrs().type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
+ && focusedWin.getAttrs().type <= WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
+ }
+
private boolean forceShowsStatusBarTransiently() {
final WindowState win = mPolicy.getStatusBar();
return win != null && (win.mAttrs.privateFlags & PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR) != 0;
@@ -321,10 +374,7 @@ class InsetsPolicy {
// We need to force system bars when the docked stack is visible, when the freeform stack
// is visible but also when we are resizing for the transitions when docked stack
// visibility changes.
- return isDockedStackVisible
- || isFreeformStackVisible
- || isResizing
- || mPolicy.getForceShowSystemBars();
+ return isDockedStackVisible || isFreeformStackVisible || isResizing;
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 4700864c03bc..84a389349204 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2300,8 +2300,12 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
}
for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
- boolean resumedOnDisplay = false;
final DisplayContent display = getChildAt(displayNdx);
+ if (display.shouldSleep()) {
+ continue;
+ }
+
+ boolean resumedOnDisplay = false;
for (int tdaNdx = display.getTaskDisplayAreaCount() - 1; tdaNdx >= 0; --tdaNdx) {
final TaskDisplayArea taskDisplayArea = display.getTaskDisplayAreaAt(tdaNdx);
for (int sNdx = taskDisplayArea.getStackCount() - 1; sNdx >= 0; --sNdx) {
@@ -2390,7 +2394,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
// process the keyguard going away, which can happen before the sleep
// token is released. As a result, it is important we resume the
// activity here.
- resumeFocusedStacksTopActivities();
+ stack.resumeTopActivityUncheckedLocked(null, null);
}
// The visibility update must not be called before resuming the top, so the
// display orientation can be updated first if needed. Otherwise there may
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index ef81c0a5d206..b7ac54f3470e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2163,6 +2163,10 @@ public class WindowManagerService extends IWindowManager.Stub
throw new IllegalArgumentException(
"Window type can not be changed after the window is added.");
}
+ if (!Arrays.equals(win.mAttrs.providesInsetsTypes, attrs.providesInsetsTypes)) {
+ throw new IllegalArgumentException(
+ "Insets types can not be changed after the window is added.");
+ }
// Odd choice but less odd than embedding in copyFrom()
if ((attrs.privateFlags & WindowManager.LayoutParams.PRIVATE_FLAG_PRESERVE_GEOMETRY)
@@ -5827,27 +5831,6 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- @Override
- public void setForceShowSystemBars(boolean show) {
- boolean isAutomotive = mContext.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_AUTOMOTIVE);
- if (!isAutomotive) {
- throw new UnsupportedOperationException("Force showing system bars is only supported"
- + "for Automotive use cases.");
- }
- if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Caller does not hold permission "
- + android.Manifest.permission.STATUS_BAR);
- }
- synchronized (mGlobalLock) {
- final PooledConsumer c = PooledLambda.obtainConsumer(
- DisplayPolicy::setForceShowSystemBars, PooledLambda.__(), show);
- mRoot.forAllDisplayPolicies(c);
- c.recycle();
- }
- }
-
public void setNavBarVirtualKeyHapticFeedbackEnabled(boolean enabled) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR)
!= PackageManager.PERMISSION_GRANTED) {
diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
index ec56e1ebc8e0..b5c9375fcc0d 100644
--- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
@@ -122,6 +122,7 @@ import com.android.server.testing.shadows.ShadowBackupDataOutput;
import com.android.server.testing.shadows.ShadowEventLog;
import com.android.server.testing.shadows.ShadowSystemServiceRegistry;
+import com.google.common.base.Charsets;
import com.google.common.truth.IterableSubject;
import org.junit.After;
@@ -1910,7 +1911,8 @@ public class KeyValueBackupTaskTest {
}
@Test
- public void testRunTask_whenTransportReturnsError_updatesFilesAndCleansUp() throws Exception {
+ public void testRunTask_whenTransportReturnsErrorForGenericPackage_updatesFilesAndCleansUp()
+ throws Exception {
TransportMock transportMock = setUpInitializedTransport(mTransport);
when(transportMock.transport.performBackup(
argThat(packageInfo(PACKAGE_1)), any(), anyInt()))
@@ -1926,6 +1928,39 @@ public class KeyValueBackupTaskTest {
assertCleansUpFilesAndAgent(mTransport, PACKAGE_1);
}
+ /**
+ * Checks that TRANSPORT_ERROR during @pm@ backup keeps the state file untouched.
+ * http://b/144030477
+ */
+ @Test
+ public void testRunTask_whenTransportReturnsErrorForPm_updatesFilesAndCleansUp()
+ throws Exception {
+ // TODO(tobiast): Refactor this method to share code with
+ // testRunTask_whenTransportReturnsErrorForGenericPackage_updatesFilesAndCleansUp
+ // See patchset 7 of http://ag/11762961
+ final PackageData packageData = PM_PACKAGE;
+ TransportMock transportMock = setUpInitializedTransport(mTransport);
+ when(transportMock.transport.performBackup(
+ argThat(packageInfo(packageData)), any(), anyInt()))
+ .thenReturn(BackupTransport.TRANSPORT_ERROR);
+
+ byte[] pmStateBytes = "fake @pm@ state for testing".getBytes(Charsets.UTF_8);
+
+ Path pmStatePath = createPmStateFile(pmStateBytes.clone());
+ PackageManagerBackupAgent pmAgent = spy(createPmAgent());
+ KeyValueBackupTask task = createKeyValueBackupTask(transportMock, packageData);
+ runTask(task);
+ verify(pmAgent, never()).onBackup(any(), any(), any());
+
+ assertThat(Files.readAllBytes(pmStatePath)).isEqualTo(pmStateBytes.clone());
+
+ boolean existed = deletePmStateFile();
+ assertThat(existed).isTrue();
+ // unbindAgent() is skipped for @pm@. Comment in KeyValueBackupTask.java:
+ // "For PM metadata (for which applicationInfo is null) there is no agent-bound state."
+ assertCleansUpFiles(mTransport, packageData);
+ }
+
@Test
public void testRunTask_whenTransportGetBackupQuotaThrowsForPm() throws Exception {
TransportMock transportMock = setUpInitializedTransport(mTransport);
@@ -2707,21 +2742,29 @@ public class KeyValueBackupTaskTest {
* </ul>
* </ul>
*/
- private void createPmStateFile() throws IOException {
- createPmStateFile(mTransport);
+ private Path createPmStateFile() throws IOException {
+ return createPmStateFile("pmState".getBytes());
+ }
+
+ private Path createPmStateFile(byte[] bytes) throws IOException {
+ return createPmStateFile(bytes, mTransport);
+ }
+
+ private Path createPmStateFile(TransportData transport) throws IOException {
+ return createPmStateFile("pmState".getBytes(), mTransport);
}
- /** @see #createPmStateFile() */
- private void createPmStateFile(TransportData transport) throws IOException {
- Files.write(getStateFile(transport, PM_PACKAGE), "pmState".getBytes());
+ /** @see #createPmStateFile(byte[]) */
+ private Path createPmStateFile(byte[] bytes, TransportData transport) throws IOException {
+ return Files.write(getStateFile(transport, PM_PACKAGE), bytes);
}
/**
* Forces transport initialization and call to {@link
* UserBackupManagerService#resetBackupState(File)}
*/
- private void deletePmStateFile() throws IOException {
- Files.deleteIfExists(getStateFile(mTransport, PM_PACKAGE));
+ private boolean deletePmStateFile() throws IOException {
+ return Files.deleteIfExists(getStateFile(mTransport, PM_PACKAGE));
}
/**
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index 30bb38a075be..ad0b092b5543 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -28,6 +28,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
@@ -72,6 +73,8 @@ public class AuthServiceTest {
IIrisService mIrisService;
@Mock
IFaceService mFaceService;
+ @Mock
+ AppOpsManager mAppOpsManager;
@Before
public void setUp() {
@@ -90,6 +93,7 @@ public class AuthServiceTest {
when(mInjector.getFingerprintService()).thenReturn(mFingerprintService);
when(mInjector.getFaceService()).thenReturn(mFaceService);
when(mInjector.getIrisService()).thenReturn(mIrisService);
+ when(mInjector.getAppOps(any())).thenReturn(mAppOpsManager);
}
@Test
@@ -137,7 +141,9 @@ public class AuthServiceTest {
// TODO(b/141025588): Check that an exception is thrown when the userId != callingUserId
@Test
- public void testAuthenticate_callsBiometricServiceAuthenticate() throws Exception {
+ public void testAuthenticate_appOpsOk_callsBiometricServiceAuthenticate() throws Exception {
+ when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_USE_BIOMETRIC), anyInt(), any(), any(),
+ any())).thenReturn(AppOpsManager.MODE_ALLOWED);
mAuthService = new AuthService(mContext, mInjector);
mAuthService.onStart();
@@ -167,6 +173,38 @@ public class AuthServiceTest {
}
@Test
+ public void testAuthenticate_appOpsDenied_doesNotCallBiometricService() throws Exception {
+ when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_USE_BIOMETRIC), anyInt(), any(), any(),
+ any())).thenReturn(AppOpsManager.MODE_ERRORED);
+ mAuthService = new AuthService(mContext, mInjector);
+ mAuthService.onStart();
+
+ final Binder token = new Binder();
+ final Bundle bundle = new Bundle();
+ final long sessionId = 0;
+ final int userId = 0;
+
+ mAuthService.mImpl.authenticate(
+ token,
+ sessionId,
+ userId,
+ mReceiver,
+ TEST_OP_PACKAGE_NAME,
+ bundle);
+ waitForIdle();
+ verify(mBiometricService, never()).authenticate(
+ eq(token),
+ eq(sessionId),
+ eq(userId),
+ eq(mReceiver),
+ eq(TEST_OP_PACKAGE_NAME),
+ eq(bundle),
+ eq(Binder.getCallingUid()),
+ eq(Binder.getCallingPid()),
+ eq(UserHandle.getCallingUserId()));
+ }
+
+ @Test
public void testCanAuthenticate_callsBiometricServiceCanAuthenticate() throws Exception {
mAuthService = new AuthService(mContext, mInjector);
mAuthService.onStart();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 66dfbfdda604..cbccfb3725e9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -96,13 +96,10 @@ import android.platform.test.annotations.Presubmit;
import android.util.DisplayMetrics;
import android.view.DisplayCutout;
import android.view.Gravity;
-import android.view.IDisplayWindowInsetsController;
import android.view.IDisplayWindowRotationCallback;
import android.view.IDisplayWindowRotationController;
import android.view.ISystemGestureExclusionListener;
import android.view.IWindowManager;
-import android.view.InsetsSourceControl;
-import android.view.InsetsState;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceControl.Transaction;
@@ -934,28 +931,6 @@ public class DisplayContentTests extends WindowTestsBase {
assertEquals(mAppWindow, mDisplayContent.computeImeControlTarget());
}
- private IDisplayWindowInsetsController createDisplayWindowInsetsController() {
- return new IDisplayWindowInsetsController.Stub() {
-
- @Override
- public void insetsChanged(InsetsState insetsState) throws RemoteException {
- }
-
- @Override
- public void insetsControlChanged(InsetsState insetsState,
- InsetsSourceControl[] insetsSourceControls) throws RemoteException {
- }
-
- @Override
- public void showInsets(int i, boolean b) throws RemoteException {
- }
-
- @Override
- public void hideInsets(int i, boolean b) throws RemoteException {
- }
- };
- }
-
@Test
public void testUpdateSystemGestureExclusion() throws Exception {
final DisplayContent dc = createNewDisplay();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 1922351ac1eb..2f3afbcb6d72 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -40,8 +40,13 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DEC
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
+import static android.view.WindowManagerPolicyConstants.ALT_BAR_BOTTOM;
+import static android.view.WindowManagerPolicyConstants.ALT_BAR_LEFT;
+import static android.view.WindowManagerPolicyConstants.ALT_BAR_RIGHT;
+import static android.view.WindowManagerPolicyConstants.ALT_BAR_TOP;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
@@ -60,6 +65,7 @@ import android.util.Pair;
import android.util.SparseArray;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
+import android.view.Gravity;
import android.view.InsetsState;
import android.view.WindowInsets.Side;
import android.view.WindowInsets.Type;
@@ -67,7 +73,6 @@ import android.view.WindowManager;
import androidx.test.filters.SmallTest;
-import com.android.server.policy.WindowManagerPolicy;
import com.android.server.wm.utils.WmDisplayCutout;
import org.junit.Before;
@@ -148,6 +153,8 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase {
@Test
public void addingWindow_withInsetsTypes() {
+ mDisplayPolicy.removeWindowLw(mStatusBarWindow); // Removes the existing one.
+
WindowState win = createWindow(null, TYPE_STATUS_BAR_SUB_PANEL, "StatusBarSubPanel");
win.mAttrs.providesInsetsTypes = new int[]{ITYPE_STATUS_BAR, ITYPE_TOP_GESTURES};
win.getFrameLw().set(0, 0, 500, 100);
@@ -197,6 +204,47 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase {
}
@Test
+ public void addingWindow_variousGravities_alternateBarPosUpdated() {
+ mDisplayPolicy.removeWindowLw(mNavBarWindow); // Removes the existing one.
+
+ WindowState win1 = createWindow(null, TYPE_NAVIGATION_BAR_PANEL, "NavBarPanel1");
+ win1.mAttrs.providesInsetsTypes = new int[]{ITYPE_NAVIGATION_BAR};
+ win1.mAttrs.gravity = Gravity.TOP;
+ win1.getFrameLw().set(0, 0, 200, 500);
+ addWindow(win1);
+
+ assertEquals(mDisplayPolicy.getAlternateNavBarPosition(), ALT_BAR_TOP);
+ mDisplayPolicy.removeWindowLw(win1);
+
+ WindowState win2 = createWindow(null, TYPE_NAVIGATION_BAR_PANEL, "NavBarPanel2");
+ win2.mAttrs.providesInsetsTypes = new int[]{ITYPE_NAVIGATION_BAR};
+ win2.mAttrs.gravity = Gravity.BOTTOM;
+ win2.getFrameLw().set(0, 0, 200, 500);
+ addWindow(win2);
+
+ assertEquals(mDisplayPolicy.getAlternateNavBarPosition(), ALT_BAR_BOTTOM);
+ mDisplayPolicy.removeWindowLw(win2);
+
+ WindowState win3 = createWindow(null, TYPE_NAVIGATION_BAR_PANEL, "NavBarPanel3");
+ win3.mAttrs.providesInsetsTypes = new int[]{ITYPE_NAVIGATION_BAR};
+ win3.mAttrs.gravity = Gravity.LEFT;
+ win3.getFrameLw().set(0, 0, 200, 500);
+ addWindow(win3);
+
+ assertEquals(mDisplayPolicy.getAlternateNavBarPosition(), ALT_BAR_LEFT);
+ mDisplayPolicy.removeWindowLw(win3);
+
+ WindowState win4 = createWindow(null, TYPE_NAVIGATION_BAR_PANEL, "NavBarPanel4");
+ win4.mAttrs.providesInsetsTypes = new int[]{ITYPE_NAVIGATION_BAR};
+ win4.mAttrs.gravity = Gravity.RIGHT;
+ win4.getFrameLw().set(0, 0, 200, 500);
+ addWindow(win4);
+
+ assertEquals(mDisplayPolicy.getAlternateNavBarPosition(), ALT_BAR_RIGHT);
+ mDisplayPolicy.removeWindowLw(win4);
+ }
+
+ @Test
public void layoutWindowLw_fitStatusBars() {
mWindow.mAttrs.setFitInsetsTypes(Type.statusBars());
addWindow(mWindow);
@@ -807,27 +855,6 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase {
}
@Test
- public void forceShowSystemBars_clearsSystemUIFlags() {
- mDisplayPolicy.mLastSystemUiFlags |= SYSTEM_UI_FLAG_FULLSCREEN;
- mWindow.mAttrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
- mWindow.mAttrs.flags =
- FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
- mWindow.mSystemUiVisibility = SYSTEM_UI_FLAG_FULLSCREEN;
- mDisplayPolicy.setForceShowSystemBars(true);
- addWindow(mWindow);
-
- mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
- mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
- // triggers updateSystemUiVisibilityLw which will reset the flags as needed
- int finishPostLayoutPolicyLw = mDisplayPolicy.focusChangedLw(mWindow, mWindow);
-
- assertEquals(WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT, finishPostLayoutPolicyLw);
- assertEquals(0, mDisplayPolicy.mLastSystemUiFlags);
- assertEquals(0, mWindow.mAttrs.systemUiVisibility);
- assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
- }
-
- @Test
public void testScreenDecorWindows() {
final WindowState decorWindow = createWindow(null, TYPE_APPLICATION_OVERLAY, "decorWindow");
mWindow.mAttrs.flags = FLAG_NOT_FOCUSABLE | FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index c794e1a3b328..87bc7f1bf781 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -172,8 +172,9 @@ public class InsetsPolicyTest extends WindowTestsBase {
}
@Test
- public void testControlsForDispatch_forceShowSystemBarsFromExternal_appHasNoControl() {
- mDisplayContent.getDisplayPolicy().setForceShowSystemBars(true);
+ public void testControlsForDispatch_remoteInsetsControllerControlsBars_appHasNoControl() {
+ mDisplayContent.setRemoteInsetsController(createDisplayWindowInsetsController());
+ mDisplayContent.getInsetsPolicy().setRemoteInsetsControllerControlsSystemBars(true);
addWindow(TYPE_STATUS_BAR, "statusBar");
addWindow(TYPE_NAVIGATION_BAR, "navBar");
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
index 51db099676b0..c848736a64c2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
@@ -896,6 +896,24 @@ public class RootActivityContainerTests extends ActivityTestsBase {
assertEquals(taskDisplayArea.getTopStack(), taskDisplayArea.getRootHomeTask());
}
+ @Test
+ public void testResumeFocusedStackOnSleepingDisplay() {
+ // Create an activity on secondary display.
+ final TestDisplayContent secondDisplay = addNewDisplayContentAt(
+ DisplayContent.POSITION_TOP);
+ final ActivityStack stack = secondDisplay.getDefaultTaskDisplayArea()
+ .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ final ActivityRecord activity = new ActivityBuilder(mService).setStack(stack).build();
+ spyOn(activity);
+ spyOn(stack);
+
+ // Cannot resumed activities on secondary display if the display should sleep.
+ doReturn(true).when(secondDisplay).shouldSleep();
+ mRootWindowContainer.resumeFocusedStacksTopActivities();
+ verify(stack, never()).resumeTopActivityUncheckedLocked(any(), any());
+ verify(activity, never()).makeActiveIfNeeded(any());
+ }
+
/**
* Mock {@link RootWindowContainer#resolveHomeActivity} for returning consistent activity
* info for test cases.
diff --git a/services/tests/wmtests/src/com/android/server/wm/ScreenDecorWindowTests.java b/services/tests/wmtests/src/com/android/server/wm/ScreenDecorWindowTests.java
index 31a102ae3bad..ef74861e9422 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ScreenDecorWindowTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ScreenDecorWindowTests.java
@@ -25,8 +25,8 @@ import static android.view.Gravity.BOTTOM;
import static android.view.Gravity.LEFT;
import static android.view.Gravity.RIGHT;
import static android.view.Gravity.TOP;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
+import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
@@ -190,7 +190,7 @@ public class ScreenDecorWindowTests {
@Test
public void testProvidesInsetsTypes() {
- int[] providesInsetsTypes = new int[]{ITYPE_STATUS_BAR};
+ int[] providesInsetsTypes = new int[]{ITYPE_CLIMATE_BAR};
final View win = createWindow("StatusBarSubPanel", TOP, MATCH_PARENT, mDecorThickness, RED,
FLAG_LAYOUT_IN_SCREEN, 0, providesInsetsTypes);
@@ -199,7 +199,7 @@ public class ScreenDecorWindowTests {
private View createDecorWindow(int gravity, int width, int height) {
int[] providesInsetsTypes =
- new int[]{gravity == TOP ? ITYPE_STATUS_BAR : ITYPE_NAVIGATION_BAR};
+ new int[]{gravity == TOP ? ITYPE_CLIMATE_BAR : ITYPE_EXTRA_NAVIGATION_BAR};
return createWindow("decorWindow", gravity, width, height, RED,
FLAG_LAYOUT_IN_SCREEN, PRIVATE_FLAG_IS_SCREEN_DECOR, providesInsetsTypes);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index f52905ef6ae9..499bf668decf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -60,24 +60,6 @@ public class WindowManagerServiceTests extends WindowTestsBase {
@Rule
public ExpectedException mExpectedException = ExpectedException.none();
- @Test
- public void testForceShowSystemBarsThrowsExceptionForNonAutomotive() {
- if (!isAutomotive()) {
- mExpectedException.expect(UnsupportedOperationException.class);
-
- mWm.setForceShowSystemBars(true);
- }
- }
-
- @Test
- public void testForceShowSystemBarsDoesNotThrowExceptionForAutomotiveWithStatusBarPermission() {
- if (isAutomotive()) {
- mExpectedException.none();
-
- mWm.setForceShowSystemBars(true);
- }
- }
-
private boolean isAutomotive() {
return getInstrumentation().getTargetContext().getPackageManager().hasSystemFeature(
PackageManager.FEATURE_AUTOMOTIVE);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index a1e5b80eb2ed..156298c86d41 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -41,11 +41,15 @@ import static org.mockito.Mockito.mock;
import android.content.Context;
import android.content.Intent;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.view.Display;
import android.view.DisplayInfo;
+import android.view.IDisplayWindowInsetsController;
import android.view.IWindow;
+import android.view.InsetsSourceControl;
+import android.view.InsetsState;
import android.view.SurfaceControl.Transaction;
import android.view.View;
import android.view.WindowManager;
@@ -123,7 +127,7 @@ class WindowTestsBase extends SystemServiceTestsBase {
mChildAppWindowBelow = createCommonWindow(mAppWindow,
TYPE_APPLICATION_MEDIA_OVERLAY,
"mChildAppWindowBelow");
- mDisplayContent.getDisplayPolicy().setForceShowSystemBars(false);
+ mDisplayContent.getInsetsPolicy().setRemoteInsetsControllerControlsSystemBars(false);
// Adding a display will cause freezing the display. Make sure to wait until it's
// unfrozen to not run into race conditions with the tests.
@@ -344,6 +348,32 @@ class WindowTestsBase extends SystemServiceTestsBase {
return createNewDisplay(displayInfo, false /* supportIme */);
}
+ IDisplayWindowInsetsController createDisplayWindowInsetsController() {
+ return new IDisplayWindowInsetsController.Stub() {
+
+ @Override
+ public void insetsChanged(InsetsState insetsState) throws RemoteException {
+ }
+
+ @Override
+ public void insetsControlChanged(InsetsState insetsState,
+ InsetsSourceControl[] insetsSourceControls) throws RemoteException {
+ }
+
+ @Override
+ public void showInsets(int i, boolean b) throws RemoteException {
+ }
+
+ @Override
+ public void hideInsets(int i, boolean b) throws RemoteException {
+ }
+
+ @Override
+ public void topFocusedWindowChanged(String packageName) {
+ }
+ };
+ }
+
/** Sets the default minimum task size to 1 so that tests can use small task sizes */
void removeGlobalMinSizeRestriction() {
mWm.mAtmService.mRootWindowContainer.mDefaultMinSizeOfResizeableTaskDp = 1;
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index bf708d96e9b1..b6152e200e65 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -2373,6 +2373,16 @@ public class CarrierConfigManager {
"call_forwarding_blocks_while_roaming_string_array";
/**
+ * Call forwarding number prefixes defined by {@link
+ * #KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY} which will be allowed while the
+ * device is reporting that it is roaming and IMS is registered over LTE or Wi-Fi.
+ * By default this value is {@code true}.
+ * @hide
+ */
+ public static final String KEY_SUPPORT_IMS_CALL_FORWARDING_WHILE_ROAMING_BOOL =
+ "support_ims_call_forwarding_while_roaming_bool";
+
+ /**
* The day of the month (1-31) on which the data cycle rolls over.
* <p>
* If the current month does not have this day, the cycle will roll over at
@@ -4213,6 +4223,7 @@ public class CarrierConfigManager {
sDefaults.putBoolean(KEY_SHOW_VIDEO_CALL_CHARGES_ALERT_DIALOG_BOOL, false);
sDefaults.putStringArray(KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY,
null);
+ sDefaults.putBoolean(KEY_SUPPORT_IMS_CALL_FORWARDING_WHILE_ROAMING_BOOL, true);
sDefaults.putInt(KEY_LTE_EARFCNS_RSRP_BOOST_INT, 0);
sDefaults.putStringArray(KEY_BOOSTED_LTE_EARFCNS_STRING_ARRAY, null);
sDefaults.putBoolean(KEY_USE_ONLY_RSRP_FOR_LTE_SIGNAL_BAR_BOOL, false);