diff options
108 files changed, 2632 insertions, 567 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/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/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..5cdcaba6ce48 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -427,6 +427,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"> @@ -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/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 d50dc7cba52b..7f7afcbf11f5 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -317,6 +317,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/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• Use resource-intensive apps (such as gaming, video, or navigation apps)\n\t• Download or upload large files\n\t• 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/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/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..85444303490d 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -214,9 +214,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 +245,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset } }; - /** - * @param context everything needs a context :( - */ @Inject public GlobalScreenshot( Context context, @Main Resources resources, @@ -320,6 +317,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 +503,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 +563,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 +613,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 +643,78 @@ 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) { + + // 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(); + }); + }); } /** - * 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 +764,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 +1016,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 +1047,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 +1119,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/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/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 5d3f3c0401ef..cdbc7d2f4861 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -5774,6 +5774,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 d6e038d8d027..85736ab11e65 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; @@ -954,28 +951,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); |