diff options
122 files changed, 2879 insertions, 787 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index aeed1885dae5..36ccaf9c6fb8 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -1120,6 +1120,10 @@ public class AppStandbyController implements AppStandbyInternal { if (isDeviceProvisioningPackage(packageName)) { return STANDBY_BUCKET_EXEMPTED; } + + if (mInjector.isWellbeingPackage(packageName)) { + return STANDBY_BUCKET_WORKING_SET; + } } // Check this last, as it can be the most expensive check @@ -1929,6 +1933,7 @@ public class AppStandbyController implements AppStandbyInternal { */ @GuardedBy("mPowerWhitelistedApps") private final ArraySet<String> mPowerWhitelistedApps = new ArraySet<>(); + private String mWellbeingApp = null; Injector(Context context, Looper looper) { mContext = context; @@ -1962,6 +1967,9 @@ public class AppStandbyController implements AppStandbyInternal { if (activityManager.isLowRamDevice() || ActivityManager.isSmallBatteryDevice()) { mAutoRestrictedBucketDelayMs = 12 * ONE_HOUR; } + + final PackageManager packageManager = mContext.getPackageManager(); + mWellbeingApp = packageManager.getWellbeingPackageName(); } mBootPhase = phase; } @@ -2006,6 +2014,14 @@ public class AppStandbyController implements AppStandbyInternal { } } + /** + * Returns {@code true} if the supplied package is the wellbeing app. Otherwise, + * returns {@code false}. + */ + boolean isWellbeingPackage(String packageName) { + return mWellbeingApp != null && mWellbeingApp.equals(packageName); + } + void updatePowerWhitelistCache() { try { // Don't call out to DeviceIdleController with the lock held. diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 8d64661ef8a6..b47d44d60790 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -3250,12 +3250,6 @@ public final class ActivityThread extends ClientTransactionHandler { sendMessage(H.CLEAN_UP_CONTEXT, cci); } - @Override - public void handleFixedRotationAdjustments(@NonNull IBinder token, - @Nullable FixedRotationAdjustments fixedRotationAdjustments) { - handleFixedRotationAdjustments(token, fixedRotationAdjustments, null /* overrideConfig */); - } - /** * Applies the rotation adjustments to override display information in resources belong to the * provided token. If the token is activity token, the adjustments also apply to application @@ -3265,51 +3259,39 @@ public final class ActivityThread extends ClientTransactionHandler { * @param fixedRotationAdjustments The information to override the display adjustments of * corresponding resources. If it is null, the exiting override * will be cleared. - * @param overrideConfig The override configuration of activity. It is used to override - * application configuration. If it is non-null, it means the token is - * confirmed as activity token. Especially when launching new activity, - * {@link #mActivities} hasn't put the new token. */ - private void handleFixedRotationAdjustments(@NonNull IBinder token, - @Nullable FixedRotationAdjustments fixedRotationAdjustments, - @Nullable Configuration overrideConfig) { - // The element of application configuration override is set only if the application - // adjustments are needed, because activity already has its own override configuration. - final Configuration[] appConfigOverride; - final Consumer<DisplayAdjustments> override; - if (fixedRotationAdjustments != null) { - appConfigOverride = new Configuration[1]; - override = displayAdjustments -> { - displayAdjustments.setFixedRotationAdjustments(fixedRotationAdjustments); - if (appConfigOverride[0] != null) { - displayAdjustments.getConfiguration().updateFrom(appConfigOverride[0]); - } - }; - } else { - appConfigOverride = null; - override = null; - } + @Override + public void handleFixedRotationAdjustments(@NonNull IBinder token, + @Nullable FixedRotationAdjustments fixedRotationAdjustments) { + final Consumer<DisplayAdjustments> override = fixedRotationAdjustments != null + ? displayAdjustments -> displayAdjustments + .setFixedRotationAdjustments(fixedRotationAdjustments) + : null; if (!mResourcesManager.overrideTokenDisplayAdjustments(token, override)) { // No resources are associated with the token. return; } - if (overrideConfig == null) { - final ActivityClientRecord r = mActivities.get(token); - if (r == null) { - // It is not an activity token. Nothing to do for application. - return; - } - overrideConfig = r.overrideConfig; - } - if (appConfigOverride != null) { - appConfigOverride[0] = overrideConfig; + if (mActivities.get(token) == null) { + // Nothing to do for application if it is not an activity token. + return; } - // Apply the last override to application resources for compatibility. Because the Resources - // of Display can be from application, e.g. - // applicationContext.getSystemService(DisplayManager.class).getDisplay(displayId) - // and the deprecated usage: - // applicationContext.getSystemService(WindowManager.class).getDefaultDisplay(); + overrideApplicationDisplayAdjustments(token, override); + } + + /** + * Applies the last override to application resources for compatibility. Because the Resources + * of Display can be from application, e.g. + * applicationContext.getSystemService(DisplayManager.class).getDisplay(displayId) + * and the deprecated usage: + * applicationContext.getSystemService(WindowManager.class).getDefaultDisplay(); + * + * @param token The owner and target of the override. + * @param override The display adjustments override for application resources. If it is null, + * the override of the token will be removed and pop the last one to use. + */ + private void overrideApplicationDisplayAdjustments(@NonNull IBinder token, + @Nullable Consumer<DisplayAdjustments> override) { final Consumer<DisplayAdjustments> appOverride; if (mActiveRotationAdjustments == null) { mActiveRotationAdjustments = new ArrayList<>(2); @@ -3542,8 +3524,13 @@ public final class ActivityThread extends ClientTransactionHandler { // The rotation adjustments must be applied before creating the activity, so the activity // can get the adjusted display info during creation. if (r.mPendingFixedRotationAdjustments != null) { - handleFixedRotationAdjustments(r.token, r.mPendingFixedRotationAdjustments, - r.overrideConfig); + // The adjustments should have been set by handleLaunchActivity, so the last one is the + // override for activity resources. + if (mActiveRotationAdjustments != null && !mActiveRotationAdjustments.isEmpty()) { + mResourcesManager.overrideTokenDisplayAdjustments(r.token, + mActiveRotationAdjustments.get( + mActiveRotationAdjustments.size() - 1).second); + } r.mPendingFixedRotationAdjustments = null; } @@ -3582,6 +3569,13 @@ public final class ActivityThread extends ClientTransactionHandler { mProfiler.startProfiling(); } + if (r.mPendingFixedRotationAdjustments != null) { + // The rotation adjustments must be applied before handling configuration, so process + // level display metrics can be adjusted. + overrideApplicationDisplayAdjustments(r.token, adjustments -> + adjustments.setFixedRotationAdjustments(r.mPendingFixedRotationAdjustments)); + } + // Make sure we are running with the most recent config. handleConfigurationChanged(null, null); @@ -5777,7 +5771,15 @@ public final class ActivityThread extends ClientTransactionHandler { if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle configuration changed: " + config); - mResourcesManager.applyConfigurationToResourcesLocked(config, compat); + final Resources appResources = mInitialApplication.getResources(); + if (appResources.hasOverrideDisplayAdjustments()) { + // The value of Display#getRealSize will be adjusted by FixedRotationAdjustments, + // but Display#getSize refers to DisplayAdjustments#mConfiguration. So the rotated + // configuration also needs to set to the adjustments for consistency. + appResources.getDisplayAdjustments().getConfiguration().updateFrom(config); + } + mResourcesManager.applyConfigurationToResourcesLocked(config, compat, + appResources.getDisplayAdjustments()); updateLocaleListFromAppContext(mInitialApplication.getApplicationContext(), mResourcesManager.getConfiguration().getLocales()); @@ -7390,7 +7392,8 @@ public final class ActivityThread extends ClientTransactionHandler { // We need to apply this change to the resources immediately, because upon returning // the view hierarchy will be informed about it. if (mResourcesManager.applyConfigurationToResourcesLocked(globalConfig, - null /* compat */)) { + null /* compat */, + mInitialApplication.getResources().getDisplayAdjustments())) { updateLocaleListFromAppContext(mInitialApplication.getApplicationContext(), mResourcesManager.getConfiguration().getLocales()); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index c51a84649a07..7effbb3f7c84 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -187,16 +187,6 @@ class ContextImpl extends Context { private static final String XATTR_INODE_CODE_CACHE = "user.inode_code_cache"; /** - * Special intent extra that critical system apps can use to hide the notification for a - * foreground service. This extra should be placed in the intent passed into {@link - * #startForegroundService(Intent)}. - * - * @hide - */ - private static final String EXTRA_HIDDEN_FOREGROUND_SERVICE = - "android.intent.extra.HIDDEN_FOREGROUND_SERVICE"; - - /** * Map from package name, to preference name, to cached preferences. */ @GuardedBy("ContextImpl.class") @@ -1707,12 +1697,9 @@ class ContextImpl extends Context { try { validateServiceIntent(service); service.prepareToLeaveProcess(this); - final boolean hideForegroundNotification = requireForeground - && service.getBooleanExtra(EXTRA_HIDDEN_FOREGROUND_SERVICE, false); ComponentName cn = ActivityManager.getService().startService( mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(getContentResolver()), requireForeground, - hideForegroundNotification, getOpPackageName(), getAttributionTag(), user.getIdentifier()); if (cn != null) { if (cn.getPackageName().equals("!")) { diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index f98e26338063..2fe7eea3b51f 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -155,8 +155,7 @@ interface IActivityManager { boolean refContentProvider(in IBinder connection, int stableDelta, int unstableDelta); PendingIntent getRunningServiceControlPanel(in ComponentName service); ComponentName startService(in IApplicationThread caller, in Intent service, - in String resolvedType, boolean requireForeground, - boolean hideForegroundNotification, in String callingPackage, + in String resolvedType, boolean requireForeground, in String callingPackage, in String callingFeatureId, int userId); @UnsupportedAppUsage int stopService(in IApplicationThread caller, in Intent service, diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index 4cba6ea74955..273336d8189a 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -40,7 +40,6 @@ import android.os.Trace; import android.util.ArrayMap; import android.util.DisplayMetrics; import android.util.Log; -import android.util.LruCache; import android.util.Pair; import android.util.Slog; import android.view.Display; @@ -63,7 +62,6 @@ import java.util.List; import java.util.Objects; import java.util.WeakHashMap; import java.util.function.Consumer; -import java.util.function.Predicate; /** @hide */ public class ResourcesManager { @@ -130,17 +128,30 @@ public class ResourcesManager { } } - private static final boolean ENABLE_APK_ASSETS_CACHE = false; - /** - * The ApkAssets we are caching and intend to hold strong references to. + * Loads {@link ApkAssets} and caches them to prevent their garbage collection while the + * instance is alive and reachable. */ - private final LruCache<ApkKey, ApkAssets> mLoadedApkAssets = - (ENABLE_APK_ASSETS_CACHE) ? new LruCache<>(3) : null; + private class ApkAssetsSupplier { + final ArrayMap<ApkKey, ApkAssets> mLocalCache = new ArrayMap<>(); + + /** + * Retrieves the {@link ApkAssets} corresponding to the specified key, caches the ApkAssets + * within this instance, and inserts the loaded ApkAssets into the {@link #mCachedApkAssets} + * cache. + */ + ApkAssets load(final ApkKey apkKey) throws IOException { + ApkAssets apkAssets = mLocalCache.get(apkKey); + if (apkAssets == null) { + apkAssets = loadApkAssets(apkKey); + mLocalCache.put(apkKey, apkAssets); + } + return apkAssets; + } + } /** - * The ApkAssets that are being referenced in the wild that we can reuse, even if they aren't - * in our LRU cache. Bonus resources :) + * The ApkAssets that are being referenced in the wild that we can reuse. */ private final ArrayMap<ApkKey, WeakReference<ApkAssets>> mCachedApkAssets = new ArrayMap<>(); @@ -338,113 +349,116 @@ public class ResourcesManager { return "/data/resource-cache/" + path.substring(1).replace('/', '@') + "@idmap"; } - private @NonNull ApkAssets loadApkAssets(String path, boolean sharedLib, boolean overlay) - throws IOException { - final ApkKey newKey = new ApkKey(path, sharedLib, overlay); - ApkAssets apkAssets = null; - if (mLoadedApkAssets != null) { - apkAssets = mLoadedApkAssets.get(newKey); - if (apkAssets != null && apkAssets.isUpToDate()) { - return apkAssets; - } - } + private @NonNull ApkAssets loadApkAssets(@NonNull final ApkKey key) throws IOException { + ApkAssets apkAssets; // Optimistically check if this ApkAssets exists somewhere else. - final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(newKey); - if (apkAssetsRef != null) { - apkAssets = apkAssetsRef.get(); - if (apkAssets != null && apkAssets.isUpToDate()) { - if (mLoadedApkAssets != null) { - mLoadedApkAssets.put(newKey, apkAssets); + synchronized (this) { + final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(key); + if (apkAssetsRef != null) { + apkAssets = apkAssetsRef.get(); + if (apkAssets != null && apkAssets.isUpToDate()) { + return apkAssets; + } else { + // Clean up the reference. + mCachedApkAssets.remove(key); } - - return apkAssets; - } else { - // Clean up the reference. - mCachedApkAssets.remove(newKey); } } // We must load this from disk. - if (overlay) { - apkAssets = ApkAssets.loadOverlayFromPath(overlayPathToIdmapPath(path), 0 /*flags*/); + if (key.overlay) { + apkAssets = ApkAssets.loadOverlayFromPath(overlayPathToIdmapPath(key.path), + 0 /*flags*/); } else { - apkAssets = ApkAssets.loadFromPath(path, sharedLib ? ApkAssets.PROPERTY_DYNAMIC : 0); + apkAssets = ApkAssets.loadFromPath(key.path, + key.sharedLib ? ApkAssets.PROPERTY_DYNAMIC : 0); } - if (mLoadedApkAssets != null) { - mLoadedApkAssets.put(newKey, apkAssets); + synchronized (this) { + mCachedApkAssets.put(key, new WeakReference<>(apkAssets)); } - mCachedApkAssets.put(newKey, new WeakReference<>(apkAssets)); return apkAssets; } /** - * Creates an AssetManager from the paths within the ResourcesKey. - * - * This can be overridden in tests so as to avoid creating a real AssetManager with - * real APK paths. - * @param key The key containing the resource paths to add to the AssetManager. - * @return a new AssetManager. - */ - @VisibleForTesting - @UnsupportedAppUsage - protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) { - final AssetManager.Builder builder = new AssetManager.Builder(); + * Retrieves a list of apk keys representing the ApkAssets that should be loaded for + * AssetManagers mapped to the {@param key}. + */ + private static @NonNull ArrayList<ApkKey> extractApkKeys(@NonNull final ResourcesKey key) { + final ArrayList<ApkKey> apkKeys = new ArrayList<>(); // resDir can be null if the 'android' package is creating a new Resources object. // This is fine, since each AssetManager automatically loads the 'android' package // already. if (key.mResDir != null) { - try { - builder.addApkAssets(loadApkAssets(key.mResDir, false /*sharedLib*/, - false /*overlay*/)); - } catch (IOException e) { - Log.e(TAG, "failed to add asset path " + key.mResDir); - return null; - } + apkKeys.add(new ApkKey(key.mResDir, false /*sharedLib*/, false /*overlay*/)); } if (key.mSplitResDirs != null) { for (final String splitResDir : key.mSplitResDirs) { - try { - builder.addApkAssets(loadApkAssets(splitResDir, false /*sharedLib*/, - false /*overlay*/)); - } catch (IOException e) { - Log.e(TAG, "failed to add split asset path " + splitResDir); - return null; - } + apkKeys.add(new ApkKey(splitResDir, false /*sharedLib*/, false /*overlay*/)); } } if (key.mLibDirs != null) { for (final String libDir : key.mLibDirs) { + // Avoid opening files we know do not have resources, like code-only .jar files. if (libDir.endsWith(".apk")) { - // Avoid opening files we know do not have resources, - // like code-only .jar files. - try { - builder.addApkAssets(loadApkAssets(libDir, true /*sharedLib*/, - false /*overlay*/)); - } catch (IOException e) { - Log.w(TAG, "Asset path '" + libDir + - "' does not exist or contains no resources."); - - // continue. - } + apkKeys.add(new ApkKey(libDir, true /*sharedLib*/, false /*overlay*/)); } } } if (key.mOverlayDirs != null) { for (final String idmapPath : key.mOverlayDirs) { - try { - builder.addApkAssets(loadApkAssets(idmapPath, false /*sharedLib*/, - true /*overlay*/)); - } catch (IOException e) { - Log.w(TAG, "failed to add overlay path " + idmapPath); + apkKeys.add(new ApkKey(idmapPath, false /*sharedLib*/, true /*overlay*/)); + } + } - // continue. + return apkKeys; + } + + /** + * Creates an AssetManager from the paths within the ResourcesKey. + * + * This can be overridden in tests so as to avoid creating a real AssetManager with + * real APK paths. + * @param key The key containing the resource paths to add to the AssetManager. + * @return a new AssetManager. + */ + @VisibleForTesting + @UnsupportedAppUsage + protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) { + return createAssetManager(key, /* apkSupplier */ null); + } + + /** + * Variant of {@link #createAssetManager(ResourcesKey)} that attempts to load ApkAssets + * from an {@link ApkAssetsSupplier} if non-null; otherwise ApkAssets are loaded using + * {@link #loadApkAssets(ApkKey)}. + */ + private @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key, + @Nullable ApkAssetsSupplier apkSupplier) { + final AssetManager.Builder builder = new AssetManager.Builder(); + + final ArrayList<ApkKey> apkKeys = extractApkKeys(key); + for (int i = 0, n = apkKeys.size(); i < n; i++) { + final ApkKey apkKey = apkKeys.get(i); + try { + builder.addApkAssets( + (apkSupplier != null) ? apkSupplier.load(apkKey) : loadApkAssets(apkKey)); + } catch (IOException e) { + if (apkKey.overlay) { + Log.w(TAG, String.format("failed to add overlay path '%s'", apkKey.path), e); + } else if (apkKey.sharedLib) { + Log.w(TAG, String.format( + "asset path '%s' does not exist or contains no resources", + apkKey.path), e); + } else { + Log.e(TAG, String.format("failed to add asset path '%s'", apkKey.path), e); + return null; } } } @@ -481,24 +495,6 @@ public class ResourcesManager { pw.println("ResourcesManager:"); pw.increaseIndent(); - if (mLoadedApkAssets != null) { - pw.print("cached apks: total="); - pw.print(mLoadedApkAssets.size()); - pw.print(" created="); - pw.print(mLoadedApkAssets.createCount()); - pw.print(" evicted="); - pw.print(mLoadedApkAssets.evictionCount()); - pw.print(" hit="); - pw.print(mLoadedApkAssets.hitCount()); - pw.print(" miss="); - pw.print(mLoadedApkAssets.missCount()); - pw.print(" max="); - pw.print(mLoadedApkAssets.maxSize()); - } else { - pw.print("cached apks: 0 [cache disabled]"); - } - pw.println(); - pw.print("total apks: "); pw.println(countLiveReferences(mCachedApkAssets.values())); @@ -534,11 +530,12 @@ public class ResourcesManager { return config; } - private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) { + private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key, + @Nullable ApkAssetsSupplier apkSupplier) { final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration); daj.setCompatibilityInfo(key.mCompatInfo); - final AssetManager assets = createAssetManager(key); + final AssetManager assets = createAssetManager(key, apkSupplier); if (assets == null) { return null; } @@ -576,9 +573,18 @@ public class ResourcesManager { */ private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked( @NonNull ResourcesKey key) { + return findOrCreateResourcesImplForKeyLocked(key, /* apkSupplier */ null); + } + + /** + * Variant of {@link #findOrCreateResourcesImplForKeyLocked(ResourcesKey)} that attempts to + * load ApkAssets from a {@link ApkAssetsSupplier} when creating a new ResourcesImpl. + */ + private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked( + @NonNull ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier) { ResourcesImpl impl = findResourcesImplForKeyLocked(key); if (impl == null) { - impl = createResourcesImpl(key); + impl = createResourcesImpl(key, apkSupplier); if (impl != null) { mResourceImpls.put(key, new WeakReference<>(impl)); } @@ -767,7 +773,7 @@ public class ResourcesManager { } // Now request an actual Resources object. - return createResources(token, key, classLoader); + return createResources(token, key, classLoader, /* apkSupplier */ null); } finally { Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); } @@ -811,18 +817,45 @@ public class ResourcesManager { } /** + * Creates an {@link ApkAssetsSupplier} and loads all the ApkAssets required by the {@param key} + * into the supplier. This should be done while the lock is not held to prevent performing I/O + * while holding the lock. + */ + private @NonNull ApkAssetsSupplier createApkAssetsSupplierNotLocked(@NonNull ResourcesKey key) { + Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, + "ResourcesManager#createApkAssetsSupplierNotLocked"); + try { + final ApkAssetsSupplier supplier = new ApkAssetsSupplier(); + final ArrayList<ApkKey> apkKeys = extractApkKeys(key); + for (int i = 0, n = apkKeys.size(); i < n; i++) { + final ApkKey apkKey = apkKeys.get(i); + try { + supplier.load(apkKey); + } catch (IOException e) { + Log.w(TAG, String.format("failed to preload asset path '%s'", apkKey.path), e); + } + } + return supplier; + } finally { + Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); + } + } + + /** * Creates a Resources object set with a ResourcesImpl object matching the given key. * * @param activityToken The Activity this Resources object should be associated with. * @param key The key describing the parameters of the ResourcesImpl object. * @param classLoader The classloader to use for the Resources object. * If null, {@link ClassLoader#getSystemClassLoader()} is used. + * @param apkSupplier The apk assets supplier to use when creating a new ResourcesImpl object. * @return A Resources object that gets updated when * {@link #applyConfigurationToResourcesLocked(Configuration, CompatibilityInfo)} * is called. */ private @Nullable Resources createResources(@Nullable IBinder activityToken, - @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) { + @NonNull ResourcesKey key, @NonNull ClassLoader classLoader, + @Nullable ApkAssetsSupplier apkSupplier) { synchronized (this) { if (DEBUG) { Throwable here = new Throwable(); @@ -830,7 +863,7 @@ public class ResourcesManager { Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here); } - ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key); + ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key, apkSupplier); if (resourcesImpl == null) { return null; } @@ -899,7 +932,10 @@ public class ResourcesManager { rebaseKeyForActivity(activityToken, key); } - return createResources(activityToken, key, classLoader); + // Preload the ApkAssets required by the key to prevent performing heavy I/O while the + // ResourcesManager lock is held. + final ApkAssetsSupplier assetsSupplier = createApkAssetsSupplierNotLocked(key); + return createResources(activityToken, key, classLoader, assetsSupplier); } finally { Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); } @@ -970,7 +1006,13 @@ public class ResourcesManager { final ResourcesKey newKey = rebaseActivityOverrideConfig(resources, oldConfig, overrideConfig, displayId); if (newKey != null) { - updateActivityResources(resources, newKey, false); + final ResourcesImpl resourcesImpl = + findOrCreateResourcesImplForKeyLocked(newKey); + if (resourcesImpl != null && resourcesImpl != resources.getImpl()) { + // Set the ResourcesImpl, updating it for all users of this Resources + // object. + resources.setImpl(resourcesImpl); + } } } } @@ -1025,34 +1067,22 @@ public class ResourcesManager { return newKey; } - private void updateActivityResources(Resources resources, ResourcesKey newKey, - boolean hasLoader) { - final ResourcesImpl resourcesImpl; - - if (hasLoader) { - // Loaders always get new Impls because they cannot be shared - resourcesImpl = createResourcesImpl(newKey); - } else { - resourcesImpl = findOrCreateResourcesImplForKeyLocked(newKey); - } - - if (resourcesImpl != null && resourcesImpl != resources.getImpl()) { - // Set the ResourcesImpl, updating it for all users of this Resources - // object. - resources.setImpl(resourcesImpl); - } - } - @TestApi public final boolean applyConfigurationToResources(@NonNull Configuration config, @Nullable CompatibilityInfo compat) { synchronized(this) { - return applyConfigurationToResourcesLocked(config, compat); + return applyConfigurationToResourcesLocked(config, compat, null /* adjustments */); } } public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config, - @Nullable CompatibilityInfo compat) { + @Nullable CompatibilityInfo compat) { + return applyConfigurationToResourcesLocked(config, compat, null /* adjustments */); + } + + /** Applies the global configuration to the managed resources. */ + public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config, + @Nullable CompatibilityInfo compat, @Nullable DisplayAdjustments adjustments) { try { Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#applyConfigurationToResourcesLocked"); @@ -1076,6 +1106,11 @@ public class ResourcesManager { | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; } + if (adjustments != null) { + // Currently the only case where the adjustment takes effect is to simulate placing + // an app in a rotated display. + adjustments.adjustGlobalAppMetrics(defaultDisplayMetrics); + } Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat); ApplicationPackageManager.configurationChanged(); diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java index 7c6eff143724..06d1b74abc86 100644 --- a/core/java/android/app/UiModeManager.java +++ b/core/java/android/app/UiModeManager.java @@ -510,6 +510,9 @@ public class UiModeManager { } /** + * Activating night mode for the current user + * + * @return {@code true} if the change is successful * @hide */ public boolean setNightModeActivated(boolean active) { diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index f257326904fd..2138f53e9f54 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -785,4 +785,6 @@ interface IPackageManager { List<String> getMimeGroup(String packageName, String group); boolean isAutoRevokeWhitelisted(String packageName); + + void grantImplicitAccess(int queryingUid, String visibleAuthority); } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index ea4a2a0b8c35..8a7214db31eb 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -8011,6 +8011,20 @@ public abstract class PackageManager { "getMimeGroup not implemented in subclass"); } + /** + * Grants implicit visibility of the package that provides an authority to a querying UID. + * + * @throws SecurityException when called by a package other than the contacts provider + * @hide + */ + public void grantImplicitAccess(int queryingUid, String visibleAuthority) { + try { + ActivityThread.getPackageManager().grantImplicitAccess(queryingUid, visibleAuthority); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + // Some of the flags don't affect the query result, but let's be conservative and cache // each combination of flags separately. diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 4f0c84e586a2..e0195e4eafc1 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -16,11 +16,11 @@ package android.inputmethodservice; +import static android.graphics.Color.TRANSPARENT; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE; import static android.view.WindowInsets.Type.navigationBars; -import static android.view.WindowInsets.Type.statusBars; import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; import static java.lang.annotation.RetentionPolicy.SOURCE; @@ -69,7 +69,6 @@ import android.view.ViewGroup; import android.view.ViewRootImpl; import android.view.ViewTreeObserver; import android.view.Window; -import android.view.WindowInsets; import android.view.WindowInsets.Side; import android.view.WindowManager; import android.view.animation.AnimationUtils; @@ -1203,25 +1202,22 @@ public class InputMethodService extends AbstractInputMethodService { Context.LAYOUT_INFLATER_SERVICE); mWindow = new SoftInputWindow(this, "InputMethod", mTheme, null, null, mDispatcherState, WindowManager.LayoutParams.TYPE_INPUT_METHOD, Gravity.BOTTOM, false); - mWindow.getWindow().getAttributes().setFitInsetsTypes(statusBars() | navigationBars()); + mWindow.getWindow().getAttributes().setFitInsetsTypes(navigationBars()); mWindow.getWindow().getAttributes().setFitInsetsSides(Side.all() & ~Side.BOTTOM); mWindow.getWindow().getAttributes().setFitInsetsIgnoringVisibility(true); - // IME layout should always be inset by navigation bar, no matter its current visibility, - // unless automotive requests it. Automotive devices may request the navigation bar to be - // hidden when the IME shows up (controlled via config_automotiveHideNavBarForKeyboard) - // in order to maximize the visible screen real estate. When this happens, the IME window - // should animate from the bottom of the screen to reduce the jank that happens from the - // lack of synchronization between the bottom system window and the IME window. + // Our window will extend into the status bar area no matter the bar is visible or not. + // We don't want the ColorView to be visible when status bar is shown. + mWindow.getWindow().setStatusBarColor(TRANSPARENT); + + // Automotive devices may request the navigation bar to be hidden when the IME shows up + // (controlled via config_automotiveHideNavBarForKeyboard) in order to maximize the visible + // screen real estate. When this happens, the IME window should animate from the bottom of + // the screen to reduce the jank that happens from the lack of synchronization between the + // bottom system window and the IME window. if (mIsAutomotive && mAutomotiveHideNavBarForKeyboard) { mWindow.getWindow().setDecorFitsSystemWindows(false); } - mWindow.getWindow().getDecorView().setOnApplyWindowInsetsListener( - (v, insets) -> v.onApplyWindowInsets( - new WindowInsets.Builder(insets).setInsets( - navigationBars(), - insets.getInsetsIgnoringVisibility(navigationBars())) - .build())); // For ColorView in DecorView to work, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS needs to be set // by default (but IME developers can opt this out later if they want a new behavior). diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index bde332792e18..018bb2c9f9b2 100755 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -135,8 +135,8 @@ public class Build { * * <p>Starting with API level 29, persistent device identifiers are guarded behind additional * restrictions, and apps are recommended to use resettable identifiers (see <a - * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of - * the following requirements is met: + * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This + * method can be invoked if one of the following requirements is met: * <ul> * <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this * is a privileged permission that can only be granted to apps preloaded on the device. diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 8b45ba994d7e..b07dabd4bb39 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -8980,6 +8980,22 @@ public final class Settings { }; /** + * How long Assistant handles have enabled in milliseconds. + * + * @hide + */ + public static final String ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS = + "reminder_exp_learning_time_elapsed"; + + /** + * How many times the Assistant has been triggered using the touch gesture. + * + * @hide + */ + public static final String ASSIST_HANDLES_LEARNING_EVENT_COUNT = + "reminder_exp_learning_event_count"; + + /** * These entries are considered common between the personal and the managed profile, * since the managed profile doesn't get to change them. */ diff --git a/core/java/android/view/DisplayAdjustments.java b/core/java/android/view/DisplayAdjustments.java index c726bee9f402..7c01f7a8739a 100644 --- a/core/java/android/view/DisplayAdjustments.java +++ b/core/java/android/view/DisplayAdjustments.java @@ -130,14 +130,16 @@ public class DisplayAdjustments { w = metrics.noncompatWidthPixels; metrics.noncompatWidthPixels = metrics.noncompatHeightPixels; metrics.noncompatHeightPixels = w; + } - float x = metrics.xdpi; - metrics.xdpi = metrics.ydpi; - metrics.ydpi = x; - - x = metrics.noncompatXdpi; - metrics.noncompatXdpi = metrics.noncompatYdpi; - metrics.noncompatYdpi = x; + /** Adjusts global display metrics that is available to applications. */ + public void adjustGlobalAppMetrics(@NonNull DisplayMetrics metrics) { + final FixedRotationAdjustments rotationAdjustments = mFixedRotationAdjustments; + if (rotationAdjustments == null) { + return; + } + metrics.noncompatWidthPixels = metrics.widthPixels = rotationAdjustments.mAppWidth; + metrics.noncompatHeightPixels = metrics.heightPixels = rotationAdjustments.mAppHeight; } /** Returns the adjusted cutout if available. Otherwise the original cutout is returned. */ @@ -178,7 +180,7 @@ public class DisplayAdjustments { /** * An application can be launched in different rotation than the real display. This class - * provides the information to adjust the values returned by {@link #Display}. + * provides the information to adjust the values returned by {@link Display}. * @hide */ public static class FixedRotationAdjustments implements Parcelable { @@ -186,12 +188,24 @@ public class DisplayAdjustments { @Surface.Rotation final int mRotation; + /** + * The rotated {@link DisplayInfo#appWidth}. The value cannot be simply swapped according + * to rotation because it minus the region of screen decorations. + */ + final int mAppWidth; + + /** The rotated {@link DisplayInfo#appHeight}. */ + final int mAppHeight; + /** Non-null if the device has cutout. */ @Nullable final DisplayCutout mRotatedDisplayCutout; - public FixedRotationAdjustments(@Surface.Rotation int rotation, DisplayCutout cutout) { + public FixedRotationAdjustments(@Surface.Rotation int rotation, int appWidth, int appHeight, + DisplayCutout cutout) { mRotation = rotation; + mAppWidth = appWidth; + mAppHeight = appHeight; mRotatedDisplayCutout = cutout; } @@ -199,6 +213,8 @@ public class DisplayAdjustments { public int hashCode() { int hash = 17; hash = hash * 31 + mRotation; + hash = hash * 31 + mAppWidth; + hash = hash * 31 + mAppHeight; hash = hash * 31 + Objects.hashCode(mRotatedDisplayCutout); return hash; } @@ -210,12 +226,14 @@ public class DisplayAdjustments { } final FixedRotationAdjustments other = (FixedRotationAdjustments) o; return mRotation == other.mRotation + && mAppWidth == other.mAppWidth && mAppHeight == other.mAppHeight && Objects.equals(mRotatedDisplayCutout, other.mRotatedDisplayCutout); } @Override public String toString() { return "FixedRotationAdjustments{rotation=" + Surface.rotationToString(mRotation) + + " appWidth=" + mAppWidth + " appHeight=" + mAppHeight + " cutout=" + mRotatedDisplayCutout + "}"; } @@ -227,12 +245,16 @@ public class DisplayAdjustments { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mRotation); + dest.writeInt(mAppWidth); + dest.writeInt(mAppHeight); dest.writeTypedObject( new DisplayCutout.ParcelableWrapper(mRotatedDisplayCutout), flags); } private FixedRotationAdjustments(Parcel in) { mRotation = in.readInt(); + mAppWidth = in.readInt(); + mAppHeight = in.readInt(); final DisplayCutout.ParcelableWrapper cutoutWrapper = in.readTypedObject(DisplayCutout.ParcelableWrapper.CREATOR); mRotatedDisplayCutout = cutoutWrapper != null ? cutoutWrapper.get() : null; diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index 0b43547f03de..81378831adbc 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -76,6 +76,10 @@ public class InsetsState implements Parcelable { ITYPE_BOTTOM_GESTURES, ITYPE_LEFT_GESTURES, ITYPE_RIGHT_GESTURES, + ITYPE_TOP_MANDATORY_GESTURES, + ITYPE_BOTTOM_MANDATORY_GESTURES, + ITYPE_LEFT_MANDATORY_GESTURES, + ITYPE_RIGHT_MANDATORY_GESTURES, ITYPE_TOP_TAPPABLE_ELEMENT, ITYPE_BOTTOM_TAPPABLE_ELEMENT, ITYPE_LEFT_DISPLAY_CUTOUT, @@ -104,20 +108,27 @@ public class InsetsState implements Parcelable { public static final int ITYPE_BOTTOM_GESTURES = 4; public static final int ITYPE_LEFT_GESTURES = 5; public static final int ITYPE_RIGHT_GESTURES = 6; - public static final int ITYPE_TOP_TAPPABLE_ELEMENT = 7; - public static final int ITYPE_BOTTOM_TAPPABLE_ELEMENT = 8; - public static final int ITYPE_LEFT_DISPLAY_CUTOUT = 9; - public static final int ITYPE_TOP_DISPLAY_CUTOUT = 10; - public static final int ITYPE_RIGHT_DISPLAY_CUTOUT = 11; - public static final int ITYPE_BOTTOM_DISPLAY_CUTOUT = 12; + /** Additional gesture inset types that map into {@link Type.MANDATORY_SYSTEM_GESTURES}. */ + public static final int ITYPE_TOP_MANDATORY_GESTURES = 7; + public static final int ITYPE_BOTTOM_MANDATORY_GESTURES = 8; + public static final int ITYPE_LEFT_MANDATORY_GESTURES = 9; + public static final int ITYPE_RIGHT_MANDATORY_GESTURES = 10; + + public static final int ITYPE_TOP_TAPPABLE_ELEMENT = 11; + public static final int ITYPE_BOTTOM_TAPPABLE_ELEMENT = 12; + + public static final int ITYPE_LEFT_DISPLAY_CUTOUT = 13; + public static final int ITYPE_TOP_DISPLAY_CUTOUT = 14; + public static final int ITYPE_RIGHT_DISPLAY_CUTOUT = 15; + public static final int ITYPE_BOTTOM_DISPLAY_CUTOUT = 16; /** Input method window. */ - public static final int ITYPE_IME = 13; + public static final int ITYPE_IME = 17; /** Additional system decorations inset type. */ - public static final int ITYPE_CLIMATE_BAR = 14; - public static final int ITYPE_EXTRA_NAVIGATION_BAR = 15; + public static final int ITYPE_CLIMATE_BAR = 18; + public static final int ITYPE_EXTRA_NAVIGATION_BAR = 19; static final int LAST_TYPE = ITYPE_EXTRA_NAVIGATION_BAR; public static final int SIZE = LAST_TYPE + 1; @@ -493,6 +504,10 @@ public class InsetsState implements Parcelable { return Type.IME; case ITYPE_TOP_GESTURES: case ITYPE_BOTTOM_GESTURES: + case ITYPE_TOP_MANDATORY_GESTURES: + case ITYPE_BOTTOM_MANDATORY_GESTURES: + case ITYPE_LEFT_MANDATORY_GESTURES: + case ITYPE_RIGHT_MANDATORY_GESTURES: return Type.MANDATORY_SYSTEM_GESTURES; case ITYPE_LEFT_GESTURES: case ITYPE_RIGHT_GESTURES: @@ -552,6 +567,14 @@ public class InsetsState implements Parcelable { return "ITYPE_LEFT_GESTURES"; case ITYPE_RIGHT_GESTURES: return "ITYPE_RIGHT_GESTURES"; + case ITYPE_TOP_MANDATORY_GESTURES: + return "ITYPE_TOP_MANDATORY_GESTURES"; + case ITYPE_BOTTOM_MANDATORY_GESTURES: + return "ITYPE_BOTTOM_MANDATORY_GESTURES"; + case ITYPE_LEFT_MANDATORY_GESTURES: + return "ITYPE_LEFT_MANDATORY_GESTURES"; + case ITYPE_RIGHT_MANDATORY_GESTURES: + return "ITYPE_RIGHT_MANDATORY_GESTURES"; case ITYPE_TOP_TAPPABLE_ELEMENT: return "ITYPE_TOP_TAPPABLE_ELEMENT"; case ITYPE_BOTTOM_TAPPABLE_ELEMENT: diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index ffeeb806ba54..8fd2f07f3f39 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -99,7 +99,7 @@ public class ViewConfiguration { * Defines the duration in milliseconds a user needs to hold down the * appropriate buttons (power + volume down) to trigger the screenshot chord. */ - private static final int SCREENSHOT_CHORD_KEY_TIMEOUT = 500; + private static final int SCREENSHOT_CHORD_KEY_TIMEOUT = 0; /** * Defines the duration in milliseconds a user needs to hold down the diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 4a56761bbce5..3763728fa071 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1981,11 +1981,7 @@ public final class ViewRootImpl implements ViewParent, mCompatibleVisibilityInfo.globalVisibility = (mCompatibleVisibilityInfo.globalVisibility & ~View.SYSTEM_UI_FLAG_LOW_PROFILE) | (mAttachInfo.mSystemUiVisibility & View.SYSTEM_UI_FLAG_LOW_PROFILE); - if (mDispatchedSystemUiVisibility != mCompatibleVisibilityInfo.globalVisibility) { - mHandler.removeMessages(MSG_DISPATCH_SYSTEM_UI_VISIBILITY); - mHandler.sendMessage(mHandler.obtainMessage( - MSG_DISPATCH_SYSTEM_UI_VISIBILITY, mCompatibleVisibilityInfo)); - } + dispatchDispatchSystemUiVisibilityChanged(mCompatibleVisibilityInfo); if (mAttachInfo.mKeepScreenOn != oldScreenOn || mAttachInfo.mSystemUiVisibility != params.subtreeSystemUiVisibility || mAttachInfo.mHasSystemUiListeners != params.hasSystemUiListeners) { @@ -2039,9 +2035,30 @@ public final class ViewRootImpl implements ViewParent, info.globalVisibility |= systemUiFlag; info.localChanges &= ~systemUiFlag; } - if (mDispatchedSystemUiVisibility != info.globalVisibility) { + dispatchDispatchSystemUiVisibilityChanged(info); + } + + /** + * If the system is forcing showing any system bar, the legacy low profile flag should be + * cleared for compatibility. + * + * @param showTypes {@link InsetsType types} shown by the system. + * @param fromIme {@code true} if the invocation is from IME. + */ + private void clearLowProfileModeIfNeeded(@InsetsType int showTypes, boolean fromIme) { + final SystemUiVisibilityInfo info = mCompatibleVisibilityInfo; + if ((showTypes & Type.systemBars()) != 0 && !fromIme + && (info.globalVisibility & SYSTEM_UI_FLAG_LOW_PROFILE) != 0) { + info.globalVisibility &= ~SYSTEM_UI_FLAG_LOW_PROFILE; + info.localChanges |= SYSTEM_UI_FLAG_LOW_PROFILE; + dispatchDispatchSystemUiVisibilityChanged(info); + } + } + + private void dispatchDispatchSystemUiVisibilityChanged(SystemUiVisibilityInfo args) { + if (mDispatchedSystemUiVisibility != args.globalVisibility) { mHandler.removeMessages(MSG_DISPATCH_SYSTEM_UI_VISIBILITY); - mHandler.sendMessage(mHandler.obtainMessage(MSG_DISPATCH_SYSTEM_UI_VISIBILITY, info)); + mHandler.sendMessage(mHandler.obtainMessage(MSG_DISPATCH_SYSTEM_UI_VISIBILITY, args)); } } @@ -5008,6 +5025,7 @@ public final class ViewRootImpl implements ViewParent, String.format("Calling showInsets(%d,%b) on window that no longer" + " has views.", msg.arg1, msg.arg2 == 1)); } + clearLowProfileModeIfNeeded(msg.arg1, msg.arg2 == 1); mInsetsController.show(msg.arg1, msg.arg2 == 1); break; } diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java index 1a9003581078..24538c5205b0 100644 --- a/core/java/android/view/WindowInsetsController.java +++ b/core/java/android/view/WindowInsetsController.java @@ -33,7 +33,6 @@ import java.lang.annotation.RetentionPolicy; /** * Interface to control windows that generate insets. * - * TODO(118118435): Needs more information and examples once the API is more baked. */ public interface WindowInsetsController { diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index fe774780c133..76c2f2e4c60b 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -273,8 +273,6 @@ public class ChooserActivity extends ResolverActivity implements private int mLastNumberOfChildren = -1; private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment"; - // TODO: Update to handle landscape instead of using static value - private static final int MAX_RANKED_TARGETS = 4; private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>(); private final Set<Pair<ComponentName, UserHandle>> mServicesRequested = new HashSet<>(); @@ -951,7 +949,7 @@ public class ChooserActivity extends ResolverActivity implements updateStickyContentPreview(); if (shouldShowStickyContentPreview() || mChooserMultiProfilePagerAdapter - .getCurrentRootAdapter().getContentPreviewRowCount() != 0) { + .getCurrentRootAdapter().getSystemRowCount() != 0) { logActionShareWithPreview(); } return postRebuildListInternal(rebuildCompleted); @@ -1316,13 +1314,14 @@ public class ChooserActivity extends ResolverActivity implements ViewGroup parent) { ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate( R.layout.chooser_grid_preview_image, parent, false); + ViewGroup imagePreview = contentPreviewLayout.findViewById(R.id.content_preview_image_area); final ViewGroup actionRow = (ViewGroup) contentPreviewLayout.findViewById(R.id.chooser_action_row); //TODO: addActionButton(actionRow, createCopyButton()); addActionButton(actionRow, createNearbyButton(targetIntent)); - mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, true); + mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, false); String action = targetIntent.getAction(); if (Intent.ACTION_SEND.equals(action)) { @@ -1342,7 +1341,7 @@ public class ChooserActivity extends ResolverActivity implements if (imageUris.size() == 0) { Log.i(TAG, "Attempted to display image preview area with zero" + " available images detected in EXTRA_STREAM list"); - contentPreviewLayout.setVisibility(View.GONE); + imagePreview.setVisibility(View.GONE); return contentPreviewLayout; } @@ -2683,7 +2682,7 @@ public class ChooserActivity extends ResolverActivity implements final int bottomInset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0; int offset = bottomInset; - int rowsToShow = gridAdapter.getContentPreviewRowCount() + int rowsToShow = gridAdapter.getSystemRowCount() + gridAdapter.getProfileRowCount() + gridAdapter.getServiceTargetRowCount() + gridAdapter.getCallerAndRankedTargetRowCount(); @@ -3283,7 +3282,7 @@ public class ChooserActivity extends ResolverActivity implements public int getRowCount() { return (int) ( - getContentPreviewRowCount() + getSystemRowCount() + getProfileRowCount() + getServiceTargetRowCount() + getCallerAndRankedTargetRowCount() @@ -3295,22 +3294,21 @@ public class ChooserActivity extends ResolverActivity implements } /** - * Returns either {@code 0} or {@code 1} depending on whether we want to show the list item - * content preview. Not to be confused with the sticky content preview which is above the - * personal and work tabs. + * Whether the "system" row of targets is displayed. + * This area includes the content preview (if present) and action row. */ - public int getContentPreviewRowCount() { + public int getSystemRowCount() { // For the tabbed case we show the sticky content preview above the tabs, // please refer to shouldShowStickyContentPreview if (shouldShowTabs()) { return 0; } + if (!isSendAction(getTargetIntent())) { return 0; } - if (mHideContentPreview || mChooserListAdapter == null - || mChooserListAdapter.getCount() == 0) { + if (mChooserListAdapter == null || mChooserListAdapter.getCount() == 0) { return 0; } @@ -3352,7 +3350,7 @@ public class ChooserActivity extends ResolverActivity implements @Override public int getItemCount() { return (int) ( - getContentPreviewRowCount() + getSystemRowCount() + getProfileRowCount() + getServiceTargetRowCount() + getCallerAndRankedTargetRowCount() @@ -3407,7 +3405,7 @@ public class ChooserActivity extends ResolverActivity implements public int getItemViewType(int position) { int count; - int countSum = (count = getContentPreviewRowCount()); + int countSum = (count = getSystemRowCount()); if (count > 0 && position < countSum) return VIEW_TYPE_CONTENT_PREVIEW; countSum += (count = getProfileRowCount()); @@ -3631,7 +3629,7 @@ public class ChooserActivity extends ResolverActivity implements } int getListPosition(int position) { - position -= getContentPreviewRowCount() + getProfileRowCount(); + position -= getSystemRowCount() + getProfileRowCount(); final int serviceCount = mChooserListAdapter.getServiceTargetCount(); final int serviceRows = (int) Math.ceil((float) serviceCount diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index eb59f0f59be1..b4cd145ca374 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -390,6 +390,20 @@ public final class SystemUiDeviceConfigFlags { public static final String CHOOSER_TARGET_RANKING_ENABLED = "chooser_target_ranking_enabled"; /** + * (float) Weight bonus applied on top sharing shortcuts as per native ranking provided by apps. + * Its range need to be 0 ~ 1. + */ + public static final String TOP_NATIVE_RANKED_SHARING_SHORTCUTS_BOOSTER = + "top_native_ranked_sharing_shortcut_booster"; + + /** + * (float) Weight bonus applied on 2nd top sharing shortcuts as per native ranking provided by + * apps. Its range need to be 0 ~ 1. + */ + public static final String NON_TOP_NATIVE_RANKED_SHARING_SHORTCUTS_BOOSTER = + "non_top_native_ranked_sharing_shortcut_booster"; + + /** * (boolean) Whether to enable user-drag resizing for PIP. */ public static final String PIP_USER_RESIZE = "pip_user_resize"; @@ -409,6 +423,11 @@ public final class SystemUiDeviceConfigFlags { */ public static final String BACK_GESTURE_SLOP_MULTIPLIER = "back_gesture_slop_multiplier"; + /** + * (long) Screenshot keychord delay (how long the buttons must be pressed), in ms + */ + public static final String SCREENSHOT_KEYCHORD_DELAY = "screenshot_keychord_delay"; + private SystemUiDeviceConfigFlags() { } } diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java index 3bcba75ec163..37f68233db53 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java +++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java @@ -224,8 +224,6 @@ public final class InputMethodDebug { return "HIDE_DOCKED_STACK_ATTACHED"; case SoftInputShowHideReason.HIDE_RECENTS_ANIMATION: return "HIDE_RECENTS_ANIMATION"; - case SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR: - return "HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR"; default: return "Unknown=" + reason; } diff --git a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java index f46626be48a8..4b968b45f122 100644 --- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java +++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java @@ -47,8 +47,7 @@ import java.lang.annotation.Retention; SoftInputShowHideReason.HIDE_POWER_BUTTON_GO_HOME, SoftInputShowHideReason.HIDE_DOCKED_STACK_ATTACHED, SoftInputShowHideReason.HIDE_RECENTS_ANIMATION, - SoftInputShowHideReason.HIDE_BUBBLES, - SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR}) + SoftInputShowHideReason.HIDE_BUBBLES}) public @interface SoftInputShowHideReason { /** Show soft input by {@link android.view.inputmethod.InputMethodManager#showSoftInput}. */ int SHOW_SOFT_INPUT = 0; @@ -148,17 +147,4 @@ public @interface SoftInputShowHideReason { * switching, or collapsing Bubbles. */ int HIDE_BUBBLES = 19; - - /** - * Hide soft input when focusing the same window (e.g. screen turned-off and turn-on) which no - * valid focused editor. - * - * Note: From Android R, the window focus change callback is processed by InputDispatcher, - * some focus behavior changes (e.g. There are an activity with a dialog window, after - * screen turned-off and turned-on, before Android R the window focus sequence would be - * the activity first and then the dialog focused, however, in R the focus sequence would be - * only the dialog focused as it's the latest window with input focus) makes we need to hide - * soft-input when the same window focused again to align with the same behavior prior to R. - */ - int HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR = 20; } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 5a1af84eccac..2ac7e5f28e52 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -151,7 +151,7 @@ public class BatteryStatsImpl extends BatteryStats { private static final int MAGIC = 0xBA757475; // 'BATSTATS' // Current on-disk Parcel version - static final int VERSION = 186 + (USE_OLD_HISTORY ? 1000 : 0); + static final int VERSION = 188 + (USE_OLD_HISTORY ? 1000 : 0); // The maximum number of names wakelocks we will keep track of // per uid; once the limit is reached, we batch the remaining wakelocks @@ -13596,6 +13596,7 @@ public class BatteryStatsImpl extends BatteryStats { mDailyStartTime = in.readLong(); mNextMinDailyDeadline = in.readLong(); mNextMaxDailyDeadline = in.readLong(); + mBatteryTimeToFullSeconds = in.readLong(); mStartCount++; @@ -14086,6 +14087,7 @@ public class BatteryStatsImpl extends BatteryStats { out.writeLong(mDailyStartTime); out.writeLong(mNextMinDailyDeadline); out.writeLong(mNextMaxDailyDeadline); + out.writeLong(mBatteryTimeToFullSeconds); mScreenOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); mScreenDozeTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS); @@ -14669,6 +14671,7 @@ public class BatteryStatsImpl extends BatteryStats { mDischargeLightDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in); mDischargeDeepDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in); mLastWriteTime = in.readLong(); + mBatteryTimeToFullSeconds = in.readLong(); mRpmStats.clear(); int NRPMS = in.readInt(); @@ -14861,6 +14864,7 @@ public class BatteryStatsImpl extends BatteryStats { mDischargeLightDozeCounter.writeToParcel(out); mDischargeDeepDozeCounter.writeToParcel(out); out.writeLong(mLastWriteTime); + out.writeLong(mBatteryTimeToFullSeconds); out.writeInt(mRpmStats.size()); for (Map.Entry<String, SamplingTimer> ent : mRpmStats.entrySet()) { diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index b12c5e9ba5b0..fbbf7916b31e 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -1094,13 +1094,15 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind mLastWindowFlags = attrs.flags; if (insets != null) { - final Insets systemBarInsets = insets.getInsets(WindowInsets.Type.systemBars()); final Insets stableBarInsets = insets.getInsetsIgnoringVisibility( WindowInsets.Type.systemBars()); - mLastTopInset = systemBarInsets.top; - mLastBottomInset = systemBarInsets.bottom; - mLastRightInset = systemBarInsets.right; - mLastLeftInset = systemBarInsets.left; + final Insets systemInsets = Insets.min( + insets.getInsets(WindowInsets.Type.systemBars() + | WindowInsets.Type.displayCutout()), stableBarInsets); + mLastTopInset = systemInsets.top; + mLastBottomInset = systemInsets.bottom; + mLastRightInset = systemInsets.right; + mLastLeftInset = systemInsets.left; // Don't animate if the presence of stable insets has changed, because that // indicates that the window was either just added and received them for the diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto index acf8cc4944d9..cfd428cbacc3 100644 --- a/core/proto/android/providers/settings/secure.proto +++ b/core/proto/android/providers/settings/secure.proto @@ -119,6 +119,14 @@ message SecureSettingsProto { } optional Assist assist = 7; + message AssistHandles { + option (android.msg_privacy).dest = DEST_EXPLICIT; + + optional SettingProto learning_time_elapsed_millis = 1 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto learning_event_count = 2 [ (android.privacy).dest = DEST_AUTOMATIC ]; + } + optional AssistHandles assist_handles = 86; + message Autofill { option (android.msg_privacy).dest = DEST_EXPLICIT; @@ -596,5 +604,5 @@ message SecureSettingsProto { // Please insert fields in alphabetical order and group them into messages // if possible (to avoid reaching the method limit). - // Next tag = 85; + // Next tag = 87; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index d0ca0a86efb0..6a92a83c9899 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -5036,6 +5036,10 @@ <permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS" android:protectionLevel="signature|appPredictor" /> + <!-- @hide Allows an application to create/destroy input consumer. --> + <permission android:name="android.permission.INPUT_CONSUMER" + android:protectionLevel="signature" /> + <!-- Attribution for Country Detector. --> <attribution android:tag="CountryDetector" android:label="@string/country_detector"/> <!-- Attribution for Location service. --> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 186af6ea5a3a..550601af0faa 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2256,7 +2256,7 @@ <!-- Amount of time in ms the user needs to press the relevant keys to trigger the screenshot chord --> - <integer name="config_screenshotChordKeyTimeout">500</integer> + <integer name="config_screenshotChordKeyTimeout">0</integer> <!-- Default width of a vertical scrollbar and height of a horizontal scrollbar. Takes effect only if the scrollbar drawables have no intrinsic size. --> diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java index f11adef81793..7d2e32ab08d3 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java @@ -191,7 +191,7 @@ public class TransactionParcelTests { PersistableBundle persistableBundle = new PersistableBundle(); persistableBundle.putInt("k", 4); FixedRotationAdjustments fixedRotationAdjustments = new FixedRotationAdjustments( - Surface.ROTATION_90, DisplayCutout.NO_CUTOUT); + Surface.ROTATION_90, 1920, 1080, DisplayCutout.NO_CUTOUT); LaunchActivityItem item = LaunchActivityItem.obtain(intent, ident, activityInfo, config(), overrideConfig, compat, referrer, null /* voiceInteractor */, @@ -351,7 +351,8 @@ public class TransactionParcelTests { ClientTransaction transaction = ClientTransaction.obtain(new StubAppThread(), null /* activityToken */); transaction.addCallback(FixedRotationAdjustmentsItem.obtain(new Binder(), - new FixedRotationAdjustments(Surface.ROTATION_270, DisplayCutout.NO_CUTOUT))); + new FixedRotationAdjustments(Surface.ROTATION_270, 1920, 1080, + DisplayCutout.NO_CUTOUT))); writeAndPrepareForReading(transaction); diff --git a/core/tests/coretests/src/android/view/DisplayAdjustmentsTests.java b/core/tests/coretests/src/android/view/DisplayAdjustmentsTests.java index 2fc42e91a8cc..3cf1722d49d3 100644 --- a/core/tests/coretests/src/android/view/DisplayAdjustmentsTests.java +++ b/core/tests/coretests/src/android/view/DisplayAdjustmentsTests.java @@ -77,8 +77,10 @@ public class DisplayAdjustmentsTests { final int realRotation = Surface.ROTATION_0; final int fixedRotation = Surface.ROTATION_90; - mDisplayAdjustments.setFixedRotationAdjustments( - new FixedRotationAdjustments(fixedRotation, null /* cutout */)); + final int appWidth = 1080; + final int appHeight = 1920; + mDisplayAdjustments.setFixedRotationAdjustments(new FixedRotationAdjustments( + fixedRotation, appWidth, appHeight, null /* cutout */)); final int w = 1000; final int h = 2000; @@ -95,13 +97,21 @@ public class DisplayAdjustmentsTests { metrics.heightPixels = metrics.noncompatHeightPixels = h; final DisplayMetrics flippedMetrics = new DisplayMetrics(); - flippedMetrics.xdpi = flippedMetrics.noncompatXdpi = h; + // The physical dpi should not be adjusted. + flippedMetrics.xdpi = flippedMetrics.noncompatXdpi = w; flippedMetrics.widthPixels = flippedMetrics.noncompatWidthPixels = h; - flippedMetrics.ydpi = flippedMetrics.noncompatYdpi = w; + flippedMetrics.ydpi = flippedMetrics.noncompatYdpi = h; flippedMetrics.heightPixels = flippedMetrics.noncompatHeightPixels = w; mDisplayAdjustments.adjustMetrics(metrics, realRotation); assertEquals(flippedMetrics, metrics); + + mDisplayAdjustments.adjustGlobalAppMetrics(metrics); + + assertEquals(appWidth, metrics.widthPixels); + assertEquals(appWidth, metrics.noncompatWidthPixels); + assertEquals(appHeight, metrics.heightPixels); + assertEquals(appHeight, metrics.noncompatHeightPixels); } } diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 5f34a5eb58a1..950666b81003 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -168,6 +168,7 @@ applications that come with the platform <permission name="android.permission.INTERACT_ACROSS_USERS"/> <permission name="android.permission.LOCAL_MAC_ADDRESS"/> <permission name="android.permission.MANAGE_USERS"/> + <permission name="android.permission.MANAGE_SUBSCRIPTION_PLANS" /> <permission name="android.permission.MODIFY_PHONE_STATE"/> <permission name="android.permission.PACKAGE_USAGE_STATS"/> <permission name="android.permission.PERFORM_CDMA_PROVISIONING"/> diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index 6bf6034bbbf4..b77a249d0fe9 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -732,7 +732,7 @@ public class LocationManager { mContext.getAttributionTag(), transport.getListenerId()); if (cancelRemote != null) { transport.register(mContext.getSystemService(AlarmManager.class), - cancellationSignal); + cancellationSignal, cancelRemote); if (cancellationSignal != null) { cancellationSignal.setRemote(cancelRemote); } @@ -2571,7 +2571,8 @@ public class LocationManager { } public synchronized void register(AlarmManager alarmManager, - CancellationSignal cancellationSignal) { + CancellationSignal cancellationSignal, + ICancellationSignal remoteCancellationSignal) { if (mConsumer == null) { return; } @@ -2587,15 +2588,21 @@ public class LocationManager { if (cancellationSignal != null) { cancellationSignal.setOnCancelListener(this); } + + mRemoteCancellationSignal = remoteCancellationSignal; } @Override public void onCancel() { + synchronized (this) { + mRemoteCancellationSignal = null; + } remove(); } private Consumer<Location> remove() { Consumer<Location> consumer; + ICancellationSignal cancellationSignal; synchronized (this) { mExecutor = null; consumer = mConsumer; @@ -2605,6 +2612,18 @@ public class LocationManager { mAlarmManager.cancel(this); mAlarmManager = null; } + + // ensure only one cancel event will go through + cancellationSignal = mRemoteCancellationSignal; + mRemoteCancellationSignal = null; + } + + if (cancellationSignal != null) { + try { + cancellationSignal.cancel(); + } catch (RemoteException e) { + // ignore + } } return consumer; diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java index 3c2be5f93e30..dba86d93a809 100644 --- a/media/java/android/media/browse/MediaBrowser.java +++ b/media/java/android/media/browse/MediaBrowser.java @@ -185,7 +185,7 @@ public final class MediaBrowser { boolean bound = false; try { bound = mContext.bindService(intent, mServiceConnection, - Context.BIND_AUTO_CREATE); + Context.BIND_AUTO_CREATE | Context.BIND_INCLUDE_CAPABILITIES); } catch (Exception ex) { Log.e(TAG, "Failed binding to service " + mServiceComponent); } diff --git a/packages/CarSystemUI/res/layout/sysui_overlay_window.xml b/packages/CarSystemUI/res/layout/sysui_overlay_window.xml index 2c9788955bfa..e7295aa6383d 100644 --- a/packages/CarSystemUI/res/layout/sysui_overlay_window.xml +++ b/packages/CarSystemUI/res/layout/sysui_overlay_window.xml @@ -25,7 +25,8 @@ <ViewStub android:id="@+id/notification_panel_stub" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout="@layout/notification_panel_container"/> + android:layout="@layout/notification_panel_container" + android:layout_marginBottom="@dimen/car_bottom_navigation_bar_height"/> <ViewStub android:id="@+id/keyguard_stub" android:layout_width="match_parent" diff --git a/packages/CarSystemUI/res/values/dimens.xml b/packages/CarSystemUI/res/values/dimens.xml index 8359dac6a30f..e8ac9e14eca1 100644 --- a/packages/CarSystemUI/res/values/dimens.xml +++ b/packages/CarSystemUI/res/values/dimens.xml @@ -194,6 +194,12 @@ <dimen name="car_navigation_bar_width">760dp</dimen> <dimen name="car_left_navigation_bar_width">96dp</dimen> <dimen name="car_right_navigation_bar_width">96dp</dimen> + <!-- In order to change the height of the bottom nav bar, overlay navigation_bar_height in + frameworks/base/core/res/res instead. --> + <dimen name="car_bottom_navigation_bar_height">@*android:dimen/navigation_bar_height</dimen> + <!-- In order to change the height of the top nav bar, overlay status_bar_height in + frameworks/base/core/res/res instead. --> + <dimen name="car_top_navigation_bar_height">@*android:dimen/status_bar_height</dimen> <dimen name="car_user_switcher_container_height">420dp</dimen> <!-- This must be the negative of car_user_switcher_container_height for the animation. --> diff --git a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java index 218c95c2496f..ec018f9bb62e 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java @@ -21,10 +21,13 @@ import android.car.user.CarUserManager; import android.content.Context; import android.os.Bundle; import android.os.Handler; +import android.os.UserHandle; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.view.ViewRootImpl; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; import androidx.annotation.VisibleForTesting; @@ -61,7 +64,7 @@ import dagger.Lazy; public class CarKeyguardViewController extends OverlayViewController implements KeyguardViewController { private static final String TAG = "CarKeyguardViewController"; - private static final boolean DEBUG = true; + private static final boolean DEBUG = false; private final Context mContext; private final Handler mHandler; @@ -75,9 +78,10 @@ public class CarKeyguardViewController extends OverlayViewController implements private final DismissCallbackRegistry mDismissCallbackRegistry; private final ViewMediatorCallback mViewMediatorCallback; private final CarNavigationBarController mCarNavigationBarController; + private final InputMethodManager mInputMethodManager; // Needed to instantiate mBouncer. - private final KeyguardBouncer.BouncerExpansionCallback - mExpansionCallback = new KeyguardBouncer.BouncerExpansionCallback() { + private final KeyguardBouncer.BouncerExpansionCallback mExpansionCallback = + new KeyguardBouncer.BouncerExpansionCallback() { @Override public void onFullyShown() { } @@ -96,7 +100,8 @@ public class CarKeyguardViewController extends OverlayViewController implements }; private final CarUserManager.UserLifecycleListener mUserLifecycleListener = (e) -> { if (e.getEventType() == CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING) { - revealKeyguardIfBouncerPrepared(); + UserHandle currentUser = e.getUserHandle(); + revealKeyguardIfBouncerPrepared(currentUser); } }; @@ -136,6 +141,8 @@ public class CarKeyguardViewController extends OverlayViewController implements mDismissCallbackRegistry = dismissCallbackRegistry; mViewMediatorCallback = viewMediatorCallback; mCarNavigationBarController = carNavigationBarController; + // TODO(b/169280588): Inject InputMethodManager instead. + mInputMethodManager = mContext.getSystemService(InputMethodManager.class); registerUserSwitchedListener(); } @@ -363,9 +370,9 @@ public class CarKeyguardViewController extends OverlayViewController implements } /** - * Hides Keyguard so that the transitioning Bouncer can be hidden until it is prepared. To be - * called by {@link com.android.systemui.car.userswitcher.FullscreenUserSwitcherViewMediator} - * when a new user is selected. + * Hides Keyguard so that the transitioning Bouncer can be hidden until it is prepared. To be + * called by {@link com.android.systemui.car.userswitcher.FullscreenUserSwitcherViewMediator} + * when a new user is selected. */ public void hideKeyguardToPrepareBouncer() { getLayout().setVisibility(View.INVISIBLE); @@ -376,29 +383,41 @@ public class CarKeyguardViewController extends OverlayViewController implements mBouncer = keyguardBouncer; } - private void revealKeyguardIfBouncerPrepared() { + private void revealKeyguardIfBouncerPrepared(UserHandle currentUser) { int reattemptDelayMillis = 50; Runnable revealKeyguard = () -> { if (mBouncer == null) { if (DEBUG) { Log.d(TAG, "revealKeyguardIfBouncerPrepared: revealKeyguard request is ignored " - + "since the Bouncer has not been initialized yet."); + + "since the Bouncer has not been initialized yet."); } return; } if (!mBouncer.inTransit() || !mBouncer.isSecure()) { getLayout().setVisibility(View.VISIBLE); + updateCurrentUserForPasswordEntry(currentUser); } else { if (DEBUG) { Log.d(TAG, "revealKeyguardIfBouncerPrepared: Bouncer is not prepared " + "yet so reattempting after " + reattemptDelayMillis + "ms."); } - mHandler.postDelayed(this::revealKeyguardIfBouncerPrepared, reattemptDelayMillis); + mHandler.postDelayed(() -> revealKeyguardIfBouncerPrepared(currentUser), + reattemptDelayMillis); } }; mHandler.post(revealKeyguard); } + private void updateCurrentUserForPasswordEntry(UserHandle currentUser) { + EditText passwordEntry = getLayout().findViewById(R.id.passwordEntry); + if (passwordEntry != null) { + mHandler.post(() -> { + mInputMethodManager.restartInput(passwordEntry); + passwordEntry.setTextOperationUser(currentUser); + }); + } + } + private void notifyKeyguardUpdateMonitor() { mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(mShowing); if (mBouncer != null) { diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/SystemBarConfigs.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/SystemBarConfigs.java index 694ae6d93b93..2efa2b3d4870 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/SystemBarConfigs.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/SystemBarConfigs.java @@ -96,9 +96,11 @@ public class SystemBarConfigs { populateMaps(); readConfigs(); + checkEnabledBarsHaveUniqueBarTypes(); checkSystemBarEnabledForNotificationPanel(); checkHideBottomBarForKeyboardConfigSync(); + setInsetPaddingsForOverlappingCorners(); sortSystemBarSidesByZOrder(); } @@ -153,10 +155,10 @@ public class SystemBarConfigs { BAR_TITLE_MAP.put(LEFT, "LeftCarSystemBar"); BAR_TITLE_MAP.put(RIGHT, "RightCarSystemBar"); - BAR_GESTURE_MAP.put(TOP, InsetsState.ITYPE_TOP_GESTURES); - BAR_GESTURE_MAP.put(BOTTOM, InsetsState.ITYPE_BOTTOM_GESTURES); - BAR_GESTURE_MAP.put(LEFT, InsetsState.ITYPE_LEFT_GESTURES); - BAR_GESTURE_MAP.put(RIGHT, InsetsState.ITYPE_RIGHT_GESTURES); + BAR_GESTURE_MAP.put(TOP, InsetsState.ITYPE_TOP_MANDATORY_GESTURES); + BAR_GESTURE_MAP.put(BOTTOM, InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES); + BAR_GESTURE_MAP.put(LEFT, InsetsState.ITYPE_LEFT_MANDATORY_GESTURES); + BAR_GESTURE_MAP.put(RIGHT, InsetsState.ITYPE_RIGHT_MANDATORY_GESTURES); } private void readConfigs() { @@ -170,7 +172,7 @@ public class SystemBarConfigs { new SystemBarConfigBuilder() .setSide(TOP) .setGirth(mResources.getDimensionPixelSize( - com.android.internal.R.dimen.status_bar_height)) + R.dimen.car_top_navigation_bar_height)) .setBarType(mResources.getInteger(R.integer.config_topSystemBarType)) .setZOrder(mResources.getInteger(R.integer.config_topSystemBarZOrder)) .setHideForKeyboard(mResources.getBoolean( @@ -184,7 +186,7 @@ public class SystemBarConfigs { new SystemBarConfigBuilder() .setSide(BOTTOM) .setGirth(mResources.getDimensionPixelSize( - com.android.internal.R.dimen.navigation_bar_height)) + R.dimen.car_bottom_navigation_bar_height)) .setBarType(mResources.getInteger(R.integer.config_bottomSystemBarType)) .setZOrder( mResources.getInteger(R.integer.config_bottomSystemBarZOrder)) diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java index 7cd559a11158..fd804c71c9d0 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java @@ -16,8 +16,6 @@ package com.android.systemui.car.notification; -import static android.view.WindowInsets.Type.navigationBars; - import android.app.ActivityManager; import android.car.Car; import android.car.drivingstate.CarUxRestrictionsManager; @@ -25,6 +23,8 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.inputmethodservice.InputMethodService; +import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import android.view.GestureDetector; @@ -82,6 +82,7 @@ public class NotificationPanelViewController extends OverlayPanelViewController private final StatusBarStateController mStatusBarStateController; private final boolean mEnableHeadsUpNotificationWhenNotificationShadeOpen; private final NotificationVisibilityLogger mNotificationVisibilityLogger; + private final int mNavBarHeight; private float mInitialBackgroundAlpha; private float mBackgroundAlphaDiff; @@ -138,7 +139,10 @@ public class NotificationPanelViewController extends OverlayPanelViewController mStatusBarStateController = statusBarStateController; mNotificationVisibilityLogger = notificationVisibilityLogger; + mNavBarHeight = mResources.getDimensionPixelSize(R.dimen.car_bottom_navigation_bar_height); + mCommandQueue.addCallback(this); + // Notification background setup. mInitialBackgroundAlpha = (float) mResources.getInteger( R.integer.config_initialNotificationBackgroundAlpha) / 100; @@ -179,6 +183,27 @@ public class NotificationPanelViewController extends OverlayPanelViewController } } + @Override + public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition, + boolean showImeSwitcher) { + if (mContext.getDisplayId() != displayId) { + return; + } + boolean isKeyboardVisible = (vis & InputMethodService.IME_VISIBLE) != 0; + int bottomMargin = isKeyboardVisible ? 0 : mNavBarHeight; + ViewGroup container = (ViewGroup) getLayout(); + if (container == null) { + // Notification panel hasn't been inflated before. We shouldn't try to update the layout + // params. + return; + } + + ViewGroup.MarginLayoutParams params = + (ViewGroup.MarginLayoutParams) container.getLayoutParams(); + params.setMargins(params.leftMargin, params.topMargin, params.rightMargin, bottomMargin); + container.setLayoutParams(params); + } + // OverlayViewController @Override @@ -204,7 +229,7 @@ public class NotificationPanelViewController extends OverlayPanelViewController @Override protected int getInsetTypesToFit() { - return navigationBars(); + return 0; } @Override diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml index 87f3b7a4f2e8..1ab88ed8e00e 100644 --- a/packages/SettingsLib/res/values-bn/strings.xml +++ b/packages/SettingsLib/res/values-bn/strings.xml @@ -487,8 +487,8 @@ <string name="status_unavailable" msgid="5279036186589861608">"অনুপলভ্য"</string> <string name="wifi_status_mac_randomized" msgid="466382542497832189">"MAC র্যান্ডমাইজ করা হয়েছে"</string> <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139"> - <item quantity="one">%1$dটি ডিভাইস কানেক্ট</item> - <item quantity="other">%1$dটি ডিভাইস কানেক্ট</item> + <item quantity="one">%1$dটি ডিভাইস কানেক্ট রয়েছে</item> + <item quantity="other">%1$dটি ডিভাইস কানেক্ট রয়েছে</item> </plurals> <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"আরও বেশি।"</string> <string name="accessibility_manual_zen_less_time" msgid="6828877595848229965">"আরও কম।"</string> diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml index 904a70e73958..6e734bc873b8 100644 --- a/packages/SettingsLib/res/values-hi/strings.xml +++ b/packages/SettingsLib/res/values-hi/strings.xml @@ -487,7 +487,7 @@ <string name="status_unavailable" msgid="5279036186589861608">"मौजूद नहीं है"</string> <string name="wifi_status_mac_randomized" msgid="466382542497832189">"एमएसी पता रैंडम पर सेट है"</string> <plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139"> - <item quantity="one">%1$d डिवाइस जुड़े हैं</item> + <item quantity="one">%1$d डिवाइस जुड़ा है</item> <item quantity="other">%1$d डिवाइस जुड़े हैं</item> </plurals> <string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"ज़्यादा समय."</string> diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml index fec2dd6d523f..d86d88b3345d 100644 --- a/packages/SettingsLib/res/values-hu/strings.xml +++ b/packages/SettingsLib/res/values-hu/strings.xml @@ -145,7 +145,7 @@ <string name="data_usage_ota" msgid="7984667793701597001">"Rendszerfrissítések"</string> <string name="tether_settings_title_usb" msgid="3728686573430917722">"USB-megosztás"</string> <string name="tether_settings_title_wifi" msgid="4803402057533895526">"Hordozható hotspot"</string> - <string name="tether_settings_title_bluetooth" msgid="916519902721399656">"Bluetooth megosztása"</string> + <string name="tether_settings_title_bluetooth" msgid="916519902721399656">"Bluetooth-megosztás"</string> <string name="tether_settings_title_usb_bluetooth" msgid="1727111807207577322">"Megosztás"</string> <string name="tether_settings_title_all" msgid="8910259483383010470">"Megosztás és hotspot"</string> <string name="managed_user_title" msgid="449081789742645723">"Összes munkaalkalmazás"</string> diff --git a/packages/SettingsLib/res/values-hy/arrays.xml b/packages/SettingsLib/res/values-hy/arrays.xml index 2696f5a01785..a279872a088e 100644 --- a/packages/SettingsLib/res/values-hy/arrays.xml +++ b/packages/SettingsLib/res/values-hy/arrays.xml @@ -55,7 +55,7 @@ </string-array> <string-array name="hdcp_checking_summaries"> <item msgid="4045840870658484038">"Երբեք չօգտագործել HDCP ստուգումը"</item> - <item msgid="8254225038262324761">"Օգտագործել HDCP-ը` միայն DRM-ի բովանդակությունը ստուգելու համար"</item> + <item msgid="8254225038262324761">"Օգտագործել HDCP-ը՝ միայն DRM-ի բովանդակությունը ստուգելու համար"</item> <item msgid="6421717003037072581">"Միշտ օգտագործել HDCP ստուգումը"</item> </string-array> <string-array name="bt_hci_snoop_log_entries"> diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml index ffd95a45b61e..e434cac36b34 100644 --- a/packages/SettingsLib/res/values-hy/strings.xml +++ b/packages/SettingsLib/res/values-hy/strings.xml @@ -343,7 +343,7 @@ <string name="show_hw_layers_updates_summary" msgid="5850955890493054618">"Թարմացվելիս ընդգծել սարքաշարի ծածկույթները կանաչ գույնով"</string> <string name="debug_hw_overdraw" msgid="8944851091008756796">"Վրիպազերծել GPU գերազանցումները"</string> <string name="disable_overlays" msgid="4206590799671557143">"Կասեցնել HW վրադրումները"</string> - <string name="disable_overlays_summary" msgid="1954852414363338166">"Միշտ օգտագործել GPU-ն` էկրանի կազմման համար"</string> + <string name="disable_overlays_summary" msgid="1954852414363338166">"Միշտ օգտագործել GPU-ն՝ էկրանի կազմման համար"</string> <string name="simulate_color_space" msgid="1206503300335835151">"Նմանակել գունատարածքը"</string> <string name="enable_opengl_traces_title" msgid="4638773318659125196">"Ակտիվացնել OpenGL հետքերը"</string> <string name="usb_audio_disable_routing" msgid="3367656923544254975">"Անջատել USB աուդիո երթուղումը"</string> diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml index 762d8304dd64..ee368fd5bfcf 100644 --- a/packages/SettingsLib/res/values-ne/strings.xml +++ b/packages/SettingsLib/res/values-ne/strings.xml @@ -143,9 +143,9 @@ <string name="data_usage_uninstalled_apps" msgid="1933665711856171491">"हटाइएका एपहरू"</string> <string name="data_usage_uninstalled_apps_users" msgid="5533981546921913295">"एपहरू र प्रयोगकर्ताहरू हटाइयो।"</string> <string name="data_usage_ota" msgid="7984667793701597001">"प्रणालीसम्बन्धी अद्यावधिकहरू"</string> - <string name="tether_settings_title_usb" msgid="3728686573430917722">"USB टेथर गर्दै"</string> + <string name="tether_settings_title_usb" msgid="3728686573430917722">"USB टेदर गर्दै"</string> <string name="tether_settings_title_wifi" msgid="4803402057533895526">"पोर्टेबल हटस्पट"</string> - <string name="tether_settings_title_bluetooth" msgid="916519902721399656">"ब्लुटुथ टेथर गर्दै"</string> + <string name="tether_settings_title_bluetooth" msgid="916519902721399656">"ब्लुटुथ टेदर गर्दै"</string> <string name="tether_settings_title_usb_bluetooth" msgid="1727111807207577322">"टेदर गर्दै"</string> <string name="tether_settings_title_all" msgid="8910259483383010470">"टेदर गर्ने र पोर्टेबल हटस्पट"</string> <string name="managed_user_title" msgid="449081789742645723">"कार्य प्रोफाइलका सबै एपहरू"</string> diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml index d2891a06402a..c5d70ac866b1 100644 --- a/packages/SettingsLib/res/values-sw/strings.xml +++ b/packages/SettingsLib/res/values-sw/strings.xml @@ -143,11 +143,11 @@ <string name="data_usage_uninstalled_apps" msgid="1933665711856171491">"Programu zilizoondolewa"</string> <string name="data_usage_uninstalled_apps_users" msgid="5533981546921913295">"Watumiaji na programu ziilizoondolewa"</string> <string name="data_usage_ota" msgid="7984667793701597001">"Masasisho ya mfumo"</string> - <string name="tether_settings_title_usb" msgid="3728686573430917722">"Shiriki intaneti kwa USB"</string> + <string name="tether_settings_title_usb" msgid="3728686573430917722">"Sambaza mtandao kwa USB"</string> <string name="tether_settings_title_wifi" msgid="4803402057533895526">"Intaneti ya kusambazwa"</string> - <string name="tether_settings_title_bluetooth" msgid="916519902721399656">"Shiriki intaneti kwa Bluetooth"</string> - <string name="tether_settings_title_usb_bluetooth" msgid="1727111807207577322">"Inazuia"</string> - <string name="tether_settings_title_all" msgid="8910259483383010470">"Kushiriki na kusambaza intaneti"</string> + <string name="tether_settings_title_bluetooth" msgid="916519902721399656">"Sambaza mtandao kwa Bluetooth"</string> + <string name="tether_settings_title_usb_bluetooth" msgid="1727111807207577322">"Kusambaza mtandao"</string> + <string name="tether_settings_title_all" msgid="8910259483383010470">"Kushiriki na kusambaza mtandao"</string> <string name="managed_user_title" msgid="449081789742645723">"Programu zote za kazini"</string> <string name="user_guest" msgid="6939192779649870792">"Mgeni"</string> <string name="unknown" msgid="3544487229740637809">"Haijulikani"</string> diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml index defc33ee9233..7468d0450a7e 100644 --- a/packages/SettingsLib/res/values-th/strings.xml +++ b/packages/SettingsLib/res/values-th/strings.xml @@ -143,7 +143,7 @@ <string name="data_usage_uninstalled_apps" msgid="1933665711856171491">"แอปพลิเคชันที่นำออก"</string> <string name="data_usage_uninstalled_apps_users" msgid="5533981546921913295">"แอปพลิเคชันและผู้ใช้ที่นำออก"</string> <string name="data_usage_ota" msgid="7984667793701597001">"การอัปเดตระบบ"</string> - <string name="tether_settings_title_usb" msgid="3728686573430917722">"ปล่อยสัญญาณผ่าน USB"</string> + <string name="tether_settings_title_usb" msgid="3728686573430917722">"เชื่อมต่อเน็ตผ่าน USB"</string> <string name="tether_settings_title_wifi" msgid="4803402057533895526">"ฮอตสปอตแบบพกพาได้"</string> <string name="tether_settings_title_bluetooth" msgid="916519902721399656">"ปล่อยสัญญาณบลูทูธ"</string> <string name="tether_settings_title_usb_bluetooth" msgid="1727111807207577322">"การปล่อยสัญญาณ"</string> diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index a311f0702a4b..3055104714a1 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -167,6 +167,8 @@ public class SecureSettings { Settings.Secure.MEDIA_CONTROLS_RESUME, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, - Settings.Secure.ADAPTIVE_CONNECTIVITY_ENABLED + Settings.Secure.ADAPTIVE_CONNECTIVITY_ENABLED, + Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS, + Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT }; } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 9c5b60065118..f1846db9d7b5 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -252,5 +252,8 @@ public class SecureSettingsValidators { Secure.ACCESSIBILITY_BUTTON_TARGETS, ACCESSIBILITY_SHORTCUT_TARGET_LIST_VALIDATOR); VALIDATORS.put(Secure.ADAPTIVE_CONNECTIVITY_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put( + Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS, NONE_NEGATIVE_LONG_VALIDATOR); + VALIDATORS.put(Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR); } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index 845c0a3847c1..5977ebe5e142 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -1878,6 +1878,15 @@ class SettingsProtoDumpUtil { SecureSettingsProto.Assist.GESTURE_SETUP_COMPLETE); p.end(assistToken); + final long assistHandlesToken = p.start(SecureSettingsProto.ASSIST_HANDLES); + dumpSetting(s, p, + Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS, + SecureSettingsProto.AssistHandles.LEARNING_TIME_ELAPSED_MILLIS); + dumpSetting(s, p, + Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT, + SecureSettingsProto.AssistHandles.LEARNING_EVENT_COUNT); + p.end(assistHandlesToken); + final long autofillToken = p.start(SecureSettingsProto.AUTOFILL); dumpSetting(s, p, Settings.Secure.AUTOFILL_SERVICE, diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index b55854131709..6e74184cef02 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -113,6 +113,7 @@ <uses-permission android:name="android.permission.SET_ORIENTATION" /> <uses-permission android:name="android.permission.DISABLE_KEYGUARD" /> <uses-permission android:name="android.permission.MONITOR_INPUT" /> + <uses-permission android:name="android.permission.INPUT_CONSUMER" /> <!-- DreamManager --> <uses-permission android:name="android.permission.READ_DREAM_STATE" /> diff --git a/packages/SystemUI/res/layout/media_view.xml b/packages/SystemUI/res/layout/media_view.xml index ed870f8bb2ef..170f2c4e0bea 100644 --- a/packages/SystemUI/res/layout/media_view.xml +++ b/packages/SystemUI/res/layout/media_view.xml @@ -166,8 +166,7 @@ android:layout_height="wrap_content" android:clickable="true" android:maxHeight="@dimen/qs_media_enabled_seekbar_height" - android:paddingTop="16dp" - android:paddingBottom="16dp" + android:paddingVertical="@dimen/qs_media_enabled_seekbar_vertical_padding" android:thumbTint="@color/media_primary_text" android:progressTint="@color/media_seekbar_progress" android:progressBackgroundTint="@color/media_disabled" diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index 410edfb74eb7..c5a825ffb6c6 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -979,7 +979,7 @@ <string name="slice_permission_text_2" msgid="6758906940360746983">"- میتواند در <xliff:g id="APP">%1$s</xliff:g> اقدام انجام دهد"</string> <string name="slice_permission_checkbox" msgid="4242888137592298523">"به <xliff:g id="APP">%1$s</xliff:g> اجازه داده شود تکههایی از برنامهها نشان دهد"</string> <string name="slice_permission_allow" msgid="6340449521277951123">"مجاز"</string> - <string name="slice_permission_deny" msgid="6870256451658176895">"رد کردن"</string> + <string name="slice_permission_deny" msgid="6870256451658176895">"مجاز نبودن"</string> <string name="auto_saver_title" msgid="6873691178754086596">"برای زمانبندی «بهینهسازی باتری» ضربه بزنید"</string> <string name="auto_saver_text" msgid="3214960308353838764">"وقتی باتری روبهاتمام است، بهینهسازی باتری را روشن کنید"</string> <string name="no_auto_saver_action" msgid="7467924389609773835">"نه متشکرم"</string> diff --git a/packages/SystemUI/res/values-h740dp-port/dimens.xml b/packages/SystemUI/res/values-h740dp-port/dimens.xml new file mode 100644 index 000000000000..966066ffe56b --- /dev/null +++ b/packages/SystemUI/res/values-h740dp-port/dimens.xml @@ -0,0 +1,27 @@ +<!-- + ~ 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 + --> + +<resources> + <dimen name="qs_tile_height">106dp</dimen> + <dimen name="qs_tile_margin_vertical">24dp</dimen> + + <!-- The height of the qs customize header. Should be + (qs_panel_padding_top (48dp) + brightness_mirror_height (48dp) + qs_tile_margin_top (18dp)) - + (Toolbar_minWidth (56dp) + qs_tile_margin_top_bottom (12dp)) + --> + <dimen name="qs_customize_header_min_height">46dp</dimen> + <dimen name="qs_tile_margin_top">18dp</dimen> +</resources>
\ No newline at end of file diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml index b7e8581c2010..75944662ed12 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -371,7 +371,7 @@ <string name="quick_settings_time_label" msgid="3352680970557509303">"시간"</string> <string name="quick_settings_user_label" msgid="1253515509432672496">"나"</string> <string name="quick_settings_user_title" msgid="8673045967216204537">"사용자"</string> - <string name="quick_settings_user_new_user" msgid="3347905871336069666">"새 사용자"</string> + <string name="quick_settings_user_new_user" msgid="3347905871336069666">"신규 사용자"</string> <string name="quick_settings_wifi_label" msgid="2879507532983487244">"Wi-Fi"</string> <string name="quick_settings_wifi_not_connected" msgid="4071097522427039160">"연결되어 있지 않음"</string> <string name="quick_settings_wifi_no_network" msgid="6003178398713839313">"네트워크가 연결되지 않음"</string> @@ -473,7 +473,7 @@ <string name="accessibility_multi_user_switch_inactive" msgid="383168614528618402">"현재 사용자: <xliff:g id="CURRENT_USER_NAME">%s</xliff:g>"</string> <string name="accessibility_multi_user_switch_quick_contact" msgid="4504508915324898576">"프로필 표시"</string> <string name="user_add_user" msgid="4336657383006913022">"사용자 추가"</string> - <string name="user_new_user_name" msgid="2019166282704195789">"새 사용자"</string> + <string name="user_new_user_name" msgid="2019166282704195789">"신규 사용자"</string> <string name="guest_exit_guest_dialog_title" msgid="5015697561580641422">"게스트를 삭제하시겠습니까?"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"이 세션에 있는 모든 앱과 데이터가 삭제됩니다."</string> <string name="guest_exit_guest_dialog_remove" msgid="7505817591242703757">"삭제"</string> diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml index b16427624cc6..6a167509684e 100644 --- a/packages/SystemUI/res/values-ms/strings.xml +++ b/packages/SystemUI/res/values-ms/strings.xml @@ -987,7 +987,7 @@ <string name="auto_saver_enabled_text" msgid="7889491183116752719">"Penjimat Bateri akan dihidupkan secara automatik setelah kuasa bateri kurang daripada <xliff:g id="PERCENTAGE">%d</xliff:g>%%."</string> <string name="open_saver_setting_action" msgid="2111461909782935190">"Tetapan"</string> <string name="auto_saver_okay_action" msgid="7815925750741935386">"OK"</string> - <string name="heap_dump_tile_name" msgid="2464189856478823046">"Longgok Tmbunn SysUI"</string> + <string name="heap_dump_tile_name" msgid="2464189856478823046">"DumpSys"</string> <string name="ongoing_privacy_chip_content_single_app" msgid="2969750601815230385">"<xliff:g id="APP">%1$s</xliff:g> sedang menggunakan <xliff:g id="TYPES_LIST">%2$s</xliff:g> anda."</string> <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"Aplikasi sedang menggunakan <xliff:g id="TYPES_LIST">%s</xliff:g> anda."</string> <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">", "</string> diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml index 2d37a0ae1739..439153bf5931 100644 --- a/packages/SystemUI/res/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/strings.xml @@ -987,7 +987,7 @@ <string name="auto_saver_enabled_text" msgid="7889491183116752719">"省電模式將會在電量低於 <xliff:g id="PERCENTAGE">%d</xliff:g>%% 時自動開啟。"</string> <string name="open_saver_setting_action" msgid="2111461909782935190">"設定"</string> <string name="auto_saver_okay_action" msgid="7815925750741935386">"知道了"</string> - <string name="heap_dump_tile_name" msgid="2464189856478823046">"轉儲 SysUI 堆"</string> + <string name="heap_dump_tile_name" msgid="2464189856478823046">"Dump SysUI Heap"</string> <string name="ongoing_privacy_chip_content_single_app" msgid="2969750601815230385">"「<xliff:g id="APP">%1$s</xliff:g>」正在使用<xliff:g id="TYPES_LIST">%2$s</xliff:g>。"</string> <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"有多個應用程式正在使用<xliff:g id="TYPES_LIST">%s</xliff:g>。"</string> <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">"、 "</string> diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml index 14345873f2cf..eeff9e63ec7c 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -987,7 +987,7 @@ <string name="auto_saver_enabled_text" msgid="7889491183116752719">"省電模式會在電量低於 <xliff:g id="PERCENTAGE">%d</xliff:g>%% 時自動開啟。"</string> <string name="open_saver_setting_action" msgid="2111461909782935190">"設定"</string> <string name="auto_saver_okay_action" msgid="7815925750741935386">"我知道了"</string> - <string name="heap_dump_tile_name" msgid="2464189856478823046">"傾印 SysUI 記憶體快照"</string> + <string name="heap_dump_tile_name" msgid="2464189856478823046">"Dump SysUI Heap"</string> <string name="ongoing_privacy_chip_content_single_app" msgid="2969750601815230385">"「<xliff:g id="APP">%1$s</xliff:g>」正在使用<xliff:g id="TYPES_LIST">%2$s</xliff:g>。"</string> <string name="ongoing_privacy_chip_content_multiple_apps" msgid="8341216022442383954">"有多個應用程式正在使用<xliff:g id="TYPES_LIST">%s</xliff:g>。"</string> <string name="ongoing_privacy_dialog_separator" msgid="1866222499727706187">"、 "</string> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 5b7bee653154..f07627a1a346 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -482,20 +482,21 @@ <!-- The size of the gesture span needed to activate the "pull" notification expansion --> <dimen name="pull_span_min">25dp</dimen> - <dimen name="qs_tile_height">106dp</dimen> + <dimen name="qs_tile_height">96dp</dimen> <!--notification_side_paddings + notification_content_margin_start - (qs_quick_tile_size - qs_tile_background_size) / 2 --> <dimen name="qs_tile_layout_margin_side">18dp</dimen> <dimen name="qs_tile_margin_horizontal">18dp</dimen> <dimen name="qs_tile_margin_horizontal_two_line">2dp</dimen> - <dimen name="qs_tile_margin_vertical">24dp</dimen> + <dimen name="qs_tile_margin_vertical">2dp</dimen> <dimen name="qs_tile_margin_top_bottom">12dp</dimen> <dimen name="qs_tile_margin_top_bottom_negative">-12dp</dimen> <!-- The height of the qs customize header. Should be - (qs_panel_padding_top (48dp) + brightness_mirror_height (48dp) + qs_tile_margin_top (18dp)) - + (qs_panel_padding_top (48dp) + brightness_mirror_height (48dp) + qs_tile_margin_top (0dp)) - (Toolbar_minWidth (56dp) + qs_tile_margin_top_bottom (12dp)) --> - <dimen name="qs_customize_header_min_height">46dp</dimen> - <dimen name="qs_tile_margin_top">18dp</dimen> + <dimen name="qs_customize_header_min_height">28dp</dimen> + <dimen name="qs_tile_margin_top">0dp</dimen> + <dimen name="qs_tile_icon_background_stroke_width">-1dp</dimen> <dimen name="qs_tile_background_size">44dp</dimen> <dimen name="qs_quick_tile_size">48dp</dimen> <dimen name="qs_quick_tile_padding">12dp</dimen> @@ -1295,6 +1296,8 @@ <dimen name="qs_footer_horizontal_margin">22dp</dimen> <dimen name="qs_media_disabled_seekbar_height">1dp</dimen> <dimen name="qs_media_enabled_seekbar_height">3dp</dimen> + <dimen name="qs_media_enabled_seekbar_vertical_padding">15dp</dimen> + <dimen name="qs_media_disabled_seekbar_vertical_padding">16dp</dimen> <dimen name="magnification_border_size">5dp</dimen> <dimen name="magnification_frame_move_short">5dp</dimen> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index a56f6f56836a..b8e8db5ecf50 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -177,6 +177,9 @@ <item type="id" name="accessibility_action_controls_move_before" /> <item type="id" name="accessibility_action_controls_move_after" /> + <item type="id" name="accessibility_action_qs_move_to_position" /> + <item type="id" name="accessibility_action_qs_add_to_position" /> + <!-- Accessibility actions for PIP --> <item type="id" name="action_pip_resize" /> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 38501eb8da3d..77ce39fe9953 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2267,23 +2267,26 @@ <!-- Accessibility action for moving docked stack divider to make the bottom screen full screen [CHAR LIMIT=NONE] --> <string name="accessibility_action_divider_bottom_full">Bottom full screen</string> - <!-- Accessibility description of a QS tile while editing positions [CHAR LIMIT=NONE] --> - <string name="accessibility_qs_edit_tile_label">Position <xliff:g id="position" example="2">%1$d</xliff:g>, <xliff:g id="tile_name" example="Wi-Fi">%2$s</xliff:g>. Double tap to edit.</string> + <!-- Accessibility description of action to remove QS tile on click. It will read as "Double-tap to remove tile" in screen readers [CHAR LIMIT=NONE] --> + <string name="accessibility_qs_edit_remove_tile_action">remove tile</string> - <!-- Accessibility description of a QS tile while editing positions [CHAR LIMIT=NONE] --> - <string name="accessibility_qs_edit_add_tile_label"><xliff:g id="tile_name" example="Wi-Fi">%1$s</xliff:g>. Double tap to add.</string> + <!-- Accessibility action of action to add QS tile to end. It will read as "Double-tap to add tile to end" in screen readers [CHAR LIMIT=NONE] --> + <string name="accessibility_qs_edit_tile_add_action">add tile to end</string> - <!-- Accessibility description of option to move QS tile [CHAR LIMIT=NONE] --> - <string name="accessibility_qs_edit_move_tile">Move <xliff:g id="tile_name" example="Wi-Fi">%1$s</xliff:g></string> + <!-- Accessibility action for context menu to move QS tile [CHAR LIMIT=NONE] --> + <string name="accessibility_qs_edit_tile_start_move">Move tile</string> - <!-- Accessibility description of option to remove QS tile [CHAR LIMIT=NONE] --> - <string name="accessibility_qs_edit_remove_tile">Remove <xliff:g id="tile_name" example="Wi-Fi">%1$s</xliff:g></string> + <!-- Accessibility action for context menu to add QS tile [CHAR LIMIT=NONE] --> + <string name="accessibility_qs_edit_tile_start_add">Add tile</string> - <!-- Accessibility action when QS tile is to be added [CHAR LIMIT=NONE] --> - <string name="accessibility_qs_edit_tile_add">Add <xliff:g id="tile_name" example="Wi-Fi">%1$s</xliff:g> to position <xliff:g id="position" example="5">%2$d</xliff:g></string> + <!-- Accessibility description when QS tile is to be moved, indicating the destination position [CHAR LIMIT=NONE] --> + <string name="accessibility_qs_edit_tile_move_to_position">Move to <xliff:g id="position" example="5">%1$d</xliff:g></string> - <!-- Accessibility action when QS tile is to be moved [CHAR LIMIT=NONE] --> - <string name="accessibility_qs_edit_tile_move">Move <xliff:g id="tile_name" example="Wi-Fi">%1$s</xliff:g> to position <xliff:g id="position" example="5">%2$d</xliff:g></string> + <!-- Accessibility description when QS tile is to be added, indicating the destination position [CHAR LIMIT=NONE] --> + <string name="accessibility_qs_edit_tile_add_to_position">Add to position <xliff:g id="position" example="5">%1$d</xliff:g></string> + + <!-- Accessibility description indicating the currently selected tile's position. Only used for tiles that are currently in use [CHAR LIMIT=NONE] --> + <string name="accessibility_qs_edit_position">Position <xliff:g id="position" example="5">%1$d</xliff:g></string> <!-- Accessibility label for window when QS editing is happening [CHAR LIMIT=NONE] --> <string name="accessibility_desc_quick_settings_edit">Quick settings editor.</string> diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java index 64b35caeeef2..2b9514f6d23f 100644 --- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java @@ -16,8 +16,13 @@ package com.android.systemui.appops; +import static android.media.AudioManager.ACTION_MICROPHONE_MUTE_CHANGED; + import android.app.AppOpsManager; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; import android.location.LocationManager; import android.media.AudioManager; @@ -35,6 +40,7 @@ import androidx.annotation.WorkerThread; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dumpable; +import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dump.DumpManager; @@ -54,7 +60,7 @@ import javax.inject.Singleton; * NotificationPresenter to be displayed to the user. */ @Singleton -public class AppOpsControllerImpl implements AppOpsController, +public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsController, AppOpsManager.OnOpActiveChangedInternalListener, AppOpsManager.OnOpNotedListener, Dumpable { @@ -65,6 +71,7 @@ public class AppOpsControllerImpl implements AppOpsController, private static final String TAG = "AppOpsControllerImpl"; private static final boolean DEBUG = false; + private final BroadcastDispatcher mDispatcher; private final AppOpsManager mAppOps; private final AudioManager mAudioManager; private final LocationManager mLocationManager; @@ -79,6 +86,7 @@ public class AppOpsControllerImpl implements AppOpsController, private final ArrayMap<Integer, Set<Callback>> mCallbacksByCode = new ArrayMap<>(); private final PermissionFlagsCache mFlagsCache; private boolean mListening; + private boolean mMicMuted; @GuardedBy("mActiveItems") private final List<AppOpItem> mActiveItems = new ArrayList<>(); @@ -104,8 +112,10 @@ public class AppOpsControllerImpl implements AppOpsController, @Background Looper bgLooper, DumpManager dumpManager, PermissionFlagsCache cache, - AudioManager audioManager + AudioManager audioManager, + BroadcastDispatcher dispatcher ) { + mDispatcher = dispatcher; mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); mFlagsCache = cache; mBGHandler = new H(bgLooper); @@ -114,6 +124,7 @@ public class AppOpsControllerImpl implements AppOpsController, mCallbacksByCode.put(OPS[i], new ArraySet<>()); } mAudioManager = audioManager; + mMicMuted = audioManager.isMicrophoneMute(); mLocationManager = context.getSystemService(LocationManager.class); dumpManager.registerDumpable(TAG, this); } @@ -132,6 +143,8 @@ public class AppOpsControllerImpl implements AppOpsController, mAudioManager.registerAudioRecordingCallback(mAudioRecordingCallback, mBGHandler); mBGHandler.post(() -> mAudioRecordingCallback.onRecordingConfigChanged( mAudioManager.getActiveRecordingConfigurations())); + mDispatcher.registerReceiverWithHandler(this, + new IntentFilter(ACTION_MICROPHONE_MUTE_CHANGED), mBGHandler); } else { mAppOps.stopWatchingActive(this); @@ -139,6 +152,7 @@ public class AppOpsControllerImpl implements AppOpsController, mAudioManager.unregisterAudioRecordingCallback(mAudioRecordingCallback); mBGHandler.removeCallbacksAndMessages(null); // null removes all + mDispatcher.unregisterReceiver(this); synchronized (mActiveItems) { mActiveItems.clear(); mRecordingsByUid.clear(); @@ -466,6 +480,9 @@ public class AppOpsControllerImpl implements AppOpsController, } private boolean isAnyRecordingPausedLocked(int uid) { + if (mMicMuted) { + return true; + } List<AudioRecordingConfiguration> configs = mRecordingsByUid.get(uid); if (configs == null) return false; int configsNum = configs.size(); @@ -520,6 +537,12 @@ public class AppOpsControllerImpl implements AppOpsController, } }; + @Override + public void onReceive(Context context, Intent intent) { + mMicMuted = mAudioManager.isMicrophoneMute(); + updateRecordingPausedStatus(); + } + protected class H extends Handler { H(Looper looper) { super(looper); diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java index 8e49d584788a..008571099a64 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleReminderExpBehavior.java @@ -26,6 +26,8 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ResolveInfo; +import android.database.ContentObserver; +import android.net.Uri; import android.os.Handler; import android.provider.Settings; @@ -66,8 +68,10 @@ import dagger.Lazy; @Singleton final class AssistHandleReminderExpBehavior implements BehaviorController { - private static final String LEARNING_TIME_ELAPSED_KEY = "reminder_exp_learning_time_elapsed"; - private static final String LEARNING_EVENT_COUNT_KEY = "reminder_exp_learning_event_count"; + private static final Uri LEARNING_TIME_ELAPSED_URI = + Settings.Secure.getUriFor(Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS); + private static final Uri LEARNING_EVENT_COUNT_URI = + Settings.Secure.getUriFor(Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT); private static final String LEARNED_HINT_LAST_SHOWN_KEY = "reminder_exp_learned_hint_last_shown"; private static final long DEFAULT_LEARNING_TIME_MS = TimeUnit.DAYS.toMillis(10); @@ -181,6 +185,7 @@ final class AssistHandleReminderExpBehavior implements BehaviorController { private boolean mIsNavBarHidden; private boolean mIsLauncherShowing; private int mConsecutiveTaskSwitches; + @Nullable private ContentObserver mSettingObserver; /** Whether user has learned the gesture. */ private boolean mIsLearned; @@ -248,9 +253,22 @@ final class AssistHandleReminderExpBehavior implements BehaviorController { mWakefulnessLifecycle.get().addObserver(mWakefulnessLifecycleObserver); mLearningTimeElapsed = Settings.Secure.getLong( - context.getContentResolver(), LEARNING_TIME_ELAPSED_KEY, /* default = */ 0); + context.getContentResolver(), + Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS, + /* default = */ 0); mLearningCount = Settings.Secure.getInt( - context.getContentResolver(), LEARNING_EVENT_COUNT_KEY, /* default = */ 0); + context.getContentResolver(), + Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT, + /* default = */ 0); + mSettingObserver = new SettingsObserver(context, mHandler); + context.getContentResolver().registerContentObserver( + LEARNING_TIME_ELAPSED_URI, + /* notifyForDescendants = */ true, + mSettingObserver); + context.getContentResolver().registerContentObserver( + LEARNING_EVENT_COUNT_URI, + /* notifyForDescendants = */ true, + mSettingObserver); mLearnedHintLastShownEpochDay = Settings.Secure.getLong( context.getContentResolver(), LEARNED_HINT_LAST_SHOWN_KEY, /* default = */ 0); mLastLearningTimestamp = mClock.currentTimeMillis(); @@ -264,8 +282,20 @@ final class AssistHandleReminderExpBehavior implements BehaviorController { if (mContext != null) { mBroadcastDispatcher.get().unregisterReceiver(mDefaultHomeBroadcastReceiver); mBootCompleteCache.get().removeListener(mBootCompleteListener); - Settings.Secure.putLong(mContext.getContentResolver(), LEARNING_TIME_ELAPSED_KEY, 0); - Settings.Secure.putInt(mContext.getContentResolver(), LEARNING_EVENT_COUNT_KEY, 0); + mContext.getContentResolver().unregisterContentObserver(mSettingObserver); + mSettingObserver = null; + // putString to use overrideableByRestore + Settings.Secure.putString( + mContext.getContentResolver(), + Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS, + Long.toString(0L), + /* overrideableByRestore = */ true); + // putString to use overrideableByRestore + Settings.Secure.putString( + mContext.getContentResolver(), + Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT, + Integer.toString(0), + /* overrideableByRestore = */ true); Settings.Secure.putLong(mContext.getContentResolver(), LEARNED_HINT_LAST_SHOWN_KEY, 0); mContext = null; } @@ -282,8 +312,12 @@ final class AssistHandleReminderExpBehavior implements BehaviorController { return; } - Settings.Secure.putLong( - mContext.getContentResolver(), LEARNING_EVENT_COUNT_KEY, ++mLearningCount); + // putString to use overrideableByRestore + Settings.Secure.putString( + mContext.getContentResolver(), + Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT, + Integer.toString(++mLearningCount), + /* overrideableByRestore = */ true); } @Override @@ -460,8 +494,12 @@ final class AssistHandleReminderExpBehavior implements BehaviorController { mIsLearned = mLearningCount >= getLearningCount() || mLearningTimeElapsed >= getLearningTimeMs(); - mHandler.post(() -> Settings.Secure.putLong( - mContext.getContentResolver(), LEARNING_TIME_ELAPSED_KEY, mLearningTimeElapsed)); + // putString to use overrideableByRestore + mHandler.post(() -> Settings.Secure.putString( + mContext.getContentResolver(), + Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS, + Long.toString(mLearningTimeElapsed), + /* overrideableByRestore = */ true)); } private void resetConsecutiveTaskSwitches() { @@ -589,4 +627,32 @@ final class AssistHandleReminderExpBehavior implements BehaviorController { + "=" + getShowWhenTaught()); } + + private final class SettingsObserver extends ContentObserver { + + private final Context mContext; + + SettingsObserver(Context context, Handler handler) { + super(handler); + mContext = context; + } + + @Override + public void onChange(boolean selfChange, @Nullable Uri uri) { + if (LEARNING_TIME_ELAPSED_URI.equals(uri)) { + mLastLearningTimestamp = mClock.currentTimeMillis(); + mLearningTimeElapsed = Settings.Secure.getLong( + mContext.getContentResolver(), + Settings.Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS, + /* default = */ 0); + } else if (LEARNING_EVENT_COUNT_URI.equals(uri)) { + mLearningCount = Settings.Secure.getInt( + mContext.getContentResolver(), + Settings.Secure.ASSIST_HANDLES_LEARNING_EVENT_COUNT, + /* default = */ 0); + } + + super.onChange(selfChange, uri); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java index c6d128631930..b71e3adae8ac 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java @@ -286,6 +286,15 @@ class Bubble implements BubbleViewProvider { } /** + * Sets whether this bubble is considered visually interruptive. Normally pulled from the + * {@link NotificationEntry}, this method is purely for testing. + */ + @VisibleForTesting + void setVisuallyInterruptiveForTest(boolean visuallyInterruptive) { + mIsVisuallyInterruptive = visuallyInterruptive; + } + + /** * Starts a task to inflate & load any necessary information to display a bubble. * * @param callback the callback to notify one the bubble is ready to be displayed. @@ -411,6 +420,7 @@ class Bubble implements BubbleViewProvider { } else if (mIntent != null && entry.getBubbleMetadata().getIntent() == null) { // Was an intent bubble now it's a shortcut bubble... still unregister the listener mIntent.unregisterCancelListener(mIntentCancelListener); + mIntentActive = false; mIntent = null; } mDeleteIntent = entry.getBubbleMetadata().getDeleteIntent(); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 2948b47103bf..5deae925ba30 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -82,7 +82,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.Dumpable; -import com.android.systemui.bubbles.animation.StackAnimationController; import com.android.systemui.bubbles.dagger.BubbleModule; import com.android.systemui.dump.DumpManager; import com.android.systemui.model.SysUiState; @@ -407,7 +406,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (bubble.getBubbleIntent() == null) { return; } - if (bubble.isIntentActive()) { + if (bubble.isIntentActive() + || mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) { bubble.setPendingIntentCanceled(); return; } @@ -1120,8 +1120,17 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (notif.getImportance() >= NotificationManager.IMPORTANCE_HIGH) { notif.setInterruption(); } - Bubble bubble = mBubbleData.getOrCreateBubble(notif, null /* persistedBubble */); - inflateAndAdd(bubble, suppressFlyout, showInShade); + if (!notif.getRanking().visuallyInterruptive() + && (notif.getBubbleMetadata() != null + && !notif.getBubbleMetadata().getAutoExpandBubble()) + && mBubbleData.hasOverflowBubbleWithKey(notif.getKey())) { + // Update the bubble but don't promote it out of overflow + Bubble b = mBubbleData.getOverflowBubbleWithKey(notif.getKey()); + b.setEntry(notif); + } else { + Bubble bubble = mBubbleData.getOrCreateBubble(notif, null /* persistedBubble */); + inflateAndAdd(bubble, suppressFlyout, showInShade); + } } void inflateAndAdd(Bubble bubble, boolean suppressFlyout, boolean showInShade) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java index d2dc506c8e5c..85ea8bc91484 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java @@ -277,7 +277,8 @@ public class BubbleData { } else { // Updates an existing bubble bubble.setSuppressFlyout(suppressFlyout); - doUpdate(bubble); + // If there is no flyout, we probably shouldn't show the bubble at the top + doUpdate(bubble, !suppressFlyout /* reorder */); } if (bubble.shouldAutoExpand()) { @@ -431,12 +432,12 @@ public class BubbleData { } } - private void doUpdate(Bubble bubble) { + private void doUpdate(Bubble bubble, boolean reorder) { if (DEBUG_BUBBLE_DATA) { Log.d(TAG, "doUpdate: " + bubble); } mStateChange.updatedBubble = bubble; - if (!isExpanded()) { + if (!isExpanded() && reorder) { int prevPos = mBubbles.indexOf(bubble); mBubbles.remove(bubble); mBubbles.add(0, bubble); diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaBrowserFactory.java b/packages/SystemUI/src/com/android/systemui/media/MediaBrowserFactory.java new file mode 100644 index 000000000000..aca033e99623 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/MediaBrowserFactory.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media; + +import android.content.ComponentName; +import android.content.Context; +import android.media.browse.MediaBrowser; +import android.os.Bundle; + +import javax.inject.Inject; + +/** + * Testable wrapper around {@link MediaBrowser} constructor + */ +public class MediaBrowserFactory { + private final Context mContext; + + @Inject + public MediaBrowserFactory(Context context) { + mContext = context; + } + + /** + * Creates a new MediaBrowser + * + * @param serviceComponent + * @param callback + * @param rootHints + * @return + */ + public MediaBrowser create(ComponentName serviceComponent, + MediaBrowser.ConnectionCallback callback, Bundle rootHints) { + return new MediaBrowser(mContext, serviceComponent, callback, rootHints); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt index 3aa37a2a927d..d8d9bd7e95b8 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt @@ -174,7 +174,7 @@ class MediaCarouselController @Inject constructor( mediaManager.addListener(object : MediaDataManager.Listener { override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { addOrUpdatePlayer(key, oldKey, data) - val canRemove = data.isPlaying?.let { !it } ?: data.isClearable + val canRemove = data.isPlaying?.let { !it } ?: data.isClearable && !data.active if (canRemove && !Utils.useMediaResumption(context)) { // This view isn't playing, let's remove this! This happens e.g when // dismissing/timing out a view. We still have the data around because @@ -250,13 +250,13 @@ class MediaCarouselController @Inject constructor( val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) newPlayer.view?.player?.setLayoutParams(lp) - newPlayer.bind(data) + newPlayer.bind(data, key) newPlayer.setListening(currentlyExpanded) MediaPlayerData.addMediaPlayer(key, data, newPlayer) updatePlayerToState(newPlayer, noAnimation = true) reorderAllPlayers() } else { - existingPlayer.bind(data) + existingPlayer.bind(data, key) MediaPlayerData.addMediaPlayer(key, data, existingPlayer) if (visualStabilityManager.isReorderingAllowed) { reorderAllPlayers() diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index e55678dc986b..810cecca517f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -82,6 +82,7 @@ public class MediaControlPanel { private Context mContext; private PlayerViewHolder mViewHolder; + private String mKey; private MediaViewController mMediaViewController; private MediaSession.Token mToken; private MediaController mController; @@ -206,10 +207,11 @@ public class MediaControlPanel { /** * Bind this view based on the data given */ - public void bind(@NonNull MediaData data) { + public void bind(@NonNull MediaData data, String key) { if (mViewHolder == null) { return; } + mKey = key; MediaSession.Token token = data.getToken(); mBackgroundColor = data.getBackgroundColor(); if (mToken == null || !mToken.equals(token)) { @@ -359,10 +361,10 @@ public class MediaControlPanel { // Dismiss mViewHolder.getDismiss().setOnClickListener(v -> { - if (data.getNotificationKey() != null) { + if (mKey != null) { closeGuts(); mKeyguardDismissUtil.executeWhenUnlocked(() -> { - mMediaDataManagerLazy.get().dismissMediaData(data.getNotificationKey(), + mMediaDataManagerLazy.get().dismissMediaData(mKey, MediaViewController.GUTS_ANIMATION_DURATION + 100); return true; }, /* requiresShadeOpen */ true); diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt index 1ac3034ea7c1..936db8735ad8 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt @@ -28,6 +28,7 @@ import android.os.UserHandle import android.provider.Settings import android.service.media.MediaBrowserService import android.util.Log +import com.android.internal.annotations.VisibleForTesting import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.tuner.TunerService @@ -47,7 +48,8 @@ class MediaResumeListener @Inject constructor( private val context: Context, private val broadcastDispatcher: BroadcastDispatcher, @Background private val backgroundExecutor: Executor, - private val tunerService: TunerService + private val tunerService: TunerService, + private val mediaBrowserFactory: ResumeMediaBrowserFactory ) : MediaDataManager.Listener { private var useMediaResumption: Boolean = Utils.useMediaResumption(context) @@ -58,7 +60,8 @@ class MediaResumeListener @Inject constructor( private var mediaBrowser: ResumeMediaBrowser? = null private var currentUserId: Int = context.userId - private val userChangeReceiver = object : BroadcastReceiver() { + @VisibleForTesting + val userChangeReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (Intent.ACTION_USER_UNLOCKED == intent.action) { loadMediaResumptionControls() @@ -142,7 +145,7 @@ class MediaResumeListener @Inject constructor( } resumeComponents.forEach { - val browser = ResumeMediaBrowser(context, mediaBrowserCallback, it) + val browser = mediaBrowserFactory.create(mediaBrowserCallback, it) browser.findRecentMedia() } } @@ -181,14 +184,10 @@ class MediaResumeListener @Inject constructor( private fun tryUpdateResumptionList(key: String, componentName: ComponentName) { Log.d(TAG, "Testing if we can connect to $componentName") mediaBrowser?.disconnect() - mediaBrowser = ResumeMediaBrowser(context, + mediaBrowser = mediaBrowserFactory.create( object : ResumeMediaBrowser.Callback() { override fun onConnected() { - Log.d(TAG, "yes we can resume with $componentName") - mediaDataManager.setResumeAction(key, getResumeAction(componentName)) - updateResumptionList(componentName) - mediaBrowser?.disconnect() - mediaBrowser = null + Log.d(TAG, "Connected to $componentName") } override fun onError() { @@ -197,6 +196,19 @@ class MediaResumeListener @Inject constructor( mediaBrowser?.disconnect() mediaBrowser = null } + + override fun addTrack( + desc: MediaDescription, + component: ComponentName, + browser: ResumeMediaBrowser + ) { + // Since this is a test, just save the component for later + Log.d(TAG, "Can get resumable media from $componentName") + mediaDataManager.setResumeAction(key, getResumeAction(componentName)) + updateResumptionList(componentName) + mediaBrowser?.disconnect() + mediaBrowser = null + } }, componentName) mediaBrowser?.testConnection() @@ -233,7 +245,7 @@ class MediaResumeListener @Inject constructor( private fun getResumeAction(componentName: ComponentName): Runnable { return Runnable { mediaBrowser?.disconnect() - mediaBrowser = ResumeMediaBrowser(context, + mediaBrowser = mediaBrowserFactory.create( object : ResumeMediaBrowser.Callback() { override fun onConnected() { if (mediaBrowser?.token == null) { diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt index 8662aacfdab2..dcb7767a680a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt @@ -54,32 +54,35 @@ class MediaTimeoutListener @Inject constructor( if (mediaListeners.containsKey(key)) { return } - // Having an old key means that we're migrating from/to resumption. We should invalidate - // the old listener and create a new one. + // Having an old key means that we're migrating from/to resumption. We should update + // the old listener to make sure that events will be dispatched to the new location. val migrating = oldKey != null && key != oldKey var wasPlaying = false if (migrating) { - if (mediaListeners.containsKey(oldKey)) { - val oldListener = mediaListeners.remove(oldKey) - wasPlaying = oldListener?.playing ?: false - oldListener?.destroy() + val reusedListener = mediaListeners.remove(oldKey) + if (reusedListener != null) { + wasPlaying = reusedListener.playing ?: false if (DEBUG) Log.d(TAG, "migrating key $oldKey to $key, for resumption") + reusedListener.mediaData = data + reusedListener.key = key + mediaListeners[key] = reusedListener + if (wasPlaying != reusedListener.playing) { + // If a player becomes active because of a migration, we'll need to broadcast + // its state. Doing it now would lead to reentrant callbacks, so let's wait + // until we're done. + mainExecutor.execute { + if (mediaListeners[key]?.playing == true) { + if (DEBUG) Log.d(TAG, "deliver delayed playback state for $key") + timeoutCallback.invoke(key, false /* timedOut */) + } + } + } + return } else { Log.w(TAG, "Old key $oldKey for player $key doesn't exist. Continuing...") } } mediaListeners[key] = PlaybackStateListener(key, data) - - // If a player becomes active because of a migration, we'll need to broadcast its state. - // Doing it now would lead to reentrant callbacks, so let's wait until we're done. - if (migrating && mediaListeners[key]?.playing != wasPlaying) { - mainExecutor.execute { - if (mediaListeners[key]?.playing == true) { - if (DEBUG) Log.d(TAG, "deliver delayed playback state for $key") - timeoutCallback.invoke(key, false /* timedOut */) - } - } - } } override fun onMediaDataRemoved(key: String) { @@ -91,30 +94,39 @@ class MediaTimeoutListener @Inject constructor( } private inner class PlaybackStateListener( - private val key: String, + var key: String, data: MediaData ) : MediaController.Callback() { var timedOut = false var playing: Boolean? = null + var mediaData: MediaData = data + set(value) { + mediaController?.unregisterCallback(this) + field = value + mediaController = if (field.token != null) { + mediaControllerFactory.create(field.token) + } else { + null + } + mediaController?.registerCallback(this) + // Let's register the cancellations, but not dispatch events now. + // Timeouts didn't happen yet and reentrant events are troublesome. + processState(mediaController?.playbackState, dispatchEvents = false) + } + // Resume controls may have null token - private val mediaController = if (data.token != null) { - mediaControllerFactory.create(data.token) - } else { - null - } + private var mediaController: MediaController? = null private var cancellation: Runnable? = null init { - mediaController?.registerCallback(this) - // Let's register the cancellations, but not dispatch events now. - // Timeouts didn't happen yet and reentrant events are troublesome. - processState(mediaController?.playbackState, dispatchEvents = false) + mediaData = data } fun destroy() { mediaController?.unregisterCallback(this) + cancellation?.run() } override fun onPlaybackStateChanged(state: PlaybackState?) { @@ -171,4 +183,4 @@ class MediaTimeoutListener @Inject constructor( cancellation = null } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java index 68b6785849aa..a4d44367be73 100644 --- a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java +++ b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java @@ -30,6 +30,8 @@ import android.service.media.MediaBrowserService; import android.text.TextUtils; import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; + import java.util.List; /** @@ -46,6 +48,7 @@ public class ResumeMediaBrowser { private static final String TAG = "ResumeMediaBrowser"; private final Context mContext; private final Callback mCallback; + private MediaBrowserFactory mBrowserFactory; private MediaBrowser mMediaBrowser; private ComponentName mComponentName; @@ -55,10 +58,12 @@ public class ResumeMediaBrowser { * @param callback used to report media items found * @param componentName Component name of the MediaBrowserService this browser will connect to */ - public ResumeMediaBrowser(Context context, Callback callback, ComponentName componentName) { + public ResumeMediaBrowser(Context context, Callback callback, ComponentName componentName, + MediaBrowserFactory browserFactory) { mContext = context; mCallback = callback; mComponentName = componentName; + mBrowserFactory = browserFactory; } /** @@ -74,7 +79,7 @@ public class ResumeMediaBrowser { disconnect(); Bundle rootHints = new Bundle(); rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true); - mMediaBrowser = new MediaBrowser(mContext, + mMediaBrowser = mBrowserFactory.create( mComponentName, mConnectionCallback, rootHints); @@ -88,17 +93,19 @@ public class ResumeMediaBrowser { List<MediaBrowser.MediaItem> children) { if (children.size() == 0) { Log.d(TAG, "No children found for " + mComponentName); - return; - } - // We ask apps to return a playable item as the first child when sending - // a request with EXTRA_RECENT; if they don't, no resume controls - MediaBrowser.MediaItem child = children.get(0); - MediaDescription desc = child.getDescription(); - if (child.isPlayable() && mMediaBrowser != null) { - mCallback.addTrack(desc, mMediaBrowser.getServiceComponent(), - ResumeMediaBrowser.this); + mCallback.onError(); } else { - Log.d(TAG, "Child found but not playable for " + mComponentName); + // We ask apps to return a playable item as the first child when sending + // a request with EXTRA_RECENT; if they don't, no resume controls + MediaBrowser.MediaItem child = children.get(0); + MediaDescription desc = child.getDescription(); + if (child.isPlayable() && mMediaBrowser != null) { + mCallback.addTrack(desc, mMediaBrowser.getServiceComponent(), + ResumeMediaBrowser.this); + } else { + Log.d(TAG, "Child found but not playable for " + mComponentName); + mCallback.onError(); + } } disconnect(); } @@ -131,7 +138,7 @@ public class ResumeMediaBrowser { Log.d(TAG, "Service connected for " + mComponentName); if (mMediaBrowser != null && mMediaBrowser.isConnected()) { String root = mMediaBrowser.getRoot(); - if (!TextUtils.isEmpty(root)) { + if (!TextUtils.isEmpty(root) && mMediaBrowser != null) { mCallback.onConnected(); mMediaBrowser.subscribe(root, mSubscriptionCallback); return; @@ -182,7 +189,7 @@ public class ResumeMediaBrowser { disconnect(); Bundle rootHints = new Bundle(); rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true); - mMediaBrowser = new MediaBrowser(mContext, mComponentName, + mMediaBrowser = mBrowserFactory.create(mComponentName, new MediaBrowser.ConnectionCallback() { @Override public void onConnected() { @@ -192,7 +199,7 @@ public class ResumeMediaBrowser { return; } MediaSession.Token token = mMediaBrowser.getSessionToken(); - MediaController controller = new MediaController(mContext, token); + MediaController controller = createMediaController(token); controller.getTransportControls(); controller.getTransportControls().prepare(); controller.getTransportControls().play(); @@ -212,6 +219,11 @@ public class ResumeMediaBrowser { mMediaBrowser.connect(); } + @VisibleForTesting + protected MediaController createMediaController(MediaSession.Token token) { + return new MediaController(mContext, token); + } + /** * Get the media session token * @return the token, or null if the MediaBrowser is null or disconnected @@ -235,42 +247,19 @@ public class ResumeMediaBrowser { /** * Used to test if SystemUI is allowed to connect to the given component as a MediaBrowser. - * ResumeMediaBrowser.Callback#onError or ResumeMediaBrowser.Callback#onConnected will be called - * depending on whether it was successful. + * If it can connect, ResumeMediaBrowser.Callback#onConnected will be called. If valid media is + * found, then ResumeMediaBrowser.Callback#addTrack will also be called. This allows for more + * detailed logging if the service has issues. If it cannot connect, or cannot find valid media, + * then ResumeMediaBrowser.Callback#onError will be called. * ResumeMediaBrowser#disconnect should be called after this to ensure the connection is closed. */ public void testConnection() { disconnect(); - final MediaBrowser.ConnectionCallback connectionCallback = - new MediaBrowser.ConnectionCallback() { - @Override - public void onConnected() { - Log.d(TAG, "connected"); - if (mMediaBrowser == null || !mMediaBrowser.isConnected() - || TextUtils.isEmpty(mMediaBrowser.getRoot())) { - mCallback.onError(); - } else { - mCallback.onConnected(); - } - } - - @Override - public void onConnectionSuspended() { - Log.d(TAG, "suspended"); - mCallback.onError(); - } - - @Override - public void onConnectionFailed() { - Log.d(TAG, "failed"); - mCallback.onError(); - } - }; Bundle rootHints = new Bundle(); rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true); - mMediaBrowser = new MediaBrowser(mContext, + mMediaBrowser = mBrowserFactory.create( mComponentName, - connectionCallback, + mConnectionCallback, rootHints); mMediaBrowser.connect(); } diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserFactory.java b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserFactory.java new file mode 100644 index 000000000000..2261aa5ac265 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowserFactory.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media; + +import android.content.ComponentName; +import android.content.Context; + +import javax.inject.Inject; + +/** + * Testable wrapper around {@link ResumeMediaBrowser} constructor + */ +public class ResumeMediaBrowserFactory { + private final Context mContext; + private final MediaBrowserFactory mBrowserFactory; + + @Inject + public ResumeMediaBrowserFactory(Context context, MediaBrowserFactory browserFactory) { + mContext = context; + mBrowserFactory = browserFactory; + } + + /** + * Creates a new ResumeMediaBrowser. + * + * @param callback will be called on connection or error, and addTrack when media item found + * @param componentName component to browse + * @return + */ + public ResumeMediaBrowser create(ResumeMediaBrowser.Callback callback, + ComponentName componentName) { + return new ResumeMediaBrowser(mContext, callback, componentName, mBrowserFactory); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt index 1ae54d60d3fa..d789501ffdef 100644 --- a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt @@ -28,10 +28,14 @@ import com.android.systemui.R */ class SeekBarObserver(private val holder: PlayerViewHolder) : Observer<SeekBarViewModel.Progress> { - val seekBarDefaultMaxHeight = holder.seekBar.context.resources + val seekBarEnabledMaxHeight = holder.seekBar.context.resources .getDimensionPixelSize(R.dimen.qs_media_enabled_seekbar_height) val seekBarDisabledHeight = holder.seekBar.context.resources .getDimensionPixelSize(R.dimen.qs_media_disabled_seekbar_height) + val seekBarEnabledVerticalPadding = holder.seekBar.context.resources + .getDimensionPixelSize(R.dimen.qs_media_enabled_seekbar_vertical_padding) + val seekBarDisabledVerticalPadding = holder.seekBar.context.resources + .getDimensionPixelSize(R.dimen.qs_media_disabled_seekbar_vertical_padding) /** Updates seek bar views when the data model changes. */ @UiThread @@ -39,6 +43,7 @@ class SeekBarObserver(private val holder: PlayerViewHolder) : Observer<SeekBarVi if (!data.enabled) { if (holder.seekBar.maxHeight != seekBarDisabledHeight) { holder.seekBar.maxHeight = seekBarDisabledHeight + setVerticalPadding(seekBarDisabledVerticalPadding) } holder.seekBar.setEnabled(false) holder.seekBar.getThumb().setAlpha(0) @@ -51,8 +56,9 @@ class SeekBarObserver(private val holder: PlayerViewHolder) : Observer<SeekBarVi holder.seekBar.getThumb().setAlpha(if (data.seekAvailable) 255 else 0) holder.seekBar.setEnabled(data.seekAvailable) - if (holder.seekBar.maxHeight != seekBarDefaultMaxHeight) { - holder.seekBar.maxHeight = seekBarDefaultMaxHeight + if (holder.seekBar.maxHeight != seekBarEnabledMaxHeight) { + holder.seekBar.maxHeight = seekBarEnabledMaxHeight + setVerticalPadding(seekBarEnabledVerticalPadding) } data.duration?.let { @@ -67,4 +73,11 @@ class SeekBarObserver(private val holder: PlayerViewHolder) : Observer<SeekBarVi it / DateUtils.SECOND_IN_MILLIS)) } } + + @UiThread + fun setVerticalPadding(padding: Int) { + val leftPadding = holder.seekBar.paddingLeft + val rightPadding = holder.seekBar.paddingRight + holder.seekBar.setPadding(leftPadding, padding, rightPadding, padding) + } } diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java index e6abea72da62..54df53dbe6d7 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java @@ -303,15 +303,20 @@ public class PipTaskOrganizer extends TaskOrganizer implements * @param animationDurationMs duration in millisecond for the exiting PiP transition */ public void exitPip(int animationDurationMs) { - if (!mState.isInPip() || mState == State.EXITING_PIP || mToken == null) { + if (!mState.isInPip() || mToken == null) { Log.wtf(TAG, "Not allowed to exitPip in current state" + " mState=" + mState + " mToken=" + mToken); return; } + final PipWindowConfigurationCompact config = mCompactState.remove(mToken.asBinder()); + if (config == null) { + Log.wtf(TAG, "Token not in record, this should not happen mToken=" + mToken); + return; + } + mPipUiEventLoggerLogger.log( PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_EXPAND_TO_FULLSCREEN); - final PipWindowConfigurationCompact config = mCompactState.remove(mToken.asBinder()); config.syncWithScreenOrientation(mRequestedOrientation, mPipBoundsHandler.getDisplayRotation()); final boolean orientationDiffers = config.getRotation() diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java index 2a83aa06a237..586399c6dfd5 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java @@ -562,8 +562,10 @@ public class PipMenuActivity extends Activity { // TODO: Check if the action drawable has changed before we reload it action.getIcon().loadDrawableAsync(this, d -> { - d.setTint(Color.WHITE); - actionView.setImageDrawable(d); + if (d != null) { + d.setTint(Color.WHITE); + actionView.setImageDrawable(d); + } }, mHandler); actionView.setContentDescription(action.getContentDescription()); if (action.isEnabled()) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index 7cd1c7861164..44803aedc4ea 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -177,6 +177,16 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { } @Override + public void endFakeDrag() { + try { + super.endFakeDrag(); + } catch (NullPointerException e) { + // Not sure what's going on. Let's log it + Log.e(TAG, "endFakeDrag called without velocityTracker", e); + } + } + + @Override public void computeScroll() { if (!mScroller.isFinished() && mScroller.computeScrollOffset()) { if (!isFakeDragging()) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java index e738cec4962a..bffeb3ec3c70 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java @@ -14,11 +14,8 @@ package com.android.systemui.qs.customize; -import android.app.AlertDialog; -import android.app.AlertDialog.Builder; import android.content.ComponentName; import android.content.Context; -import android.content.DialogInterface; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.drawable.Drawable; @@ -28,10 +25,11 @@ import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnLayoutChangeListener; import android.view.ViewGroup; -import android.view.accessibility.AccessibilityManager; import android.widget.FrameLayout; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.core.view.AccessibilityDelegateCompat; import androidx.core.view.ViewCompat; import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup; import androidx.recyclerview.widget.ItemTouchHelper; @@ -49,7 +47,6 @@ import com.android.systemui.qs.customize.TileQueryHelper.TileInfo; import com.android.systemui.qs.customize.TileQueryHelper.TileStateListener; import com.android.systemui.qs.external.CustomTile; import com.android.systemui.qs.tileimpl.QSIconViewImpl; -import com.android.systemui.statusbar.phone.SystemUIDialog; import java.util.ArrayList; import java.util.List; @@ -78,10 +75,10 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta private final List<TileInfo> mTiles = new ArrayList<>(); private final ItemTouchHelper mItemTouchHelper; private final ItemDecoration mDecoration; - private final AccessibilityManager mAccessibilityManager; private final int mMinNumTiles; private int mEditIndex; private int mTileDividerIndex; + private int mFocusIndex; private boolean mNeedsFocus; private List<String> mCurrentSpecs; private List<TileInfo> mOtherTiles; @@ -90,17 +87,28 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta private Holder mCurrentDrag; private int mAccessibilityAction = ACTION_NONE; private int mAccessibilityFromIndex; - private CharSequence mAccessibilityFromLabel; private QSTileHost mHost; private final UiEventLogger mUiEventLogger; + private final AccessibilityDelegateCompat mAccessibilityDelegate; + private RecyclerView mRecyclerView; public TileAdapter(Context context, UiEventLogger uiEventLogger) { mContext = context; mUiEventLogger = uiEventLogger; - mAccessibilityManager = context.getSystemService(AccessibilityManager.class); mItemTouchHelper = new ItemTouchHelper(mCallbacks); mDecoration = new TileItemDecoration(context); mMinNumTiles = context.getResources().getInteger(R.integer.quick_settings_min_num_tiles); + mAccessibilityDelegate = new TileAdapterDelegate(); + } + + @Override + public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) { + mRecyclerView = recyclerView; + } + + @Override + public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) { + mRecyclerView = null; } public void setHost(QSTileHost host) { @@ -130,7 +138,6 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta // Remove blank tile from last spot mTiles.remove(--mEditIndex); // Update the tile divider position - mTileDividerIndex--; notifyDataSetChanged(); } mAccessibilityAction = ACTION_NONE; @@ -241,14 +248,12 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta } private void setSelectableForHeaders(View view) { - if (mAccessibilityManager.isTouchExplorationEnabled()) { - final boolean selectable = mAccessibilityAction == ACTION_NONE; - view.setFocusable(selectable); - view.setImportantForAccessibility(selectable - ? View.IMPORTANT_FOR_ACCESSIBILITY_YES - : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); - view.setFocusableInTouchMode(selectable); - } + final boolean selectable = mAccessibilityAction == ACTION_NONE; + view.setFocusable(selectable); + view.setImportantForAccessibility(selectable + ? View.IMPORTANT_FOR_ACCESSIBILITY_YES + : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); + view.setFocusableInTouchMode(selectable); } @Override @@ -285,12 +290,11 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta holder.mTileView.setVisibility(View.VISIBLE); holder.mTileView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); holder.mTileView.setContentDescription(mContext.getString( - R.string.accessibility_qs_edit_tile_add, mAccessibilityFromLabel, - position)); + R.string.accessibility_qs_edit_tile_add_to_position, position)); holder.mTileView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - selectPosition(holder.getAdapterPosition(), v); + selectPosition(holder.getLayoutPosition()); } }); focusOnHolder(holder); @@ -299,54 +303,49 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta TileInfo info = mTiles.get(position); - if (position > mEditIndex) { - info.state.contentDescription = mContext.getString( - R.string.accessibility_qs_edit_add_tile_label, info.state.label); - } else if (mAccessibilityAction == ACTION_ADD) { + final boolean selectable = 0 < position && position < mEditIndex; + if (selectable && mAccessibilityAction == ACTION_ADD) { info.state.contentDescription = mContext.getString( - R.string.accessibility_qs_edit_tile_add, mAccessibilityFromLabel, position); - } else if (mAccessibilityAction == ACTION_MOVE) { + R.string.accessibility_qs_edit_tile_add_to_position, position); + } else if (selectable && mAccessibilityAction == ACTION_MOVE) { info.state.contentDescription = mContext.getString( - R.string.accessibility_qs_edit_tile_move, mAccessibilityFromLabel, position); + R.string.accessibility_qs_edit_tile_move_to_position, position); } else { - info.state.contentDescription = mContext.getString( - R.string.accessibility_qs_edit_tile_label, position, info.state.label); + info.state.contentDescription = info.state.label; } + info.state.expandedAccessibilityClassName = ""; + holder.mTileView.handleStateChanged(info.state); holder.mTileView.setShowAppLabel(position > mEditIndex && !info.isSystem); + holder.mTileView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); + holder.mTileView.setClickable(true); + holder.mTileView.setOnClickListener(null); + holder.mTileView.setFocusable(true); + holder.mTileView.setFocusableInTouchMode(true); - if (mAccessibilityManager.isTouchExplorationEnabled()) { - final boolean selectable = mAccessibilityAction == ACTION_NONE || position < mEditIndex; + if (mAccessibilityAction != ACTION_NONE) { holder.mTileView.setClickable(selectable); holder.mTileView.setFocusable(selectable); + holder.mTileView.setFocusableInTouchMode(selectable); holder.mTileView.setImportantForAccessibility(selectable ? View.IMPORTANT_FOR_ACCESSIBILITY_YES : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); - holder.mTileView.setFocusableInTouchMode(selectable); if (selectable) { holder.mTileView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - int position = holder.getAdapterPosition(); + int position = holder.getLayoutPosition(); if (position == RecyclerView.NO_POSITION) return; if (mAccessibilityAction != ACTION_NONE) { - selectPosition(position, v); - } else { - if (position < mEditIndex && canRemoveTiles()) { - showAccessibilityDialog(position, v); - } else if (position < mEditIndex && !canRemoveTiles()) { - startAccessibleMove(position); - } else { - startAccessibleAdd(position); - } + selectPosition(position); } } }); - if (position == mAccessibilityFromIndex) { - focusOnHolder(holder); - } } } + if (position == mFocusIndex) { + focusOnHolder(holder); + } } private void focusOnHolder(Holder holder) { @@ -360,9 +359,13 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta int oldLeft, int oldTop, int oldRight, int oldBottom) { holder.mTileView.removeOnLayoutChangeListener(this); holder.mTileView.requestFocus(); + if (mAccessibilityAction == ACTION_NONE) { + holder.mTileView.clearFocus(); + } } }); mNeedsFocus = false; + mFocusIndex = RecyclerView.NO_POSITION; } } @@ -370,72 +373,77 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta return mCurrentSpecs.size() > mMinNumTiles; } - private void selectPosition(int position, View v) { + private void selectPosition(int position) { if (mAccessibilityAction == ACTION_ADD) { // Remove the placeholder. mTiles.remove(mEditIndex--); - notifyItemRemoved(mEditIndex); } mAccessibilityAction = ACTION_NONE; - move(mAccessibilityFromIndex, position, v); + move(mAccessibilityFromIndex, position, false); + mFocusIndex = position; + mNeedsFocus = true; notifyDataSetChanged(); } - private void showAccessibilityDialog(final int position, final View v) { - final TileInfo info = mTiles.get(position); - CharSequence[] options = new CharSequence[] { - mContext.getString(R.string.accessibility_qs_edit_move_tile, info.state.label), - mContext.getString(R.string.accessibility_qs_edit_remove_tile, info.state.label), - }; - AlertDialog dialog = new Builder(mContext) - .setItems(options, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (which == 0) { - startAccessibleMove(position); - } else { - move(position, info.isSystem ? mEditIndex : mTileDividerIndex, v); - notifyItemChanged(mTileDividerIndex); - notifyDataSetChanged(); - } - } - }).setNegativeButton(android.R.string.cancel, null) - .create(); - SystemUIDialog.setShowForAllUsers(dialog, true); - SystemUIDialog.applyFlags(dialog); - dialog.show(); - } - private void startAccessibleAdd(int position) { mAccessibilityFromIndex = position; - mAccessibilityFromLabel = mTiles.get(position).state.label; mAccessibilityAction = ACTION_ADD; // Add placeholder for last slot. mTiles.add(mEditIndex++, null); // Update the tile divider position mTileDividerIndex++; + mFocusIndex = mEditIndex - 1; mNeedsFocus = true; + if (mRecyclerView != null) { + mRecyclerView.post(() -> mRecyclerView.smoothScrollToPosition(mFocusIndex)); + } notifyDataSetChanged(); } private void startAccessibleMove(int position) { mAccessibilityFromIndex = position; - mAccessibilityFromLabel = mTiles.get(position).state.label; mAccessibilityAction = ACTION_MOVE; + mFocusIndex = position; mNeedsFocus = true; notifyDataSetChanged(); } + private boolean canRemoveFromPosition(int position) { + return canRemoveTiles() && isCurrentTile(position); + } + + private boolean isCurrentTile(int position) { + return position < mEditIndex; + } + + private boolean canAddFromPosition(int position) { + return position > mEditIndex; + } + + private void addFromPosition(int position) { + if (!canAddFromPosition(position)) return; + move(position, mEditIndex); + } + + private void removeFromPosition(int position) { + if (!canRemoveFromPosition(position)) return; + TileInfo info = mTiles.get(position); + move(position, info.isSystem ? mEditIndex : mTileDividerIndex); + } + public SpanSizeLookup getSizeLookup() { return mSizeLookup; } - private boolean move(int from, int to, View v) { + private boolean move(int from, int to) { + return move(from, to, true); + } + + private boolean move(int from, int to, boolean notify) { if (to == from) { return true; } - CharSequence fromLabel = mTiles.get(from).state.label; - move(from, to, mTiles); + move(from, to, mTiles, notify); updateDividerLocations(); if (to >= mEditIndex) { mUiEventLogger.log(QSEditEvent.QS_EDIT_REMOVE, 0, strip(mTiles.get(to))); @@ -477,9 +485,11 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta return spec; } - private <T> void move(int from, int to, List<T> list) { + private <T> void move(int from, int to, List<T> list, boolean notify) { list.add(to, list.remove(from)); - notifyItemMoved(from, to); + if (notify) { + notifyItemMoved(from, to); + } } public class Holder extends ViewHolder { @@ -491,6 +501,8 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta mTileView = (CustomizeTileView) ((FrameLayout) itemView).getChildAt(0); mTileView.setBackground(null); mTileView.getIcon().disableAnimation(); + mTileView.setTag(this); + ViewCompat.setAccessibilityDelegate(mTileView, mAccessibilityDelegate); } } @@ -527,6 +539,46 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta .setDuration(DRAG_LENGTH) .alpha(.6f); } + + boolean canRemove() { + return canRemoveFromPosition(getLayoutPosition()); + } + + boolean canAdd() { + return canAddFromPosition(getLayoutPosition()); + } + + void toggleState() { + if (canAdd()) { + add(); + } else { + remove(); + } + } + + private void add() { + addFromPosition(getLayoutPosition()); + } + + private void remove() { + removeFromPosition(getLayoutPosition()); + } + + boolean isCurrentTile() { + return TileAdapter.this.isCurrentTile(getLayoutPosition()); + } + + void startAccessibleAdd() { + TileAdapter.this.startAccessibleAdd(getLayoutPosition()); + } + + void startAccessibleMove() { + TileAdapter.this.startAccessibleMove(getLayoutPosition()); + } + + boolean canTakeAccessibleAction() { + return mAccessibilityAction == ACTION_NONE; + } } private final SpanSizeLookup mSizeLookup = new SpanSizeLookup() { @@ -648,7 +700,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta to == 0 || to == RecyclerView.NO_POSITION) { return false; } - return move(from, to, target.itemView); + return move(from, to); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapterDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapterDelegate.java new file mode 100644 index 000000000000..1e426adac0b8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapterDelegate.java @@ -0,0 +1,152 @@ +/* + * 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.qs.customize; + +import android.os.Bundle; +import android.view.View; +import android.view.accessibility.AccessibilityNodeInfo; + +import androidx.core.view.AccessibilityDelegateCompat; +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; + +import com.android.systemui.R; + +import java.util.List; + +/** + * Accessibility delegate for {@link TileAdapter} views. + * + * This delegate will populate the accessibility info with the proper actions that can be taken for + * the different tiles: + * <ul> + * <li>Add to end if the tile is not a current tile (by double tap).</li> + * <li>Add to a given position (by context menu). This will let the user select a position.</li> + * <li>Remove, if the tile is a current tile (by double tap).</li> + * <li>Move to a given position (by context menu). This will let the user select a position.</li> + * </ul> + * + * This only handles generating the associated actions. The logic for selecting positions is handled + * by {@link TileAdapter}. + * + * In order for the delegate to work properly, the asociated {@link TileAdapter.Holder} should be + * passed along with the view using {@link View#setTag}. + */ +class TileAdapterDelegate extends AccessibilityDelegateCompat { + + private static final int MOVE_TO_POSITION_ID = R.id.accessibility_action_qs_move_to_position; + private static final int ADD_TO_POSITION_ID = R.id.accessibility_action_qs_add_to_position; + + private TileAdapter.Holder getHolder(View view) { + return (TileAdapter.Holder) view.getTag(); + } + + @Override + public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { + super.onInitializeAccessibilityNodeInfo(host, info); + TileAdapter.Holder holder = getHolder(host); + info.setCollectionItemInfo(null); + info.setStateDescription(""); + if (holder == null || !holder.canTakeAccessibleAction()) { + // If there's not a holder (not a regular Tile) or an action cannot be taken + // because we are in the middle of an accessibility action, don't create a special node. + return; + } + + addClickAction(host, info, holder); + maybeAddActionAddToPosition(host, info, holder); + maybeAddActionMoveToPosition(host, info, holder); + + if (holder.isCurrentTile()) { + info.setStateDescription(host.getContext().getString( + R.string.accessibility_qs_edit_position, holder.getLayoutPosition())); + } + } + + @Override + public boolean performAccessibilityAction(View host, int action, Bundle args) { + TileAdapter.Holder holder = getHolder(host); + + if (holder == null || !holder.canTakeAccessibleAction()) { + // If there's not a holder (not a regular Tile) or an action cannot be taken + // because we are in the middle of an accessibility action, perform the default action. + return super.performAccessibilityAction(host, action, args); + } + if (action == AccessibilityNodeInfo.ACTION_CLICK) { + holder.toggleState(); + return true; + } else if (action == MOVE_TO_POSITION_ID) { + holder.startAccessibleMove(); + return true; + } else if (action == ADD_TO_POSITION_ID) { + holder.startAccessibleAdd(); + return true; + } else { + return super.performAccessibilityAction(host, action, args); + } + } + + private void addClickAction( + View host, AccessibilityNodeInfoCompat info, TileAdapter.Holder holder) { + String clickActionString; + if (holder.canAdd()) { + clickActionString = host.getContext().getString( + R.string.accessibility_qs_edit_tile_add_action); + } else if (holder.canRemove()) { + clickActionString = host.getContext().getString( + R.string.accessibility_qs_edit_remove_tile_action); + } else { + // Remove the default click action if tile can't either be added or removed (for example + // if there's the minimum number of tiles) + List<AccessibilityNodeInfoCompat.AccessibilityActionCompat> listOfActions = + info.getActionList(); // This is a copy + int numActions = listOfActions.size(); + for (int i = 0; i < numActions; i++) { + if (listOfActions.get(i).getId() == AccessibilityNodeInfo.ACTION_CLICK) { + info.removeAction(listOfActions.get(i)); + } + } + return; + } + + AccessibilityNodeInfoCompat.AccessibilityActionCompat action = + new AccessibilityNodeInfoCompat.AccessibilityActionCompat( + AccessibilityNodeInfo.ACTION_CLICK, clickActionString); + info.addAction(action); + } + + private void maybeAddActionMoveToPosition( + View host, AccessibilityNodeInfoCompat info, TileAdapter.Holder holder) { + if (holder.isCurrentTile()) { + AccessibilityNodeInfoCompat.AccessibilityActionCompat action = + new AccessibilityNodeInfoCompat.AccessibilityActionCompat(MOVE_TO_POSITION_ID, + host.getContext().getString( + R.string.accessibility_qs_edit_tile_start_move)); + info.addAction(action); + } + } + + private void maybeAddActionAddToPosition( + View host, AccessibilityNodeInfoCompat info, TileAdapter.Holder holder) { + if (holder.canAdd()) { + AccessibilityNodeInfoCompat.AccessibilityActionCompat action = + new AccessibilityNodeInfoCompat.AccessibilityActionCompat(ADD_TO_POSITION_ID, + host.getContext().getString( + R.string.accessibility_qs_edit_tile_start_add)); + info.addAction(action); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java index 2f582727c766..acd2846fef3c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java @@ -60,7 +60,7 @@ public class NightDisplayTile extends QSTileImpl<BooleanState> implements private static final String PATTERN_HOUR_MINUTE = "h:mm a"; private static final String PATTERN_HOUR_NINUTE_24 = "HH:mm"; - private final ColorDisplayManager mManager; + private ColorDisplayManager mManager; private final LocationController mLocationController; private NightDisplayListener mListener; private boolean mIsListening; @@ -105,6 +105,8 @@ public class NightDisplayTile extends QSTileImpl<BooleanState> implements mListener.setCallback(null); } + mManager = getHost().getUserContext().getSystemService(ColorDisplayManager.class); + // Make a new controller for the new user. mListener = new NightDisplayListener(mContext, newUserId, new Handler(Looper.myLooper())); if (mIsListening) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java index 7b83c20d4b86..f777553bb5fe 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java @@ -50,16 +50,15 @@ public class UiModeNightTile extends QSTileImpl<QSTile.BooleanState> implements public static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("hh:mm a"); private final Icon mIcon = ResourceIcon.get( com.android.internal.R.drawable.ic_qs_ui_mode_night); - private final UiModeManager mUiModeManager; + private UiModeManager mUiModeManager; private final BatteryController mBatteryController; private final LocationController mLocationController; - @Inject public UiModeNightTile(QSHost host, ConfigurationController configurationController, BatteryController batteryController, LocationController locationController) { super(host); mBatteryController = batteryController; - mUiModeManager = mContext.getSystemService(UiModeManager.class); + mUiModeManager = host.getUserContext().getSystemService(UiModeManager.class); mLocationController = locationController; configurationController.observe(getLifecycle(), this); batteryController.observe(getLifecycle(), this); 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 ff7793d506fd..4699ace90bd3 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 @@ -167,7 +167,7 @@ class NotificationSectionsManager @Inject internal constructor( peopleHubSubscription = null peopleHeaderView = reinflateView(peopleHeaderView, layoutInflater, R.layout.people_strip) .apply { - setOnHeaderClickListener(View.OnClickListener { onGentleHeaderClick() }) + setOnHeaderClickListener(View.OnClickListener { onPeopleHeaderClick() }) } if (ENABLE_SNOOZED_CONVERSATION_HUB) { peopleHubSubscription = peopleHubViewAdapter.bindView(peopleHubViewBoundary) @@ -522,6 +522,15 @@ class NotificationSectionsManager @Inject internal constructor( Intent.FLAG_ACTIVITY_SINGLE_TOP) } + private fun onPeopleHeaderClick() { + val intent = Intent(Settings.ACTION_CONVERSATION_SETTINGS) + activityStarter.startActivity( + intent, + true, + true, + Intent.FLAG_ACTIVITY_SINGLE_TOP) + } + private fun onClearGentleNotifsClick(v: View) { onClearSilentNotifsClickListener?.onClick(v) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index 541c7845a5d3..744733597db4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -29,6 +29,7 @@ import com.android.systemui.R; import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.notification.NotificationUtils; +import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.FooterView; @@ -687,15 +688,27 @@ public class StackScrollAlgorithm { AmbientState ambientState) { int childCount = algorithmState.visibleChildren.size(); float childrenOnTop = 0.0f; + + int topHunIndex = -1; + for (int i = 0; i < childCount; i++) { + ExpandableView child = algorithmState.visibleChildren.get(i); + if (child instanceof ActivatableNotificationView + && (child.isAboveShelf() || child.showingPulsing())) { + topHunIndex = i; + break; + } + } + for (int i = childCount - 1; i >= 0; i--) { childrenOnTop = updateChildZValue(i, childrenOnTop, - algorithmState, ambientState); + algorithmState, ambientState, i == topHunIndex); } } protected float updateChildZValue(int i, float childrenOnTop, StackScrollAlgorithmState algorithmState, - AmbientState ambientState) { + AmbientState ambientState, + boolean shouldElevateHun) { ExpandableView child = algorithmState.visibleChildren.get(i); ExpandableViewState childViewState = child.getViewState(); int zDistanceBetweenElements = ambientState.getZDistanceBetweenElements(); @@ -713,8 +726,7 @@ public class StackScrollAlgorithm { } childViewState.zTranslation = baseZ + childrenOnTop * zDistanceBetweenElements; - } else if (child == ambientState.getTrackedHeadsUpRow() - || (i == 0 && (child.isAboveShelf() || child.showingPulsing()))) { + } else if (shouldElevateHun) { // In case this is a new view that has never been measured before, we don't want to // elevate if we are currently expanded more then the notification int shelfHeight = ambientState.getShelf() == null ? 0 : 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 c43ad36d4462..82ad00ad7c6d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java @@ -20,6 +20,7 @@ import static com.android.systemui.statusbar.notification.row.NotificationRowCon import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.Notification; import android.content.Context; import android.content.res.Resources; import android.database.ContentObserver; @@ -359,6 +360,11 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { return false; } + private static boolean isOngoingCallNotif(NotificationEntry entry) { + return entry.getSbn().isOngoing() && Notification.CATEGORY_CALL.equals( + entry.getSbn().getNotification().category); + } + /** * This represents a notification and how long it is in a heads up mode. It also manages its * lifecycle automatically when created. @@ -391,6 +397,15 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { return 1; } + boolean selfCall = isOngoingCallNotif(mEntry); + boolean otherCall = isOngoingCallNotif(headsUpEntry.mEntry); + + if (selfCall && !otherCall) { + return -1; + } else if (!selfCall && otherCall) { + return 1; + } + if (remoteInputActive && !headsUpEntry.remoteInputActive) { return -1; } else if (!remoteInputActive && headsUpEntry.remoteInputActive) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java index 8f082c15df36..ade329011b7d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java @@ -47,6 +47,7 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; import org.junit.Before; @@ -82,6 +83,8 @@ public class AppOpsControllerTest extends SysuiTestCase { private PackageManager mPackageManager; @Mock(stubOnly = true) private AudioManager mAudioManager; + @Mock() + private BroadcastDispatcher mDispatcher; @Mock(stubOnly = true) private AudioManager.AudioRecordingCallback mRecordingCallback; @Mock(stubOnly = true) @@ -120,7 +123,8 @@ public class AppOpsControllerTest extends SysuiTestCase { mTestableLooper.getLooper(), mDumpManager, mFlagsCache, - mAudioManager + mAudioManager, + mDispatcher ); } @@ -128,12 +132,14 @@ public class AppOpsControllerTest extends SysuiTestCase { public void testOnlyListenForFewOps() { mController.setListening(true); verify(mAppOpsManager, times(1)).startWatchingActive(AppOpsControllerImpl.OPS, mController); + verify(mDispatcher, times(1)).registerReceiverWithHandler(eq(mController), any(), any()); } @Test public void testStopListening() { mController.setListening(false); verify(mAppOpsManager, times(1)).stopWatchingActive(mController); + verify(mDispatcher, times(1)).unregisterReceiver(mController); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index 15828b41cc7c..1ad88560bf9c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -999,6 +999,29 @@ public class BubbleControllerTest extends SysuiTestCase { verify(mNotificationGroupManager, times(1)).onEntryRemoved(groupSummary.getEntry()); } + + /** + * Verifies that when a non visually interruptive update occurs for a bubble in the overflow, + * the that bubble does not get promoted from the overflow. + */ + @Test + public void test_notVisuallyInterruptive_updateOverflowBubble_notAdded() { + // Setup + mBubbleController.updateBubble(mRow.getEntry()); + mBubbleController.updateBubble(mRow2.getEntry()); + assertTrue(mBubbleController.hasBubbles()); + + // Overflow it + mBubbleData.dismissBubbleWithKey(mRow.getEntry().getKey(), + BubbleController.DISMISS_USER_GESTURE); + assertThat(mBubbleData.hasBubbleInStackWithKey(mRow.getEntry().getKey())).isFalse(); + assertThat(mBubbleData.hasOverflowBubbleWithKey(mRow.getEntry().getKey())).isTrue(); + + // Test + mBubbleController.updateBubble(mRow.getEntry()); + assertThat(mBubbleData.hasBubbleInStackWithKey(mRow.getEntry().getKey())).isFalse(); + } + /** * Sets the bubble metadata flags for this entry. These ]flags are normally set by * NotificationManagerService when the notification is sent, however, these tests do not diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java index 315caeebe0e9..4bbc41e517b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java @@ -513,6 +513,26 @@ public class BubbleDataTest extends SysuiTestCase { } /** + * Verifies that when a non visually interruptive update occurs, that the selection does not + * change. + */ + @Test + public void test_notVisuallyInterruptive_updateBubble_selectionDoesntChange() { + // Setup + sendUpdatedEntryAtTime(mEntryA1, 1000); + sendUpdatedEntryAtTime(mEntryB1, 2000); + sendUpdatedEntryAtTime(mEntryB2, 3000); + sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, B2, B1, A1] + mBubbleData.setListener(mListener); + + assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA2); + + // Test + sendUpdatedEntryAtTime(mEntryB1, 5000, false /* isVisuallyInterruptive */); + assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA2); + } + + /** * Verifies that a request to expand the stack has no effect if there are no bubbles. */ @Test @@ -883,9 +903,15 @@ public class BubbleDataTest extends SysuiTestCase { } private void sendUpdatedEntryAtTime(NotificationEntry entry, long postTime) { + sendUpdatedEntryAtTime(entry, postTime, true /* visuallyInterruptive */); + } + + private void sendUpdatedEntryAtTime(NotificationEntry entry, long postTime, + boolean visuallyInterruptive) { setPostTime(entry, postTime); // BubbleController calls this: Bubble b = mBubbleData.getOrCreateBubble(entry, null /* persistedBubble */); + b.setVisuallyInterruptiveForTest(visuallyInterruptive); // And then this mBubbleData.notificationEntryUpdated(b, false /* suppressFlyout*/, true /* showInShade */); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt index 8a30b00e609d..81139f192070 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt @@ -43,7 +43,6 @@ import com.android.systemui.statusbar.phone.KeyguardDismissUtil import com.android.systemui.util.animation.TransitionLayout import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.any import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import dagger.Lazy @@ -53,7 +52,6 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor -import org.mockito.ArgumentMatchers import org.mockito.ArgumentMatchers.anyLong import org.mockito.Mock import org.mockito.Mockito.anyBoolean @@ -203,7 +201,7 @@ public class MediaControlPanelTest : SysuiTestCase() { fun bindWhenUnattached() { val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), emptyList(), PACKAGE, null, null, device, true, null) - player.bind(state) + player.bind(state, PACKAGE) assertThat(player.isPlaying()).isFalse() } @@ -212,7 +210,7 @@ public class MediaControlPanelTest : SysuiTestCase() { player.attach(holder) val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null) - player.bind(state) + player.bind(state, PACKAGE) assertThat(appName.getText()).isEqualTo(APP) assertThat(titleText.getText()).isEqualTo(TITLE) assertThat(artistText.getText()).isEqualTo(ARTIST) @@ -223,7 +221,7 @@ public class MediaControlPanelTest : SysuiTestCase() { player.attach(holder) val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null) - player.bind(state) + player.bind(state, PACKAGE) val list = ArgumentCaptor.forClass(ColorStateList::class.java) verify(view).setBackgroundTintList(list.capture()) assertThat(list.value).isEqualTo(ColorStateList.valueOf(BG_COLOR)) @@ -234,7 +232,7 @@ public class MediaControlPanelTest : SysuiTestCase() { player.attach(holder) val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null) - player.bind(state) + player.bind(state, PACKAGE) assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME) assertThat(seamless.isEnabled()).isTrue() } @@ -246,7 +244,7 @@ public class MediaControlPanelTest : SysuiTestCase() { player.attach(holder) val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), emptyList(), PACKAGE, session.getSessionToken(), null, disabledDevice, true, null) - player.bind(state) + player.bind(state, PACKAGE) verify(expandedSet).setVisibility(seamless.id, View.GONE) verify(expandedSet).setVisibility(seamlessFallback.id, View.VISIBLE) verify(collapsedSet).setVisibility(seamless.id, View.GONE) @@ -258,7 +256,7 @@ public class MediaControlPanelTest : SysuiTestCase() { player.attach(holder) val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null) - player.bind(state) + player.bind(state, PACKAGE) assertThat(seamless.isEnabled()).isTrue() assertThat(seamlessText.getText()).isEqualTo(context.getResources().getString( com.android.internal.R.string.ext_media_seamless_action)) @@ -270,7 +268,7 @@ public class MediaControlPanelTest : SysuiTestCase() { val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null, resumption = true) - player.bind(state) + player.bind(state, PACKAGE) assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME) assertThat(seamless.isEnabled()).isFalse() } @@ -322,31 +320,18 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun dismissButtonClick() { + val mediaKey = "key for dismissal" player.attach(holder) val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null, notificationKey = KEY) - player.bind(state) + player.bind(state, mediaKey) dismiss.callOnClick() val captor = ArgumentCaptor.forClass(ActivityStarter.OnDismissAction::class.java) verify(keyguardDismissUtil).executeWhenUnlocked(captor.capture(), anyBoolean()) captor.value.onDismiss() - verify(mediaDataManager).dismissMediaData(eq(KEY), anyLong()) - } - - @Test - fun dismissButtonClick_nullNotificationKey() { - player.attach(holder) - val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(), - emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null) - player.bind(state) - - verify(keyguardDismissUtil, never()) - .executeWhenUnlocked( - any(ActivityStarter.OnDismissAction::class.java), - ArgumentMatchers.anyBoolean() - ) + verify(mediaDataManager).dismissMediaData(eq(mediaKey), anyLong()) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt new file mode 100644 index 000000000000..5d81de6bce00 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media + +import android.app.PendingIntent +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.SharedPreferences +import android.content.pm.PackageManager +import android.content.pm.ResolveInfo +import android.content.pm.ServiceInfo +import android.graphics.Color +import android.media.MediaDescription +import android.media.session.MediaSession +import android.provider.Settings +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.tuner.TunerService +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.time.FakeSystemClock +import org.junit.After +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations + +private const val KEY = "TEST_KEY" +private const val OLD_KEY = "RESUME_KEY" +private const val APP = "APP" +private const val BG_COLOR = Color.RED +private const val PACKAGE_NAME = "PKG" +private const val CLASS_NAME = "CLASS" +private const val ARTIST = "ARTIST" +private const val TITLE = "TITLE" +private const val USER_ID = 0 +private const val MEDIA_PREFERENCES = "media_control_prefs" +private const val RESUME_COMPONENTS = "package1/class1:package2/class2:package3/class3" + +private fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture() +private fun <T> eq(value: T): T = Mockito.eq(value) ?: value +private fun <T> any(): T = Mockito.any<T>() + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class MediaResumeListenerTest : SysuiTestCase() { + + @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher + @Mock private lateinit var mediaDataManager: MediaDataManager + @Mock private lateinit var device: MediaDeviceData + @Mock private lateinit var token: MediaSession.Token + @Mock private lateinit var tunerService: TunerService + @Mock private lateinit var resumeBrowserFactory: ResumeMediaBrowserFactory + @Mock private lateinit var resumeBrowser: ResumeMediaBrowser + @Mock private lateinit var sharedPrefs: SharedPreferences + @Mock private lateinit var sharedPrefsEditor: SharedPreferences.Editor + @Mock private lateinit var mockContext: Context + @Mock private lateinit var pendingIntent: PendingIntent + + @Captor lateinit var callbackCaptor: ArgumentCaptor<ResumeMediaBrowser.Callback> + + private lateinit var executor: FakeExecutor + private lateinit var data: MediaData + private lateinit var resumeListener: MediaResumeListener + + private var originalQsSetting = Settings.Global.getInt(context.contentResolver, + Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1) + private var originalResumeSetting = Settings.Secure.getInt(context.contentResolver, + Settings.Secure.MEDIA_CONTROLS_RESUME, 0) + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + + Settings.Global.putInt(context.contentResolver, + Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1) + Settings.Secure.putInt(context.contentResolver, + Settings.Secure.MEDIA_CONTROLS_RESUME, 1) + + whenever(resumeBrowserFactory.create(capture(callbackCaptor), any())) + .thenReturn(resumeBrowser) + + // resume components are stored in sharedpreferences + whenever(mockContext.getSharedPreferences(eq(MEDIA_PREFERENCES), anyInt())) + .thenReturn(sharedPrefs) + whenever(sharedPrefs.getString(any(), any())).thenReturn(RESUME_COMPONENTS) + whenever(sharedPrefs.edit()).thenReturn(sharedPrefsEditor) + whenever(sharedPrefsEditor.putString(any(), any())).thenReturn(sharedPrefsEditor) + whenever(mockContext.packageManager).thenReturn(context.packageManager) + whenever(mockContext.contentResolver).thenReturn(context.contentResolver) + + executor = FakeExecutor(FakeSystemClock()) + resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor, + tunerService, resumeBrowserFactory) + resumeListener.setManager(mediaDataManager) + mediaDataManager.addListener(resumeListener) + + data = MediaData( + userId = USER_ID, + initialized = true, + backgroundColor = BG_COLOR, + app = APP, + appIcon = null, + artist = ARTIST, + song = TITLE, + artwork = null, + actions = emptyList(), + actionsToShowInCompact = emptyList(), + packageName = PACKAGE_NAME, + token = token, + clickIntent = null, + device = device, + active = true, + notificationKey = KEY, + resumeAction = null) + } + + @After + fun tearDown() { + Settings.Global.putInt(context.contentResolver, + Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, originalQsSetting) + Settings.Secure.putInt(context.contentResolver, + Settings.Secure.MEDIA_CONTROLS_RESUME, originalResumeSetting) + } + + @Test + fun testWhenNoResumption_doesNothing() { + Settings.Secure.putInt(context.contentResolver, + Settings.Secure.MEDIA_CONTROLS_RESUME, 0) + + // When listener is created, we do NOT register a user change listener + val listener = MediaResumeListener(context, broadcastDispatcher, executor, tunerService, + resumeBrowserFactory) + listener.setManager(mediaDataManager) + verify(broadcastDispatcher, never()).registerReceiver(eq(listener.userChangeReceiver), + any(), any(), any()) + + // When data is loaded, we do NOT execute or update anything + listener.onMediaDataLoaded(KEY, OLD_KEY, data) + assertThat(executor.numPending()).isEqualTo(0) + verify(mediaDataManager, never()).setResumeAction(any(), any()) + } + + @Test + fun testOnLoad_checksForResume_noService() { + // When media data is loaded that has not been checked yet, and does not have a MBS + resumeListener.onMediaDataLoaded(KEY, null, data) + + // Then we report back to the manager + verify(mediaDataManager).setResumeAction(KEY, null) + } + + @Test + fun testOnLoad_checksForResume_hasService() { + // Set up mocks to successfully find a MBS that returns valid media + val pm = mock(PackageManager::class.java) + whenever(mockContext.packageManager).thenReturn(pm) + val resolveInfo = ResolveInfo() + val serviceInfo = ServiceInfo() + serviceInfo.packageName = PACKAGE_NAME + resolveInfo.serviceInfo = serviceInfo + resolveInfo.serviceInfo.name = CLASS_NAME + val resumeInfo = listOf(resolveInfo) + whenever(pm.queryIntentServices(any(), anyInt())).thenReturn(resumeInfo) + + val description = MediaDescription.Builder().setTitle(TITLE).build() + val component = ComponentName(PACKAGE_NAME, CLASS_NAME) + whenever(resumeBrowser.testConnection()).thenAnswer { + callbackCaptor.value.addTrack(description, component, resumeBrowser) + } + + // When media data is loaded that has not been checked yet, and does have a MBS + val dataCopy = data.copy(resumeAction = null, hasCheckedForResume = false) + resumeListener.onMediaDataLoaded(KEY, null, dataCopy) + + // Then we test whether the service is valid + executor.runAllReady() + verify(resumeBrowser).testConnection() + + // And since it is, we report back to the manager + verify(mediaDataManager).setResumeAction(eq(KEY), any()) + + // But we do not tell it to add new controls + verify(mediaDataManager, never()) + .addResumptionControls(anyInt(), any(), any(), any(), any(), any(), any()) + + // Finally, make sure the resume browser disconnected + verify(resumeBrowser).disconnect() + } + + @Test + fun testOnLoad_doesNotCheckAgain() { + // When a media data is loaded that has been checked already + var dataCopy = data.copy(hasCheckedForResume = true) + resumeListener.onMediaDataLoaded(KEY, null, dataCopy) + + // Then we should not check it again + verify(resumeBrowser, never()).testConnection() + verify(mediaDataManager, never()).setResumeAction(KEY, null) + } + + @Test + fun testOnUserUnlock_loadsTracks() { + // Set up mock service to successfully find valid media + val description = MediaDescription.Builder().setTitle(TITLE).build() + val component = ComponentName(PACKAGE_NAME, CLASS_NAME) + whenever(resumeBrowser.token).thenReturn(token) + whenever(resumeBrowser.appIntent).thenReturn(pendingIntent) + whenever(resumeBrowser.findRecentMedia()).thenAnswer { + callbackCaptor.value.addTrack(description, component, resumeBrowser) + } + + // Make sure broadcast receiver is registered + resumeListener.setManager(mediaDataManager) + verify(broadcastDispatcher).registerReceiver(eq(resumeListener.userChangeReceiver), + any(), any(), any()) + + // When we get an unlock event + val intent = Intent(Intent.ACTION_USER_UNLOCKED) + resumeListener.userChangeReceiver.onReceive(context, intent) + + // Then we should attempt to find recent media for each saved component + verify(resumeBrowser, times(3)).findRecentMedia() + + // Then since the mock service found media, the manager should be informed + verify(mediaDataManager, times(3)).addResumptionControls(anyInt(), + any(), any(), any(), any(), any(), eq(PACKAGE_NAME)) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt index 7a8e4f7e9b85..f3979592c894 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt @@ -23,8 +23,9 @@ import android.media.session.PlaybackState import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.capture +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Rule @@ -33,7 +34,6 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyBoolean -import org.mockito.ArgumentMatchers.anyLong import org.mockito.ArgumentMatchers.anyString import org.mockito.Captor import org.mockito.Mock @@ -63,10 +63,8 @@ class MediaTimeoutListenerTest : SysuiTestCase() { @Mock private lateinit var mediaControllerFactory: MediaControllerFactory @Mock private lateinit var mediaController: MediaController - @Mock private lateinit var executor: DelayableExecutor + private lateinit var executor: FakeExecutor @Mock private lateinit var timeoutCallback: (String, Boolean) -> Unit - @Mock private lateinit var cancellationRunnable: Runnable - @Captor private lateinit var timeoutCaptor: ArgumentCaptor<Runnable> @Captor private lateinit var mediaCallbackCaptor: ArgumentCaptor<MediaController.Callback> @JvmField @Rule val mockito = MockitoJUnit.rule() private lateinit var metadataBuilder: MediaMetadata.Builder @@ -78,7 +76,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { @Before fun setup() { `when`(mediaControllerFactory.create(any())).thenReturn(mediaController) - `when`(executor.executeDelayed(any(), anyLong())).thenReturn(cancellationRunnable) + executor = FakeExecutor(FakeSystemClock()) mediaTimeoutListener = MediaTimeoutListener(mediaControllerFactory, executor) mediaTimeoutListener.timeoutCallback = timeoutCallback @@ -120,7 +118,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { fun testOnMediaDataLoaded_registersTimeout_whenPaused() { mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) verify(mediaController).registerCallback(capture(mediaCallbackCaptor)) - verify(executor).executeDelayed(capture(timeoutCaptor), anyLong()) + assertThat(executor.numPending()).isEqualTo(1) verify(timeoutCallback, never()).invoke(anyString(), anyBoolean()) } @@ -137,6 +135,17 @@ class MediaTimeoutListenerTest : SysuiTestCase() { } @Test + fun testOnMediaDataRemoved_clearsTimeout() { + // GIVEN media that is paused + mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) + assertThat(executor.numPending()).isEqualTo(1) + // WHEN the media is removed + mediaTimeoutListener.onMediaDataRemoved(KEY) + // THEN the timeout runnable is cancelled + assertThat(executor.numPending()).isEqualTo(0) + } + + @Test fun testOnMediaDataLoaded_migratesKeys() { // From not playing mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) @@ -151,7 +160,24 @@ class MediaTimeoutListenerTest : SysuiTestCase() { verify(mediaController).registerCallback(anyObject()) // Enqueues callback - verify(executor).execute(anyObject()) + assertThat(executor.numPending()).isEqualTo(1) + } + + @Test + fun testOnMediaDataLoaded_migratesKeys_noTimeoutExtension() { + // From not playing + mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) + clearInvocations(mediaController) + + // Migrate, still not playing + val playingState = mock(android.media.session.PlaybackState::class.java) + `when`(playingState.state).thenReturn(PlaybackState.STATE_PAUSED) + `when`(mediaController.playbackState).thenReturn(playingState) + mediaTimeoutListener.onMediaDataLoaded("NEWKEY", KEY, mediaData) + + // The number of queued timeout tasks remains the same. The timeout task isn't cancelled nor + // is another scheduled + assertThat(executor.numPending()).isEqualTo(1) } @Test @@ -161,7 +187,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder() .setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()) - verify(executor).executeDelayed(capture(timeoutCaptor), anyLong()) + assertThat(executor.numPending()).isEqualTo(1) } @Test @@ -171,7 +197,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder() .setState(PlaybackState.STATE_PLAYING, 0L, 0f).build()) - verify(cancellationRunnable).run() + assertThat(executor.numPending()).isEqualTo(0) } @Test @@ -179,10 +205,9 @@ class MediaTimeoutListenerTest : SysuiTestCase() { // Assuming we have a pending timeout testOnPlaybackStateChanged_schedulesTimeout_whenPaused() - clearInvocations(cancellationRunnable) mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder() .setState(PlaybackState.STATE_STOPPED, 0L, 0f).build()) - verify(cancellationRunnable, never()).run() + assertThat(executor.numPending()).isEqualTo(1) } @Test @@ -190,7 +215,10 @@ class MediaTimeoutListenerTest : SysuiTestCase() { // Assuming we're have a pending timeout testOnPlaybackStateChanged_schedulesTimeout_whenPaused() - timeoutCaptor.value.run() + with(executor) { + advanceClockToNext() + runAllReady() + } verify(timeoutCallback).invoke(eq(KEY), eq(true)) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt new file mode 100644 index 000000000000..d26229edf71a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.media + +import android.content.ComponentName +import android.content.Context +import android.media.MediaDescription +import android.media.browse.MediaBrowser +import android.media.session.MediaController +import android.media.session.MediaSession +import android.service.media.MediaBrowserService +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import org.mockito.Mockito.`when` as whenever + +private const val PACKAGE_NAME = "package" +private const val CLASS_NAME = "class" +private const val TITLE = "song title" +private const val MEDIA_ID = "media ID" +private const val ROOT = "media browser root" + +private fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture() +private fun <T> eq(value: T): T = Mockito.eq(value) ?: value +private fun <T> any(): T = Mockito.any<T>() + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +public class ResumeMediaBrowserTest : SysuiTestCase() { + + private lateinit var resumeBrowser: TestableResumeMediaBrowser + private val component = ComponentName(PACKAGE_NAME, CLASS_NAME) + private val description = MediaDescription.Builder() + .setTitle(TITLE) + .setMediaId(MEDIA_ID) + .build() + + @Mock lateinit var callback: ResumeMediaBrowser.Callback + @Mock lateinit var listener: MediaResumeListener + @Mock lateinit var service: MediaBrowserService + @Mock lateinit var browserFactory: MediaBrowserFactory + @Mock lateinit var browser: MediaBrowser + @Mock lateinit var token: MediaSession.Token + @Mock lateinit var mediaController: MediaController + @Mock lateinit var transportControls: MediaController.TransportControls + + @Captor lateinit var connectionCallback: ArgumentCaptor<MediaBrowser.ConnectionCallback> + @Captor lateinit var subscriptionCallback: ArgumentCaptor<MediaBrowser.SubscriptionCallback> + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + whenever(browserFactory.create(any(), capture(connectionCallback), any())) + .thenReturn(browser) + + whenever(mediaController.transportControls).thenReturn(transportControls) + + resumeBrowser = TestableResumeMediaBrowser(context, callback, component, browserFactory, + mediaController) + } + + @Test + fun testConnection_connectionFails_callsOnError() { + // When testConnection cannot connect to the service + setupBrowserFailed() + resumeBrowser.testConnection() + + // Then it calls onError + verify(callback).onError() + } + + @Test + fun testConnection_connects_onConnected() { + // When testConnection can connect to the service + setupBrowserConnection() + resumeBrowser.testConnection() + + // Then it calls onConnected + verify(callback).onConnected() + } + + @Test + fun testConnection_noValidMedia_error() { + // When testConnection can connect to the service, and does not find valid media + setupBrowserConnectionNoResults() + resumeBrowser.testConnection() + + // Then it calls onError + verify(callback).onError() + } + + @Test + fun testConnection_hasValidMedia_addTrack() { + // When testConnection can connect to the service, and finds valid media + setupBrowserConnectionValidMedia() + resumeBrowser.testConnection() + + // Then it calls addTrack + verify(callback).onConnected() + verify(callback).addTrack(eq(description), eq(component), eq(resumeBrowser)) + } + + @Test + fun testFindRecentMedia_connectionFails_error() { + // When findRecentMedia is called and we cannot connect + setupBrowserFailed() + resumeBrowser.findRecentMedia() + + // Then it calls onError + verify(callback).onError() + } + + @Test + fun testFindRecentMedia_noRoot_error() { + // When findRecentMedia is called and does not get a valid root + setupBrowserConnection() + whenever(browser.getRoot()).thenReturn(null) + resumeBrowser.findRecentMedia() + + // Then it calls onError + verify(callback).onError() + } + + @Test + fun testFindRecentMedia_connects_onConnected() { + // When findRecentMedia is called and we connect + setupBrowserConnection() + resumeBrowser.findRecentMedia() + + // Then it calls onConnected + verify(callback).onConnected() + } + + @Test + fun testFindRecentMedia_noChildren_error() { + // When findRecentMedia is called and we connect, but do not get any results + setupBrowserConnectionNoResults() + resumeBrowser.findRecentMedia() + + // Then it calls onError + verify(callback).onError() + } + + @Test + fun testFindRecentMedia_notPlayable_error() { + // When findRecentMedia is called and we connect, but do not get a playable child + setupBrowserConnectionNotPlayable() + resumeBrowser.findRecentMedia() + + // Then it calls onError + verify(callback).onError() + } + + @Test + fun testFindRecentMedia_hasValidMedia_addTrack() { + // When findRecentMedia is called and we can connect and get playable media + setupBrowserConnectionValidMedia() + resumeBrowser.findRecentMedia() + + // Then it calls addTrack + verify(callback).addTrack(eq(description), eq(component), eq(resumeBrowser)) + } + + @Test + fun testRestart_connectionFails_error() { + // When restart is called and we cannot connect + setupBrowserFailed() + resumeBrowser.restart() + + // Then it calls onError + verify(callback).onError() + } + + @Test + fun testRestart_connects() { + // When restart is called and we connect successfully + setupBrowserConnection() + resumeBrowser.restart() + + // Then it creates a new controller and sends play command + verify(transportControls).prepare() + verify(transportControls).play() + + // Then it calls onConnected + verify(callback).onConnected() + } + + /** + * Helper function to mock a failed connection + */ + private fun setupBrowserFailed() { + whenever(browser.connect()).thenAnswer { + connectionCallback.value.onConnectionFailed() + } + } + + /** + * Helper function to mock a successful connection only + */ + private fun setupBrowserConnection() { + whenever(browser.connect()).thenAnswer { + connectionCallback.value.onConnected() + } + whenever(browser.isConnected()).thenReturn(true) + whenever(browser.getRoot()).thenReturn(ROOT) + whenever(browser.sessionToken).thenReturn(token) + } + + /** + * Helper function to mock a successful connection, but no media results + */ + private fun setupBrowserConnectionNoResults() { + setupBrowserConnection() + whenever(browser.subscribe(any(), capture(subscriptionCallback))).thenAnswer { + subscriptionCallback.value.onChildrenLoaded(ROOT, emptyList()) + } + } + + /** + * Helper function to mock a successful connection, but no playable results + */ + private fun setupBrowserConnectionNotPlayable() { + setupBrowserConnection() + + val child = MediaBrowser.MediaItem(description, 0) + + whenever(browser.subscribe(any(), capture(subscriptionCallback))).thenAnswer { + subscriptionCallback.value.onChildrenLoaded(ROOT, listOf(child)) + } + } + + /** + * Helper function to mock a successful connection with playable media + */ + private fun setupBrowserConnectionValidMedia() { + setupBrowserConnection() + + val child = MediaBrowser.MediaItem(description, MediaBrowser.MediaItem.FLAG_PLAYABLE) + + whenever(browser.serviceComponent).thenReturn(component) + whenever(browser.subscribe(any(), capture(subscriptionCallback))).thenAnswer { + subscriptionCallback.value.onChildrenLoaded(ROOT, listOf(child)) + } + } + + /** + * Override so media controller use is testable + */ + private class TestableResumeMediaBrowser( + context: Context, + callback: Callback, + componentName: ComponentName, + browserFactory: MediaBrowserFactory, + private val fakeController: MediaController + ) : ResumeMediaBrowser(context, callback, componentName, browserFactory) { + + override fun createMediaController(token: MediaSession.Token): MediaController { + return fakeController + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java new file mode 100644 index 000000000000..a5dead0f3258 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java @@ -0,0 +1,265 @@ +/* + * 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.qs.customize; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.fail; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.os.Bundle; +import android.testing.AndroidTestingRunner; +import android.view.View; +import android.view.accessibility.AccessibilityNodeInfo; + +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; +import androidx.test.filters.SmallTest; + +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class TileAdapterDelegateTest extends SysuiTestCase { + + private static final int MOVE_TO_POSITION_ID = R.id.accessibility_action_qs_move_to_position; + private static final int ADD_TO_POSITION_ID = R.id.accessibility_action_qs_add_to_position; + private static final int POSITION_STRING_ID = R.string.accessibility_qs_edit_position; + + @Mock + private TileAdapter.Holder mHolder; + + private AccessibilityNodeInfoCompat mInfo; + private TileAdapterDelegate mDelegate; + private View mView; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mView = new View(mContext); + mDelegate = new TileAdapterDelegate(); + mInfo = AccessibilityNodeInfoCompat.obtain(); + } + + @Test + public void testInfoNoSpecialActionsWhenNoHolder() { + mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo); + for (AccessibilityNodeInfoCompat.AccessibilityActionCompat action : mInfo.getActionList()) { + if (action.getId() == MOVE_TO_POSITION_ID || action.getId() == ADD_TO_POSITION_ID + || action.getId() == AccessibilityNodeInfo.ACTION_CLICK) { + fail("It should not have special action " + action.getId()); + } + } + } + + @Test + public void testInfoNoSpecialActionsWhenCannotStartAccessibleAction() { + mView.setTag(mHolder); + when(mHolder.canTakeAccessibleAction()).thenReturn(false); + mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo); + for (AccessibilityNodeInfoCompat.AccessibilityActionCompat action : mInfo.getActionList()) { + if (action.getId() == MOVE_TO_POSITION_ID || action.getId() == ADD_TO_POSITION_ID + || action.getId() == AccessibilityNodeInfo.ACTION_CLICK) { + fail("It should not have special action " + action.getId()); + } + } + } + + @Test + public void testNoCollectionItemInfo() { + mInfo.setCollectionItemInfo( + AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(0, 1, 0, 1, false)); + + mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo); + assertThat(mInfo.getCollectionItemInfo()).isNull(); + } + + @Test + public void testStateDescriptionHasPositionForCurrentTile() { + mView.setTag(mHolder); + int position = 3; + when(mHolder.getLayoutPosition()).thenReturn(position); + when(mHolder.isCurrentTile()).thenReturn(true); + when(mHolder.canTakeAccessibleAction()).thenReturn(true); + + String expectedString = mContext.getString(POSITION_STRING_ID, position); + + mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo); + assertThat(mInfo.getStateDescription()).isEqualTo(expectedString); + } + + @Test + public void testStateDescriptionEmptyForNotCurrentTile() { + mView.setTag(mHolder); + int position = 3; + when(mHolder.getLayoutPosition()).thenReturn(position); + when(mHolder.isCurrentTile()).thenReturn(false); + when(mHolder.canTakeAccessibleAction()).thenReturn(true); + + mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo); + assertThat(mInfo.getStateDescription()).isEqualTo(""); + } + + @Test + public void testClickAddAction() { + mView.setTag(mHolder); + when(mHolder.canTakeAccessibleAction()).thenReturn(true); + when(mHolder.canAdd()).thenReturn(true); + when(mHolder.canRemove()).thenReturn(false); + + mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo); + + String expectedString = mContext.getString(R.string.accessibility_qs_edit_tile_add_action); + AccessibilityNodeInfoCompat.AccessibilityActionCompat action = + getActionForId(mInfo, AccessibilityNodeInfo.ACTION_CLICK); + assertThat(action.getLabel().toString()).contains(expectedString); + } + + @Test + public void testClickRemoveAction() { + mView.setTag(mHolder); + when(mHolder.canTakeAccessibleAction()).thenReturn(true); + when(mHolder.canAdd()).thenReturn(false); + when(mHolder.canRemove()).thenReturn(true); + + mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo); + + String expectedString = mContext.getString( + R.string.accessibility_qs_edit_remove_tile_action); + AccessibilityNodeInfoCompat.AccessibilityActionCompat action = + getActionForId(mInfo, AccessibilityNodeInfo.ACTION_CLICK); + assertThat(action.getLabel().toString()).contains(expectedString); + } + + @Test + public void testNoClickAction() { + mView.setTag(mHolder); + when(mHolder.canTakeAccessibleAction()).thenReturn(true); + when(mHolder.canAdd()).thenReturn(false); + when(mHolder.canRemove()).thenReturn(false); + mInfo.addAction(AccessibilityNodeInfo.ACTION_CLICK); + + mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo); + + AccessibilityNodeInfoCompat.AccessibilityActionCompat action = + getActionForId(mInfo, AccessibilityNodeInfo.ACTION_CLICK); + assertThat(action).isNull(); + } + + @Test + public void testAddToPositionAction() { + mView.setTag(mHolder); + when(mHolder.canTakeAccessibleAction()).thenReturn(true); + when(mHolder.canAdd()).thenReturn(true); + + mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo); + assertThat(getActionForId(mInfo, ADD_TO_POSITION_ID)).isNotNull(); + } + + @Test + public void testNoAddToPositionAction() { + mView.setTag(mHolder); + when(mHolder.canTakeAccessibleAction()).thenReturn(true); + when(mHolder.canAdd()).thenReturn(false); + + mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo); + assertThat(getActionForId(mInfo, ADD_TO_POSITION_ID)).isNull(); + } + + @Test + public void testMoveToPositionAction() { + mView.setTag(mHolder); + when(mHolder.canTakeAccessibleAction()).thenReturn(true); + when(mHolder.isCurrentTile()).thenReturn(true); + + mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo); + assertThat(getActionForId(mInfo, MOVE_TO_POSITION_ID)).isNotNull(); + } + + @Test + public void testNoMoveToPositionAction() { + mView.setTag(mHolder); + when(mHolder.canTakeAccessibleAction()).thenReturn(true); + when(mHolder.isCurrentTile()).thenReturn(false); + + mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo); + assertThat(getActionForId(mInfo, MOVE_TO_POSITION_ID)).isNull(); + } + + @Test + public void testNoInteractionsWhenCannotTakeAccessibleAction() { + mView.setTag(mHolder); + when(mHolder.canTakeAccessibleAction()).thenReturn(false); + + mDelegate.performAccessibilityAction(mView, AccessibilityNodeInfo.ACTION_CLICK, null); + mDelegate.performAccessibilityAction(mView, MOVE_TO_POSITION_ID, new Bundle()); + mDelegate.performAccessibilityAction(mView, ADD_TO_POSITION_ID, new Bundle()); + + verify(mHolder, never()).toggleState(); + verify(mHolder, never()).startAccessibleAdd(); + verify(mHolder, never()).startAccessibleMove(); + } + + @Test + public void testClickActionTogglesState() { + mView.setTag(mHolder); + when(mHolder.canTakeAccessibleAction()).thenReturn(true); + + mDelegate.performAccessibilityAction(mView, AccessibilityNodeInfo.ACTION_CLICK, null); + + verify(mHolder).toggleState(); + } + + @Test + public void testAddToPositionActionStartsAccessibleAdd() { + mView.setTag(mHolder); + when(mHolder.canTakeAccessibleAction()).thenReturn(true); + + mDelegate.performAccessibilityAction(mView, ADD_TO_POSITION_ID, null); + + verify(mHolder).startAccessibleAdd(); + } + + @Test + public void testMoveToPositionActionStartsAccessibleMove() { + mView.setTag(mHolder); + when(mHolder.canTakeAccessibleAction()).thenReturn(true); + + mDelegate.performAccessibilityAction(mView, MOVE_TO_POSITION_ID, null); + + verify(mHolder).startAccessibleMove(); + } + + private AccessibilityNodeInfoCompat.AccessibilityActionCompat getActionForId( + AccessibilityNodeInfoCompat info, int action) { + for (AccessibilityNodeInfoCompat.AccessibilityActionCompat a : info.getActionList()) { + if (a.getId() == action) { + return a; + } + } + return null; + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java index 402a99d4c23d..dee6020dfd56 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java @@ -108,11 +108,7 @@ public class AlertingNotificationManagerTest extends SysuiTestCase { return new TestableAlertingNotificationManager(); } - protected StatusBarNotification createNewNotification(int id) { - Notification.Builder n = new Notification.Builder(mContext, "") - .setSmallIcon(R.drawable.ic_person) - .setContentTitle("Title") - .setContentText("Text"); + protected StatusBarNotification createNewSbn(int id, Notification.Builder n) { return new StatusBarNotification( TEST_PACKAGE_NAME /* pkg */, TEST_PACKAGE_NAME, @@ -126,6 +122,14 @@ public class AlertingNotificationManagerTest extends SysuiTestCase { 0 /* postTime */); } + protected StatusBarNotification createNewNotification(int id) { + Notification.Builder n = new Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .setContentTitle("Title") + .setContentText("Text"); + return createNewSbn(id, n); + } + @Before public void setUp() { mTestHandler = Handler.createAsync(Looper.myLooper()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java index fc7d0ce30687..0e4b053833b9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java @@ -16,12 +16,15 @@ package com.android.systemui.statusbar.policy; +import static com.google.common.truth.Truth.assertThat; + import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doReturn; +import android.app.Notification; import android.content.Context; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -30,6 +33,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.statusbar.AlertingNotificationManager; import com.android.systemui.statusbar.AlertingNotificationManagerTest; +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import org.junit.Before; import org.junit.Test; @@ -84,5 +88,25 @@ public class HeadsUpManagerTest extends AlertingNotificationManagerTest { assertTrue("Heads up should live long enough", mLivesPastNormalTime); assertFalse(mHeadsUpManager.isAlerting(mEntry.getKey())); } + + @Test + public void testAlertEntryCompareTo_ongoingCallLessThanActiveRemoteInput() { + HeadsUpManager.HeadsUpEntry ongoingCall = mHeadsUpManager.new HeadsUpEntry(); + ongoingCall.setEntry(new NotificationEntryBuilder() + .setSbn(createNewSbn(0, + new Notification.Builder(mContext, "") + .setCategory(Notification.CATEGORY_CALL) + .setOngoing(true))) + .build()); + + HeadsUpManager.HeadsUpEntry activeRemoteInput = mHeadsUpManager.new HeadsUpEntry(); + activeRemoteInput.setEntry(new NotificationEntryBuilder() + .setSbn(createNewNotification(1)) + .build()); + activeRemoteInput.remoteInputActive = true; + + assertThat(ongoingCall.compareTo(activeRemoteInput)).isLessThan(0); + assertThat(activeRemoteInput.compareTo(ongoingCall)).isGreaterThan(0); + } } diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java index 829fca66ec0d..9fc8f0b5a3c3 100644 --- a/services/core/java/com/android/server/RescueParty.java +++ b/services/core/java/com/android/server/RescueParty.java @@ -454,10 +454,14 @@ public class RescueParty { public boolean mayObservePackage(String packageName) { PackageManager pm = mContext.getPackageManager(); try { - // A package is a Mainline module if this is non-null + // A package is a module if this is non-null if (pm.getModuleInfo(packageName, 0) != null) { return true; } + } catch (PackageManager.NameNotFoundException ignore) { + } + + try { ApplicationInfo info = pm.getApplicationInfo(packageName, 0); return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK; } catch (PackageManager.NameNotFoundException e) { diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java index b5aea75d78a5..daae1a11059c 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -134,6 +134,7 @@ final class UiModeManagerService extends SystemService { int mCurUiMode = 0; private int mSetUiMode = 0; private boolean mHoldingConfiguration = false; + private int mCurrentUser; private Configuration mConfiguration = new Configuration(); boolean mSystemReady; @@ -323,6 +324,7 @@ final class UiModeManagerService extends SystemService { @Override public void onSwitchUser(int userHandle) { super.onSwitchUser(userHandle); + mCurrentUser = userHandle; getContext().getContentResolver().unregisterContentObserver(mSetupWizardObserver); verifySetupWizardCompleted(); } @@ -728,16 +730,30 @@ final class UiModeManagerService extends SystemService { @Override public boolean setNightModeActivated(boolean active) { + if (isNightModeLocked() && (getContext().checkCallingOrSelfPermission( + android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) + != PackageManager.PERMISSION_GRANTED)) { + Slog.e(TAG, "Night mode locked, requires MODIFY_DAY_NIGHT_MODE permission"); + return false; + } + final int user = Binder.getCallingUserHandle().getIdentifier(); + if (user != mCurrentUser && getContext().checkCallingOrSelfPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS) + != PackageManager.PERMISSION_GRANTED) { + Slog.e(TAG, "Target user is not current user," + + " INTERACT_ACROSS_USERS permission is required"); + return false; + + } synchronized (mLock) { - final int user = UserHandle.getCallingUserId(); final long ident = Binder.clearCallingIdentity(); try { if (mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM) { unregisterScreenOffEventLocked(); mOverrideNightModeOff = !active; mOverrideNightModeOn = active; - mOverrideNightModeUser = user; - persistNightModeOverrides(user); + mOverrideNightModeUser = mCurrentUser; + persistNightModeOverrides(mCurrentUser); } else if (mNightMode == UiModeManager.MODE_NIGHT_NO && active) { mNightMode = UiModeManager.MODE_NIGHT_YES; @@ -747,7 +763,7 @@ final class UiModeManagerService extends SystemService { } updateConfigurationLocked(); applyConfigurationExternallyLocked(); - persistNightMode(user); + persistNightMode(mCurrentUser); return true; } finally { Binder.restoreCallingIdentity(ident); diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index e1bb4cda8dc2..dd0e1f6458f9 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -453,16 +453,16 @@ public final class ActiveServices { } ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType, - int callingPid, int callingUid, boolean fgRequired, boolean hideFgNotification, - String callingPackage, @Nullable String callingFeatureId, final int userId) + int callingPid, int callingUid, boolean fgRequired, String callingPackage, + @Nullable String callingFeatureId, final int userId) throws TransactionTooLargeException { return startServiceLocked(caller, service, resolvedType, callingPid, callingUid, fgRequired, - hideFgNotification, callingPackage, callingFeatureId, userId, false); + callingPackage, callingFeatureId, userId, false); } ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType, - int callingPid, int callingUid, boolean fgRequired, boolean hideFgNotification, - String callingPackage, @Nullable String callingFeatureId, final int userId, + int callingPid, int callingUid, boolean fgRequired, String callingPackage, + @Nullable String callingFeatureId, final int userId, boolean allowBackgroundActivityStarts) throws TransactionTooLargeException { if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "startService: " + service + " type=" + resolvedType + " args=" + service.getExtras()); @@ -609,7 +609,6 @@ public final class ActiveServices { r.startRequested = true; r.delayedStop = false; r.fgRequired = fgRequired; - r.hideFgNotification = hideFgNotification; r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(), service, neededGrants, callingUid)); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 491579a5a294..3b8518004309 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1438,10 +1438,6 @@ public class ActivityManagerService extends IActivityManager.Stub final Injector mInjector; - /** The package verifier app. */ - private String mPackageVerifier; - private int mPackageVerifierUid = UserHandle.USER_NULL; - static final class ProcessChangeItem { static final int CHANGE_ACTIVITIES = 1<<0; static final int CHANGE_FOREGROUND_SERVICES = 1<<1; @@ -2350,18 +2346,6 @@ public class ActivityManagerService extends IActivityManager.Stub if (phase == PHASE_SYSTEM_SERVICES_READY) { mService.mBatteryStatsService.systemServicesReady(); mService.mServices.systemServicesReady(); - mService.mPackageVerifier = ArrayUtils.firstOrNull( - LocalServices.getService(PackageManagerInternal.class).getKnownPackageNames( - PackageManagerInternal.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM)); - if (mService.mPackageVerifier != null) { - try { - mService.mPackageVerifierUid = - getContext().getPackageManager().getPackageUid( - mService.mPackageVerifier, UserHandle.USER_SYSTEM); - } catch (NameNotFoundException e) { - Slog.wtf(TAG, "Package manager couldn't get package verifier uid", e); - } - } } else if (phase == PHASE_ACTIVITY_MANAGER_READY) { mService.startBroadcastObservers(); } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { @@ -10530,7 +10514,7 @@ public class ActivityManagerService extends IActivityManager.Stub private void dumpEverything(FileDescriptor fd, PrintWriter pw, String[] args, int opti, boolean dumpAll, String dumpPackage, boolean dumpClient, boolean dumpNormalPriority, - int dumpAppId) { + int dumpAppId, boolean dumpProxies) { ActiveServices.ServiceDumper sdumper; @@ -10589,7 +10573,7 @@ public class ActivityManagerService extends IActivityManager.Stub } sdumper.dumpWithClient(); } - if (dumpPackage == null) { + if (dumpPackage == null && dumpProxies) { // Intentionally dropping the lock for this, because dumpBinderProxies() will make many // outgoing binder calls to retrieve interface descriptors; while that is system code, // there is nothing preventing an app from overriding this implementation by talking to @@ -10998,13 +10982,14 @@ public class ActivityManagerService extends IActivityManager.Stub // dumpEverything() will take the lock when needed, and momentarily drop // it for dumping client state. dumpEverything(fd, pw, args, opti, dumpAll, dumpPackage, dumpClient, - dumpNormalPriority, dumpAppId); + dumpNormalPriority, dumpAppId, true /* dumpProxies */); } else { // Take the lock here, so we get a consistent state for the entire dump; - // dumpEverything() will take the lock as well, but that is fine. + // dumpEverything() will take the lock as well, which is fine for everything + // except dumping proxies, which can take a long time; exclude them. synchronized(this) { dumpEverything(fd, pw, args, opti, dumpAll, dumpPackage, dumpClient, - dumpNormalPriority, dumpAppId); + dumpNormalPriority, dumpAppId, false /* dumpProxies */); } } } @@ -15007,8 +14992,8 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public ComponentName startService(IApplicationThread caller, Intent service, - String resolvedType, boolean requireForeground, boolean hideForegroundNotification, - String callingPackage, String callingFeatureId, int userId) + String resolvedType, boolean requireForeground, String callingPackage, + String callingFeatureId, int userId) throws TransactionTooLargeException { enforceNotIsolatedCaller("startService"); // Refuse possible leaked file descriptors @@ -15020,27 +15005,17 @@ public class ActivityManagerService extends IActivityManager.Stub throw new IllegalArgumentException("callingPackage cannot be null"); } - final int callingUid = Binder.getCallingUid(); - if (requireForeground && hideForegroundNotification) { - if (!UserHandle.isSameApp(callingUid, mPackageVerifierUid) - || !callingPackage.equals(mPackageVerifier)) { - throw new IllegalArgumentException( - "Only the package verifier can hide its foreground service notification"); - } - Slog.i(TAG, "Foreground service notification hiding requested by " + callingPackage); - } - if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "*** startService: " + service + " type=" + resolvedType + " fg=" + requireForeground); synchronized(this) { final int callingPid = Binder.getCallingPid(); + final int callingUid = Binder.getCallingUid(); final long origId = Binder.clearCallingIdentity(); ComponentName res; try { res = mServices.startServiceLocked(caller, service, resolvedType, callingPid, callingUid, - requireForeground, hideForegroundNotification, - callingPackage, callingFeatureId, userId); + requireForeground, callingPackage, callingFeatureId, userId); } finally { Binder.restoreCallingIdentity(origId); } @@ -19480,7 +19455,7 @@ public class ActivityManagerService extends IActivityManager.Stub ComponentName res; try { res = mServices.startServiceLocked(null, service, - resolvedType, -1, uid, fgRequired, false, callingPackage, + resolvedType, -1, uid, fgRequired, callingPackage, callingFeatureId, userId, allowBackgroundActivityStarts); } finally { Binder.restoreCallingIdentity(origId); diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index a512cca7bac4..149e3baa90e7 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -654,7 +654,7 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println("Starting service: " + intent); pw.flush(); ComponentName cn = mInterface.startService(null, intent, intent.getType(), - asForeground, false, SHELL_PACKAGE_NAME, null, mUserId); + asForeground, SHELL_PACKAGE_NAME, null, mUserId); if (cn == null) { err.println("Error: Not found; no service started."); return -1; diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index 19d5a3125f3a..1b65dbac2294 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -104,7 +104,6 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN boolean whitelistManager; // any bindings to this service have BIND_ALLOW_WHITELIST_MANAGEMENT? boolean delayed; // are we waiting to start this service in the background? boolean fgRequired; // is the service required to go foreground after starting? - boolean hideFgNotification; // Hide the fg service notification boolean fgWaiting; // is a timeout for going foreground already scheduled? boolean isForeground; // is service currently in foreground mode? int foregroundId; // Notification ID of last foreground req. @@ -824,9 +823,6 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN } public void postNotification() { - if (hideFgNotification) { - return; - } final int appUid = appInfo.uid; final int appPid = app.pid; if (foregroundId != 0 && foregroundNoti != null) { diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 75c27603d785..7bb3e36d959e 100755 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -6490,14 +6490,17 @@ public class AudioService extends IAudioService.Stub if (msg.obj == null) { break; } - // If the app corresponding to this mode death handler object is not - // capturing or playing audio anymore after 3 seconds, remove it - // from the stack. Otherwise, check again in 3 seconds. + // If no other app is currently owning the audio mode and + // the app corresponding to this mode death handler object is still in the + // mode owner stack but not capturing or playing audio after 3 seconds, + // remove it from the stack. + // Otherwise, check again in 3 seconds. SetModeDeathHandler h = (SetModeDeathHandler) msg.obj; if (mSetModeDeathHandlers.indexOf(h) < 0) { break; } - if (mRecordMonitor.isRecordingActiveForUid(h.getUid()) + if (getModeOwnerUid() != h.getUid() + || mRecordMonitor.isRecordingActiveForUid(h.getUid()) || mPlaybackMonitor.isPlaybackActiveForUid(h.getUid())) { sendMsg(mAudioHandler, MSG_CHECK_MODE_FOR_UID, diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 588b7af5b902..7cce78bb661d 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -71,7 +71,6 @@ import android.inputmethodservice.InputMethodService; import android.media.AudioManagerInternal; import android.net.Uri; import android.os.Binder; -import android.os.Build; import android.os.Bundle; import android.os.Debug; import android.os.Handler; @@ -1701,7 +1700,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub Intent intent = new Intent(ACTION_SHOW_INPUT_METHOD_PICKER) .setPackage(mContext.getPackageName()); - mImeSwitchPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); + mImeSwitchPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, + PendingIntent.FLAG_IMMUTABLE); mShowOngoingImeSwitcherForPhones = false; @@ -2530,7 +2530,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL, com.android.internal.R.string.input_method_binding_label); mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity( - mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0)); + mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), + PendingIntent.FLAG_IMMUTABLE)); if (bindCurrentInputMethodServiceLocked(mCurIntent, this, IME_CONNECTION_BIND_FLAGS)) { mLastBindTime = SystemClock.uptimeMillis(); @@ -3274,9 +3275,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub boolean hideCurrentInputLocked(IBinder windowToken, int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { - if (mCurClient == null || mCurClient.curSession == null) { - return false; - } if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0 && (mShowExplicitlyRequested || mShowForced)) { if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide"); @@ -3461,9 +3459,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // pre-rendering not supported on low-ram devices. cs.shouldPreRenderIme = DebugFlags.FLAG_PRE_RENDER_IME_VIEWS.value() && !mIsLowRam; - final boolean sameWindowFocused = mCurFocusedWindow == windowToken; - final boolean isTextEditor = (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0; - if (sameWindowFocused && isTextEditor) { + if (mCurFocusedWindow == windowToken) { if (DEBUG) { Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client + " attribute=" + attribute + ", token = " + windowToken @@ -3478,7 +3474,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY, null, null, null, -1, null); } - mCurFocusedWindow = windowToken; mCurFocusedWindowSoftInputMode = softInputMode; mCurFocusedWindowClient = cs; @@ -3496,6 +3491,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub == LayoutParams.SOFT_INPUT_ADJUST_RESIZE || mRes.getConfiguration().isLayoutSizeAtLeast( Configuration.SCREENLAYOUT_SIZE_LARGE); + final boolean isTextEditor = (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0; // We want to start input before showing the IME, but after closing // it. We want to do this after closing it to help the IME disappear @@ -3555,11 +3551,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } break; case LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN: - if (!sameWindowFocused) { - if (DEBUG) Slog.v(TAG, "Window asks to hide input"); - hideCurrentInputLocked(mCurFocusedWindow, 0, null, - SoftInputShowHideReason.HIDE_ALWAYS_HIDDEN_STATE); - } + if (DEBUG) Slog.v(TAG, "Window asks to hide input"); + hideCurrentInputLocked(mCurFocusedWindow, 0, null, + SoftInputShowHideReason.HIDE_ALWAYS_HIDDEN_STATE); break; case LayoutParams.SOFT_INPUT_STATE_VISIBLE: if ((softInputMode & LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { @@ -3584,15 +3578,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (DEBUG) Slog.v(TAG, "Window asks to always show input"); if (InputMethodUtils.isSoftInputModeStateVisibleAllowed( unverifiedTargetSdkVersion, startInputFlags)) { - if (!sameWindowFocused) { - if (attribute != null) { - res = startInputUncheckedLocked(cs, inputContext, missingMethods, - attribute, startInputFlags, startInputReason); - didStart = true; - } - showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null, - SoftInputShowHideReason.SHOW_STATE_ALWAYS_VISIBLE); + if (attribute != null) { + res = startInputUncheckedLocked(cs, inputContext, missingMethods, + attribute, startInputFlags, startInputReason); + didStart = true; } + showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null, + SoftInputShowHideReason.SHOW_STATE_ALWAYS_VISIBLE); } else { Slog.e(TAG, "SOFT_INPUT_STATE_ALWAYS_VISIBLE is ignored because" + " there is no focused view that also returns true from" @@ -3603,13 +3595,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (!didStart) { if (attribute != null) { - if (sameWindowFocused) { - hideCurrentInputLocked(mCurFocusedWindow, 0, null, - SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR); - res = new InputBindResult( - InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY, - null, null, null, -1, null); - } else if (!DebugFlags.FLAG_OPTIMIZE_START_INPUT.value() + if (!DebugFlags.FLAG_OPTIMIZE_START_INPUT.value() || (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0) { res = startInputUncheckedLocked(cs, inputContext, missingMethods, attribute, startInputFlags, startInputReason); @@ -3623,10 +3609,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return res; } - private boolean isImeVisible() { - return (mImeWindowVis & InputMethodService.IME_VISIBLE) != 0; - } - private boolean canShowInputMethodPickerLocked(IInputMethodClient client) { // TODO(yukawa): multi-display support. final int uid = Binder.getCallingUid(); diff --git a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java index 9dae1b44117b..6e655eafa0e9 100644 --- a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java +++ b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java @@ -259,7 +259,7 @@ final class MediaButtonReceiverHolder { return ""; } return String.join(COMPONENT_NAME_USER_ID_DELIM, - mComponentName.toString(), + mComponentName.flattenToString(), String.valueOf(mUserId), String.valueOf(mComponentType)); } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index d6557f6410ec..b3eb53116d49 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -96,7 +96,9 @@ import static com.android.internal.util.XmlUtils.readBooleanAttribute; import static com.android.internal.util.XmlUtils.readIntAttribute; import static com.android.internal.util.XmlUtils.readLongAttribute; import static com.android.internal.util.XmlUtils.readStringAttribute; +import static com.android.internal.util.XmlUtils.readThisIntArrayXml; import static com.android.internal.util.XmlUtils.writeBooleanAttribute; +import static com.android.internal.util.XmlUtils.writeIntArrayXml; import static com.android.internal.util.XmlUtils.writeIntAttribute; import static com.android.internal.util.XmlUtils.writeLongAttribute; import static com.android.internal.util.XmlUtils.writeStringAttribute; @@ -229,6 +231,7 @@ import com.android.internal.util.DumpUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.StatLogger; +import com.android.internal.util.XmlUtils; import com.android.server.EventLogTags; import com.android.server.LocalServices; import com.android.server.ServiceThread; @@ -239,6 +242,7 @@ import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener; import libcore.io.IoUtils; import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import java.io.File; @@ -313,7 +317,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private static final int VERSION_ADDED_NETWORK_ID = 9; private static final int VERSION_SWITCH_UID = 10; private static final int VERSION_ADDED_CYCLE = 11; - private static final int VERSION_LATEST = VERSION_ADDED_CYCLE; + private static final int VERSION_ADDED_NETWORK_TYPES = 12; + private static final int VERSION_LATEST = VERSION_ADDED_NETWORK_TYPES; @VisibleForTesting public static final int TYPE_WARNING = SystemMessage.NOTE_NET_WARNING; @@ -332,6 +337,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private static final String TAG_WHITELIST = "whitelist"; private static final String TAG_RESTRICT_BACKGROUND = "restrict-background"; private static final String TAG_REVOKED_RESTRICT_BACKGROUND = "revoked-restrict-background"; + private static final String TAG_XML_UTILS_INT_ARRAY = "int-array"; private static final String ATTR_VERSION = "version"; private static final String ATTR_RESTRICT_BACKGROUND = "restrictBackground"; @@ -360,6 +366,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private static final String ATTR_USAGE_BYTES = "usageBytes"; private static final String ATTR_USAGE_TIME = "usageTime"; private static final String ATTR_OWNER_PACKAGE = "ownerPackage"; + private static final String ATTR_NETWORK_TYPES = "networkTypes"; + private static final String ATTR_XML_UTILS_NAME = "name"; private static final String ACTION_ALLOW_BACKGROUND = "com.android.server.net.action.ALLOW_BACKGROUND"; @@ -2311,13 +2319,25 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } final int subId = readIntAttribute(in, ATTR_SUB_ID); + final String ownerPackage = readStringAttribute(in, ATTR_OWNER_PACKAGE); + + if (version >= VERSION_ADDED_NETWORK_TYPES) { + final int depth = in.getDepth(); + while (XmlUtils.nextElementWithin(in, depth)) { + if (TAG_XML_UTILS_INT_ARRAY.equals(in.getName()) + && ATTR_NETWORK_TYPES.equals( + readStringAttribute(in, ATTR_XML_UTILS_NAME))) { + final int[] networkTypes = + readThisIntArrayXml(in, TAG_XML_UTILS_INT_ARRAY, null); + builder.setNetworkTypes(networkTypes); + } + } + } + final SubscriptionPlan plan = builder.build(); mSubscriptionPlans.put(subId, ArrayUtils.appendElement( SubscriptionPlan.class, mSubscriptionPlans.get(subId), plan)); - - final String ownerPackage = readStringAttribute(in, ATTR_OWNER_PACKAGE); mSubscriptionPlansOwner.put(subId, ownerPackage); - } else if (TAG_UID_POLICY.equals(tag)) { final int uid = readIntAttribute(in, ATTR_UID); final int policy = readIntAttribute(in, ATTR_POLICY); @@ -2513,6 +2533,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { writeIntAttribute(out, ATTR_LIMIT_BEHAVIOR, plan.getDataLimitBehavior()); writeLongAttribute(out, ATTR_USAGE_BYTES, plan.getDataUsageBytes()); writeLongAttribute(out, ATTR_USAGE_TIME, plan.getDataUsageTime()); + try { + writeIntArrayXml(plan.getNetworkTypes(), ATTR_NETWORK_TYPES, out); + } catch (XmlPullParserException ignored) { } out.endTag(null, TAG_SUBSCRIPTION_PLAN); } } @@ -3310,7 +3333,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // let in core system components (like the Settings app). final String ownerPackage = mSubscriptionPlansOwner.get(subId); if (Objects.equals(ownerPackage, callingPackage) - || (UserHandle.getCallingAppId() == android.os.Process.SYSTEM_UID)) { + || (UserHandle.getCallingAppId() == android.os.Process.SYSTEM_UID) + || (UserHandle.getCallingAppId() == android.os.Process.PHONE_UID)) { return mSubscriptionPlans.get(subId); } else { Log.w(TAG, "Not returning plans because caller " + callingPackage diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index daca0b34ec2a..6246b28a3dd6 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -280,6 +280,7 @@ import android.os.storage.StorageManagerInternal; import android.os.storage.VolumeInfo; import android.os.storage.VolumeRecord; import android.permission.IPermissionManager; +import android.provider.ContactsContract; import android.provider.DeviceConfig; import android.provider.Settings.Global; import android.provider.Settings.Secure; @@ -25426,6 +25427,32 @@ public class PackageManagerService extends IPackageManager.Stub } } + @Override + public void grantImplicitAccess(int recipientUid, String visibleAuthority) { + // This API is exposed temporarily to only the contacts provider. (b/158688602) + final int callingUid = Binder.getCallingUid(); + ProviderInfo contactsProvider = resolveContentProviderInternal( + ContactsContract.AUTHORITY, 0, UserHandle.USER_SYSTEM); + if (contactsProvider == null || contactsProvider.applicationInfo == null + || !UserHandle.isSameApp(contactsProvider.applicationInfo.uid, callingUid)) { + throw new SecurityException(callingUid + " is not allow to call grantImplicitAccess"); + } + final int userId = UserHandle.getUserId(recipientUid); + final long token = Binder.clearCallingIdentity(); + final ProviderInfo providerInfo; + try { + providerInfo = resolveContentProvider(visibleAuthority, 0 /*flags*/, userId); + } finally { + Binder.restoreCallingIdentity(token); + } + if (providerInfo == null) { + return; + } + int visibleUid = providerInfo.applicationInfo.uid; + mPmInternal.grantImplicitAccess(userId, null /*Intent*/, UserHandle.getAppId(recipientUid), + visibleUid, false); + } + boolean canHaveOatDir(String packageName) { synchronized (mLock) { AndroidPackage p = mPackages.get(packageName); diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 3faf3be564c7..3c42e930389b 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -69,6 +69,7 @@ import static android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION; import static android.view.WindowManagerGlobal.ADD_OKAY; import static android.view.WindowManagerGlobal.ADD_PERMISSION_DENIED; +import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY; import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED; import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT; import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_UNCOVERED; @@ -148,6 +149,7 @@ import android.os.UEventObserver; import android.os.UserHandle; import android.os.VibrationEffect; import android.os.Vibrator; +import android.provider.DeviceConfig; import android.provider.MediaStore; import android.provider.Settings; import android.service.dreams.DreamManagerInternal; @@ -1379,12 +1381,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { } private long getScreenshotChordLongPressDelay() { + long delayMs = DeviceConfig.getLong( + DeviceConfig.NAMESPACE_SYSTEMUI, SCREENSHOT_KEYCHORD_DELAY, + ViewConfiguration.get(mContext).getScreenshotChordKeyTimeout()); if (mKeyguardDelegate.isShowing()) { // Double the time it takes to take a screenshot from the keyguard - return (long) (KEYGUARD_SCREENSHOT_CHORD_DELAY_MULTIPLIER * - ViewConfiguration.get(mContext).getScreenshotChordKeyTimeout()); + return (long) (KEYGUARD_SCREENSHOT_CHORD_DELAY_MULTIPLIER * delayMs); } - return ViewConfiguration.get(mContext).getScreenshotChordKeyTimeout(); + return delayMs; } private long getRingerToggleChordDelay() { diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index 959aedcd1e4a..6d452c39eccc 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -1700,8 +1700,9 @@ class ActivityStack extends Task { // If the most recent activity was noHistory but was only stopped rather // than stopped+finished because the device went to sleep, we need to make // sure to finish it as we're making a new activity topmost. - if (shouldSleepActivities() && mLastNoHistoryActivity != null && - !mLastNoHistoryActivity.finishing) { + if (shouldSleepActivities() && mLastNoHistoryActivity != null + && !mLastNoHistoryActivity.finishing + && mLastNoHistoryActivity != next) { if (DEBUG_STATES) Slog.d(TAG_STATES, "no-history finish of " + mLastNoHistoryActivity + " on new resume"); mLastNoHistoryActivity.finishIfPossible("resume-no-history", false /* oomAdj */); diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index 3dd82a6221c6..7e6b7cd05762 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -732,6 +732,11 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { final ActivityStack stack = task.getStack(); beginDeferResume(); + // The LaunchActivityItem also contains process configuration, so the configuration change + // from WindowProcessController#setProcess can be deferred. The major reason is that if + // the activity has FixedRotationAdjustments, it needs to be applied with configuration. + // In general, this reduces a binder transaction if process configuration is changed. + proc.pauseConfigurationDispatch(); try { r.startFreezingScreenLocked(proc, 0); @@ -826,9 +831,9 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { // Because we could be starting an Activity in the system process this may not go // across a Binder interface which would create a new Configuration. Consequently // we have to always create a new Configuration here. - + final Configuration procConfig = proc.prepareConfigurationForLaunchingActivity(); final MergedConfiguration mergedConfiguration = new MergedConfiguration( - proc.getConfiguration(), r.getMergedOverrideConfiguration()); + procConfig, r.getMergedOverrideConfiguration()); r.setLastReportedConfiguration(mergedConfiguration); logIfTransactionTooLarge(r.intent, r.getSavedState()); @@ -862,6 +867,11 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { // Schedule transaction. mService.getLifecycleManager().scheduleTransaction(clientTransaction); + if (procConfig.seq > mRootWindowContainer.getConfiguration().seq) { + // If the seq is increased, there should be something changed (e.g. registered + // activity configuration). + proc.setLastReportedConfiguration(procConfig); + } if ((proc.mInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0 && mService.mHasHeavyWeightFeature) { // This may be a heavy-weight process! Note that the package manager will ensure @@ -896,6 +906,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { } } finally { endDeferResume(); + proc.resumeConfigurationDispatch(); } r.launchFailed = false; diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index a78232c05397..7af237b80cfa 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1481,9 +1481,10 @@ class ActivityStarter { // anyone interested in this piece of information. final ActivityStack homeStack = targetTask.getDisplayArea().getRootHomeTask(); final boolean homeTaskVisible = homeStack != null && homeStack.shouldBeVisible(null); + final ActivityRecord top = targetTask.getTopNonFinishingActivity(); + final boolean visible = top != null && top.isVisible(); mService.getTaskChangeNotificationController().notifyActivityRestartAttempt( - targetTask.getTaskInfo(), homeTaskVisible, clearedTask, - targetTask.getTopNonFinishingActivity().isVisible()); + targetTask.getTaskInfo(), homeTaskVisible, clearedTask, visible); } } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 4a40fc6e572c..c691a0e220ed 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -3400,9 +3400,12 @@ public class DisplayPolicy { ? Type.navigationBars() : 0) | (requestedState.getSourceOrDefaultVisibility(ITYPE_STATUS_BAR) ? Type.statusBars() : 0) - | (requestedState.getSourceOrDefaultVisibility(ITYPE_EXTRA_NAVIGATION_BAR) + | (mExtraNavBarAlt != null + && requestedState.getSourceOrDefaultVisibility( + ITYPE_EXTRA_NAVIGATION_BAR) ? Type.navigationBars() : 0) - | (requestedState.getSourceOrDefaultVisibility(ITYPE_CLIMATE_BAR) + | (mClimateBarAlt != null + && requestedState.getSourceOrDefaultVisibility(ITYPE_CLIMATE_BAR) ? Type.statusBars() : 0); if (swipeTarget == mNavigationBar diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 0216db471843..3663ee14ea99 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -219,6 +219,11 @@ final class InputMonitor { WindowManagerPolicy.InputConsumer createInputConsumer(Looper looper, String name, InputEventReceiver.Factory inputEventReceiverFactory) { + if (!name.contentEquals(INPUT_CONSUMER_NAVIGATION)) { + throw new IllegalArgumentException("Illegal input consumer : " + name + + ", display: " + mDisplayId); + } + if (mInputConsumers.containsKey(name)) { throw new IllegalStateException("Existing input consumer found with name: " + name + ", display: " + mDisplayId); @@ -248,6 +253,11 @@ final class InputMonitor { // stack, and we need FLAG_NOT_TOUCH_MODAL to ensure other events fall through consumer.mWindowHandle.layoutParamsFlags |= FLAG_NOT_TOUCH_MODAL; break; + case INPUT_CONSUMER_RECENTS_ANIMATION: + break; + default: + throw new IllegalArgumentException("Illegal input consumer : " + name + + ", display: " + mDisplayId); } addInputConsumer(name, consumer); } @@ -459,9 +469,6 @@ final class InputMonitor { mDisplayContent.forAllWindows(this, true /* traverseTopToBottom */); - if (mAddWallpaperInputConsumerHandle) { - mWallpaperInputConsumer.show(mInputTransaction, 0); - } if (!mUpdateInputWindowsImmediately) { mDisplayContent.getPendingTransaction().merge(mInputTransaction); mDisplayContent.scheduleAnimation(); diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index a65eca832f48..72cd32f8d057 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -48,6 +48,7 @@ import static com.android.server.wm.ActivityStack.ActivityState.PAUSED; import static com.android.server.wm.ActivityStack.ActivityState.RESUMED; import static com.android.server.wm.ActivityStack.ActivityState.STOPPED; import static com.android.server.wm.ActivityStack.ActivityState.STOPPING; +import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_INVISIBLE; import static com.android.server.wm.ActivityStackSupervisor.DEFER_RESUME; import static com.android.server.wm.ActivityStackSupervisor.ON_TOP; import static com.android.server.wm.ActivityStackSupervisor.dumpHistoryList; @@ -1931,24 +1932,29 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } boolean attachApplication(WindowProcessController app) throws RemoteException { - final String processName = app.mName; boolean didSomething = false; for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) { - final DisplayContent display = getChildAt(displayNdx); - final ActivityStack stack = display.getFocusedStack(); - if (stack == null) { - continue; - } - mTmpRemoteException = null; mTmpBoolean = false; // Set to true if an activity was started. - final PooledFunction c = PooledLambda.obtainFunction( - RootWindowContainer::startActivityForAttachedApplicationIfNeeded, this, - PooledLambda.__(ActivityRecord.class), app, stack.topRunningActivity()); - stack.forAllActivities(c); - c.recycle(); - if (mTmpRemoteException != null) { - throw mTmpRemoteException; + + final DisplayContent display = getChildAt(displayNdx); + for (int areaNdx = display.getTaskDisplayAreaCount() - 1; areaNdx >= 0; --areaNdx) { + final TaskDisplayArea taskDisplayArea = display.getTaskDisplayAreaAt(areaNdx); + for (int taskNdx = taskDisplayArea.getStackCount() - 1; taskNdx >= 0; --taskNdx) { + final ActivityStack rootTask = taskDisplayArea.getStackAt(taskNdx); + if (rootTask.getVisibility(null /*starting*/) == STACK_VISIBILITY_INVISIBLE) { + break; + } + final PooledFunction c = PooledLambda.obtainFunction( + RootWindowContainer::startActivityForAttachedApplicationIfNeeded, this, + PooledLambda.__(ActivityRecord.class), app, + rootTask.topRunningActivity()); + rootTask.forAllActivities(c); + c.recycle(); + if (mTmpRemoteException != null) { + throw mTmpRemoteException; + } + } } didSomething |= mTmpBoolean; } @@ -1966,8 +1972,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } try { - if (mStackSupervisor.realStartActivityLocked(r, app, top == r /*andResume*/, - true /*checkConfig*/)) { + if (mStackSupervisor.realStartActivityLocked(r, app, + top == r && r.isFocusable() /*andResume*/, true /*checkConfig*/)) { mTmpBoolean = true; } } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 3a750f2c9da3..db3c74fc94af 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3671,6 +3671,10 @@ class Task extends WindowContainer<WindowContainer> { return STACK_VISIBILITY_INVISIBLE; } + if (isTopActivityLaunchedBehind()) { + return STACK_VISIBILITY_VISIBLE; + } + boolean gotSplitScreenStack = false; boolean gotOpaqueSplitScreenPrimary = false; boolean gotOpaqueSplitScreenSecondary = false; @@ -3788,6 +3792,14 @@ class Task extends WindowContainer<WindowContainer> { : STACK_VISIBILITY_VISIBLE; } + private boolean isTopActivityLaunchedBehind() { + final ActivityRecord top = topRunningActivity(); + if (top != null && top.mLaunchTaskBehind) { + return true; + } + return false; + } + ActivityRecord isInTask(ActivityRecord r) { if (r == null) { return null; diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index f0f8c7522d6f..03834c348d4b 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -17,6 +17,7 @@ package com.android.server.wm; import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS; +import static android.Manifest.permission.INPUT_CONSUMER; import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS; import static android.Manifest.permission.MANAGE_APP_TOKENS; @@ -5874,6 +5875,11 @@ public class WindowManagerService extends IWindowManager.Stub @Override public void createInputConsumer(IBinder token, String name, int displayId, InputChannel inputChannel) { + if (!mAtmInternal.isCallerRecents(Binder.getCallingUid()) + && mContext.checkCallingOrSelfPermission(INPUT_CONSUMER) != PERMISSION_GRANTED) { + throw new SecurityException("createInputConsumer requires INPUT_CONSUMER permission"); + } + synchronized (mGlobalLock) { DisplayContent display = mRoot.getDisplayContent(displayId); if (display != null) { @@ -5885,6 +5891,11 @@ public class WindowManagerService extends IWindowManager.Stub @Override public boolean destroyInputConsumer(String name, int displayId) { + if (!mAtmInternal.isCallerRecents(Binder.getCallingUid()) + && mContext.checkCallingOrSelfPermission(INPUT_CONSUMER) != PERMISSION_GRANTED) { + throw new SecurityException("destroyInputConsumer requires INPUT_CONSUMER permission"); + } + synchronized (mGlobalLock) { DisplayContent display = mRoot.getDisplayContent(displayId); if (display != null) { diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index bd959aba5bb1..df49ac71334f 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -186,13 +186,16 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio // Last configuration that was reported to the process. private final Configuration mLastReportedConfiguration = new Configuration(); - // Configuration that is waiting to be dispatched to the process. - private Configuration mPendingConfiguration; + /** Whether the process configuration is waiting to be dispatched to the process. */ + private boolean mHasPendingConfigurationChange; // Registered display id as a listener to override config change private int mDisplayId; private ActivityRecord mConfigActivityRecord; // Whether the activity config override is allowed for this process. private volatile boolean mIsActivityConfigOverrideAllowed = true; + /** Non-zero to pause dispatching process configuration change. */ + private int mPauseConfigurationDispatchCount; + /** * Activities that hosts some UI drawn by the current process. The activities live * in another process. This is used to check if the process is currently showing anything @@ -1115,8 +1118,10 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio onMergedOverrideConfigurationChanged(Configuration.EMPTY); } - private void registerActivityConfigurationListener(ActivityRecord activityRecord) { - if (activityRecord == null || activityRecord.containsListener(this)) { + void registerActivityConfigurationListener(ActivityRecord activityRecord) { + if (activityRecord == null || activityRecord.containsListener(this) + // Check for the caller from outside of this class. + || !mIsActivityConfigOverrideAllowed) { return; } // A process can only register to one activityRecord to listen to the override configuration @@ -1168,25 +1173,25 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio @Override public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) { - super.onRequestedOverrideConfigurationChanged( - sanitizeProcessConfiguration(overrideConfiguration)); + super.onRequestedOverrideConfigurationChanged(overrideConfiguration); } @Override public void onMergedOverrideConfigurationChanged(Configuration mergedOverrideConfig) { - super.onRequestedOverrideConfigurationChanged( - sanitizeProcessConfiguration(mergedOverrideConfig)); + super.onRequestedOverrideConfigurationChanged(mergedOverrideConfig); } - private static Configuration sanitizeProcessConfiguration(Configuration config) { + @Override + void resolveOverrideConfiguration(Configuration newParentConfig) { + super.resolveOverrideConfiguration(newParentConfig); + final Configuration resolvedConfig = getResolvedOverrideConfiguration(); // Make sure that we don't accidentally override the activity type. - if (config.windowConfiguration.getActivityType() != ACTIVITY_TYPE_UNDEFINED) { - final Configuration sanitizedConfig = new Configuration(config); - sanitizedConfig.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED); - return sanitizedConfig; - } - - return config; + resolvedConfig.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED); + // Activity has an independent ActivityRecord#mConfigurationSeq. If this process registers + // activity configuration, its config seq shouldn't go backwards by activity configuration. + // Otherwise if other places send wpc.getConfiguration() to client, the configuration may + // be ignored due to the seq is older. + resolvedConfig.seq = newParentConfig.seq; } private void updateConfiguration() { @@ -1204,11 +1209,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio if (mListener.isCached()) { // This process is in a cached state. We will delay delivering the config change to the // process until the process is no longer cached. - if (mPendingConfiguration == null) { - mPendingConfiguration = new Configuration(config); - } else { - mPendingConfiguration.setTo(config); - } + mHasPendingConfigurationChange = true; return; } @@ -1216,6 +1217,11 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio } private void dispatchConfigurationChange(Configuration config) { + if (mPauseConfigurationDispatchCount > 0) { + mHasPendingConfigurationChange = true; + return; + } + mHasPendingConfigurationChange = false; if (mThread == null) { if (Build.IS_DEBUGGABLE && mHasImeService) { // TODO (b/135719017): Temporary log for debugging IME service. @@ -1242,7 +1248,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio } } - private void setLastReportedConfiguration(Configuration config) { + void setLastReportedConfiguration(Configuration config) { mLastReportedConfiguration.setTo(config); } @@ -1250,6 +1256,30 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio return mLastReportedConfiguration; } + void pauseConfigurationDispatch() { + mPauseConfigurationDispatchCount++; + } + + void resumeConfigurationDispatch() { + mPauseConfigurationDispatchCount--; + } + + /** + * This is called for sending {@link android.app.servertransaction.LaunchActivityItem}. + * The caller must call {@link #setLastReportedConfiguration} if the delivered configuration + * is newer. + */ + Configuration prepareConfigurationForLaunchingActivity() { + final Configuration config = getConfiguration(); + if (mHasPendingConfigurationChange) { + mHasPendingConfigurationChange = false; + // The global configuration may not change, so the client process may have the same + // config seq. This increment ensures that the client won't ignore the configuration. + config.seq = mAtm.increaseConfigurationSeqLocked(); + } + return config; + } + /** Returns the total time (in milliseconds) spent executing in both user and system code. */ public long getCpuTime() { return (mListener != null) ? mListener.getCpuTime() : 0; @@ -1341,10 +1371,8 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio public void onProcCachedStateChanged(boolean isCached) { if (!isCached) { synchronized (mAtm.mGlobalLockWithoutBoost) { - if (mPendingConfiguration != null) { - final Configuration config = mPendingConfiguration; - mPendingConfiguration = null; - dispatchConfigurationChange(config); + if (mHasPendingConfigurationChange) { + dispatchConfigurationChange(getConfiguration()); } } } diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 1716dcd5ee16..d86f6c998baa 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -172,6 +172,12 @@ class WindowToken extends WindowContainer<WindowState> { } } } + + /** The state may not only be used by self. Make sure to leave the influence by others. */ + void disassociate(WindowToken token) { + mAssociatedTokens.remove(token); + mRotatedContainers.remove(token); + } } private class DeathRecipient implements IBinder.DeathRecipient { @@ -531,7 +537,7 @@ class WindowToken extends WindowContainer<WindowState> { void applyFixedRotationTransform(DisplayInfo info, DisplayFrames displayFrames, Configuration config) { if (mFixedRotationTransformState != null) { - cleanUpFixedRotationTransformState(true /* replacing */); + mFixedRotationTransformState.disassociate(this); } mFixedRotationTransformState = new FixedRotationTransformState(info, displayFrames, new Configuration(config), mDisplayContent.getRotation()); @@ -539,8 +545,7 @@ class WindowToken extends WindowContainer<WindowState> { mDisplayContent.getDisplayPolicy().simulateLayoutDisplay(displayFrames, mFixedRotationTransformState.mInsetsState, mFixedRotationTransformState.mBarContentFrames); - onConfigurationChanged(getParent().getConfiguration()); - notifyFixedRotationTransform(true /* enabled */); + onFixedRotationStatePrepared(); } /** @@ -553,12 +558,29 @@ class WindowToken extends WindowContainer<WindowState> { return; } if (mFixedRotationTransformState != null) { - cleanUpFixedRotationTransformState(true /* replacing */); + mFixedRotationTransformState.disassociate(this); } mFixedRotationTransformState = fixedRotationState; fixedRotationState.mAssociatedTokens.add(this); - onConfigurationChanged(getParent().getConfiguration()); + onFixedRotationStatePrepared(); + } + + /** + * Makes the rotated states take effect for this window container and its client process. + * This should only be called when {@link #mFixedRotationTransformState} is non-null. + */ + private void onFixedRotationStatePrepared() { + // Send the adjustment info first so when the client receives configuration change, it can + // get the rotated display metrics. notifyFixedRotationTransform(true /* enabled */); + // Resolve the rotated configuration. + onConfigurationChanged(getParent().getConfiguration()); + final ActivityRecord r = asActivityRecord(); + if (r != null && r.hasProcess()) { + // The application needs to be configured as in a rotated environment for compatibility. + // This registration will send the rotated configuration to its process. + r.app.registerActivityConfigurationListener(r); + } } /** @@ -609,21 +631,12 @@ class WindowToken extends WindowContainer<WindowState> { // The state is cleared at the end, because it is used to indicate that other windows can // use seamless rotation when applying rotation to display. for (int i = state.mAssociatedTokens.size() - 1; i >= 0; i--) { - state.mAssociatedTokens.get(i).cleanUpFixedRotationTransformState( - false /* replacing */); + final WindowToken token = state.mAssociatedTokens.get(i); + token.mFixedRotationTransformState = null; + token.notifyFixedRotationTransform(false /* enabled */); } } - private void cleanUpFixedRotationTransformState(boolean replacing) { - if (replacing && mFixedRotationTransformState.mAssociatedTokens.size() > 1) { - // The state is not only used by self. Make sure to leave the influence by others. - mFixedRotationTransformState.mAssociatedTokens.remove(this); - mFixedRotationTransformState.mRotatedContainers.remove(this); - } - mFixedRotationTransformState = null; - notifyFixedRotationTransform(false /* enabled */); - } - /** Notifies application side to enable or disable the rotation adjustment of display info. */ private void notifyFixedRotationTransform(boolean enabled) { FixedRotationAdjustments adjustments = null; @@ -687,8 +700,9 @@ class WindowToken extends WindowContainer<WindowState> { if (!isFixedRotationTransforming()) { return null; } - return new FixedRotationAdjustments(mFixedRotationTransformState.mDisplayInfo.rotation, - mFixedRotationTransformState.mDisplayInfo.displayCutout); + final DisplayInfo displayInfo = mFixedRotationTransformState.mDisplayInfo; + return new FixedRotationAdjustments(displayInfo.rotation, displayInfo.appWidth, + displayInfo.appHeight, displayInfo.displayCutout); } @Override diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp index a5f0d045948c..e355a20907d8 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -306,6 +306,7 @@ IncrementalService::~IncrementalService() { } mJobCondition.notify_all(); mJobProcessor.join(); + mLooper->wake(); mCmdLooperThread.join(); mTimedQueue->stop(); // Ensure that mounts are destroyed while the service is still valid. @@ -1378,7 +1379,7 @@ bool IncrementalService::mountExistingImage(std::string_view root) { } void IncrementalService::runCmdLooper() { - constexpr auto kTimeoutMsecs = 1000; + constexpr auto kTimeoutMsecs = -1; while (mRunning.load(std::memory_order_relaxed)) { mLooper->pollAll(kTimeoutMsecs); } diff --git a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java index 236ac8407faa..9e6cf845d144 100644 --- a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java +++ b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java @@ -86,8 +86,8 @@ class ShareTargetPredictor extends AppTargetPredictor { return; } List<ShareTarget> shareTargets = getDirectShareTargets(); - SharesheetModelScorer.computeScore(shareTargets, getShareEventType(mIntentFilter), - System.currentTimeMillis()); + SharesheetModelScorer.computeScoreForDirectShare(shareTargets, + getShareEventType(mIntentFilter), System.currentTimeMillis()); Collections.sort(shareTargets, Comparator.comparing(ShareTarget::getScore, reverseOrder()) .thenComparing(t -> t.getAppTarget().getRank())); diff --git a/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java b/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java index c77843cfb044..d4a502da56c7 100644 --- a/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java +++ b/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.usage.UsageEvents; +import android.provider.DeviceConfig; import android.util.ArrayMap; import android.util.Pair; import android.util.Range; @@ -27,12 +28,14 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.ChooserActivity; +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.server.people.data.AppUsageStatsData; import com.android.server.people.data.DataManager; import com.android.server.people.data.Event; import java.time.Duration; import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; @@ -46,6 +49,7 @@ class SharesheetModelScorer { private static final String TAG = "SharesheetModelScorer"; private static final boolean DEBUG = false; private static final Integer RECENCY_SCORE_COUNT = 6; + private static final Integer NATIVE_RANK_COUNT = 2; private static final float RECENCY_INITIAL_BASE_SCORE = 0.4F; private static final float RECENCY_SCORE_INITIAL_DECAY = 0.05F; private static final float RECENCY_SCORE_SUBSEQUENT_DECAY = 0.02F; @@ -174,6 +178,77 @@ class SharesheetModelScorer { postProcess(shareTargets, targetsLimit, dataManager, callingUserId); } + /** + * Computes ranking score for direct sharing. Update + * {@link ShareTargetPredictor.ShareTargetScore}. + */ + static void computeScoreForDirectShare(List<ShareTargetPredictor.ShareTarget> shareTargets, + int shareEventType, long now) { + computeScore(shareTargets, shareEventType, now); + promoteTopNativeRankedShortcuts(shareTargets); + } + + /** + * Promotes top (NATIVE_RANK_COUNT) shortcuts for each package and class, as per shortcut native + * ranking provided by apps. + */ + private static void promoteTopNativeRankedShortcuts( + List<ShareTargetPredictor.ShareTarget> shareTargets) { + float topShortcutBonus = DeviceConfig.getFloat( + DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.TOP_NATIVE_RANKED_SHARING_SHORTCUTS_BOOSTER, + 0f); + float secondTopShortcutBonus = DeviceConfig.getFloat( + DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.NON_TOP_NATIVE_RANKED_SHARING_SHORTCUTS_BOOSTER, + 0f); + // Populates a map which key is a packageName and className pair, value is a max heap + // containing top (NATIVE_RANK_COUNT) shortcuts as per shortcut native ranking provided + // by apps. + Map<Pair<String, String>, PriorityQueue<ShareTargetPredictor.ShareTarget>> + topNativeRankedShareTargetMap = new ArrayMap<>(); + for (ShareTargetPredictor.ShareTarget shareTarget : shareTargets) { + Pair<String, String> key = new Pair<>(shareTarget.getAppTarget().getPackageName(), + shareTarget.getAppTarget().getClassName()); + if (!topNativeRankedShareTargetMap.containsKey(key)) { + topNativeRankedShareTargetMap.put(key, + new PriorityQueue<>(NATIVE_RANK_COUNT, + Collections.reverseOrder(Comparator.comparingInt( + p -> p.getAppTarget().getRank())))); + } + PriorityQueue<ShareTargetPredictor.ShareTarget> rankMaxHeap = + topNativeRankedShareTargetMap.get(key); + if (rankMaxHeap.isEmpty() || shareTarget.getAppTarget().getRank() + < rankMaxHeap.peek().getAppTarget().getRank()) { + if (rankMaxHeap.size() == NATIVE_RANK_COUNT) { + rankMaxHeap.poll(); + } + rankMaxHeap.offer(shareTarget); + } + } + for (PriorityQueue<ShareTargetPredictor.ShareTarget> maxHeap : + topNativeRankedShareTargetMap.values()) { + while (!maxHeap.isEmpty()) { + ShareTargetPredictor.ShareTarget target = maxHeap.poll(); + float bonus = maxHeap.isEmpty() ? topShortcutBonus : secondTopShortcutBonus; + target.setScore(probOR(target.getScore(), bonus)); + + if (DEBUG) { + Slog.d(TAG, String.format( + "SharesheetModel: promote top shortcut as per native ranking," + + "packageName: %s, className: %s, shortcutId: %s, bonus:%.2f," + + "total:%.2f", + target.getAppTarget().getPackageName(), + target.getAppTarget().getClassName(), + target.getAppTarget().getShortcutInfo() != null + ? target.getAppTarget().getShortcutInfo().getId() : null, + bonus, + target.getScore())); + } + } + } + } + private static void postProcess(List<ShareTargetPredictor.ShareTarget> shareTargets, int targetsLimit, @NonNull DataManager dataManager, @UserIdInt int callingUserId) { // Populates a map which key is package name and value is list of shareTargets descended diff --git a/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java b/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java index 45fff48ade55..605878d85bce 100644 --- a/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java +++ b/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anySet; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -28,9 +29,13 @@ import static org.mockito.Mockito.when; import android.app.prediction.AppTarget; import android.app.prediction.AppTargetId; import android.app.usage.UsageEvents; +import android.content.Context; +import android.content.pm.ShortcutInfo; import android.os.UserHandle; +import android.provider.DeviceConfig; import android.util.Range; +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.server.people.data.AppUsageStatsData; import com.android.server.people.data.DataManager; import com.android.server.people.data.Event; @@ -121,6 +126,13 @@ public final class SharesheetModelScorerTest { private ShareTargetPredictor.ShareTarget mShareTarget5; private ShareTargetPredictor.ShareTarget mShareTarget6; + private ShareTargetPredictor.ShareTarget mShareShortcutTarget1; + private ShareTargetPredictor.ShareTarget mShareShortcutTarget2; + private ShareTargetPredictor.ShareTarget mShareShortcutTarget3; + private ShareTargetPredictor.ShareTarget mShareShortcutTarget4; + private ShareTargetPredictor.ShareTarget mShareShortcutTarget5; + private ShareTargetPredictor.ShareTarget mShareShortcutTarget6; + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -154,6 +166,46 @@ public final class SharesheetModelScorerTest { new AppTargetId("cls2#pkg3"), PACKAGE_3, UserHandle.of(USER_ID)) .setClassName(CLASS_2).build(), null, null); + + mShareShortcutTarget1 = new ShareTargetPredictor.ShareTarget( + new AppTarget.Builder( + new AppTargetId("cls1#pkg1#1"), buildShortcutInfo(PACKAGE_1, 0, "1")) + .setClassName(CLASS_1).setRank(2).build(), + mEventHistory1, null); + mShareShortcutTarget2 = new ShareTargetPredictor.ShareTarget( + new AppTarget.Builder( + new AppTargetId("cls1#pkg1#2"), buildShortcutInfo(PACKAGE_1, 0, "2")) + .setClassName(CLASS_1).setRank(1).build(), + mEventHistory2, null); + mShareShortcutTarget3 = new ShareTargetPredictor.ShareTarget( + new AppTarget.Builder( + new AppTargetId("cls1#pkg1#3"), buildShortcutInfo(PACKAGE_1, 0, "3")) + .setClassName(CLASS_1).setRank(0).build(), + mEventHistory3, null); + mShareShortcutTarget4 = new ShareTargetPredictor.ShareTarget( + new AppTarget.Builder( + new AppTargetId("cls1#pkg2#1"), buildShortcutInfo(PACKAGE_2, 0, "1")) + .setClassName(CLASS_1).setRank(2).build(), + mEventHistory4, null); + mShareShortcutTarget5 = new ShareTargetPredictor.ShareTarget( + new AppTarget.Builder( + new AppTargetId("cls1#pkg2#2"), buildShortcutInfo(PACKAGE_2, 0, "2")) + .setClassName(CLASS_1).setRank(1).build(), + mEventHistory5, null); + mShareShortcutTarget6 = new ShareTargetPredictor.ShareTarget( + new AppTarget.Builder( + new AppTargetId("cls1#pkg2#3"), buildShortcutInfo(PACKAGE_2, 0, "3")) + .setClassName(CLASS_1).setRank(3).build(), + null, null); + + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.TOP_NATIVE_RANKED_SHARING_SHORTCUTS_BOOSTER, + Float.toString(0f), + true /* makeDefault*/); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.NON_TOP_NATIVE_RANKED_SHARING_SHORTCUTS_BOOSTER, + Float.toString(0f), + true /* makeDefault*/); } @Test @@ -433,6 +485,101 @@ public final class SharesheetModelScorerTest { assertEquals(0f, mShareTarget6.getScore(), DELTA); } + @Test + public void testComputeScoreForDirectShare() { + // Frequency and recency + when(mEventHistory1.getEventIndex(anySet())).thenReturn(mEventIndex1); + when(mEventHistory2.getEventIndex(anySet())).thenReturn(mEventIndex2); + when(mEventHistory3.getEventIndex(anySet())).thenReturn(mEventIndex3); + when(mEventHistory4.getEventIndex(anySet())).thenReturn(mEventIndex4); + when(mEventHistory5.getEventIndex(anySet())).thenReturn(mEventIndex5); + + when(mEventIndex1.getActiveTimeSlots()).thenReturn( + List.of(WITHIN_ONE_DAY, TWO_DAYS_AGO, FIVE_DAYS_AGO)); + when(mEventIndex2.getActiveTimeSlots()).thenReturn(List.of(TWO_DAYS_AGO, TWELVE_DAYS_AGO)); + when(mEventIndex3.getActiveTimeSlots()).thenReturn(List.of(FIVE_DAYS_AGO, TWENTY_DAYS_AGO)); + when(mEventIndex4.getActiveTimeSlots()).thenReturn( + List.of(EIGHT_DAYS_AGO, TWELVE_DAYS_AGO, FOUR_WEEKS_AGO)); + when(mEventIndex5.getActiveTimeSlots()).thenReturn(List.of()); + + when(mEventIndex1.getMostRecentActiveTimeSlot()).thenReturn(WITHIN_ONE_DAY); + when(mEventIndex2.getMostRecentActiveTimeSlot()).thenReturn(TWO_DAYS_AGO); + when(mEventIndex3.getMostRecentActiveTimeSlot()).thenReturn(FIVE_DAYS_AGO); + when(mEventIndex4.getMostRecentActiveTimeSlot()).thenReturn(EIGHT_DAYS_AGO); + when(mEventIndex5.getMostRecentActiveTimeSlot()).thenReturn(null); + + // Frequency of the same mime type + when(mEventHistory1.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex6); + when(mEventHistory2.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex7); + when(mEventHistory3.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex8); + when(mEventHistory4.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex9); + when(mEventHistory5.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex10); + + when(mEventIndex6.getActiveTimeSlots()).thenReturn(List.of(TWO_DAYS_AGO)); + when(mEventIndex7.getActiveTimeSlots()).thenReturn(List.of(TWO_DAYS_AGO, TWELVE_DAYS_AGO)); + when(mEventIndex8.getActiveTimeSlots()).thenReturn(List.of()); + when(mEventIndex9.getActiveTimeSlots()).thenReturn(List.of(EIGHT_DAYS_AGO)); + when(mEventIndex10.getActiveTimeSlots()).thenReturn(List.of()); + + SharesheetModelScorer.computeScore( + List.of(mShareShortcutTarget1, mShareShortcutTarget2, mShareShortcutTarget3, + mShareShortcutTarget4, mShareShortcutTarget5, mShareShortcutTarget6), + Event.TYPE_SHARE_TEXT, + NOW); + + // Verification + assertEquals(0.514f, mShareShortcutTarget1.getScore(), DELTA); + assertEquals(0.475125f, mShareShortcutTarget2.getScore(), DELTA); + assertEquals(0.33f, mShareShortcutTarget3.getScore(), DELTA); + assertEquals(0.4411f, mShareShortcutTarget4.getScore(), DELTA); + assertEquals(0f, mShareShortcutTarget5.getScore(), DELTA); + assertEquals(0f, mShareShortcutTarget6.getScore(), DELTA); + } + + @Test + public void testComputeScoreForDirectShare_promoteTopNativeRankedShortcuts() { + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.TOP_NATIVE_RANKED_SHARING_SHORTCUTS_BOOSTER, + Float.toString(0.4f), + true /* makeDefault*/); + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.NON_TOP_NATIVE_RANKED_SHARING_SHORTCUTS_BOOSTER, + Float.toString(0.3f), + true /* makeDefault*/); + + when(mEventHistory1.getEventIndex(anySet())).thenReturn(mEventIndex1); + when(mEventHistory2.getEventIndex(anySet())).thenReturn(mEventIndex2); + when(mEventHistory3.getEventIndex(anySet())).thenReturn(mEventIndex3); + when(mEventHistory4.getEventIndex(anySet())).thenReturn(mEventIndex4); + when(mEventHistory5.getEventIndex(anySet())).thenReturn(mEventIndex5); + when(mEventHistory1.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex6); + when(mEventHistory2.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex7); + when(mEventHistory3.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex8); + when(mEventHistory4.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex9); + when(mEventHistory5.getEventIndex(Event.TYPE_SHARE_TEXT)).thenReturn(mEventIndex10); + + SharesheetModelScorer.computeScoreForDirectShare( + List.of(mShareShortcutTarget1, mShareShortcutTarget2, mShareShortcutTarget3, + mShareShortcutTarget4, mShareShortcutTarget5, mShareShortcutTarget6), + Event.TYPE_SHARE_TEXT, 20); + + assertEquals(0f, mShareShortcutTarget1.getScore(), DELTA); + assertEquals(0.3f, mShareShortcutTarget2.getScore(), DELTA); + assertEquals(0.4f, mShareShortcutTarget3.getScore(), DELTA); + assertEquals(0.3f, mShareShortcutTarget4.getScore(), DELTA); + assertEquals(0.4f, mShareShortcutTarget5.getScore(), DELTA); + assertEquals(0f, mShareShortcutTarget6.getScore(), DELTA); + } + + private static ShortcutInfo buildShortcutInfo(String packageName, int userId, String id) { + Context mockContext = mock(Context.class); + when(mockContext.getPackageName()).thenReturn(packageName); + when(mockContext.getUserId()).thenReturn(userId); + when(mockContext.getUser()).thenReturn(UserHandle.of(userId)); + ShortcutInfo.Builder builder = new ShortcutInfo.Builder(mockContext, id).setShortLabel(id); + return builder.build(); + } + private static UsageEvents.Event createUsageEvent(String packageName) { UsageEvents.Event e = new UsageEvents.Event(); e.mPackage = packageName; diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index 4dec7a1a0ab9..a07e60ce838e 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -112,6 +112,8 @@ public class AppStandbyControllerTests { private static final int UID_SYSTEM_HEADFULL = 10002; private static final String PACKAGE_SYSTEM_HEADLESS = "com.example.system.headless"; private static final int UID_SYSTEM_HEADLESS = 10003; + private static final String PACKAGE_WELLBEING = "com.example.wellbeing"; + private static final int UID_WELLBEING = 10004; private static final int USER_ID = 0; private static final int USER_ID2 = 10; private static final UserHandle USER_HANDLE_USER2 = new UserHandle(USER_ID2); @@ -218,6 +220,11 @@ public class AppStandbyControllerTests { } @Override + boolean isWellbeingPackage(String packageName) { + return PACKAGE_WELLBEING.equals(packageName); + } + + @Override void updatePowerWhitelistCache() { } @@ -329,6 +336,12 @@ public class AppStandbyControllerTests { pish.packageName = PACKAGE_SYSTEM_HEADLESS; packages.add(pish); + PackageInfo piw = new PackageInfo(); + piw.applicationInfo = new ApplicationInfo(); + piw.applicationInfo.uid = UID_WELLBEING; + piw.packageName = PACKAGE_WELLBEING; + packages.add(piw); + doReturn(packages).when(mockPm).getInstalledPackagesAsUser(anyInt(), anyInt()); try { for (int i = 0; i < packages.size(); ++i) { @@ -1516,6 +1529,25 @@ public class AppStandbyControllerTests { assertBucket(STANDBY_BUCKET_RARE, PACKAGE_1); } + @Test + public void testWellbeingAppElevated() { + reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_WELLBEING); + assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_WELLBEING); + reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); + assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_1); + mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD; + + // Make sure the default wellbeing app does not get lowered below WORKING_SET. + mController.setAppStandbyBucket(PACKAGE_WELLBEING, USER_ID, STANDBY_BUCKET_RARE, + REASON_MAIN_TIMEOUT); + assertBucket(STANDBY_BUCKET_WORKING_SET, PACKAGE_WELLBEING); + + // A non default wellbeing app should be able to fall lower than WORKING_SET. + mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, + REASON_MAIN_TIMEOUT); + assertBucket(STANDBY_BUCKET_RARE, PACKAGE_1); + } + private String getAdminAppsStr(int userId) { return getAdminAppsStr(userId, mController.getActiveAdminAppsForTest(userId)); } diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java index b100c8482bf8..453830004ac3 100644 --- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java @@ -16,6 +16,7 @@ package com.android.server; +import android.Manifest; import android.app.AlarmManager; import android.app.IUiModeManager; import android.content.BroadcastReceiver; @@ -24,6 +25,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.content.pm.UserInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.os.Handler; @@ -66,6 +68,7 @@ import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -227,6 +230,15 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { } @Test + public void setNightModeActivated_permissiontoChangeOtherUsers() throws RemoteException { + mUiManagerService.onSwitchUser(9); + when(mContext.checkCallingOrSelfPermission( + eq(Manifest.permission.INTERACT_ACROSS_USERS))) + .thenReturn(PackageManager.PERMISSION_DENIED); + assertFalse(mService.setNightModeActivated(true)); + } + + @Test public void autoNightModeSwitch_batterySaverOn() throws RemoteException { mService.setNightMode(MODE_NIGHT_NO); when(mTwilightState.isNight()).thenReturn(false); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 02a3bb1f5632..a37f4be506e5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -548,7 +548,7 @@ public class ActivityRecordTests extends ActivityTestsBase { final ActivityStack stack = new StackBuilder(mRootWindowContainer).build(); try { doReturn(false).when(stack).isTranslucent(any()); - assertFalse(mStack.shouldBeVisible(null /* starting */)); + assertTrue(mStack.shouldBeVisible(null /* starting */)); mActivity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(), mActivity.getConfiguration())); 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 6a84c1390150..c7a8bd857674 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -1149,8 +1149,10 @@ public class DisplayContentTests extends WindowTestsBase { verify(t, never()).setPosition(any(), eq(0), eq(0)); // Launch another activity before the transition is finished. - final ActivityRecord app2 = new ActivityTestsBase.StackBuilder(mWm.mRoot) - .setDisplay(mDisplayContent).build().getTopMostActivity(); + final ActivityStack stack2 = new ActivityTestsBase.StackBuilder(mWm.mRoot) + .setDisplay(mDisplayContent).build(); + final ActivityRecord app2 = new ActivityTestsBase.ActivityBuilder(mWm.mAtmService) + .setStack(stack2).setUseProcess(app.app).build(); app2.setVisible(false); mDisplayContent.mOpeningApps.add(app2); app2.setRequestedOrientation(newOrientation); @@ -1160,6 +1162,12 @@ public class DisplayContentTests extends WindowTestsBase { assertTrue(app.hasFixedRotationTransform(app2)); assertTrue(mDisplayContent.isFixedRotationLaunchingApp(app2)); + final Configuration expectedProcConfig = new Configuration(app2.app.getConfiguration()); + expectedProcConfig.windowConfiguration.setActivityType( + WindowConfiguration.ACTIVITY_TYPE_UNDEFINED); + assertEquals("The process should receive rotated configuration for compatibility", + expectedProcConfig, app2.app.getConfiguration()); + // The fixed rotation transform can only be finished when all animation finished. doReturn(false).when(app2).isAnimating(anyInt(), anyInt()); mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked(app2.token); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java index 24950ce6a882..a46e6d35ee97 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java @@ -266,6 +266,15 @@ public class WindowProcessControllerTests extends ActivityTestsBase { mWpc.onMergedOverrideConfigurationChanged(config); assertEquals(ACTIVITY_TYPE_HOME, config.windowConfiguration.getActivityType()); assertEquals(ACTIVITY_TYPE_UNDEFINED, mWpc.getActivityType()); + + final int globalSeq = 100; + mRootWindowContainer.getConfiguration().seq = globalSeq; + invertOrientation(mWpc.getConfiguration()); + new ActivityBuilder(mService).setCreateTask(true).setUseProcess(mWpc).build(); + + assertTrue(mWpc.registeredForActivityConfigChanges()); + assertEquals("Config seq of process should not be affected by activity", + mWpc.getConfiguration().seq, globalSeq); } private TestDisplayContent createTestDisplayContentInContainer() { diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 8ae1ee99b060..3d51d7cf7af7 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -1888,8 +1888,8 @@ public class TelephonyManager { * * <p>Starting with API level 29, persistent device identifiers are guarded behind additional * restrictions, and apps are recommended to use resettable identifiers (see <a - * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of - * the following requirements is met: + * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This + * method can be invoked if one of the following requirements is met: * <ul> * <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this * is a privileged permission that can only be granted to apps preloaded on the device. @@ -1941,8 +1941,8 @@ public class TelephonyManager { * * <p>Starting with API level 29, persistent device identifiers are guarded behind additional * restrictions, and apps are recommended to use resettable identifiers (see <a - * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of - * the following requirements is met: + * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This + * method can be invoked if one of the following requirements is met: * <ul> * <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this * is a privileged permission that can only be granted to apps preloaded on the device. @@ -2010,8 +2010,8 @@ public class TelephonyManager { * * <p>Starting with API level 29, persistent device identifiers are guarded behind additional * restrictions, and apps are recommended to use resettable identifiers (see <a - * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of - * the following requirements is met: + * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This + * method can be invoked if one of the following requirements is met: * <ul> * <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this * is a privileged permission that can only be granted to apps preloaded on the device. @@ -2088,8 +2088,8 @@ public class TelephonyManager { * * <p>Starting with API level 29, persistent device identifiers are guarded behind additional * restrictions, and apps are recommended to use resettable identifiers (see <a - * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of - * the following requirements is met: + * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This + * method can be invoked if one of the following requirements is met: * <ul> * <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this * is a privileged permission that can only be granted to apps preloaded on the device. @@ -2126,8 +2126,8 @@ public class TelephonyManager { * * <p>Starting with API level 29, persistent device identifiers are guarded behind additional * restrictions, and apps are recommended to use resettable identifiers (see <a - * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of - * the following requirements is met: + * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This + * method can be invoked if one of the following requirements is met: * <ul> * <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this * is a privileged permission that can only be granted to apps preloaded on the device. @@ -2210,8 +2210,8 @@ public class TelephonyManager { * * <p>Starting with API level 29, persistent device identifiers are guarded behind additional * restrictions, and apps are recommended to use resettable identifiers (see <a - * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of - * the following requirements is met: + * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This + * method can be invoked if one of the following requirements is met: * <ul> * <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this * is a privileged permission that can only be granted to apps preloaded on the device. @@ -2247,8 +2247,8 @@ public class TelephonyManager { * * <p>Starting with API level 29, persistent device identifiers are guarded behind additional * restrictions, and apps are recommended to use resettable identifiers (see <a - * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of - * the following requirements is met: + * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This + * method can be invoked if one of the following requirements is met: * <ul> * <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this * is a privileged permission that can only be granted to apps preloaded on the device. @@ -3853,8 +3853,8 @@ public class TelephonyManager { * * <p>Starting with API level 29, persistent device identifiers are guarded behind additional * restrictions, and apps are recommended to use resettable identifiers (see <a - * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of - * the following requirements is met: + * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This + * method can be invoked if one of the following requirements is met: * <ul> * <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this * is a privileged permission that can only be granted to apps preloaded on the device. @@ -3891,8 +3891,8 @@ public class TelephonyManager { * * <p>Starting with API level 29, persistent device identifiers are guarded behind additional * restrictions, and apps are recommended to use resettable identifiers (see <a - * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of - * the following requirements is met: + * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This + * method can be invoked if one of the following requirements is met: * <ul> * <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this * is a privileged permission that can only be granted to apps preloaded on the device. @@ -4145,8 +4145,8 @@ public class TelephonyManager { * * <p>Starting with API level 29, persistent device identifiers are guarded behind additional * restrictions, and apps are recommended to use resettable identifiers (see <a - * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of - * the following requirements is met: + * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This + * method can be invoked if one of the following requirements is met: * <ul> * <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this * is a privileged permission that can only be granted to apps preloaded on the device. @@ -4184,8 +4184,8 @@ public class TelephonyManager { * * <p>Starting with API level 29, persistent device identifiers are guarded behind additional * restrictions, and apps are recommended to use resettable identifiers (see <a - * href="c"> Best practices for unique identifiers</a>). This method can be invoked if one of - * the following requirements is met: + * href="/training/articles/user-data-ids">Best practices for unique identifiers</a>). This + * method can be invoked if one of the following requirements is met: * <ul> * <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this * is a privileged permission that can only be granted to apps preloaded on the device. |