diff options
39 files changed, 640 insertions, 223 deletions
diff --git a/api/test-current.txt b/api/test-current.txt index 1142fb631891..795d873ec69c 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -3323,6 +3323,7 @@ package android.provider { field public static final String NFC_PAYMENT_DEFAULT_COMPONENT = "nfc_payment_default_component"; field public static final String NOTIFICATION_BADGING = "notification_badging"; field public static final String POWER_MENU_LOCKED_SHOW_CONTENT = "power_menu_locked_show_content"; + field public static final String SHOW_IME_WITH_HARD_KEYBOARD = "show_ime_with_hard_keyboard"; field @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static final String SYNC_PARENT_SOUNDS = "sync_parent_sounds"; field public static final String USER_SETUP_COMPLETE = "user_setup_complete"; field public static final String VOICE_INTERACTION_SERVICE = "voice_interaction_service"; diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 1f90e401dee5..322cac81d58b 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -4247,6 +4247,12 @@ public class DevicePolicyManager { * device. After this method is called, the device must be unlocked using strong authentication * (PIN, pattern, or password). This API is intended for use only by device admins. * <p> + * From version {@link android.os.Build.VERSION_CODES#R} onwards, the caller must either have + * the LOCK_DEVICE permission or the device must have the device admin feature; if neither is + * true, then the method will return without completing any action. Before version + * {@link android.os.Build.VERSION_CODES#R}, the device needed the device admin feature, + * regardless of the caller's permissions. + * <p> * The calling device admin must have requested {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK} * to be able to call this method; if it has not, a security exception will be thrown. * <p> @@ -4274,6 +4280,12 @@ public class DevicePolicyManager { * device. After this method is called, the device must be unlocked using strong authentication * (PIN, pattern, or password). This API is intended for use only by device admins. * <p> + * From version {@link android.os.Build.VERSION_CODES#R} onwards, the caller must either have + * the LOCK_DEVICE permission or the device must have the device admin feature; if neither is + * true, then the method will return without completing any action. Before version + * {@link android.os.Build.VERSION_CODES#R}, the device needed the device admin feature, + * regardless of the caller's permissions. + * <p> * The calling device admin must have requested {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK} * to be able to call this method; if it has not, a security exception will be thrown. * <p> diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index e10fceaa5bc7..4f0a9728fcf8 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -24,6 +24,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.TestApi; import android.annotation.UserIdInt; @@ -6238,6 +6239,8 @@ public final class Settings { * determines if the IME should be shown when a hard keyboard is attached. * @hide */ + @TestApi + @SuppressLint("NoSettingsProvider") public static final String SHOW_IME_WITH_HARD_KEYBOARD = "show_ime_with_hard_keyboard"; /** diff --git a/core/java/android/service/controls/templates/ControlTemplate.java b/core/java/android/service/controls/templates/ControlTemplate.java index 1e16273c455b..e592fad394b8 100644 --- a/core/java/android/service/controls/templates/ControlTemplate.java +++ b/core/java/android/service/controls/templates/ControlTemplate.java @@ -214,10 +214,13 @@ public abstract class ControlTemplate { } /** - * Get a singleton {@link ControlTemplate} that has no features. + * Get a singleton {@link ControlTemplate}, which supports no direct user input. * - * This template has no distinctive field, not even an identifier. Used for a {@link Control} - * that accepts no type of input, or when there is no known state. + * Used by {@link Control.StatelessBuilder} when there is no known state. Can also be used + * in {@link Control.StatefulBuilder} for conveying information to a user about the + * {@link Control} but direct user interaction is not desired. Since this template has no + * corresponding {@link ControlAction}, any user interaction will launch the + * {@link Control#getAppIntent()}. * * @return a singleton {@link ControlTemplate} to indicate no specific template is used by * this {@link Control} diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java index 775ef8152ca2..8aa7b6389d85 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java @@ -23,9 +23,12 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.os.Handler; +import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; +import android.util.Log; +import android.view.IWindowManager; import android.widget.ImageView; import android.widget.TextView; @@ -44,13 +47,14 @@ import javax.inject.Singleton; */ @Singleton public class UserSwitchTransitionViewController extends OverlayViewController { - private static final String TAG = "UserSwitchTransitionViewController"; + private static final String TAG = "UserSwitchTransition"; private static final String ENABLE_DEVELOPER_MESSAGE_TRUE = "true"; private final Context mContext; private final Handler mHandler; private final Resources mResources; private final UserManager mUserManager; + private final IWindowManager mWindowManagerService; @GuardedBy("this") private boolean mShowing; @@ -62,6 +66,7 @@ public class UserSwitchTransitionViewController extends OverlayViewController { @Main Handler handler, @Main Resources resources, UserManager userManager, + IWindowManager windowManagerService, OverlayViewGlobalStateController overlayViewGlobalStateController) { super(R.id.user_switching_dialog_stub, overlayViewGlobalStateController); @@ -70,6 +75,7 @@ public class UserSwitchTransitionViewController extends OverlayViewController { mHandler = handler; mResources = resources; mUserManager = userManager; + mWindowManagerService = windowManagerService; } /** @@ -81,6 +87,13 @@ public class UserSwitchTransitionViewController extends OverlayViewController { if (mPreviousUserId == newUserId || mShowing) return; mShowing = true; mHandler.post(() -> { + try { + mWindowManagerService.setSwitchingUser(true); + mWindowManagerService.lockNow(null); + } catch (RemoteException e) { + Log.e(TAG, "unable to notify window manager service regarding user switch"); + } + start(); populateDialog(mPreviousUserId, newUserId); // next time a new user is selected, this current new user will be the previous user. 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 eab381c92d98..65c556269f13 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 @@ -28,6 +28,7 @@ import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableResources; +import android.view.IWindowManager; import android.view.LayoutInflater; import android.view.ViewGroup; @@ -52,6 +53,8 @@ public class UserSwitchTransitionViewControllerTest extends SysuiTestCase { private TestableResources mTestableResources; @Mock private OverlayViewGlobalStateController mOverlayViewGlobalStateController; + @Mock + private IWindowManager mWindowManagerService; @Before public void setUp() { @@ -62,6 +65,7 @@ public class UserSwitchTransitionViewControllerTest extends SysuiTestCase { Handler.getMain(), mTestableResources.getResources(), (UserManager) mContext.getSystemService(Context.USER_SERVICE), + mWindowManagerService, mOverlayViewGlobalStateController ); @@ -125,8 +129,10 @@ public class UserSwitchTransitionViewControllerTest extends SysuiTestCase { TestableUserSwitchTransitionViewController(Context context, Handler handler, Resources resources, UserManager userManager, + IWindowManager windowManagerService, OverlayViewGlobalStateController overlayViewGlobalStateController) { - super(context, handler, resources, userManager, overlayViewGlobalStateController); + super(context, handler, resources, userManager, windowManagerService, + overlayViewGlobalStateController); mHandler = handler; } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/BlurUtils.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/BlurUtils.java new file mode 100644 index 000000000000..9f26d851f775 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/BlurUtils.java @@ -0,0 +1,37 @@ +/* + * 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.shared.system; + +import android.app.ActivityManager; +import android.os.SystemProperties; + +public abstract class BlurUtils { + + private static boolean mBlurSupportedSysProp = SystemProperties + .getBoolean("ro.surface_flinger.supports_background_blur", false); + private static boolean mBlurDisabledSysProp = SystemProperties + .getBoolean("persist.sys.sf.disable_blurs", false); + + /** + * If this device can render blurs. + * + * @return {@code true} when supported. + */ + public static boolean supportsBlursOnWindows() { + return mBlurSupportedSysProp && !mBlurDisabledSysProp && ActivityManager.isHighEndGfx(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java index 47e6645d347f..6dcc9dcdc63c 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java @@ -163,6 +163,11 @@ public class BubbleExpandedView extends LinearLayout { Log.d(TAG, "onActivityViewReady: calling startActivity, " + "bubble=" + getBubbleKey()); } + if (mActivityView == null) { + mBubbleController.removeBubble(getBubbleKey(), + BubbleController.DISMISS_INVALID_INTENT); + return; + } try { if (!mIsOverflow && mBubble.usingShortcutInfo()) { options.setApplyActivityFlagsForBubbles(true); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 015079ff900a..d5fe9b2953de 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -1822,6 +1822,10 @@ public class BubbleStackView extends FrameLayout mExpandedBubble.getExpandedView().hideImeIfVisible(); } + // Let the expanded animation controller know that it shouldn't animate child adds/reorders + // since we're about to animate collapsed. + mExpandedAnimationController.notifyPreparingToCollapse(); + final long startDelay = (long) (ExpandedAnimationController.EXPAND_COLLAPSE_TARGET_ANIM_DURATION * 0.6f); postDelayed(() -> mExpandedAnimationController.collapseBackToStack( diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java index 86fe10dddc2c..cb8995a72dc3 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java @@ -92,6 +92,14 @@ public class ExpandedAnimationController private int mScreenOrientation; private boolean mAnimatingExpand = false; + + /** + * Whether we are animating other Bubbles UI elements out in preparation for a call to + * {@link #collapseBackToStack}. If true, we won't animate bubbles in response to adds or + * reorders. + */ + private boolean mPreparingToCollapse = false; + private boolean mAnimatingCollapse = false; private @Nullable Runnable mAfterExpand; private Runnable mAfterCollapse; @@ -150,6 +158,7 @@ public class ExpandedAnimationController */ public void expandFromStack( @Nullable Runnable after, @Nullable Runnable leadBubbleEndAction) { + mPreparingToCollapse = false; mAnimatingCollapse = false; mAnimatingExpand = true; mAfterExpand = after; @@ -165,9 +174,20 @@ public class ExpandedAnimationController expandFromStack(after, null /* leadBubbleEndAction */); } + /** + * Sets that we're animating the stack collapsed, but haven't yet called + * {@link #collapseBackToStack}. This will temporarily suspend animations for bubbles that are + * added or re-ordered, since the upcoming collapse animation will handle positioning those + * bubbles in the collapsed stack. + */ + public void notifyPreparingToCollapse() { + mPreparingToCollapse = true; + } + /** Animate collapsing the bubbles back to their stacked position. */ public void collapseBackToStack(PointF collapsePoint, Runnable after) { mAnimatingExpand = false; + mPreparingToCollapse = false; mAnimatingCollapse = true; mAfterCollapse = after; mCollapsePoint = collapsePoint; @@ -501,12 +521,18 @@ public class ExpandedAnimationController startOrUpdatePathAnimation(false /* expanding */); } else { child.setTranslationX(getBubbleLeft(index)); - animationForChild(child) - .translationY( - getExpandedY() - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR, /* from */ - getExpandedY() /* to */) - .start(); - updateBubblePositions(); + + // If we're preparing to collapse, don't start animations since the collapse animation + // will take over and animate the new bubble into the correct (stacked) position. + if (!mPreparingToCollapse) { + animationForChild(child) + .translationY( + getExpandedY() + - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR, /* from */ + getExpandedY() /* to */) + .start(); + updateBubblePositions(); + } } } @@ -532,12 +558,20 @@ public class ExpandedAnimationController @Override void onChildReordered(View child, int oldIndex, int newIndex) { - updateBubblePositions(); + if (mPreparingToCollapse) { + // If a re-order is received while we're preparing to collapse, ignore it. Once started, + // the collapse animation will animate all of the bubbles to their correct (stacked) + // position. + return; + } - // We expect reordering during collapse, since we'll put the last selected bubble on top. - // Update the collapse animation so they end up in the right stacked positions. if (mAnimatingCollapse) { + // If a re-order is received during collapse, update the animation so that the bubbles + // end up in the correct (stacked) position. startOrUpdatePathAnimation(false /* expanding */); + } else { + // Otherwise, animate the bubbles around to reflect their new order. + updateBubblePositions(); } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt index d4d4d2a7d8fe..eed55315e836 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt @@ -20,7 +20,7 @@ import android.content.ComponentName import android.service.controls.Control import android.service.controls.ControlsProviderService import android.service.controls.actions.ControlAction -import com.android.systemui.controls.UserAwareController +import com.android.systemui.util.UserAwareController import java.util.function.Consumer /** diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt index 45ba1e6012fe..496741b1cd6f 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt @@ -21,7 +21,7 @@ import android.service.controls.Control import android.service.controls.ControlsProviderService import android.service.controls.actions.ControlAction import com.android.systemui.controls.ControlStatus -import com.android.systemui.controls.UserAwareController +import com.android.systemui.util.UserAwareController import com.android.systemui.controls.management.ControlsFavoritingActivity import com.android.systemui.controls.ui.ControlsUiController import java.util.function.Consumer diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt index ebdcdccead90..40c8c6bfa9f7 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -200,9 +200,9 @@ class ControlsControllerImpl @Inject constructor ( GlobalActionsDialog.PREFS_CONTROLS_FILE, Context.MODE_PRIVATE) val completedSeedingPackageSet = prefs.getStringSet( GlobalActionsDialog.PREFS_CONTROLS_SEEDING_COMPLETED, mutableSetOf<String>()) - val favoritePackageSet = favoriteComponentSet.map { it.packageName } + val servicePackageSet = serviceInfoSet.map { it.packageName } prefs.edit().putStringSet(GlobalActionsDialog.PREFS_CONTROLS_SEEDING_COMPLETED, - completedSeedingPackageSet.intersect(favoritePackageSet)).apply() + completedSeedingPackageSet.intersect(servicePackageSet)).apply() var changed = false favoriteComponentSet.subtract(serviceInfoSet).forEach { diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt index 647daccca8bd..b9f16665944f 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt @@ -18,7 +18,7 @@ package com.android.systemui.controls.management import android.content.ComponentName import com.android.systemui.controls.ControlsServiceInfo -import com.android.systemui.controls.UserAwareController +import com.android.systemui.util.UserAwareController import com.android.systemui.statusbar.policy.CallbackController /** @@ -26,7 +26,7 @@ import com.android.systemui.statusbar.policy.CallbackController */ interface ControlsListingController : CallbackController<ControlsListingController.ControlsListingCallback>, - UserAwareController { + UserAwareController { /** * @return the current list of services that satisfies the [ServiceListing]. diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt index 353367ead7e6..ee02b85e4a00 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt @@ -89,6 +89,7 @@ class ControlViewHolder( return when { status != Control.STATUS_OK -> StatusBehavior::class deviceType == DeviceTypes.TYPE_CAMERA -> TouchBehavior::class + template == ControlTemplate.NO_TEMPLATE -> TouchBehavior::class template is ToggleTemplate -> ToggleBehavior::class template is StatelessTemplate -> TouchBehavior::class template is ToggleRangeTemplate -> ToggleRangeBehavior::class @@ -235,7 +236,10 @@ class ControlViewHolder( controlsController.action(cws.componentName, cws.ci, action) } - fun usePanel(): Boolean = deviceType in ControlViewHolder.FORCE_PANEL_DEVICES + fun usePanel(): Boolean { + return deviceType in ControlViewHolder.FORCE_PANEL_DEVICES || + controlTemplate == ControlTemplate.NO_TEMPLATE + } fun bindBehavior( existingBehavior: Behavior?, diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index bc95a2514c0b..915092134cc5 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -247,7 +247,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private final Executor mBackgroundExecutor; private List<ControlsServiceInfo> mControlsServiceInfos = new ArrayList<>(); private Optional<ControlsController> mControlsControllerOptional; - private SharedPreferences mControlsPreferences; private final RingerModeTracker mRingerModeTracker; private int mDialogPressDelay = DIALOG_PRESS_DELAY; // ms private Handler mMainHandler; @@ -405,12 +404,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, }); } - // Need to be user-specific with the context to make sure we read the correct prefs - Context userContext = context.createContextAsUser( - new UserHandle(mUserManager.getUserHandle()), 0); - mControlsPreferences = userContext.getSharedPreferences(PREFS_CONTROLS_FILE, - Context.MODE_PRIVATE); - // Listen for changes to show controls on the power menu while locked onPowerMenuLockScreenSettingsChanged(); mContext.getContentResolver().registerContentObserver( @@ -444,19 +437,22 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, Collections.emptySet()); List<ComponentName> componentsToSeed = new ArrayList<>(); - for (ControlsServiceInfo info : mControlsServiceInfos) { - String pkg = info.componentName.getPackageName(); - if (seededPackages.contains(pkg) - || mControlsControllerOptional.get().countFavoritesForComponent( - info.componentName) > 0) { - continue; - } - - for (int i = 0; i < Math.min(SEEDING_MAX, preferredControlsPackages.length); i++) { - if (pkg.equals(preferredControlsPackages[i])) { - componentsToSeed.add(info.componentName); + for (int i = 0; i < Math.min(SEEDING_MAX, preferredControlsPackages.length); i++) { + String pkg = preferredControlsPackages[i]; + for (ControlsServiceInfo info : mControlsServiceInfos) { + if (!pkg.equals(info.componentName.getPackageName())) continue; + if (seededPackages.contains(pkg)) { + break; + } else if (mControlsControllerOptional.get() + .countFavoritesForComponent(info.componentName) > 0) { + // When there are existing controls but no saved preference, assume it + // is out of sync, perhaps through a device restore, and update the + // preference + addPackageToSeededSet(prefs, pkg); break; } + componentsToSeed.add(info.componentName); + break; } } @@ -466,16 +462,20 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, componentsToSeed, (response) -> { Log.d(TAG, "Controls seeded: " + response); - Set<String> completedPkgs = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, - new HashSet<String>()); if (response.getAccepted()) { - completedPkgs.add(response.getPackageName()); - prefs.edit().putStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, - completedPkgs).apply(); + addPackageToSeededSet(prefs, response.getPackageName()); } }); } + private void addPackageToSeededSet(SharedPreferences prefs, String pkg) { + Set<String> seededPackages = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, + Collections.emptySet()); + Set<String> updatedPkgs = new HashSet<>(seededPackages); + updatedPkgs.add(pkg); + prefs.edit().putStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, updatedPkgs).apply(); + } + /** * Show the global actions dialog (creating if necessary) * diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java index 2365e67fec2f..0a84f5ee1bb9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java +++ b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java @@ -23,6 +23,7 @@ import static com.android.systemui.statusbar.phone.AutoTileManager.WORK; import android.content.Context; import android.database.ContentObserver; import android.os.Handler; +import android.os.UserHandle; import android.provider.Settings.Secure; import android.text.TextUtils; import android.util.ArraySet; @@ -30,6 +31,7 @@ import android.util.ArraySet; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Prefs; import com.android.systemui.Prefs.Key; +import com.android.systemui.util.UserAwareController; import java.util.Arrays; import java.util.Collection; @@ -37,7 +39,7 @@ import java.util.Collections; import javax.inject.Inject; -public class AutoAddTracker { +public class AutoAddTracker implements UserAwareController { private static final String[][] CONVERT_PREFS = { {Key.QS_HOTSPOT_ADDED, HOTSPOT}, @@ -49,20 +51,39 @@ public class AutoAddTracker { private final ArraySet<String> mAutoAdded; private final Context mContext; + private int mUserId; - @Inject - public AutoAddTracker(Context context) { + public AutoAddTracker(Context context, int userId) { mContext = context; + mUserId = userId; mAutoAdded = new ArraySet<>(getAdded()); // TODO: remove migration code and shared preferences keys after P release - for (String[] convertPref : CONVERT_PREFS) { - if (Prefs.getBoolean(context, convertPref[0], false)) { - setTileAdded(convertPref[1]); - Prefs.remove(context, convertPref[0]); + if (mUserId == UserHandle.USER_SYSTEM) { + for (String[] convertPref : CONVERT_PREFS) { + if (Prefs.getBoolean(context, convertPref[0], false)) { + setTileAdded(convertPref[1]); + Prefs.remove(context, convertPref[0]); + } } } mContext.getContentResolver().registerContentObserver( - Secure.getUriFor(Secure.QS_AUTO_ADDED_TILES), false, mObserver); + Secure.getUriFor(Secure.QS_AUTO_ADDED_TILES), false, mObserver, + UserHandle.USER_ALL); + } + + @Override + public void changeUser(UserHandle newUser) { + if (newUser.getIdentifier() == mUserId) { + return; + } + mUserId = newUser.getIdentifier(); + mAutoAdded.clear(); + mAutoAdded.addAll(getAdded()); + } + + @Override + public int getCurrentUserId() { + return mUserId; } public boolean isAdded(String tile) { @@ -86,12 +107,13 @@ public class AutoAddTracker { } private void saveTiles() { - Secure.putString(mContext.getContentResolver(), Secure.QS_AUTO_ADDED_TILES, - TextUtils.join(",", mAutoAdded)); + Secure.putStringForUser(mContext.getContentResolver(), Secure.QS_AUTO_ADDED_TILES, + TextUtils.join(",", mAutoAdded), mUserId); } private Collection<String> getAdded() { - String current = Secure.getString(mContext.getContentResolver(), Secure.QS_AUTO_ADDED_TILES); + String current = Secure.getStringForUser(mContext.getContentResolver(), + Secure.QS_AUTO_ADDED_TILES, mUserId); if (current == null) { return Collections.emptyList(); } @@ -102,7 +124,27 @@ public class AutoAddTracker { protected final ContentObserver mObserver = new ContentObserver(new Handler()) { @Override public void onChange(boolean selfChange) { + mAutoAdded.clear(); mAutoAdded.addAll(getAdded()); } }; + + public static class Builder { + private final Context mContext; + private int mUserId; + + @Inject + public Builder(Context context) { + mContext = context; + } + + public Builder setUserId(int userId) { + mUserId = userId; + return this; + } + + public AutoAddTracker build() { + return new AutoAddTracker(mContext, mUserId); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java index 65d3572d04a3..858a7e2e30e0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java @@ -255,6 +255,9 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D int currentUser = ActivityManager.getCurrentUser(); if (currentUser != mCurrentUser) { mUserContext = mContext.createContextAsUser(UserHandle.of(currentUser), 0); + if (mAutoTiles != null) { + mAutoTiles.changeUser(UserHandle.of(currentUser)); + } } if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return; mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java index 72d9d0ee8f8f..4d09071c6b5d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java @@ -104,6 +104,9 @@ public class NotificationListener extends NotificationListenerWithPlugins { listener.onNotificationPosted(sbn, completeMap); } } + for (NotificationHandler listener : mNotificationHandlers) { + listener.onNotificationsInitialized(); + } }); onSilentStatusBarIconsVisibilityChanged( mNotificationManager.shouldHideSilentStatusBarIcons()); @@ -224,5 +227,10 @@ public class NotificationListener extends NotificationListenerWithPlugins { void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap); void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap, int reason); void onNotificationRankingUpdate(RankingMap rankingMap); + + /** + * Called after the listener has connected to NoMan and posted any current notifications. + */ + void onNotificationsInitialized(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index adb51a5d959a..9abc66056452 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -385,6 +385,10 @@ public class NotificationEntryManager implements public void onNotificationRankingUpdate(RankingMap rankingMap) { updateNotificationRanking(rankingMap); } + + @Override + public void onNotificationsInitialized() { + } }; /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index 45987b6eb21b..c1acfbadef45 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -47,7 +47,6 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.Notification; import android.os.RemoteException; -import android.os.SystemClock; import android.os.UserHandle; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.Ranking; @@ -82,6 +81,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.No import com.android.systemui.statusbar.notification.collection.notifcollection.RankingAppliedEvent; import com.android.systemui.statusbar.notification.collection.notifcollection.RankingUpdatedEvent; import com.android.systemui.util.Assert; +import com.android.systemui.util.time.SystemClock; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -95,6 +95,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Queue; +import java.util.concurrent.TimeUnit; import javax.inject.Inject; import javax.inject.Singleton; @@ -125,6 +126,7 @@ import javax.inject.Singleton; @Singleton public class NotifCollection implements Dumpable { private final IStatusBarService mStatusBarService; + private final SystemClock mClock; private final FeatureFlags mFeatureFlags; private final NotifCollectionLogger mLogger; private final LogBufferEulogizer mEulogizer; @@ -142,20 +144,24 @@ public class NotifCollection implements Dumpable { private boolean mAttached = false; private boolean mAmDispatchingToOtherCode; + private long mInitializedTimestamp = 0; @Inject public NotifCollection( IStatusBarService statusBarService, - DumpManager dumpManager, + SystemClock clock, FeatureFlags featureFlags, NotifCollectionLogger logger, - LogBufferEulogizer logBufferEulogizer) { + LogBufferEulogizer logBufferEulogizer, + DumpManager dumpManager) { Assert.isMainThread(); mStatusBarService = statusBarService; + mClock = clock; + mFeatureFlags = featureFlags; mLogger = logger; mEulogizer = logBufferEulogizer; + dumpManager.registerDumpable(TAG, this); - mFeatureFlags = featureFlags; } /** Initializes the NotifCollection and registers it to receive notification events. */ @@ -376,9 +382,10 @@ public class NotifCollection implements Dumpable { final NotificationEntry entry = mNotificationSet.get(sbn.getKey()); if (entry == null) { - throw mEulogizer.record( + crashIfNotInitializing( new IllegalStateException("No notification to remove with key " + sbn.getKey())); + return; } entry.mCancellationReason = reason; @@ -394,6 +401,10 @@ public class NotifCollection implements Dumpable { dispatchEventsAndRebuildList(); } + private void onNotificationsInitialized() { + mInitializedTimestamp = mClock.uptimeMillis(); + } + private void postNotification( StatusBarNotification sbn, Ranking ranking) { @@ -401,7 +412,7 @@ public class NotifCollection implements Dumpable { if (entry == null) { // A new notification! - entry = new NotificationEntry(sbn, ranking, SystemClock.uptimeMillis()); + entry = new NotificationEntry(sbn, ranking, mClock.uptimeMillis()); mEventQueue.add(new InitEntryEvent(entry)); mEventQueue.add(new BindEntryEvent(entry, sbn)); mNotificationSet.put(sbn.getKey(), entry); @@ -628,6 +639,23 @@ public class NotifCollection implements Dumpable { } } + // While the NotificationListener is connecting to NotificationManager, there is a short period + // during which it's possible for us to receive events about notifications we don't yet know + // about (or that otherwise don't make sense). Until that race condition is fixed, we create a + // "forgiveness window" of five seconds during which we won't crash if we receive nonsensical + // messages from system server. + private void crashIfNotInitializing(RuntimeException exception) { + final boolean isRecentlyInitialized = mInitializedTimestamp == 0 + || mClock.uptimeMillis() - mInitializedTimestamp + < INITIALIZATION_FORGIVENESS_WINDOW; + + if (isRecentlyInitialized) { + mLogger.logIgnoredError(exception.getMessage()); + } else { + throw mEulogizer.record(exception); + } + } + private static Ranking requireRanking(RankingMap rankingMap, String key) { // TODO: Modify RankingMap so that we don't have to make a copy here Ranking ranking = new Ranking(); @@ -742,6 +770,11 @@ public class NotifCollection implements Dumpable { public void onNotificationRankingUpdate(RankingMap rankingMap) { NotifCollection.this.onNotificationRankingUpdate(rankingMap); } + + @Override + public void onNotificationsInitialized() { + NotifCollection.this.onNotificationsInitialized(); + } }; private static final String TAG = "NotifCollection"; @@ -773,4 +806,6 @@ public class NotifCollection implements Dumpable { static final int REASON_NOT_CANCELED = -1; public static final int REASON_UNKNOWN = 0; + + private static final long INITIALIZATION_FORGIVENESS_WINDOW = TimeUnit.SECONDS.toMillis(5); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java index 596235cfb4d0..1710daa16735 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java @@ -153,6 +153,11 @@ public class GroupCoalescer implements Dumpable { applyRanking(rankingMap); mHandler.onNotificationRankingUpdate(rankingMap); } + + @Override + public void onNotificationsInitialized() { + mHandler.onNotificationsInitialized(); + } }; private void maybeEmitBatch(StatusBarNotification sbn) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt index 0eb2d64e8682..76751eaaecb1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt @@ -20,6 +20,7 @@ import android.os.RemoteException import android.service.notification.NotificationListenerService.RankingMap import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogLevel.DEBUG +import com.android.systemui.log.LogLevel.ERROR import com.android.systemui.log.LogLevel.INFO import com.android.systemui.log.LogLevel.WARNING import com.android.systemui.log.LogLevel.WTF @@ -167,6 +168,14 @@ class NotifCollectionLogger @Inject constructor( "LIFETIME EXTENSION ENDED for $str1 by '$str2'; $int1 remaining extensions" }) } + + fun logIgnoredError(message: String?) { + buffer.log(TAG, ERROR, { + str1 = message + }, { + "ERROR suppressed due to initialization forgiveness: $str1" + }) + } } private const val TAG = "NotifCollection" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java index fc8c8dbba7fd..825919f17661 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java @@ -19,6 +19,7 @@ import android.content.res.Resources; import android.hardware.display.ColorDisplayManager; import android.hardware.display.NightDisplayListener; import android.os.Handler; +import android.os.UserHandle; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; @@ -34,6 +35,7 @@ import com.android.systemui.statusbar.policy.DataSaverController; import com.android.systemui.statusbar.policy.DataSaverController.Listener; import com.android.systemui.statusbar.policy.HotspotController; import com.android.systemui.statusbar.policy.HotspotController.Callback; +import com.android.systemui.util.UserAwareController; import java.util.ArrayList; import java.util.Objects; @@ -43,7 +45,7 @@ import javax.inject.Inject; /** * Manages which tiles should be automatically added to QS. */ -public class AutoTileManager { +public class AutoTileManager implements UserAwareController { private static final String TAG = "AutoTileManager"; public static final String HOTSPOT = "hotspot"; @@ -52,7 +54,9 @@ public class AutoTileManager { public static final String WORK = "work"; public static final String NIGHT = "night"; public static final String CAST = "cast"; - public static final String SETTING_SEPARATOR = ":"; + static final String SETTING_SEPARATOR = ":"; + + private UserHandle mCurrentUser; private final Context mContext; private final QSTileHost mHost; @@ -66,43 +70,56 @@ public class AutoTileManager { private final ArrayList<AutoAddSetting> mAutoAddSettingList = new ArrayList<>(); @Inject - public AutoTileManager(Context context, AutoAddTracker autoAddTracker, QSTileHost host, + public AutoTileManager(Context context, AutoAddTracker.Builder autoAddTrackerBuilder, + QSTileHost host, @Background Handler handler, HotspotController hotspotController, DataSaverController dataSaverController, ManagedProfileController managedProfileController, NightDisplayListener nightDisplayListener, CastController castController) { - mAutoTracker = autoAddTracker; mContext = context; mHost = host; + mCurrentUser = mHost.getUserContext().getUser(); + mAutoTracker = autoAddTrackerBuilder.setUserId(mCurrentUser.getIdentifier()).build(); mHandler = handler; mHotspotController = hotspotController; mDataSaverController = dataSaverController; mManagedProfileController = managedProfileController; mNightDisplayListener = nightDisplayListener; mCastController = castController; + + populateSettingsList(); + startControllersAndSettingsListeners(); + } + + protected void startControllersAndSettingsListeners() { if (!mAutoTracker.isAdded(HOTSPOT)) { - hotspotController.addCallback(mHotspotCallback); + mHotspotController.addCallback(mHotspotCallback); } if (!mAutoTracker.isAdded(SAVER)) { - dataSaverController.addCallback(mDataSaverListener); + mDataSaverController.addCallback(mDataSaverListener); } if (!mAutoTracker.isAdded(WORK)) { - managedProfileController.addCallback(mProfileCallback); + mManagedProfileController.addCallback(mProfileCallback); } if (!mAutoTracker.isAdded(NIGHT) && ColorDisplayManager.isNightDisplayAvailable(mContext)) { - nightDisplayListener.setCallback(mNightDisplayCallback); + mNightDisplayListener.setCallback(mNightDisplayCallback); } if (!mAutoTracker.isAdded(CAST)) { - castController.addCallback(mCastCallback); + mCastController.addCallback(mCastCallback); + } + + int settingsN = mAutoAddSettingList.size(); + for (int i = 0; i < settingsN; i++) { + if (!mAutoTracker.isAdded(mAutoAddSettingList.get(i).mSpec)) { + mAutoAddSettingList.get(i).setListening(true); + } } - populateSettingsList(); } - public void destroy() { - mAutoTracker.destroy(); + protected void stopListening() { mHotspotController.removeCallback(mHotspotCallback); mDataSaverController.removeCallback(mDataSaverListener); mManagedProfileController.removeCallback(mProfileCallback); @@ -116,6 +133,11 @@ public class AutoTileManager { } } + public void destroy() { + stopListening(); + mAutoTracker.destroy(); + } + /** * Populates a list with the pairs setting:spec in the config resource. * <p> @@ -137,17 +159,39 @@ public class AutoTileManager { if (split.length == 2) { String setting = split[0]; String spec = split[1]; - if (!mAutoTracker.isAdded(spec)) { - AutoAddSetting s = new AutoAddSetting(mContext, mHandler, setting, spec); - mAutoAddSettingList.add(s); - s.setListening(true); - } + // Populate all the settings. As they may not have been added in other users + AutoAddSetting s = new AutoAddSetting(mContext, mHandler, setting, spec); + mAutoAddSettingList.add(s); } else { Log.w(TAG, "Malformed item in array: " + tile); } } } + @Override + public void changeUser(UserHandle newUser) { + if (!Thread.currentThread().equals(mHandler.getLooper().getThread())) { + mHandler.post(() -> changeUser(newUser)); + return; + } + if (newUser.getIdentifier() == mCurrentUser.getIdentifier()) { + return; + } + stopListening(); + mCurrentUser = newUser; + int settingsN = mAutoAddSettingList.size(); + for (int i = 0; i < settingsN; i++) { + mAutoAddSettingList.get(i).setUserId(newUser.getIdentifier()); + } + mAutoTracker.changeUser(newUser); + startControllersAndSettingsListeners(); + } + + @Override + public int getCurrentUserId() { + return mCurrentUser.getIdentifier(); + } + public void unmarkTileAsAutoAdded(String tabSpec) { mAutoTracker.setTileRemoved(tabSpec); } diff --git a/packages/SystemUI/src/com/android/systemui/controls/UserAwareController.kt b/packages/SystemUI/src/com/android/systemui/util/UserAwareController.kt index d2776d27ae62..693c2708b0f7 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/UserAwareController.kt +++ b/packages/SystemUI/src/com/android/systemui/util/UserAwareController.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.controls +package com.android.systemui.util import android.os.UserHandle @@ -23,6 +23,8 @@ import android.os.UserHandle * changes. */ interface UserAwareController { + @JvmDefault fun changeUser(newUser: UserHandle) {} + val currentUserId: Int }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java index 0ae9461d38b1..61f5a7bdd3b7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java @@ -21,6 +21,7 @@ import static com.android.systemui.statusbar.phone.AutoTileManager.WORK; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import android.os.UserHandle; import android.provider.Settings.Secure; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; @@ -40,6 +41,8 @@ import org.junit.runner.RunWith; @SmallTest public class AutoAddTrackerTest extends SysuiTestCase { + private static final int USER = 0; + private AutoAddTracker mAutoTracker; @Before @@ -51,7 +54,7 @@ public class AutoAddTrackerTest extends SysuiTestCase { public void testMigration() { Prefs.putBoolean(mContext, Key.QS_DATA_SAVER_ADDED, true); Prefs.putBoolean(mContext, Key.QS_WORK_ADDED, true); - mAutoTracker = new AutoAddTracker(mContext); + mAutoTracker = new AutoAddTracker(mContext, USER); assertTrue(mAutoTracker.isAdded(SAVER)); assertTrue(mAutoTracker.isAdded(WORK)); @@ -68,7 +71,7 @@ public class AutoAddTrackerTest extends SysuiTestCase { @Test public void testChangeFromBackup() { - mAutoTracker = new AutoAddTracker(mContext); + mAutoTracker = new AutoAddTracker(mContext, USER); assertFalse(mAutoTracker.isAdded(SAVER)); @@ -82,7 +85,7 @@ public class AutoAddTrackerTest extends SysuiTestCase { @Test public void testSetAdded() { - mAutoTracker = new AutoAddTracker(mContext); + mAutoTracker = new AutoAddTracker(mContext, USER); assertFalse(mAutoTracker.isAdded(SAVER)); mAutoTracker.setTileAdded(SAVER); @@ -94,16 +97,35 @@ public class AutoAddTrackerTest extends SysuiTestCase { @Test public void testPersist() { - mAutoTracker = new AutoAddTracker(mContext); + mAutoTracker = new AutoAddTracker(mContext, USER); assertFalse(mAutoTracker.isAdded(SAVER)); mAutoTracker.setTileAdded(SAVER); mAutoTracker.destroy(); - mAutoTracker = new AutoAddTracker(mContext); + mAutoTracker = new AutoAddTracker(mContext, USER); assertTrue(mAutoTracker.isAdded(SAVER)); mAutoTracker.destroy(); } + + @Test + public void testIndependentUsers() { + mAutoTracker = new AutoAddTracker(mContext, USER); + mAutoTracker.setTileAdded(SAVER); + + mAutoTracker = new AutoAddTracker(mContext, USER + 1); + assertFalse(mAutoTracker.isAdded(SAVER)); + } + + @Test + public void testChangeUser() { + mAutoTracker = new AutoAddTracker(mContext, USER); + mAutoTracker.setTileAdded(SAVER); + + mAutoTracker = new AutoAddTracker(mContext, USER + 1); + mAutoTracker.changeUser(UserHandle.of(USER)); + assertTrue(mAutoTracker.isAdded(SAVER)); + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java index ca9cc299b36d..363fe95aae18 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java @@ -80,6 +80,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.No import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; @@ -131,6 +132,7 @@ public class NotifCollectionTest extends SysuiTestCase { private InOrder mListenerInOrder; private NoManSimulator mNoMan; + private FakeSystemClock mClock = new FakeSystemClock(); @Before public void setUp() { @@ -146,10 +148,11 @@ public class NotifCollectionTest extends SysuiTestCase { mCollection = new NotifCollection( mStatusBarService, - mock(DumpManager.class), + mClock, mFeatureFlags, mLogger, - mEulogizer); + mEulogizer, + mock(DumpManager.class)); mCollection.attach(mGroupCoalescer); mCollection.addCollectionListener(mCollectionListener); mCollection.setBuildListener(mBuildListener); @@ -161,6 +164,8 @@ public class NotifCollectionTest extends SysuiTestCase { mNoMan = new NoManSimulator(); mNoMan.addListener(mNotifHandler); + + mNotifHandler.onNotificationsInitialized(); } @Test @@ -1268,6 +1273,42 @@ public class NotifCollectionTest extends SysuiTestCase { verify(mInterceptor3, never()).shouldInterceptDismissal(clearable); } + @Test(expected = IllegalStateException.class) + public void testClearNotificationThrowsIfMissing() { + // GIVEN that enough time has passed that we're beyond the forgiveness window + mClock.advanceTime(5001); + + // WHEN we get a remove event for a notification we don't know about + final NotificationEntry container = new NotificationEntryBuilder() + .setPkg(TEST_PACKAGE) + .setId(47) + .build(); + mNotifHandler.onNotificationRemoved( + container.getSbn(), + new RankingMap(new Ranking[]{ container.getRanking() })); + + // THEN an exception is thrown + } + + @Test + public void testClearNotificationDoesntThrowIfInForgivenessWindow() { + // GIVEN that some time has passed but we're still within the initialization forgiveness + // window + mClock.advanceTime(4999); + + // WHEN we get a remove event for a notification we don't know about + final NotificationEntry container = new NotificationEntryBuilder() + .setPkg(TEST_PACKAGE) + .setId(47) + .build(); + mNotifHandler.onNotificationRemoved( + container.getSbn(), + new RankingMap(new Ranking[]{ container.getRanking() })); + + // THEN no exception is thrown, but no event is fired + verify(mCollectionListener, never()).onEntryRemoved(any(NotificationEntry.class), anyInt()); + } + private static NotificationEntryBuilder buildNotif(String pkg, int id, String tag) { return new NotificationEntryBuilder() .setPkg(pkg) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java index 05cdd802167a..31779cdf9e71 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java @@ -16,18 +16,34 @@ package com.android.systemui.statusbar.phone; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.isNotNull; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.ContextWrapper; import android.hardware.display.ColorDisplayManager; import android.hardware.display.NightDisplayListener; import android.os.Handler; +import android.os.UserHandle; import android.provider.Settings; import android.testing.AndroidTestingRunner; +import android.testing.TestableContentResolver; +import android.testing.TestableContext; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; @@ -38,14 +54,18 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.qs.AutoAddTracker; import com.android.systemui.qs.QSTileHost; import com.android.systemui.qs.SecureSetting; +import com.android.systemui.statusbar.phone.AutoTileManagerTest.MyContextWrapper; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.CastController.CastDevice; import com.android.systemui.statusbar.policy.DataSaverController; import com.android.systemui.statusbar.policy.HotspotController; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -64,9 +84,18 @@ public class AutoTileManagerTest extends SysuiTestCase { private static final String TEST_CUSTOM_SPEC = "custom(" + TEST_COMPONENT + ")"; private static final String SEPARATOR = AutoTileManager.SETTING_SEPARATOR; + private static final int USER = 0; + @Mock private QSTileHost mQsTileHost; @Mock private AutoAddTracker mAutoAddTracker; @Mock private CastController mCastController; + @Mock private HotspotController mHotspotController; + @Mock private DataSaverController mDataSaverController; + @Mock private ManagedProfileController mManagedProfileController; + @Mock private NightDisplayListener mNightDisplayListener; + @Mock(answer = Answers.RETURNS_SELF) + private AutoAddTracker.Builder mAutoAddTrackerBuilder; + @Mock private Context mUserContext; private AutoTileManager mAutoTileManager; @@ -82,20 +111,110 @@ public class AutoTileManagerTest extends SysuiTestCase { } ); - mAutoTileManager = createAutoTileManager(); + when(mAutoAddTrackerBuilder.build()).thenReturn(mAutoAddTracker); + when(mQsTileHost.getUserContext()).thenReturn(mUserContext); + when(mUserContext.getUser()).thenReturn(UserHandle.of(USER)); + + mAutoTileManager = createAutoTileManager(new + MyContextWrapper(mContext)); } - private AutoTileManager createAutoTileManager() { - return new AutoTileManager(mContext, mAutoAddTracker, mQsTileHost, + @After + public void tearDown() { + mAutoTileManager.destroy(); + } + + private AutoTileManager createAutoTileManager(Context context) { + return new AutoTileManager(context, mAutoAddTrackerBuilder, mQsTileHost, Handler.createAsync(TestableLooper.get(this).getLooper()), - mock(HotspotController.class), - mock(DataSaverController.class), - mock(ManagedProfileController.class), - mock(NightDisplayListener.class), + mHotspotController, + mDataSaverController, + mManagedProfileController, + mNightDisplayListener, mCastController); } @Test + public void testChangeUserCallbacksStoppedAndStarted() throws Exception { + TestableLooper.get(this).runWithLooper(() -> + mAutoTileManager.changeUser(UserHandle.of(USER + 1)) + ); + + InOrder inOrderHotspot = inOrder(mHotspotController); + inOrderHotspot.verify(mHotspotController).removeCallback(any()); + inOrderHotspot.verify(mHotspotController).addCallback(any()); + + InOrder inOrderDataSaver = inOrder(mDataSaverController); + inOrderDataSaver.verify(mDataSaverController).removeCallback(any()); + inOrderDataSaver.verify(mDataSaverController).addCallback(any()); + + InOrder inOrderManagedProfile = inOrder(mManagedProfileController); + inOrderManagedProfile.verify(mManagedProfileController).removeCallback(any()); + inOrderManagedProfile.verify(mManagedProfileController).addCallback(any()); + + InOrder inOrderNightDisplay = inOrder(mNightDisplayListener); + inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNull()); + inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNotNull()); + + InOrder inOrderCast = inOrder(mCastController); + inOrderCast.verify(mCastController).removeCallback(any()); + inOrderCast.verify(mCastController).addCallback(any()); + + SecureSetting setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING); + assertEquals(USER + 1, setting.getCurrentUser()); + assertTrue(setting.isListening()); + } + + @Test + public void testChangeUserSomeCallbacksNotAdded() throws Exception { + when(mAutoAddTracker.isAdded("hotspot")).thenReturn(true); + when(mAutoAddTracker.isAdded("work")).thenReturn(true); + when(mAutoAddTracker.isAdded("cast")).thenReturn(true); + when(mAutoAddTracker.isAdded(TEST_SPEC)).thenReturn(true); + + TestableLooper.get(this).runWithLooper(() -> + mAutoTileManager.changeUser(UserHandle.of(USER + 1)) + ); + + verify(mAutoAddTracker).changeUser(UserHandle.of(USER + 1)); + + InOrder inOrderHotspot = inOrder(mHotspotController); + inOrderHotspot.verify(mHotspotController).removeCallback(any()); + inOrderHotspot.verify(mHotspotController, never()).addCallback(any()); + + InOrder inOrderDataSaver = inOrder(mDataSaverController); + inOrderDataSaver.verify(mDataSaverController).removeCallback(any()); + inOrderDataSaver.verify(mDataSaverController).addCallback(any()); + + InOrder inOrderManagedProfile = inOrder(mManagedProfileController); + inOrderManagedProfile.verify(mManagedProfileController).removeCallback(any()); + inOrderManagedProfile.verify(mManagedProfileController, never()).addCallback(any()); + + InOrder inOrderNightDisplay = inOrder(mNightDisplayListener); + inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNull()); + inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNotNull()); + + InOrder inOrderCast = inOrder(mCastController); + inOrderCast.verify(mCastController).removeCallback(any()); + inOrderCast.verify(mCastController, never()).addCallback(any()); + + SecureSetting setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING); + assertEquals(USER + 1, setting.getCurrentUser()); + assertFalse(setting.isListening()); + } + + @Test + public void testGetCurrentUserId() throws Exception { + assertEquals(USER, mAutoTileManager.getCurrentUserId()); + + TestableLooper.get(this).runWithLooper(() -> + mAutoTileManager.changeUser(UserHandle.of(USER + 100)) + ); + + assertEquals(USER + 100, mAutoTileManager.getCurrentUserId()); + } + + @Test public void nightTileAdded_whenActivated() { if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) { return; @@ -213,14 +332,14 @@ public class AutoTileManagerTest extends SysuiTestCase { public void testEmptyArray_doesNotCrash() { mContext.getOrCreateTestableResources().addOverride( R.array.config_quickSettingsAutoAdd, new String[0]); - createAutoTileManager(); + createAutoTileManager(mContext).destroy(); } @Test public void testMissingConfig_doesNotCrash() { mContext.getOrCreateTestableResources().addOverride( R.array.config_quickSettingsAutoAdd, null); - createAutoTileManager(); + createAutoTileManager(mContext).destroy(); } // Will only notify if it's listening @@ -231,4 +350,22 @@ public class AutoTileManagerTest extends SysuiTestCase { s.onChange(false); } } + + class MyContextWrapper extends ContextWrapper { + + private TestableContentResolver mSpiedTCR; + + MyContextWrapper(TestableContext context) { + super(context); + mSpiedTCR = spy(context.getContentResolver()); + doNothing().when(mSpiedTCR).registerContentObserver(any(), anyBoolean(), any(), + anyInt()); + doNothing().when(mSpiedTCR).unregisterContentObserver(any()); + } + + @Override + public ContentResolver getContentResolver() { + return mSpiedTCR; + } + } } diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java index f14def6a3a02..fd6f171487a9 100644 --- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java +++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringConstants.java @@ -37,8 +37,8 @@ public final class TetheringConstants { private TetheringConstants() { } /** - * Extra used for communicating with the TetherService and TetherProvisioningActivity. - * Includes the type of tethering to enable if any. + * Extra used for communicating with the TetherService. Includes the type of tethering to + * enable if any. */ public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType"; /** @@ -56,38 +56,8 @@ public final class TetheringConstants { */ public static final String EXTRA_RUN_PROVISION = "extraRunProvision"; /** - * Extra used for communicating with the TetherService and TetherProvisioningActivity. - * Contains the {@link ResultReceiver} which will receive provisioning results. - * Can not be empty. + * Extra used for communicating with the TetherService. Contains the {@link ResultReceiver} + * which will receive provisioning results. Can be left empty. */ public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback"; - - /** - * Extra used for communicating with the TetherService and TetherProvisioningActivity. - * Contains the subId of current active cellular upstream. - * @hide - */ - public static final String EXTRA_TETHER_SUBID = "android.net.extra.TETHER_SUBID"; - - /** - * Extra used for telling TetherProvisioningActivity the entitlement package name and class - * name to start UI entitlement check. - * @hide - */ - public static final String EXTRA_TETHER_UI_PROVISIONING_APP_NAME = - "android.net.extra.TETHER_UI_PROVISIONING_APP_NAME"; - - /** - * Extra used for telling TetherService the intent action to start silent entitlement check. - * @hide - */ - public static final String EXTRA_TETHER_SILENT_PROVISIONING_ACTION = - "android.net.extra.TETHER_SILENT_PROVISIONING_ACTION"; - - /** - * Extra used for TetherService to receive the response of provisioning check. - * @hide - */ - public static final String EXTRA_TETHER_PROVISIONING_RESPONSE = - "android.net.extra.TETHER_PROVISIONING_RESPONSE"; } diff --git a/packages/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java b/packages/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java index 9dace709d734..3c6e8d88ed13 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java @@ -19,10 +19,6 @@ package com.android.networkstack.tethering; import static android.net.TetheringConstants.EXTRA_ADD_TETHER_TYPE; import static android.net.TetheringConstants.EXTRA_PROVISION_CALLBACK; import static android.net.TetheringConstants.EXTRA_RUN_PROVISION; -import static android.net.TetheringConstants.EXTRA_TETHER_PROVISIONING_RESPONSE; -import static android.net.TetheringConstants.EXTRA_TETHER_SILENT_PROVISIONING_ACTION; -import static android.net.TetheringConstants.EXTRA_TETHER_SUBID; -import static android.net.TetheringConstants.EXTRA_TETHER_UI_PROVISIONING_APP_NAME; import static android.net.TetheringManager.TETHERING_BLUETOOTH; import static android.net.TetheringManager.TETHERING_ETHERNET; import static android.net.TetheringManager.TETHERING_INVALID; @@ -73,6 +69,7 @@ public class EntitlementManager { protected static final String DISABLE_PROVISIONING_SYSPROP_KEY = "net.tethering.noprovisioning"; private static final String ACTION_PROVISIONING_ALARM = "com.android.networkstack.tethering.PROVISIONING_RECHECK_ALARM"; + private static final String EXTRA_SUBID = "subId"; private final ComponentName mSilentProvisioningService; private static final int MS_PER_HOUR = 60 * 60 * 1000; @@ -200,9 +197,9 @@ public class EntitlementManager { // till upstream change to cellular. if (mUsingCellularAsUpstream) { if (showProvisioningUi) { - runUiTetherProvisioning(downstreamType, config); + runUiTetherProvisioning(downstreamType, config.activeDataSubId); } else { - runSilentTetherProvisioning(downstreamType, config); + runSilentTetherProvisioning(downstreamType, config.activeDataSubId); } mNeedReRunProvisioningUi = false; } else { @@ -265,9 +262,9 @@ public class EntitlementManager { if (mCurrentEntitlementResults.indexOfKey(downstream) < 0) { if (mNeedReRunProvisioningUi) { mNeedReRunProvisioningUi = false; - runUiTetherProvisioning(downstream, config); + runUiTetherProvisioning(downstream, config.activeDataSubId); } else { - runSilentTetherProvisioning(downstream, config); + runSilentTetherProvisioning(downstream, config.activeDataSubId); } } } @@ -364,7 +361,7 @@ public class EntitlementManager { * @param subId default data subscription ID. */ @VisibleForTesting - protected Intent runSilentTetherProvisioning(int type, final TetheringConfiguration config) { + protected void runSilentTetherProvisioning(int type, int subId) { if (DBG) mLog.i("runSilentTetherProvisioning: " + type); // For silent provisioning, settings would stop tethering when entitlement fail. ResultReceiver receiver = buildProxyReceiver(type, false/* notifyFail */, null); @@ -372,20 +369,17 @@ public class EntitlementManager { Intent intent = new Intent(); intent.putExtra(EXTRA_ADD_TETHER_TYPE, type); intent.putExtra(EXTRA_RUN_PROVISION, true); - intent.putExtra(EXTRA_TETHER_SILENT_PROVISIONING_ACTION, config.provisioningAppNoUi); - intent.putExtra(EXTRA_TETHER_PROVISIONING_RESPONSE, config.provisioningResponse); intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver); - intent.putExtra(EXTRA_TETHER_SUBID, config.activeDataSubId); + intent.putExtra(EXTRA_SUBID, subId); intent.setComponent(mSilentProvisioningService); // Only admin user can change tethering and SilentTetherProvisioning don't need to // show UI, it is fine to always start setting's background service as system user. mContext.startService(intent); - return intent; } - private void runUiTetherProvisioning(int type, final TetheringConfiguration config) { + private void runUiTetherProvisioning(int type, int subId) { ResultReceiver receiver = buildProxyReceiver(type, true/* notifyFail */, null); - runUiTetherProvisioning(type, config, receiver); + runUiTetherProvisioning(type, subId, receiver); } /** @@ -395,20 +389,17 @@ public class EntitlementManager { * @param receiver to receive entitlement check result. */ @VisibleForTesting - protected Intent runUiTetherProvisioning(int type, final TetheringConfiguration config, - ResultReceiver receiver) { + protected void runUiTetherProvisioning(int type, int subId, ResultReceiver receiver) { if (DBG) mLog.i("runUiTetherProvisioning: " + type); Intent intent = new Intent(Settings.ACTION_TETHER_PROVISIONING_UI); intent.putExtra(EXTRA_ADD_TETHER_TYPE, type); - intent.putExtra(EXTRA_TETHER_UI_PROVISIONING_APP_NAME, config.provisioningApp); intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver); - intent.putExtra(EXTRA_TETHER_SUBID, config.activeDataSubId); + intent.putExtra(EXTRA_SUBID, subId); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // Only launch entitlement UI for system user. Entitlement UI should not appear for other // user because only admin user is allowed to change tethering. mContext.startActivity(intent); - return intent; } // Not needed to check if this don't run on the handler thread because it's private. @@ -640,7 +631,7 @@ public class EntitlementManager { receiver.send(cacheValue, null); } else { ResultReceiver proxy = buildProxyReceiver(downstream, false/* notifyFail */, receiver); - runUiTetherProvisioning(downstream, config, proxy); + runUiTetherProvisioning(downstream, config.activeDataSubId, proxy); } } } diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java index 1d45f129b51b..48a600dfe6e1 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java @@ -107,7 +107,6 @@ public class TetheringConfiguration { public final String[] provisioningApp; public final String provisioningAppNoUi; public final int provisioningCheckPeriod; - public final String provisioningResponse; public final int activeDataSubId; @@ -142,13 +141,10 @@ public class TetheringConfiguration { enableLegacyDhcpServer = getEnableLegacyDhcpServer(res); provisioningApp = getResourceStringArray(res, R.array.config_mobile_hotspot_provision_app); - provisioningAppNoUi = getResourceString(res, - R.string.config_mobile_hotspot_provision_app_no_ui); + provisioningAppNoUi = getProvisioningAppNoUi(res); provisioningCheckPeriod = getResourceInteger(res, R.integer.config_mobile_hotspot_provision_check_period, 0 /* No periodic re-check */); - provisioningResponse = getResourceString(res, - R.string.config_mobile_hotspot_provision_response); mOffloadPollInterval = getResourceInteger(res, R.integer.config_tether_offload_poll_interval, @@ -341,9 +337,9 @@ public class TetheringConfiguration { return copy(LEGACY_DHCP_DEFAULT_RANGE); } - private static String getResourceString(Resources res, final int resId) { + private static String getProvisioningAppNoUi(Resources res) { try { - return res.getString(resId); + return res.getString(R.string.config_mobile_hotspot_provision_app_no_ui); } catch (Resources.NotFoundException e) { return ""; } diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java index 354e75356e9f..72fa916b9e42 100644 --- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java +++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java @@ -16,16 +16,8 @@ package com.android.networkstack.tethering; -import static android.net.TetheringConstants.EXTRA_ADD_TETHER_TYPE; -import static android.net.TetheringConstants.EXTRA_PROVISION_CALLBACK; -import static android.net.TetheringConstants.EXTRA_RUN_PROVISION; -import static android.net.TetheringConstants.EXTRA_TETHER_PROVISIONING_RESPONSE; -import static android.net.TetheringConstants.EXTRA_TETHER_SILENT_PROVISIONING_ACTION; -import static android.net.TetheringConstants.EXTRA_TETHER_SUBID; -import static android.net.TetheringConstants.EXTRA_TETHER_UI_PROVISIONING_APP_NAME; import static android.net.TetheringManager.TETHERING_BLUETOOTH; import static android.net.TetheringManager.TETHERING_ETHERNET; -import static android.net.TetheringManager.TETHERING_INVALID; import static android.net.TetheringManager.TETHERING_USB; import static android.net.TetheringManager.TETHERING_WIFI; import static android.net.TetheringManager.TETHERING_WIFI_P2P; @@ -52,7 +44,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; -import android.content.Intent; import android.content.res.Resources; import android.net.util.SharedLog; import android.os.Bundle; @@ -62,7 +53,6 @@ import android.os.ResultReceiver; import android.os.SystemProperties; import android.os.test.TestLooper; import android.provider.DeviceConfig; -import android.provider.Settings; import android.telephony.CarrierConfigManager; import androidx.test.filters.SmallTest; @@ -86,7 +76,6 @@ public final class EntitlementManagerTest { private static final String[] PROVISIONING_APP_NAME = {"some", "app"}; private static final String PROVISIONING_NO_UI_APP_NAME = "no_ui_app"; - private static final String PROVISIONING_APP_RESPONSE = "app_response"; @Mock private CarrierConfigManager mCarrierConfigManager; @Mock private Context mContext; @@ -133,51 +122,15 @@ public final class EntitlementManagerTest { } @Override - protected Intent runUiTetherProvisioning(int type, - final TetheringConfiguration config, final ResultReceiver receiver) { - Intent intent = super.runUiTetherProvisioning(type, config, receiver); - assertUiTetherProvisioningIntent(type, config, receiver, intent); + protected void runUiTetherProvisioning(int type, int subId, ResultReceiver receiver) { uiProvisionCount++; receiver.send(fakeEntitlementResult, null); - return intent; - } - - private void assertUiTetherProvisioningIntent(int type, final TetheringConfiguration config, - final ResultReceiver receiver, final Intent intent) { - assertEquals(Settings.ACTION_TETHER_PROVISIONING_UI, intent.getAction()); - assertEquals(type, intent.getIntExtra(EXTRA_ADD_TETHER_TYPE, TETHERING_INVALID)); - final String[] appName = intent.getStringArrayExtra( - EXTRA_TETHER_UI_PROVISIONING_APP_NAME); - assertEquals(PROVISIONING_APP_NAME.length, appName.length); - for (int i = 0; i < PROVISIONING_APP_NAME.length; i++) { - assertEquals(PROVISIONING_APP_NAME[i], appName[i]); - } - assertEquals(receiver, intent.getParcelableExtra(EXTRA_PROVISION_CALLBACK)); - assertEquals(config.activeDataSubId, - intent.getIntExtra(EXTRA_TETHER_SUBID, INVALID_SUBSCRIPTION_ID)); } @Override - protected Intent runSilentTetherProvisioning(int type, - final TetheringConfiguration config) { - Intent intent = super.runSilentTetherProvisioning(type, config); - assertSilentTetherProvisioning(type, config, intent); + protected void runSilentTetherProvisioning(int type, int subId) { silentProvisionCount++; addDownstreamMapping(type, fakeEntitlementResult); - return intent; - } - - private void assertSilentTetherProvisioning(int type, final TetheringConfiguration config, - final Intent intent) { - assertEquals(type, intent.getIntExtra(EXTRA_ADD_TETHER_TYPE, TETHERING_INVALID)); - assertEquals(true, intent.getBooleanExtra(EXTRA_RUN_PROVISION, false)); - assertEquals(PROVISIONING_NO_UI_APP_NAME, - intent.getStringExtra(EXTRA_TETHER_SILENT_PROVISIONING_ACTION)); - assertEquals(PROVISIONING_APP_RESPONSE, - intent.getStringExtra(EXTRA_TETHER_PROVISIONING_RESPONSE)); - assertTrue(intent.hasExtra(EXTRA_PROVISION_CALLBACK)); - assertEquals(config.activeDataSubId, - intent.getIntExtra(EXTRA_TETHER_SUBID, INVALID_SUBSCRIPTION_ID)); } } @@ -234,8 +187,6 @@ public final class EntitlementManagerTest { .thenReturn(PROVISIONING_APP_NAME); when(mResources.getString(R.string.config_mobile_hotspot_provision_app_no_ui)) .thenReturn(PROVISIONING_NO_UI_APP_NAME); - when(mResources.getString(R.string.config_mobile_hotspot_provision_response)).thenReturn( - PROVISIONING_APP_RESPONSE); // Act like the CarrierConfigManager is present and ready unless told otherwise. when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE)) .thenReturn(mCarrierConfigManager); diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java index 312186391d5f..1999ad786ed4 100644 --- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java +++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java @@ -61,8 +61,6 @@ public class TetheringConfigurationTest { private final SharedLog mLog = new SharedLog("TetheringConfigurationTest"); private static final String[] PROVISIONING_APP_NAME = {"some", "app"}; - private static final String PROVISIONING_NO_UI_APP_NAME = "no_ui_app"; - private static final String PROVISIONING_APP_RESPONSE = "app_response"; @Mock private Context mContext; @Mock private TelephonyManager mTelephonyManager; @Mock private Resources mResources; @@ -390,8 +388,6 @@ public class TetheringConfigurationTest { new MockTetheringConfiguration(mMockContext, mLog, anyValidSubId); assertEquals(mockCfg.provisioningApp[0], PROVISIONING_APP_NAME[0]); assertEquals(mockCfg.provisioningApp[1], PROVISIONING_APP_NAME[1]); - assertEquals(mockCfg.provisioningAppNoUi, PROVISIONING_NO_UI_APP_NAME); - assertEquals(mockCfg.provisioningResponse, PROVISIONING_APP_RESPONSE); } private void setUpResourceForSubId() { @@ -407,10 +403,6 @@ public class TetheringConfigurationTest { new int[0]); when(mResourcesForSubId.getStringArray( R.array.config_mobile_hotspot_provision_app)).thenReturn(PROVISIONING_APP_NAME); - when(mResourcesForSubId.getString(R.string.config_mobile_hotspot_provision_app_no_ui)) - .thenReturn(PROVISIONING_NO_UI_APP_NAME); - when(mResourcesForSubId.getString( - R.string.config_mobile_hotspot_provision_response)).thenReturn( - PROVISIONING_APP_RESPONSE); } + } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 6d45abaf0234..36272278e0e4 100755 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -5322,6 +5322,15 @@ public class AudioService extends IAudioService.Stub } private void setVolumeIndexInt(int index, int device, int flags) { + // Reflect mute state of corresponding stream by forcing index to 0 if muted + // Only set audio policy BT SCO stream volume to 0 when the stream is actually muted. + // This allows RX path muting by the audio HAL only when explicitly muted but not when + // index is just set to 0 to repect BT requirements + if (mStreamStates[mPublicStreamType].isFullyMuted()) { + index = 0; + } else if (mPublicStreamType == AudioSystem.STREAM_BLUETOOTH_SCO && index == 0) { + index = 1; + } // Set the volume index AudioSystem.setVolumeIndexForAttributes(mAudioAttributes, index, device); } diff --git a/services/core/java/com/android/server/location/SettingsHelper.java b/services/core/java/com/android/server/location/SettingsHelper.java index cbb06b86a291..8a35302d6fd5 100644 --- a/services/core/java/com/android/server/location/SettingsHelper.java +++ b/services/core/java/com/android/server/location/SettingsHelper.java @@ -168,7 +168,7 @@ public class SettingsHelper { * Remove a listener for changes to the location enabled setting. */ public void removeOnLocationEnabledChangedListener(UserSettingChangedListener listener) { - mLocationMode.addListener(listener); + mLocationMode.removeListener(listener); } /** diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java index 8d4efed8604b..5787f7c48138 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java @@ -26,6 +26,7 @@ import android.content.pm.UserInfo; import android.hardware.rebootescrow.IRebootEscrow; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.ServiceSpecificException; import android.os.SystemClock; import android.os.UserManager; import android.provider.Settings; @@ -244,6 +245,9 @@ class RebootEscrowManager { } catch (RemoteException e) { Slog.w(TAG, "Could not retrieve escrow data"); return null; + } catch (ServiceSpecificException e) { + Slog.w(TAG, "Got service-specific exception: " + e.errorCode); + return null; } } @@ -335,7 +339,7 @@ class RebootEscrowManager { try { rebootEscrow.storeKey(new byte[32]); - } catch (RemoteException e) { + } catch (RemoteException | ServiceSpecificException e) { Slog.w(TAG, "Could not call RebootEscrow HAL to shred key"); } @@ -373,7 +377,7 @@ class RebootEscrowManager { rebootEscrow.storeKey(escrowKey.getKeyBytes()); armedRebootEscrow = true; Slog.i(TAG, "Reboot escrow key stored with RebootEscrow HAL"); - } catch (RemoteException e) { + } catch (RemoteException | ServiceSpecificException e) { Slog.e(TAG, "Failed escrow secret to RebootEscrow HAL", e); } diff --git a/services/core/jni/com_android_server_tv_TvInputHal.cpp b/services/core/jni/com_android_server_tv_TvInputHal.cpp index 098b2ef6439d..4e1a23416330 100644 --- a/services/core/jni/com_android_server_tv_TvInputHal.cpp +++ b/services/core/jni/com_android_server_tv_TvInputHal.cpp @@ -301,6 +301,7 @@ private: JTvInputHal(JNIEnv* env, jobject thiz, sp<ITvInput> tvInput, const sp<Looper>& looper); Mutex mLock; + Mutex mStreamLock; jweak mThiz; sp<Looper> mLooper; @@ -338,6 +339,7 @@ JTvInputHal* JTvInputHal::createInstance(JNIEnv* env, jobject thiz, const sp<Loo } int JTvInputHal::addOrUpdateStream(int deviceId, int streamId, const sp<Surface>& surface) { + Mutex::Autolock autoLock(&mStreamLock); KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId); if (connections.indexOfKey(streamId) < 0) { connections.add(streamId, Connection()); @@ -412,6 +414,7 @@ int JTvInputHal::addOrUpdateStream(int deviceId, int streamId, const sp<Surface> } int JTvInputHal::removeStream(int deviceId, int streamId) { + Mutex::Autolock autoLock(&mStreamLock); KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId); if (connections.indexOfKey(streamId) < 0) { return BAD_VALUE; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 10ad07cff847..7b624cae8141 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -631,7 +631,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { /** * Whether or not device admin feature is supported. If it isn't return defaults for all - * public methods. + * public methods, unless the caller has the appropriate permission for a particular method. */ final boolean mHasFeature; @@ -6032,7 +6032,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void lockNow(int flags, boolean parent) { - if (!mHasFeature) { + if (!mHasFeature && mContext.checkCallingPermission(android.Manifest.permission.LOCK_DEVICE) + != PackageManager.PERMISSION_GRANTED) { return; } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java index 4127fece17bd..c4d121170624 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java @@ -43,6 +43,7 @@ import android.content.ContextWrapper; import android.content.pm.UserInfo; import android.hardware.rebootescrow.IRebootEscrow; import android.os.RemoteException; +import android.os.ServiceSpecificException; import android.os.UserManager; import android.platform.test.annotations.Presubmit; @@ -178,6 +179,13 @@ public class RebootEscrowManagerTests { } @Test + public void clearCredentials_HalFailure_NonFatal() throws Exception { + doThrow(ServiceSpecificException.class).when(mRebootEscrow).storeKey(any()); + mService.clearRebootEscrow(); + verify(mRebootEscrow).storeKey(eq(new byte[32])); + } + + @Test public void armService_Success() throws Exception { RebootEscrowListener mockListener = mock(RebootEscrowListener.class); mService.setRebootEscrowListener(mockListener); @@ -200,6 +208,24 @@ public class RebootEscrowManagerTests { } @Test + public void armService_HalFailure_NonFatal() throws Exception { + RebootEscrowListener mockListener = mock(RebootEscrowListener.class); + mService.setRebootEscrowListener(mockListener); + mService.prepareRebootEscrow(); + + clearInvocations(mRebootEscrow); + mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN); + verify(mockListener).onPreparedForReboot(eq(true)); + verify(mRebootEscrow, never()).storeKey(any()); + + assertNull( + mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_ARMED_KEY, null, USER_SYSTEM)); + doThrow(ServiceSpecificException.class).when(mRebootEscrow).storeKey(any()); + assertFalse(mService.armRebootEscrowIfNeeded()); + verify(mRebootEscrow).storeKey(any()); + } + + @Test public void armService_MultipleUsers_Success() throws Exception { RebootEscrowListener mockListener = mock(RebootEscrowListener.class); mService.setRebootEscrowListener(mockListener); |