diff options
308 files changed, 6506 insertions, 4320 deletions
diff --git a/Android.bp b/Android.bp index e1cb037b252e..82da1e63a481 100644 --- a/Android.bp +++ b/Android.bp @@ -154,6 +154,7 @@ java_library { "framework-scheduling.stubs.module_lib", "framework-sdkextensions.stubs.module_lib", "framework-statsd.stubs.module_lib", + "framework-supplementalprocess.stubs.module_lib", "framework-tethering.stubs.module_lib", "framework-uwb.stubs.module_lib", "framework-wifi.stubs.module_lib", @@ -177,6 +178,7 @@ java_library { "framework-scheduling.impl", "framework-sdkextensions.impl", "framework-statsd.impl", + "framework-supplementalprocess.impl", "framework-tethering.impl", "framework-uwb.impl", "framework-wifi.impl", @@ -441,11 +443,8 @@ filegroup { "core/java/android/util/LocalLog.java", "core/java/com/android/internal/util/HexDump.java", "core/java/com/android/internal/util/IndentingPrintWriter.java", - "core/java/com/android/internal/util/IState.java", "core/java/com/android/internal/util/MessageUtils.java", "core/java/com/android/internal/util/RingBufferIndices.java", - "core/java/com/android/internal/util/State.java", - "core/java/com/android/internal/util/StateMachine.java", "core/java/com/android/internal/util/WakeupMessage.java", "core/java/com/android/internal/util/TokenBucket.java", ], diff --git a/ApiDocs.bp b/ApiDocs.bp index 31f7f6e014c3..7a4ef2ab325a 100644 --- a/ApiDocs.bp +++ b/ApiDocs.bp @@ -126,6 +126,7 @@ stubs_defaults { ":framework-scheduling-sources", ":framework-sdkextensions-sources", ":framework-statsd-sources", + ":framework-supplementalprocess-sources", ":framework-tethering-srcs", ":framework-uwb-updatable-sources", ":framework-wifi-updatable-sources", @@ -172,6 +173,7 @@ droidstubs { ":framework-scheduling{.public.stubs.source}", ":framework-sdkextensions{.public.stubs.source}", ":framework-statsd{.public.stubs.source}", + ":framework-supplementalprocess{.public.stubs.source}", ":framework-tethering{.public.stubs.source}", ":framework-uwb{.public.stubs.source}", ":framework-wifi{.public.stubs.source}", @@ -211,6 +213,7 @@ genrule { ":framework-scheduling{.public.annotations.zip}", ":framework-sdkextensions{.public.annotations.zip}", ":framework-statsd{.public.annotations.zip}", + ":framework-supplementalprocess{.public.annotations.zip}", ":framework-tethering{.public.annotations.zip}", ":framework-uwb{.public.annotations.zip}", ":framework-wifi{.public.annotations.zip}", diff --git a/StubLibraries.bp b/StubLibraries.bp index cc118f32f8bf..b6fd708a6cce 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -250,6 +250,7 @@ modules_public_stubs = [ "framework-scheduling.stubs", "framework-sdkextensions.stubs", "framework-statsd.stubs", + "framework-supplementalprocess.stubs", "framework-tethering.stubs", "framework-uwb.stubs", "framework-wifi.stubs", @@ -271,6 +272,7 @@ modules_system_stubs = [ "framework-scheduling.stubs.system", "framework-sdkextensions.stubs.system", "framework-statsd.stubs.system", + "framework-supplementalprocess.stubs", "framework-tethering.stubs.system", "framework-uwb.stubs.system", "framework-wifi.stubs.system", diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java index 1c40a06c1c86..42fb24f570d2 100644 --- a/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java +++ b/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java @@ -217,7 +217,7 @@ public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase } @Override - public void onTaskAppeared(RemoteAnimationTarget app) throws RemoteException { + public void onTasksAppeared(RemoteAnimationTarget[] app) throws RemoteException { /* no-op */ } }; diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index 31a5a050529b..392df732953f 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -490,8 +490,8 @@ public class AlarmManagerService extends SystemService { * holding the AlarmManagerService.mLock lock. */ @VisibleForTesting - final class Constants extends ContentObserver - implements DeviceConfig.OnPropertiesChangedListener { + final class Constants implements DeviceConfig.OnPropertiesChangedListener, + EconomyManagerInternal.TareStateChangeListener { @VisibleForTesting static final int MAX_EXACT_ALARM_DENY_LIST_SIZE = 250; @@ -695,7 +695,6 @@ public class AlarmManagerService extends SystemService { private int mVersion = 0; Constants(Handler handler) { - super(handler); updateAllowWhileIdleWhitelistDurationLocked(); for (int i = 0; i < APP_STANDBY_QUOTAS.length; i++) { APP_STANDBY_QUOTAS[i] = DEFAULT_APP_STANDBY_QUOTAS[i]; @@ -709,11 +708,12 @@ public class AlarmManagerService extends SystemService { } public void start() { - mInjector.registerContentObserver(this, - Settings.Global.getUriFor(Settings.Global.ENABLE_TARE)); mInjector.registerDeviceConfigListener(this); + final EconomyManagerInternal economyManagerInternal = + LocalServices.getService(EconomyManagerInternal.class); + economyManagerInternal.registerTareStateChangeListener(this); onPropertiesChanged(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_ALARM_MANAGER)); - updateTareSettings(); + updateTareSettings(economyManagerInternal.isEnabled()); } public void updateAllowWhileIdleWhitelistDurationLocked() { @@ -886,15 +886,12 @@ public class AlarmManagerService extends SystemService { } @Override - public void onChange(boolean selfChange) { - updateTareSettings(); + public void onTareEnabledStateChanged(boolean isTareEnabled) { + updateTareSettings(isTareEnabled); } - private void updateTareSettings() { + private void updateTareSettings(boolean isTareEnabled) { synchronized (mLock) { - final boolean isTareEnabled = Settings.Global.getInt( - getContext().getContentResolver(), - Settings.Global.ENABLE_TARE, Settings.Global.DEFAULT_ENABLE_TARE) == 1; if (USE_TARE_POLICY != isTareEnabled) { USE_TARE_POLICY = isTareEnabled; final boolean changed = mAlarmStore.updateAlarmDeliveries(alarm -> { diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index 78140dce12f4..714c90b9b5a9 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -43,7 +43,6 @@ import android.app.usage.UsageStatsManager; import android.app.usage.UsageStatsManagerInternal; import android.content.BroadcastReceiver; import android.content.ComponentName; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -54,7 +53,6 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManagerInternal; import android.content.pm.ParceledListSlice; import android.content.pm.ServiceInfo; -import android.database.ContentObserver; import android.net.Uri; import android.os.BatteryStats; import android.os.BatteryStatsInternal; @@ -118,6 +116,7 @@ import com.android.server.job.controllers.TimeController; import com.android.server.job.restrictions.JobRestriction; import com.android.server.job.restrictions.ThermalStatusRestriction; import com.android.server.pm.UserManagerInternal; +import com.android.server.tare.EconomyManagerInternal; import com.android.server.usage.AppStandbyInternal; import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener; import com.android.server.utils.quota.Categorizer; @@ -356,41 +355,22 @@ public class JobSchedulerService extends com.android.server.SystemService // (ScheduledJobStateChanged and JobStatusDumpProto). public static final int RESTRICTED_INDEX = 5; - private class ConstantsObserver extends ContentObserver - implements DeviceConfig.OnPropertiesChangedListener { - private final ContentResolver mContentResolver; - - ConstantsObserver(Handler handler, Context context) { - super(handler); - mContentResolver = context.getContentResolver(); - } - + private class ConstantsObserver implements DeviceConfig.OnPropertiesChangedListener, + EconomyManagerInternal.TareStateChangeListener { public void start() { DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_JOB_SCHEDULER, JobSchedulerBackgroundThread.getExecutor(), this); - mContentResolver.registerContentObserver( - Settings.Global.getUriFor(Settings.Global.ENABLE_TARE), false, this); + final EconomyManagerInternal economyManagerInternal = + LocalServices.getService(EconomyManagerInternal.class); + economyManagerInternal.registerTareStateChangeListener(this); // Load all the constants. synchronized (mLock) { - mConstants.updateSettingsConstantsLocked(mContentResolver); + mConstants.updateTareSettingsLocked(economyManagerInternal.isEnabled()); } onPropertiesChanged(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_JOB_SCHEDULER)); } @Override - public void onChange(boolean selfChange) { - synchronized (mLock) { - if (mConstants.updateSettingsConstantsLocked(mContentResolver)) { - for (int controller = 0; controller < mControllers.size(); controller++) { - final StateController sc = mControllers.get(controller); - sc.onConstantsUpdatedLocked(); - } - onControllerStateChanged(null); - } - } - } - - @Override public void onPropertiesChanged(DeviceConfig.Properties properties) { boolean apiQuotaScheduleUpdated = false; boolean concurrencyUpdated = false; @@ -465,6 +445,17 @@ public class JobSchedulerService extends com.android.server.SystemService } } } + + @Override + public void onTareEnabledStateChanged(boolean isTareEnabled) { + if (mConstants.updateTareSettingsLocked(isTareEnabled)) { + for (int controller = 0; controller < mControllers.size(); controller++) { + final StateController sc = mControllers.get(controller); + sc.onConstantsUpdatedLocked(); + } + onControllerStateChanged(null); + } + } } @VisibleForTesting @@ -719,10 +710,8 @@ public class JobSchedulerService extends com.android.server.SystemService DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)); } - private boolean updateSettingsConstantsLocked(ContentResolver contentResolver) { + private boolean updateTareSettingsLocked(boolean isTareEnabled) { boolean changed = false; - final boolean isTareEnabled = Settings.Global.getInt(contentResolver, - Settings.Global.ENABLE_TARE, Settings.Global.DEFAULT_ENABLE_TARE) == 1; if (USE_TARE_POLICY != isTareEnabled) { USE_TARE_POLICY = isTareEnabled; changed = true; @@ -1673,7 +1662,7 @@ public class JobSchedulerService extends com.android.server.SystemService mHandler = new JobHandler(context.getMainLooper()); mConstants = new Constants(); - mConstantsObserver = new ConstantsObserver(mHandler, context); + mConstantsObserver = new ConstantsObserver(); mJobSchedulerStub = new JobSchedulerStub(); mConcurrencyManager = new JobConcurrencyManager(this); diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java index b2e95a58f0ec..c5f0f32a5aa0 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java @@ -16,8 +16,12 @@ package com.android.server.tare; +import static android.text.format.DateUtils.DAY_IN_MILLIS; + import static com.android.server.tare.EconomicPolicy.REGULATION_BASIC_INCOME; import static com.android.server.tare.EconomicPolicy.REGULATION_BIRTHRIGHT; +import static com.android.server.tare.EconomicPolicy.REGULATION_DEMOTION; +import static com.android.server.tare.EconomicPolicy.REGULATION_PROMOTION; import static com.android.server.tare.EconomicPolicy.REGULATION_WEALTH_RECLAMATION; import static com.android.server.tare.EconomicPolicy.TYPE_ACTION; import static com.android.server.tare.EconomicPolicy.TYPE_REWARD; @@ -603,6 +607,51 @@ class Agent { } } + /** + * Reclaim a percentage of unused ARCs from an app that was just removed from an exemption list. + * The amount reclaimed will depend on how recently the app was used. The reclamation will not + * reduce an app's balance below its current minimum balance. + */ + @GuardedBy("mLock") + void onAppUnexemptedLocked(final int userId, @NonNull final String pkgName) { + final long curBalance = getBalanceLocked(userId, pkgName); + final long minBalance = mIrs.getMinBalanceLocked(userId, pkgName); + if (curBalance <= minBalance) { + return; + } + // AppStandby only counts elapsed time for things like this + // TODO: should we use clock time instead? + final long timeSinceLastUsedMs = + mAppStandbyInternal.getTimeSinceLastUsedByUser(pkgName, userId); + // The app is no longer exempted. We should take away some of credits so it's more in line + // with other non-exempt apps. However, don't take away as many credits if the app was used + // recently. + final double percentageToReclaim; + if (timeSinceLastUsedMs < DAY_IN_MILLIS) { + percentageToReclaim = .25; + } else if (timeSinceLastUsedMs < 2 * DAY_IN_MILLIS) { + percentageToReclaim = .5; + } else if (timeSinceLastUsedMs < 3 * DAY_IN_MILLIS) { + percentageToReclaim = .75; + } else { + percentageToReclaim = 1; + } + final long overage = curBalance - minBalance; + final long toReclaim = (long) (overage * percentageToReclaim); + if (toReclaim > 0) { + if (DEBUG) { + Slog.i(TAG, "Reclaiming bonus wealth! Taking " + toReclaim + + " from " + appToString(userId, pkgName)); + } + + final long now = getCurrentTimeMillis(); + final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName); + recordTransactionLocked(userId, pkgName, ledger, + new Ledger.Transaction(now, now, REGULATION_DEMOTION, null, -toReclaim), + true); + } + } + /** Returns true if an app should be given credits in the general distributions. */ private boolean shouldGiveCredits(@NonNull PackageInfo packageInfo) { final ApplicationInfo applicationInfo = packageInfo.applicationInfo; @@ -701,6 +750,21 @@ class Agent { } @GuardedBy("mLock") + void onAppExemptedLocked(final int userId, @NonNull final String pkgName) { + final long minBalance = mIrs.getMinBalanceLocked(userId, pkgName); + final long missing = minBalance - getBalanceLocked(userId, pkgName); + if (missing <= 0) { + return; + } + + final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName); + final long now = getCurrentTimeMillis(); + + recordTransactionLocked(userId, pkgName, ledger, + new Ledger.Transaction(now, now, REGULATION_PROMOTION, null, missing), true); + } + + @GuardedBy("mLock") void onPackageRemovedLocked(final int userId, @NonNull final String pkgName) { reclaimAssetsLocked(userId, pkgName); mBalanceThresholdAlarmQueue.removeAlarmForKey(new Package(userId, pkgName)); diff --git a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java index 2b4938bc7746..d7290191dc3c 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java @@ -35,6 +35,7 @@ import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_WA import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP; import static android.app.tare.EconomyManager.DEFAULT_AM_MAX_CIRCULATION; import static android.app.tare.EconomyManager.DEFAULT_AM_MAX_SATIATED_BALANCE; +import static android.app.tare.EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED; import static android.app.tare.EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP; import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT; import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_MAX; @@ -71,6 +72,7 @@ import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_INEXACT_WAKEUP import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP; import static android.app.tare.EconomyManager.KEY_AM_MAX_CIRCULATION; import static android.app.tare.EconomyManager.KEY_AM_MAX_SATIATED_BALANCE; +import static android.app.tare.EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED; import static android.app.tare.EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP; import static android.app.tare.EconomyManager.KEY_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT; import static android.app.tare.EconomyManager.KEY_AM_REWARD_NOTIFICATION_INTERACTION_MAX; @@ -138,7 +140,8 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { COST_MODIFIER_PROCESS_STATE }; - private long mMinSatiatedBalance; + private long mMinSatiatedBalanceExempted; + private long mMinSatiatedBalanceOther; private long mMaxSatiatedBalance; private long mMaxSatiatedCirculation; @@ -163,8 +166,11 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { @Override long getMinSatiatedBalance(final int userId, @NonNull final String pkgName) { - // TODO: take exemption into account - return mMinSatiatedBalance; + if (mInternalResourceService.isPackageExempted(userId, pkgName)) { + return mMinSatiatedBalanceExempted; + } + // TODO: take other exemptions into account + return mMinSatiatedBalanceOther; } @Override @@ -205,7 +211,9 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { Slog.e(TAG, "Global setting key incorrect: ", e); } - mMinSatiatedBalance = arcToNarc(mParser.getInt(KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, + mMinSatiatedBalanceExempted = arcToNarc(mParser.getInt(KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED, + DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED)); + mMinSatiatedBalanceOther = arcToNarc(mParser.getInt(KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP)); mMaxSatiatedBalance = arcToNarc(mParser.getInt(KEY_AM_MAX_SATIATED_BALANCE, DEFAULT_AM_MAX_SATIATED_BALANCE)); @@ -343,7 +351,11 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { @Override void dump(IndentingPrintWriter pw) { - pw.print("Min satiated balance", narcToString(mMinSatiatedBalance)).println(); + pw.println("Min satiated balances:"); + pw.increaseIndent(); + pw.print("Exempted", narcToString(mMinSatiatedBalanceExempted)).println(); + pw.print("Other", narcToString(mMinSatiatedBalanceOther)).println(); + pw.decreaseIndent(); pw.print("Max satiated balance", narcToString(mMaxSatiatedBalance)).println(); pw.print("Min satiated circulation", narcToString(mMaxSatiatedCirculation)).println(); diff --git a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java index d16e378bce9e..c1177b20bb0e 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java @@ -57,6 +57,8 @@ public abstract class EconomicPolicy { static final int REGULATION_BASIC_INCOME = TYPE_REGULATION | 0; static final int REGULATION_BIRTHRIGHT = TYPE_REGULATION | 1; static final int REGULATION_WEALTH_RECLAMATION = TYPE_REGULATION | 2; + static final int REGULATION_PROMOTION = TYPE_REGULATION | 3; + static final int REGULATION_DEMOTION = TYPE_REGULATION | 4; static final int REWARD_NOTIFICATION_SEEN = TYPE_REWARD | 0; static final int REWARD_NOTIFICATION_INTERACTION = TYPE_REWARD | 1; @@ -363,6 +365,10 @@ public abstract class EconomicPolicy { return "BIRTHRIGHT"; case REGULATION_WEALTH_RECLAMATION: return "WEALTH_RECLAMATION"; + case REGULATION_PROMOTION: + return "PROMOTION"; + case REGULATION_DEMOTION: + return "DEMOTION"; } return "UNKNOWN_REGULATION:" + Integer.toHexString(eventId); } diff --git a/apex/jobscheduler/service/java/com/android/server/tare/EconomyManagerInternal.java b/apex/jobscheduler/service/java/com/android/server/tare/EconomyManagerInternal.java index 29aa94631d68..630f1e7bfb92 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/EconomyManagerInternal.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/EconomyManagerInternal.java @@ -119,6 +119,11 @@ public interface EconomyManagerInternal { boolean canAfford); } + /** Listener for various TARE state changes. */ + interface TareStateChangeListener { + void onTareEnabledStateChanged(boolean isTareEnabled); + } + /** * Return {@code true} if the app is able to pay for the anticipated actions. */ @@ -130,6 +135,9 @@ public interface EconomyManagerInternal { */ long getMaxDurationMs(int userId, @NonNull String pkgName, @NonNull ActionBill bill); + /** Returns true if TARE is enabled. */ + boolean isEnabled(); + /** * Register an {@link AffordabilityChangeListener} to track when an app's ability to afford the * indicated bill changes. @@ -145,6 +153,16 @@ public interface EconomyManagerInternal { @NonNull AffordabilityChangeListener listener, @NonNull ActionBill bill); /** + * Register a {@link TareStateChangeListener} to track when TARE's state changes. + */ + void registerTareStateChangeListener(@NonNull TareStateChangeListener listener); + + /** + * Unregister a {@link TareStateChangeListener} from being notified when TARE's state changes. + */ + void unregisterTareStateChangeListener(@NonNull TareStateChangeListener listener); + + /** * Note that an instantaneous event has occurred. The event must be specified in one of the * EconomicPolicies. * diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java index 1cd873c675c0..437a10198d8e 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java @@ -45,10 +45,15 @@ import android.net.Uri; import android.os.BatteryManagerInternal; import android.os.Binder; import android.os.Handler; +import android.os.IDeviceIdleController; import android.os.Looper; import android.os.Message; +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; +import android.provider.DeviceConfig; import android.provider.Settings; import android.util.ArraySet; import android.util.IndentingPrintWriter; @@ -64,11 +69,13 @@ import com.android.internal.util.DumpUtils; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.pm.UserManagerInternal; +import com.android.server.tare.EconomyManagerInternal.TareStateChangeListener; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CopyOnWriteArraySet; /** * Responsible for handling app's ARC count based on events, ensuring ARCs are credited when @@ -105,6 +112,8 @@ public class InternalResourceService extends SystemService { private final PackageManager mPackageManager; private final PackageManagerInternal mPackageManagerInternal; + private IDeviceIdleController mDeviceIdleController; + private final Agent mAgent; private final ConfigObserver mConfigObserver; private final EconomyManagerStub mEconomyManagerStub; @@ -163,8 +172,17 @@ public class InternalResourceService extends SystemService { @GuardedBy("mPackageToUidCache") private final SparseArrayMap<String, Integer> mPackageToUidCache = new SparseArrayMap<>(); + private final CopyOnWriteArraySet<TareStateChangeListener> mStateChangeListeners = + new CopyOnWriteArraySet<>(); + + /** List of packages that are "exempted" from battery restrictions. */ + // TODO(144864180): include userID + @GuardedBy("mLock") + private ArraySet<String> mExemptedApps = new ArraySet<>(); + private volatile boolean mIsEnabled; private volatile int mBootPhase; + private volatile boolean mExemptListLoaded; // In the range [0,100] to represent 0% to 100% battery. @GuardedBy("mLock") private int mCurrentBatteryLevel; @@ -213,6 +231,9 @@ public class InternalResourceService extends SystemService { onUserRemoved(userId); } break; + case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED: + onExemptionListChanged(); + break; } } }; @@ -246,6 +267,7 @@ public class InternalResourceService extends SystemService { private static final int MSG_SCHEDULE_UNUSED_WEALTH_RECLAMATION_EVENT = 1; private static final int MSG_PROCESS_USAGE_EVENT = 2; private static final int MSG_MAYBE_FORCE_RECLAIM = 3; + private static final int MSG_NOTIFY_STATE_CHANGE_LISTENERS = 4; private static final String ALARM_TAG_WEALTH_RECLAMATION = "*tare.reclamation*"; /** @@ -285,7 +307,22 @@ public class InternalResourceService extends SystemService { if (PHASE_SYSTEM_SERVICES_READY == phase) { mConfigObserver.start(); + mDeviceIdleController = IDeviceIdleController.Stub.asInterface( + ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); setupEverything(); + } else if (PHASE_BOOT_COMPLETED == phase) { + if (!mExemptListLoaded) { + synchronized (mLock) { + try { + mExemptedApps = + new ArraySet<>(mDeviceIdleController.getFullPowerWhitelist()); + } catch (RemoteException e) { + // Shouldn't happen. + Slog.wtf(TAG, e); + } + mExemptListLoaded = true; + } + } } } @@ -350,6 +387,12 @@ public class InternalResourceService extends SystemService { return mIsEnabled; } + boolean isPackageExempted(final int userId, @NonNull String pkgName) { + synchronized (mLock) { + return mExemptedApps.contains(pkgName); + } + } + boolean isSystem(final int userId, @NonNull String pkgName) { if ("android".equals(pkgName)) { return true; @@ -375,6 +418,53 @@ public class InternalResourceService extends SystemService { } } + void onExemptionListChanged() { + final int[] userIds = LocalServices.getService(UserManagerInternal.class).getUserIds(); + synchronized (mLock) { + final ArraySet<String> removed = mExemptedApps; + final ArraySet<String> added = new ArraySet<>(); + try { + mExemptedApps = new ArraySet<>(mDeviceIdleController.getFullPowerWhitelist()); + } catch (RemoteException e) { + // Shouldn't happen. + Slog.wtf(TAG, e); + return; + } + + for (int i = mExemptedApps.size() - 1; i >= 0; --i) { + final String pkg = mExemptedApps.valueAt(i); + if (!removed.contains(pkg)) { + added.add(pkg); + } + removed.remove(pkg); + } + for (int a = added.size() - 1; a >= 0; --a) { + final String pkgName = added.valueAt(a); + for (int userId : userIds) { + // Since the exemption list doesn't specify user ID and we track by user ID, + // we need to see if the app exists on the user before talking to the agent. + // Otherwise, we may end up with invalid ledgers. + final boolean appExists = getUid(userId, pkgName) >= 0; + if (appExists) { + mAgent.onAppExemptedLocked(userId, pkgName); + } + } + } + for (int r = removed.size() - 1; r >= 0; --r) { + final String pkgName = removed.valueAt(r); + for (int userId : userIds) { + // Since the exemption list doesn't specify user ID and we track by user ID, + // we need to see if the app exists on the user before talking to the agent. + // Otherwise, we may end up with invalid ledgers. + final boolean appExists = getUid(userId, pkgName) >= 0; + if (appExists) { + mAgent.onAppUnexemptedLocked(userId, pkgName); + } + } + } + } + } + void onPackageAdded(final int uid, @NonNull final String pkgName) { final int userId = UserHandle.getUserId(uid); final PackageInfo packageInfo; @@ -602,6 +692,7 @@ public class InternalResourceService extends SystemService { private void registerListeners() { final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_BATTERY_LEVEL_CHANGED); + filter.addAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED); getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null); final IntentFilter pkgFilter = new IntentFilter(); @@ -625,6 +716,15 @@ public class InternalResourceService extends SystemService { private void setupHeavyWork() { synchronized (mLock) { loadInstalledPackageListLocked(); + if (mBootPhase >= PHASE_BOOT_COMPLETED && !mExemptListLoaded) { + try { + mExemptedApps = new ArraySet<>(mDeviceIdleController.getFullPowerWhitelist()); + } catch (RemoteException e) { + // Shouldn't happen. + Slog.wtf(TAG, e); + } + mExemptListLoaded = true; + } final boolean isFirstSetup = !mScribe.recordExists(); if (isFirstSetup) { mAgent.grantBirthrightsLocked(); @@ -654,6 +754,8 @@ public class InternalResourceService extends SystemService { synchronized (mLock) { mAgent.tearDownLocked(); mCompleteEconomicPolicy.tearDown(); + mExemptedApps.clear(); + mExemptListLoaded = false; mHandler.post(() -> { // Never call out to AlarmManager with the lock held. This sits below AM. AlarmManager alarmManager = getContext().getSystemService(AlarmManager.class); @@ -707,6 +809,13 @@ public class InternalResourceService extends SystemService { } break; + case MSG_NOTIFY_STATE_CHANGE_LISTENERS: { + for (TareStateChangeListener listener : mStateChangeListeners) { + listener.onTareEnabledStateChanged(mIsEnabled); + } + } + break; + case MSG_PROCESS_USAGE_EVENT: { final int userId = msg.arg1; final UsageEvents.Event event = (UsageEvents.Event) msg.obj; @@ -797,6 +906,16 @@ public class InternalResourceService extends SystemService { } @Override + public void registerTareStateChangeListener(@NonNull TareStateChangeListener listener) { + mStateChangeListeners.add(listener); + } + + @Override + public void unregisterTareStateChangeListener(@NonNull TareStateChangeListener listener) { + mStateChangeListeners.remove(listener); + } + + @Override public boolean canPayFor(int userId, @NonNull String pkgName, @NonNull ActionBill bill) { if (!mIsEnabled) { return true; @@ -849,6 +968,11 @@ public class InternalResourceService extends SystemService { } @Override + public boolean isEnabled() { + return mIsEnabled; + } + + @Override public void noteInstantaneousEvent(int userId, @NonNull String pkgName, int eventId, @Nullable String tag) { if (!mIsEnabled) { @@ -885,7 +1009,10 @@ public class InternalResourceService extends SystemService { } } - private class ConfigObserver extends ContentObserver { + private class ConfigObserver extends ContentObserver + implements DeviceConfig.OnPropertiesChangedListener { + private static final String KEY_DC_ENABLE_TARE = "enable_tare"; + private final ContentResolver mContentResolver; ConfigObserver(Handler handler, Context context) { @@ -894,12 +1021,15 @@ public class InternalResourceService extends SystemService { } public void start() { + DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_TARE, + TareHandlerThread.getExecutor(), this); mContentResolver.registerContentObserver( Settings.Global.getUriFor(Settings.Global.ENABLE_TARE), false, this); mContentResolver.registerContentObserver( Settings.Global.getUriFor(TARE_ALARM_MANAGER_CONSTANTS), false, this); mContentResolver.registerContentObserver( Settings.Global.getUriFor(TARE_JOB_SCHEDULER_CONSTANTS), false, this); + onPropertiesChanged(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_TARE)); updateEnabledStatus(); } @@ -913,9 +1043,31 @@ public class InternalResourceService extends SystemService { } } + @Override + public void onPropertiesChanged(DeviceConfig.Properties properties) { + synchronized (mLock) { + for (String name : properties.getKeyset()) { + if (name == null) { + continue; + } + switch (name) { + case KEY_DC_ENABLE_TARE: + updateEnabledStatus(); + break; + } + } + } + } + private void updateEnabledStatus() { + // User setting should override DeviceConfig setting. + // NOTE: There's currently no way for a user to reset the value (via UI), so if a user + // manually toggles TARE via UI, we'll always defer to the user's current setting + // TODO: add a "reset" value if the user toggle is an issue + final boolean isTareEnabledDC = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TARE, + KEY_DC_ENABLE_TARE, Settings.Global.DEFAULT_ENABLE_TARE == 1); final boolean isTareEnabled = Settings.Global.getInt(mContentResolver, - Settings.Global.ENABLE_TARE, Settings.Global.DEFAULT_ENABLE_TARE) == 1; + Settings.Global.ENABLE_TARE, isTareEnabledDC ? 1 : 0) == 1; if (mIsEnabled != isTareEnabled) { mIsEnabled = isTareEnabled; if (mIsEnabled) { @@ -923,6 +1075,7 @@ public class InternalResourceService extends SystemService { } else { tearDownEverything(); } + mHandler.sendEmptyMessage(MSG_NOTIFY_STATE_CHANGE_LISTENERS); } } @@ -953,9 +1106,9 @@ public class InternalResourceService extends SystemService { pw.print("Current battery level: "); pw.println(mCurrentBatteryLevel); - final long maxCircluation = getMaxCirculationLocked(); + final long maxCirculation = getMaxCirculationLocked(); pw.print("Max circulation (current/satiated): "); - pw.print(narcToString(maxCircluation)); + pw.print(narcToString(maxCirculation)); pw.print("/"); pw.println(narcToString(mCompleteEconomicPolicy.getMaxSatiatedCirculation())); @@ -963,10 +1116,14 @@ public class InternalResourceService extends SystemService { pw.print("Current GDP: "); pw.print(narcToString(currentCirculation)); pw.print(" ("); - pw.print(String.format("%.2f", 100f * currentCirculation / maxCircluation)); + pw.print(String.format("%.2f", 100f * currentCirculation / maxCirculation)); pw.println("% of current max)"); pw.println(); + pw.print("Exempted apps", mExemptedApps); + pw.println(); + + pw.println(); mCompleteEconomicPolicy.dump(pw); pw.println(); diff --git a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java index 728f5e80d4ca..2318026d9f16 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java @@ -40,6 +40,7 @@ import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_TIMEOUT_PENA import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP; import static android.app.tare.EconomyManager.DEFAULT_JS_MAX_CIRCULATION; import static android.app.tare.EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE; +import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED; import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP; import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_INSTANT; import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_NOTIFICATION_INTERACTION_MAX; @@ -80,6 +81,7 @@ import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_ import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP; import static android.app.tare.EconomyManager.KEY_JS_MAX_CIRCULATION; import static android.app.tare.EconomyManager.KEY_JS_MAX_SATIATED_BALANCE; +import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED; import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP; import static android.app.tare.EconomyManager.KEY_JS_REWARD_NOTIFICATION_INTERACTION_INSTANT; import static android.app.tare.EconomyManager.KEY_JS_REWARD_NOTIFICATION_INTERACTION_MAX; @@ -140,7 +142,8 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { COST_MODIFIER_PROCESS_STATE }; - private long mMinSatiatedBalance; + private long mMinSatiatedBalanceExempted; + private long mMinSatiatedBalanceOther; private long mMaxSatiatedBalance; private long mMaxSatiatedCirculation; @@ -165,8 +168,11 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { @Override long getMinSatiatedBalance(final int userId, @NonNull final String pkgName) { - // TODO: incorporate time since usage - return mMinSatiatedBalance; + if (mInternalResourceService.isPackageExempted(userId, pkgName)) { + return mMinSatiatedBalanceExempted; + } + // TODO: take other exemptions into account + return mMinSatiatedBalanceOther; } @Override @@ -207,7 +213,10 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { Slog.e(TAG, "Global setting key incorrect: ", e); } - mMinSatiatedBalance = arcToNarc( + mMinSatiatedBalanceExempted = arcToNarc( + mParser.getInt(KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, + DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED)); + mMinSatiatedBalanceOther = arcToNarc( mParser.getInt(KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP)); mMaxSatiatedBalance = arcToNarc(mParser.getInt(KEY_JS_MAX_SATIATED_BALANCE, @@ -317,7 +326,11 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { @Override void dump(IndentingPrintWriter pw) { - pw.print("Min satiated balance", narcToString(mMinSatiatedBalance)).println(); + pw.println("Min satiated balances:"); + pw.increaseIndent(); + pw.print("Exempted", narcToString(mMinSatiatedBalanceExempted)).println(); + pw.print("Other", narcToString(mMinSatiatedBalanceOther)).println(); + pw.decreaseIndent(); pw.print("Max satiated balance", narcToString(mMaxSatiatedBalance)).println(); pw.print("Min satiated circulation", narcToString(mMaxSatiatedCirculation)).println(); diff --git a/apex/jobscheduler/service/java/com/android/server/tare/TareHandlerThread.java b/apex/jobscheduler/service/java/com/android/server/tare/TareHandlerThread.java index 5b2b6607ba14..65ef8bfe0d06 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/TareHandlerThread.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/TareHandlerThread.java @@ -17,10 +17,13 @@ package com.android.server.tare; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.HandlerThread; import android.os.Looper; import android.os.Trace; +import java.util.concurrent.Executor; + /** * Singleton thread for all of TARE. * @@ -29,6 +32,7 @@ import android.os.Trace; final class TareHandlerThread extends HandlerThread { private static TareHandlerThread sInstance; + private static Executor sHandlerExecutor; private static Handler sHandler; private TareHandlerThread() { @@ -42,6 +46,7 @@ final class TareHandlerThread extends HandlerThread { final Looper looper = sInstance.getLooper(); looper.setTraceTag(Trace.TRACE_TAG_SYSTEM_SERVER); sHandler = new Handler(sInstance.getLooper()); + sHandlerExecutor = new HandlerExecutor(sHandler); } } @@ -52,6 +57,14 @@ final class TareHandlerThread extends HandlerThread { return sInstance; } + /** Returns the singleton handler executor for TareHandlerThread */ + public static Executor getExecutor() { + synchronized (TareHandlerThread.class) { + ensureThreadLocked(); + return sHandlerExecutor; + } + } + /** Returns the singleton handler for TareHandlerThread. */ public static Handler getHandler() { synchronized (TareHandlerThread.class) { diff --git a/api/Android.bp b/api/Android.bp index 1bc50bd409a2..1ec1b3c6ba93 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -106,6 +106,7 @@ genrule { ":framework-scheduling{.public.api.txt}", ":framework-sdkextensions{.public.api.txt}", ":framework-statsd{.public.api.txt}", + ":framework-supplementalprocess{.public.api.txt}", ":framework-tethering{.public.api.txt}", ":framework-uwb{.public.api.txt}", ":framework-wifi{.public.api.txt}", @@ -167,6 +168,7 @@ genrule { ":framework-scheduling{.public.stubs.source}", ":framework-sdkextensions{.public.stubs.source}", ":framework-statsd{.public.stubs.source}", + ":framework-supplementalprocess{.public.stubs.source}", ":framework-tethering{.public.stubs.source}", ":framework-uwb{.public.stubs.source}", ":framework-wifi{.public.stubs.source}", @@ -195,6 +197,7 @@ genrule { ":framework-scheduling{.public.removed-api.txt}", ":framework-sdkextensions{.public.removed-api.txt}", ":framework-statsd{.public.removed-api.txt}", + ":framework-supplementalprocess{.public.removed-api.txt}", ":framework-tethering{.public.removed-api.txt}", ":framework-uwb{.public.removed-api.txt}", ":framework-wifi{.public.removed-api.txt}", @@ -237,6 +240,7 @@ genrule { ":framework-scheduling{.system.api.txt}", ":framework-sdkextensions{.system.api.txt}", ":framework-statsd{.system.api.txt}", + ":framework-supplementalprocess{.system.api.txt}", ":framework-tethering{.system.api.txt}", ":framework-uwb{.system.api.txt}", ":framework-wifi{.system.api.txt}", @@ -297,6 +301,7 @@ genrule { ":framework-scheduling{.system.removed-api.txt}", ":framework-sdkextensions{.system.removed-api.txt}", ":framework-statsd{.system.removed-api.txt}", + ":framework-supplementalprocess{.system.removed-api.txt}", ":framework-tethering{.system.removed-api.txt}", ":framework-uwb{.system.removed-api.txt}", ":framework-wifi{.system.removed-api.txt}", @@ -339,6 +344,7 @@ genrule { ":framework-scheduling{.module-lib.api.txt}", ":framework-sdkextensions{.module-lib.api.txt}", ":framework-statsd{.module-lib.api.txt}", + ":framework-supplementalprocess{.module-lib.api.txt}", ":framework-tethering{.module-lib.api.txt}", ":framework-uwb{.module-lib.api.txt}", ":framework-wifi{.module-lib.api.txt}", @@ -401,6 +407,7 @@ genrule { ":framework-scheduling{.module-lib.removed-api.txt}", ":framework-sdkextensions{.module-lib.removed-api.txt}", ":framework-statsd{.module-lib.removed-api.txt}", + ":framework-supplementalprocess{.module-lib.removed-api.txt}", ":framework-tethering{.module-lib.removed-api.txt}", ":framework-uwb{.module-lib.removed-api.txt}", ":framework-wifi{.module-lib.removed-api.txt}", @@ -521,6 +528,7 @@ genrule { ":framework-scheduling.stubs{.jar}", ":framework-sdkextensions.stubs{.jar}", ":framework-statsd.stubs{.jar}", + ":framework-supplementalprocess.stubs{.jar}", ":framework-tethering.stubs{.jar}", ":framework-uwb.stubs{.jar}", ":framework-wifi.stubs{.jar}", diff --git a/boot/Android.bp b/boot/Android.bp index d88e839cc79d..3273f2c6ed8e 100644 --- a/boot/Android.bp +++ b/boot/Android.bp @@ -96,6 +96,10 @@ platform_bootclasspath { module: "com.android.sdkext-bootclasspath-fragment", }, { + apex: "com.android.supplementalprocess", + module: "com.android.supplementalprocess-bootclasspath-fragment", + }, + { apex: "com.android.tethering", module: "com.android.tethering-bootclasspath-fragment", }, diff --git a/config/README.md b/config/README.md new file mode 100644 index 000000000000..450a5c695c82 --- /dev/null +++ b/config/README.md @@ -0,0 +1,13 @@ +# Configuration files for ART compiling the framework + +* boot-image-profile.txt: A list of methods from the boot classpath to be compiled by dex2oat. + The order in the file is not relevant. +* boot-profile.txt: An ordered list of methods from the boot classpath to be compiled by + the JIT in the order provided in the file. Used by JIT zygote, when on-device + signing failed. +* dirty-image-objects: List of objects in the boot image which are known to + become dirty. This helps binning objects in the image file. +* preloaded-classes: classes that will be allocated in the boot image, and + initialized by the zygote. +* preloaded-classes-denylist: Classes that should not be initialized in the + zygote, as they have app-specific behavior. diff --git a/core/api/current.txt b/core/api/current.txt index 7370af382b14..5656c31c330a 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -411,6 +411,7 @@ package android { field public static final int calendarViewShown = 16843596; // 0x101034c field public static final int calendarViewStyle = 16843613; // 0x101035d field public static final int canControlMagnification = 16844039; // 0x1010507 + field public static final int canDisplayOnRemoteDevices; field public static final int canPauseRecording = 16844314; // 0x101061a field public static final int canPerformGestures = 16844045; // 0x101050d field public static final int canRecord = 16844060; // 0x101051c @@ -31636,6 +31637,8 @@ package android.os { method @Nullable public double[] createDoubleArray(); method @Nullable public float[] createFloatArray(); method @Nullable public int[] createIntArray(); + method @Nullable public <T extends android.os.IInterface> T[] createInterfaceArray(@NonNull java.util.function.IntFunction<T[]>, @NonNull java.util.function.Function<android.os.IBinder,T>); + method @Nullable public <T extends android.os.IInterface> java.util.ArrayList<T> createInterfaceArrayList(@NonNull java.util.function.Function<android.os.IBinder,T>); method @Nullable public long[] createLongArray(); method @Nullable public String[] createStringArray(); method @Nullable public java.util.ArrayList<java.lang.String> createStringArrayList(); @@ -31674,13 +31677,17 @@ package android.os { method public float readFloat(); method public void readFloatArray(@NonNull float[]); method @Nullable public java.util.HashMap readHashMap(@Nullable ClassLoader); + method @Nullable public <K, V> java.util.HashMap<K,V> readHashMap(@Nullable ClassLoader, @NonNull Class<? extends K>, @NonNull Class<? extends V>); method public int readInt(); method public void readIntArray(@NonNull int[]); + method public <T extends android.os.IInterface> void readInterfaceArray(@NonNull T[], @NonNull java.util.function.Function<android.os.IBinder,T>); + method public <T extends android.os.IInterface> void readInterfaceList(@NonNull java.util.List<T>, @NonNull java.util.function.Function<android.os.IBinder,T>); method public void readList(@NonNull java.util.List, @Nullable ClassLoader); method public <T> void readList(@NonNull java.util.List<? super T>, @Nullable ClassLoader, @NonNull Class<T>); method public long readLong(); method public void readLongArray(@NonNull long[]); method public void readMap(@NonNull java.util.Map, @Nullable ClassLoader); + method public <K, V> void readMap(@NonNull java.util.Map<? super K,? super V>, @Nullable ClassLoader, @NonNull Class<K>, @NonNull Class<V>); method @Nullable public <T extends android.os.Parcelable> T readParcelable(@Nullable ClassLoader); method @Nullable public <T extends android.os.Parcelable> T readParcelable(@Nullable ClassLoader, @NonNull Class<T>); method @Nullable public android.os.Parcelable[] readParcelableArray(@Nullable ClassLoader); @@ -31728,6 +31735,8 @@ package android.os { method public void writeFloatArray(@Nullable float[]); method public void writeInt(int); method public void writeIntArray(@Nullable int[]); + method public <T extends android.os.IInterface> void writeInterfaceArray(@Nullable T[]); + method public <T extends android.os.IInterface> void writeInterfaceList(@Nullable java.util.List<T>); method public void writeInterfaceToken(@NonNull String); method public void writeList(@Nullable java.util.List); method public void writeLong(long); @@ -40931,6 +40940,7 @@ package android.telephony { method @NonNull public java.util.List<java.lang.Integer> getBands(); method @NonNull public java.util.List<java.lang.String> getMccMncs(); method public int getPriority(); + method @NonNull public java.util.List<android.telephony.RadioAccessSpecifier> getRadioAccessSpecifiers(); method public int getSubId(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.AvailableNetworkInfo> CREATOR; @@ -40939,6 +40949,14 @@ package android.telephony { field public static final int PRIORITY_MED = 2; // 0x2 } + public static final class AvailableNetworkInfo.Builder { + ctor public AvailableNetworkInfo.Builder(int); + method @NonNull public android.telephony.AvailableNetworkInfo build(); + method @NonNull public android.telephony.AvailableNetworkInfo.Builder setMccMncs(@NonNull java.util.List<java.lang.String>); + method @NonNull public android.telephony.AvailableNetworkInfo.Builder setPriority(int); + method @NonNull public android.telephony.AvailableNetworkInfo.Builder setRadioAccessSpecifiers(@NonNull java.util.List<android.telephony.RadioAccessSpecifier>); + } + public final class BarringInfo implements android.os.Parcelable { method public int describeContents(); method @NonNull public android.telephony.BarringInfo.BarringServiceInfo getBarringServiceInfo(int); @@ -40972,11 +40990,11 @@ package android.telephony { } public class CarrierConfigManager { - method @Nullable public android.os.PersistableBundle getConfig(); - method @Nullable public android.os.PersistableBundle getConfigByComponentForSubId(@NonNull String, int); - method @Nullable public android.os.PersistableBundle getConfigForSubId(int); + method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.os.PersistableBundle getConfig(); + method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.os.PersistableBundle getConfigByComponentForSubId(@NonNull String, int); + method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.os.PersistableBundle getConfigForSubId(int); method public static boolean isConfigForIdentifiedCarrier(android.os.PersistableBundle); - method public void notifyConfigChangedForSubId(int); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyConfigChangedForSubId(int); field public static final String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED"; field public static final int CARRIER_NR_AVAILABILITY_NSA = 1; // 0x1 field public static final int CARRIER_NR_AVAILABILITY_SA = 2; // 0x2 @@ -41047,6 +41065,7 @@ package android.telephony { field public static final String KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL = "carrier_rcs_provisioning_required_bool"; field public static final String KEY_CARRIER_SETTINGS_ACTIVITY_COMPONENT_NAME_STRING = "carrier_settings_activity_component_name_string"; field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool"; + field public static final String KEY_CARRIER_SUPPORTS_OPP_DATA_AUTO_PROVISIONING_BOOL = "carrier_supports_opp_data_auto_provisioning_bool"; field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool"; field public static final String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL = "carrier_use_ims_first_for_emergency_bool"; field public static final String KEY_CARRIER_USSD_METHOD_INT = "carrier_ussd_method_int"; @@ -41108,6 +41127,8 @@ package android.telephony { field public static final String KEY_ENABLE_DIALER_KEY_VIBRATION_BOOL = "enable_dialer_key_vibration_bool"; field public static final String KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL = "enhanced_4g_lte_on_by_default_bool"; field public static final String KEY_ENHANCED_4G_LTE_TITLE_VARIANT_INT = "enhanced_4g_lte_title_variant_int"; + field public static final String KEY_ESIM_DOWNLOAD_RETRY_BACKOFF_TIMER_SEC_INT = "esim_download_retry_backoff_timer_sec_int"; + field public static final String KEY_ESIM_MAX_DOWNLOAD_RETRY_ATTEMPTS_INT = "esim_max_download_retry_attempts_int"; field public static final String KEY_FORCE_HOME_NETWORK_BOOL = "force_home_network_bool"; field public static final String KEY_GSM_DTMF_TONE_DELAY_INT = "gsm_dtmf_tone_delay_int"; field public static final String KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY = "gsm_nonroaming_networks_string_array"; @@ -41210,6 +41231,7 @@ package android.telephony { field public static final String KEY_SHOW_WFC_LOCATION_PRIVACY_POLICY_BOOL = "show_wfc_location_privacy_policy_bool"; field public static final String KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL = "simplified_network_settings_bool"; field public static final String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool"; + field public static final String KEY_SMDP_SERVER_ADDRESS_STRING = "smdp_server_address_string"; field public static final String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool"; field public static final String KEY_SUPPORTS_CALL_COMPOSER_BOOL = "supports_call_composer_bool"; field public static final String KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_DTMF_BOOL = "supports_device_to_device_communication_using_dtmf_bool"; @@ -42985,7 +43007,7 @@ package android.telephony { method public boolean isEmergencyNumber(@NonNull String); method public boolean isHearingAidCompatibilitySupported(); method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public boolean isManualNetworkSelectionAllowed(); - method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isModemEnabledForSlot(int); + method @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public boolean isModemEnabledForSlot(int); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int isMultiSimSupported(); method public boolean isNetworkRoaming(); method public boolean isRadioInterfaceCapabilitySupported(@NonNull String); @@ -52250,6 +52272,7 @@ package android.view.inputmethod { method public boolean commitContent(@NonNull android.view.inputmethod.InputContentInfo, int, @Nullable android.os.Bundle); method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo); method public boolean commitText(CharSequence, int); + method public default boolean commitText(@NonNull CharSequence, int, @Nullable android.view.inputmethod.TextAttribute); method public boolean deleteSurroundingText(int, int); method public boolean deleteSurroundingTextInCodePoints(int, int); method public boolean endBatchEdit(); @@ -52269,7 +52292,9 @@ package android.view.inputmethod { method public boolean requestCursorUpdates(int); method public boolean sendKeyEvent(android.view.KeyEvent); method public boolean setComposingRegion(int, int); + method public default boolean setComposingRegion(int, int, @Nullable android.view.inputmethod.TextAttribute); method public boolean setComposingText(CharSequence, int); + method public default boolean setComposingText(@NonNull CharSequence, int, @Nullable android.view.inputmethod.TextAttribute); method public default boolean setImeConsumesInput(boolean); method public boolean setSelection(int, int); method @Nullable public default android.view.inputmethod.TextSnapshot takeSnapshot(); @@ -52485,6 +52510,21 @@ package android.view.inputmethod { field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.SurroundingText> CREATOR; } + public final class TextAttribute implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public android.os.PersistableBundle getExtras(); + method @NonNull public java.util.List<java.lang.String> getTextConversionSuggestions(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.TextAttribute> CREATOR; + } + + public static final class TextAttribute.TextAttributeBuilder { + ctor public TextAttribute.TextAttributeBuilder(); + method @NonNull public android.view.inputmethod.TextAttribute build(); + method @NonNull public android.view.inputmethod.TextAttribute.TextAttributeBuilder setExtras(@NonNull android.os.PersistableBundle); + method @NonNull public android.view.inputmethod.TextAttribute.TextAttributeBuilder setTextConversionSuggestions(@NonNull java.util.List<java.lang.String>); + } + public final class TextSnapshot { ctor public TextSnapshot(@NonNull android.view.inputmethod.SurroundingText, @IntRange(from=0xffffffff) int, @IntRange(from=0xffffffff) int, int); method @IntRange(from=0xffffffff) public int getCompositionEnd(); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 27b2b2084f9c..d0a73bd3064e 100755 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -14,6 +14,7 @@ package android { field public static final String ACCESS_MTP = "android.permission.ACCESS_MTP"; field public static final String ACCESS_NETWORK_CONDITIONS = "android.permission.ACCESS_NETWORK_CONDITIONS"; field public static final String ACCESS_NOTIFICATIONS = "android.permission.ACCESS_NOTIFICATIONS"; + field public static final String ACCESS_PDB_STATE = "android.permission.ACCESS_PDB_STATE"; field public static final String ACCESS_RCS_USER_CAPABILITY_EXCHANGE = "android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE"; field public static final String ACCESS_SHARED_LIBRARIES = "android.permission.ACCESS_SHARED_LIBRARIES"; field public static final String ACCESS_SHORTCUTS = "android.permission.ACCESS_SHORTCUTS"; @@ -85,6 +86,7 @@ package android { field public static final String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA"; field public static final String COMPANION_APPROVE_WIFI_CONNECTIONS = "android.permission.COMPANION_APPROVE_WIFI_CONNECTIONS"; field public static final String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"; + field public static final String CONFIGURE_INTERACT_ACROSS_PROFILES = "android.permission.CONFIGURE_INTERACT_ACROSS_PROFILES"; field public static final String CONFIGURE_WIFI_DISPLAY = "android.permission.CONFIGURE_WIFI_DISPLAY"; field @Deprecated public static final String CONNECTIVITY_INTERNAL = "android.permission.CONNECTIVITY_INTERNAL"; field public static final String CONNECTIVITY_USE_RESTRICTED_NETWORKS = "android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"; @@ -107,6 +109,7 @@ package android { field public static final String FORCE_BACK = "android.permission.FORCE_BACK"; field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES"; field public static final String GET_APP_OPS_STATS = "android.permission.GET_APP_OPS_STATS"; + field public static final String GET_HISTORICAL_APP_OPS_STATS = "android.permission.GET_HISTORICAL_APP_OPS_STATS"; field public static final String GET_PROCESS_STATE_AND_OOM_SCORE = "android.permission.GET_PROCESS_STATE_AND_OOM_SCORE"; field public static final String GET_RUNTIME_PERMISSIONS = "android.permission.GET_RUNTIME_PERMISSIONS"; field public static final String GET_TOP_ACTIVITY_INFO = "android.permission.GET_TOP_ACTIVITY_INFO"; @@ -120,6 +123,7 @@ package android { field public static final String INJECT_EVENTS = "android.permission.INJECT_EVENTS"; field public static final String INSTALL_DPC_PACKAGES = "android.permission.INSTALL_DPC_PACKAGES"; field public static final String INSTALL_DYNAMIC_SYSTEM = "android.permission.INSTALL_DYNAMIC_SYSTEM"; + field public static final String INSTALL_EXISTING_PACKAGES = "com.android.permission.INSTALL_EXISTING_PACKAGES"; field public static final String INSTALL_GRANT_RUNTIME_PERMISSIONS = "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS"; field public static final String INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE = "android.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE"; field public static final String INSTALL_PACKAGE_UPDATES = "android.permission.INSTALL_PACKAGE_UPDATES"; @@ -146,12 +150,14 @@ package android { field public static final String MANAGE_CONTENT_CAPTURE = "android.permission.MANAGE_CONTENT_CAPTURE"; field public static final String MANAGE_CONTENT_SUGGESTIONS = "android.permission.MANAGE_CONTENT_SUGGESTIONS"; field public static final String MANAGE_DEBUGGING = "android.permission.MANAGE_DEBUGGING"; + field public static final String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS"; field public static final String MANAGE_FACTORY_RESET_PROTECTION = "android.permission.MANAGE_FACTORY_RESET_PROTECTION"; field public static final String MANAGE_HOTWORD_DETECTION = "android.permission.MANAGE_HOTWORD_DETECTION"; field public static final String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS"; field public static final String MANAGE_MUSIC_RECOGNITION = "android.permission.MANAGE_MUSIC_RECOGNITION"; field public static final String MANAGE_NOTIFICATION_LISTENERS = "android.permission.MANAGE_NOTIFICATION_LISTENERS"; field public static final String MANAGE_ONE_TIME_PERMISSION_SESSIONS = "android.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS"; + field public static final String MANAGE_PROFILE_AND_DEVICE_OWNERS = "android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS"; field public static final String MANAGE_ROLE_HOLDERS = "android.permission.MANAGE_ROLE_HOLDERS"; field public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS"; field public static final String MANAGE_ROTATION_RESOLVER = "android.permission.MANAGE_ROTATION_RESOLVER"; @@ -168,6 +174,7 @@ package android { field public static final String MANAGE_USERS = "android.permission.MANAGE_USERS"; field public static final String MANAGE_USER_OEM_UNLOCK_STATE = "android.permission.MANAGE_USER_OEM_UNLOCK_STATE"; field public static final String MANAGE_WIFI_COUNTRY_CODE = "android.permission.MANAGE_WIFI_COUNTRY_CODE"; + field public static final String MARK_DEVICE_ORGANIZATION_OWNED = "android.permission.MARK_DEVICE_ORGANIZATION_OWNED"; field public static final String MODIFY_APPWIDGET_BIND_PERMISSIONS = "android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS"; field public static final String MODIFY_AUDIO_ROUTING = "android.permission.MODIFY_AUDIO_ROUTING"; field public static final String MODIFY_CELL_BROADCASTS = "android.permission.MODIFY_CELL_BROADCASTS"; @@ -246,6 +253,7 @@ package android { field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS"; field public static final String RENOUNCE_PERMISSIONS = "android.permission.RENOUNCE_PERMISSIONS"; field public static final String REQUEST_COMPANION_PROFILE_APP_STREAMING = "android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING"; + field public static final String REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION = "android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION"; field public static final String REQUEST_NETWORK_SCORES = "android.permission.REQUEST_NETWORK_SCORES"; field public static final String REQUEST_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE"; field public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD"; @@ -299,6 +307,7 @@ package android { field public static final String UPDATE_TIME_ZONE_RULES = "android.permission.UPDATE_TIME_ZONE_RULES"; field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS"; field public static final String USER_ACTIVITY = "android.permission.USER_ACTIVITY"; + field public static final String USE_COLORIZED_NOTIFICATIONS = "android.permission.USE_COLORIZED_NOTIFICATIONS"; field public static final String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK"; field public static final String UWB_PRIVILEGED = "android.permission.UWB_PRIVILEGED"; field public static final String WHITELIST_AUTO_REVOKE_PERMISSIONS = "android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS"; @@ -368,6 +377,7 @@ package android { field public static final int config_defaultCallScreening = 17039398; // 0x1040026 field public static final int config_defaultDialer = 17039395; // 0x1040023 field public static final int config_defaultSms = 17039396; // 0x1040024 + field public static final int config_deviceManager; field public static final int config_feedbackIntentExtraKey = 17039391; // 0x104001f field public static final int config_feedbackIntentNameKey = 17039392; // 0x1040020 field public static final int config_helpIntentExtraKey = 17039389; // 0x104001d @@ -948,7 +958,7 @@ package android.app.admin { method @Nullable public android.content.Intent createProvisioningIntentFromNfcIntent(@NonNull android.content.Intent); method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwner(); - method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, "android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS"}) public android.content.ComponentName getDeviceOwnerComponentOnAnyUser(); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public android.content.ComponentName getDeviceOwnerComponentOnAnyUser(); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwnerNameOnAnyUser(); method @Nullable public CharSequence getDeviceOwnerOrganizationName(); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.UserHandle getDeviceOwnerUser(); @@ -966,7 +976,7 @@ package android.app.admin { method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long); method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long, boolean); method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean packageHasActiveAdmins(String); - method @Deprecated @RequiresPermission("android.permission.MANAGE_DEVICE_ADMINS") public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException; + method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException; method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied(); method @Deprecated @RequiresPermission(value=android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS, conditional=true) public void setProfileOwnerCanAccessDeviceIds(@NonNull android.content.ComponentName); method public void setSecondaryLockscreenEnabled(@NonNull android.content.ComponentName, boolean); @@ -2333,6 +2343,7 @@ package android.companion { public final class AssociationRequest implements android.os.Parcelable { field @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING) public static final String DEVICE_PROFILE_APP_STREAMING = "android.app.role.COMPANION_DEVICE_APP_STREAMING"; + field @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION) public static final String DEVICE_PROFILE_AUTOMOTIVE_PROJECTION = "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION"; } public final class CompanionDeviceManager { @@ -10461,7 +10472,7 @@ package android.service.oemlock { package android.service.persistentdata { public class PersistentDataBlockManager { - method @RequiresPermission("android.permission.ACCESS_PDB_STATE") public int getDataBlockSize(); + method @RequiresPermission(android.Manifest.permission.ACCESS_PDB_STATE) public int getDataBlockSize(); method @android.service.persistentdata.PersistentDataBlockManager.FlashLockState @RequiresPermission(anyOf={android.Manifest.permission.READ_OEM_UNLOCK_STATE, "android.permission.OEM_UNLOCK_STATE"}) public int getFlashLockState(); method public long getMaximumDataBlockSize(); method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_OEM_UNLOCK_STATE, "android.permission.OEM_UNLOCK_STATE"}) public boolean getOemUnlockEnabled(); diff --git a/core/api/system-removed.txt b/core/api/system-removed.txt index 9a8a49397322..9cb9ddcc6280 100644 --- a/core/api/system-removed.txt +++ b/core/api/system-removed.txt @@ -1,12 +1,4 @@ // Signature format: 2.0 -package android { - - public static final class Manifest.permission { - field public static final String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS"; - } - -} - package android.app { public class AppOpsManager { diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 60a833a9d95f..5f87630fc11e 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -114,7 +114,7 @@ package android.app { method @RequiresPermission(android.Manifest.permission.RESET_APP_ERRORS) public void resetAppErrors(); method public static void resumeAppSwitches() throws android.os.RemoteException; method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int); - method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void setStopBackgroundUsersOnSwitch(int); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void setStopUserOnSwitch(int); method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean stopUser(int, boolean); method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String); method @RequiresPermission(android.Manifest.permission.DUMP) public void waitForBroadcastIdle(); @@ -129,9 +129,9 @@ package android.app { field public static final int PROCESS_CAPABILITY_NONE = 0; // 0x0 field public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4; // 0x4 field public static final int PROCESS_STATE_TOP = 2; // 0x2 - field public static final int STOP_BG_USERS_ON_SWITCH_DEFAULT = -1; // 0xffffffff - field public static final int STOP_BG_USERS_ON_SWITCH_FALSE = 0; // 0x0 - field public static final int STOP_BG_USERS_ON_SWITCH_TRUE = 1; // 0x1 + field public static final int STOP_USER_ON_SWITCH_DEFAULT = -1; // 0xffffffff + field public static final int STOP_USER_ON_SWITCH_FALSE = 0; // 0x0 + field public static final int STOP_USER_ON_SWITCH_TRUE = 1; // 0x1 } public static class ActivityManager.RunningAppProcessInfo implements android.os.Parcelable { @@ -442,11 +442,11 @@ package android.app.admin { public class DevicePolicyManager { method public int checkProvisioningPreCondition(@Nullable String, @NonNull String); - method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void clearOrganizationId(); + method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void clearOrganizationId(); method @RequiresPermission(android.Manifest.permission.CLEAR_FREEZE_PERIOD) public void clearSystemUpdatePolicyFreezePeriodRecord(); method @Nullable public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException; method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceNetworkLogs(); - method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void forceRemoveActiveAdmin(@NonNull android.content.ComponentName, int); + method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void forceRemoveActiveAdmin(@NonNull android.content.ComponentName, int); method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceSecurityLogs(); method public void forceUpdateUserSetupComplete(int); method @NonNull public java.util.Set<java.lang.String> getDefaultCrossProfilePackages(); @@ -455,18 +455,18 @@ package android.app.admin { method public long getLastNetworkLogRetrievalTime(); method public long getLastSecurityLogRetrievalTime(); method public java.util.List<java.lang.String> getOwnerInstalledCaCerts(@NonNull android.os.UserHandle); - method @NonNull @RequiresPermission("android.permission.MANAGE_DEVICE_ADMINS") public java.util.Set<java.lang.String> getPolicyExemptApps(); + method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public java.util.Set<java.lang.String> getPolicyExemptApps(); method public boolean isCurrentInputMethodSetByOwner(); method public boolean isFactoryResetProtectionPolicySupported(); - method @RequiresPermission(anyOf={"android.permission.MARK_DEVICE_ORGANIZATION_OWNED", "android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS"}, conditional=true) public void markProfileOwnerOnOrganizationOwnedDevice(@NonNull android.content.ComponentName); + method @RequiresPermission(anyOf={android.Manifest.permission.MARK_DEVICE_ORGANIZATION_OWNED, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}, conditional=true) public void markProfileOwnerOnOrganizationOwnedDevice(@NonNull android.content.ComponentName); method @NonNull public static String operationSafetyReasonToString(int); method @NonNull public static String operationToString(int); - method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException; - method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void resetDefaultCrossProfileIntentFilters(int); - method @RequiresPermission(allOf={"android.permission.MANAGE_DEVICE_ADMINS", android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void setActiveAdmin(@NonNull android.content.ComponentName, boolean, int); - method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public boolean setDeviceOwner(@NonNull android.content.ComponentName, @Nullable String, int); - method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public boolean setDeviceOwnerOnly(@NonNull android.content.ComponentName, @Nullable String, int); - method @RequiresPermission("android.permission.MANAGE_DEVICE_ADMINS") public void setNextOperationSafety(int, int); + method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException; + method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void resetDefaultCrossProfileIntentFilters(int); + method @RequiresPermission(allOf={android.Manifest.permission.MANAGE_DEVICE_ADMINS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void setActiveAdmin(@NonNull android.content.ComponentName, boolean, int); + method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean setDeviceOwner(@NonNull android.content.ComponentName, @Nullable String, int); + method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean setDeviceOwnerOnly(@NonNull android.content.ComponentName, @Nullable String, int); + method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public void setNextOperationSafety(int, int); field public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED"; field public static final String ACTION_DEVICE_POLICY_CONSTANTS_CHANGED = "android.app.action.DEVICE_POLICY_CONSTANTS_CHANGED"; field public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; // 0x6 @@ -799,6 +799,7 @@ package android.content.pm { field public static final float OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE = 1.7777778f; field public static final long OVERRIDE_MIN_ASPECT_RATIO_MEDIUM = 180326845L; // 0xabf91bdL field public static final float OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE = 1.5f; + field public static final long OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY = 203647190L; // 0xc2368d6L field public static final int RESIZE_MODE_RESIZEABLE = 2; // 0x2 } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index d77227bdcf7c..d7ce3b8b6e1a 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4087,45 +4087,46 @@ public class ActivityManager { * @hide */ @TestApi - public static final int STOP_BG_USERS_ON_SWITCH_DEFAULT = -1; + public static final int STOP_USER_ON_SWITCH_DEFAULT = -1; /** - * Overrides value defined by the platform and stop background users on switch. + * Overrides value defined by the platform and stop user on switch. * * @hide */ @TestApi - public static final int STOP_BG_USERS_ON_SWITCH_TRUE = 1; + public static final int STOP_USER_ON_SWITCH_TRUE = 1; /** - * Overrides value defined by the platform and don't stop background users on switch. + * Overrides value defined by the platform and don't stop user on switch. * * @hide */ @TestApi - public static final int STOP_BG_USERS_ON_SWITCH_FALSE = 0; + public static final int STOP_USER_ON_SWITCH_FALSE = 0; /** @hide */ - @IntDef(prefix = { "STOP_BG_USERS_ON_SWITCH_" }, value = { - STOP_BG_USERS_ON_SWITCH_DEFAULT, - STOP_BG_USERS_ON_SWITCH_TRUE, - STOP_BG_USERS_ON_SWITCH_FALSE + @IntDef(prefix = { "STOP_USER_ON_SWITCH_" }, value = { + STOP_USER_ON_SWITCH_DEFAULT, + STOP_USER_ON_SWITCH_TRUE, + STOP_USER_ON_SWITCH_FALSE }) - public @interface StopBgUsersOnSwitch {} + public @interface StopUserOnSwitch {} /** - * Sets whether background users should be stopped when the current user is switched. + * Sets whether the current foreground user (and its profiles) should be stopped after switched + * out. * - * <p>Should only be used on tests. + * <p>Should only be used on tests. Doesn't apply to {@link UserHandle#SYSTEM system user}. * * @hide */ @TestApi @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) - public void setStopBackgroundUsersOnSwitch(@StopBgUsersOnSwitch int value) { + public void setStopUserOnSwitch(@StopUserOnSwitch int value) { try { - getService().setStopBackgroundUsersOnSwitch(value); + getService().setStopUserOnSwitch(value); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 7dbcd5f3ffb2..324e1aea81e7 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -16,7 +16,7 @@ package android.app; -import static android.app.ActivityManager.StopBgUsersOnSwitch; +import static android.app.ActivityManager.StopUserOnSwitch; import android.annotation.NonNull; import android.annotation.Nullable; @@ -688,9 +688,10 @@ public abstract class ActivityManagerInternal { @Nullable VoiceInteractionManagerProvider provider); /** - * Sets whether background users should be stopped when the current user is switched. + * Sets whether the current foreground user (and its profiles) should be stopped after switched + * out. */ - public abstract void setStopBackgroundUsersOnSwitch(@StopBgUsersOnSwitch int value); + public abstract void setStopUserOnSwitch(@StopUserOnSwitch int value); /** * Provides the interface to communicate between voice interaction manager service and diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 4b343270eb54..853d5e83abad 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -352,7 +352,7 @@ interface IActivityManager { @UnsupportedAppUsage boolean switchUser(int userid); @UnsupportedAppUsage - void setStopBackgroundUsersOnSwitch(int value); + void setStopUserOnSwitch(int value); boolean removeTask(int taskId); @UnsupportedAppUsage void registerProcessObserver(in IProcessObserver observer); diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING index 31c81bef7357..32207af22dc1 100644 --- a/core/java/android/app/TEST_MAPPING +++ b/core/java/android/app/TEST_MAPPING @@ -146,6 +146,15 @@ } ], "file_patterns": ["(/|^)ContextImpl.java"] + }, + { + "file_patterns": ["(/|^)LocaleManager.java"], + "name": "CtsLocaleManagerTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] } ], "presubmit-large": [ diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java index 8e8c035187d0..9045147f4bdd 100644 --- a/core/java/android/app/admin/DevicePolicyManagerInternal.java +++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java @@ -16,6 +16,7 @@ package android.app.admin; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.ComponentName; @@ -77,6 +78,13 @@ public abstract class DevicePolicyManagerInternal { OnCrossProfileWidgetProvidersChangeListener listener); /** + * @param userHandle the handle of the user whose profile owner is being fetched. + * @return the configured supervision app if it exists and is the device owner or policy owner. + */ + public abstract @Nullable ComponentName getProfileOwnerOrDeviceOwnerSupervisionComponent( + @NonNull UserHandle userHandle); + + /** * Checks if an app with given uid is an active device owner of its user. * * <p>This takes the DPMS lock. DO NOT call from PM/UM/AM with their lock held. diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java index 24d124855508..7d1aabc639d9 100644 --- a/core/java/android/companion/AssociationRequest.java +++ b/core/java/android/companion/AssociationRequest.java @@ -94,6 +94,21 @@ public final class AssociationRequest implements Parcelable { public static final String DEVICE_PROFILE_APP_STREAMING = "android.app.role.COMPANION_DEVICE_APP_STREAMING"; + /** + * Device profile: Android Automotive Projection + * + * Only applications that have been granted + * {@link android.Manifest.permission#REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION} are + * allowed to request to be associated with such devices. + * + * @see AssociationRequest.Builder#setDeviceProfile + * @hide + */ + @RequiresPermission(Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION) + @SystemApi + public static final String DEVICE_PROFILE_AUTOMOTIVE_PROJECTION = + "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION"; + /** @hide */ @StringDef(value = { DEVICE_PROFILE_WATCH }) public @interface DeviceProfile {} @@ -498,10 +513,10 @@ public final class AssociationRequest implements Parcelable { }; @DataClass.Generated( - time = 1634716126923L, + time = 1635190605212L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/companion/AssociationRequest.java", - inputSignatures = "private static final java.lang.String LOG_TAG\npublic static final java.lang.String DEVICE_PROFILE_WATCH\npublic static final @android.annotation.RequiresPermission @android.annotation.SystemApi java.lang.String DEVICE_PROFILE_APP_STREAMING\nprivate boolean mSingleDevice\nprivate @com.android.internal.util.DataClass.PluralOf(\"deviceFilter\") @android.annotation.NonNull java.util.List<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.String mCallingPackage\nprivate @android.annotation.Nullable java.lang.String mDeviceProfilePrivilegesDescription\nprivate long mCreationTime\nprivate boolean mSkipPrompt\nprivate void onConstructed()\npublic void setCallingPackage(java.lang.String)\npublic void setDeviceProfilePrivilegesDescription(java.lang.String)\npublic void setSkipPrompt(boolean)\npublic @android.compat.annotation.UnsupportedAppUsage boolean isSingleDevice()\npublic @android.annotation.NonNull @android.compat.annotation.UnsupportedAppUsage java.util.List<android.companion.DeviceFilter<?>> getDeviceFilters()\nclass AssociationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mSingleDevice\nprivate @android.annotation.Nullable java.util.ArrayList<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable java.lang.String mDeviceProfile\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setSingleDevice(boolean)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDeviceProfile(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override android.companion.AssociationRequest build()\nclass Builder extends android.provider.OneTimeUseBuilder<android.companion.AssociationRequest> implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true, genHiddenGetters=true, genParcelable=true, genHiddenConstructor=true, genBuilder=false, genConstDefs=false)") + inputSignatures = "private static final java.lang.String LOG_TAG\npublic static final java.lang.String DEVICE_PROFILE_WATCH\npublic static final @android.annotation.RequiresPermission @android.annotation.SystemApi java.lang.String DEVICE_PROFILE_APP_STREAMING\npublic static final @android.annotation.RequiresPermission @android.annotation.SystemApi java.lang.String DEVICE_PROFILE_AUTOMOTIVE_PROJECTION\nprivate boolean mSingleDevice\nprivate @com.android.internal.util.DataClass.PluralOf(\"deviceFilter\") @android.annotation.NonNull java.util.List<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.String mCallingPackage\nprivate @android.annotation.Nullable java.lang.String mDeviceProfilePrivilegesDescription\nprivate long mCreationTime\nprivate boolean mSkipPrompt\nprivate void onConstructed()\npublic void setCallingPackage(java.lang.String)\npublic void setDeviceProfilePrivilegesDescription(java.lang.String)\npublic void setSkipPrompt(boolean)\npublic @android.compat.annotation.UnsupportedAppUsage boolean isSingleDevice()\npublic @android.annotation.NonNull @android.compat.annotation.UnsupportedAppUsage java.util.List<android.companion.DeviceFilter<?>> getDeviceFilters()\nclass AssociationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mSingleDevice\nprivate @android.annotation.Nullable java.util.ArrayList<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable java.lang.String mDeviceProfile\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setSingleDevice(boolean)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDeviceProfile(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override android.companion.AssociationRequest build()\nclass Builder extends android.provider.OneTimeUseBuilder<android.companion.AssociationRequest> implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true, genHiddenGetters=true, genParcelable=true, genHiddenConstructor=true, genBuilder=false, genConstDefs=false)") @Deprecated private void __metadata() {} diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index e7ca76e23368..1bc930a25070 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -448,6 +448,12 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { * @see android.app.Activity#setVrModeEnabled(boolean, ComponentName) */ public static final int FLAG_ENABLE_VR_MODE = 0x8000; + /** + * Bit in {@link #flags} indicating if the activity can be displayed on a remote device. + * Corresponds to {@link android.R.attr#canDisplayOnRemoteDevices} + * @hide + */ + public static final int FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES = 0x10000; /** * Bit in {@link #flags} indicating if the activity is always focusable regardless of if it is @@ -996,10 +1002,9 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { * OVERRIDE_MIN_ASPECT_RATIO_MEDIUM * OVERRIDE_MIN_ASPECT_RATIO_LARGE * - * If OVERRIDE_MIN_ASPECT_RATIO is applied, and the activity's orientation is fixed to - * portrait, the min aspect ratio given in the app's manifest will be overridden to the - * largest enabled aspect ratio treatment unless the app's manifest value is higher. - * TODO(b/203647190): add OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY instead of portrait by default + * If OVERRIDE_MIN_ASPECT_RATIO is applied, the min aspect ratio given in the app's manifest + * will be overridden to the largest enabled aspect ratio treatment unless the app's manifest + * value is higher. * @hide */ @ChangeId @@ -1009,6 +1014,19 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { public static final long OVERRIDE_MIN_ASPECT_RATIO = 174042980L; // buganizer id /** + * This change id restricts treatments that force a given min aspect ratio to activities + * whose orientation is fixed to portrait. + * + * This treatment only takes effect if OVERRIDE_MIN_ASPECT_RATIO is also enabled. + * @hide + */ + @ChangeId + @Overridable + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S) + @TestApi + public static final long OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY = 203647190L; // buganizer id + + /** * This change id sets the activity's min aspect ratio to a medium value as defined by * OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE. * @@ -1337,9 +1355,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { */ @SizeChangesSupportMode public int supportsSizeChanges() { - if (CompatChanges.isChangeEnabled(FORCE_NON_RESIZE_APP, - applicationInfo.packageName, - UserHandle.getUserHandleForUid(applicationInfo.uid))) { + if (isChangeEnabled(FORCE_NON_RESIZE_APP)) { return SIZE_CHANGES_UNSUPPORTED_OVERRIDE; } @@ -1347,9 +1363,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { return SIZE_CHANGES_SUPPORTED_METADATA; } - if (CompatChanges.isChangeEnabled(FORCE_RESIZE_APP, - applicationInfo.packageName, - UserHandle.getUserHandleForUid(applicationInfo.uid))) { + if (isChangeEnabled(FORCE_RESIZE_APP)) { return SIZE_CHANGES_SUPPORTED_OVERRIDE; } @@ -1361,9 +1375,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { * @hide */ public boolean neverSandboxDisplayApis() { - return CompatChanges.isChangeEnabled(NEVER_SANDBOX_DISPLAY_APIS, - applicationInfo.packageName, - UserHandle.getUserHandleForUid(applicationInfo.uid)) + return isChangeEnabled(NEVER_SANDBOX_DISPLAY_APIS) || ConstrainDisplayApisConfig.neverConstrainDisplayApis(applicationInfo); } @@ -1372,9 +1384,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { * @hide */ public boolean alwaysSandboxDisplayApis() { - return CompatChanges.isChangeEnabled(ALWAYS_SANDBOX_DISPLAY_APIS, - applicationInfo.packageName, - UserHandle.getUserHandleForUid(applicationInfo.uid)) + return isChangeEnabled(ALWAYS_SANDBOX_DISPLAY_APIS) || ConstrainDisplayApisConfig.alwaysConstrainDisplayApis(applicationInfo); } @@ -1404,31 +1414,28 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { * @hide */ public float getMinAspectRatio(@ScreenOrientation int orientation) { - // TODO(b/203647190): check orientation only if OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY - // In case the activity's orientation isn't fixed to portrait, OVERRIDE_MIN_ASPECT_RATIO - // shouldn't be applied. - if (applicationInfo == null || !CompatChanges.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO, - applicationInfo.packageName, - UserHandle.getUserHandleForUid(applicationInfo.uid)) - || !isFixedOrientationPortrait(orientation)) { + if (applicationInfo == null || !isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO) || ( + isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY) + && !isFixedOrientationPortrait(orientation))) { return mMinAspectRatio; } - if (CompatChanges.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_LARGE, - applicationInfo.packageName, - UserHandle.getUserHandleForUid(applicationInfo.uid))) { + if (isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_LARGE)) { return Math.max(OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE, mMinAspectRatio); } - if (CompatChanges.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_MEDIUM, - applicationInfo.packageName, - UserHandle.getUserHandleForUid(applicationInfo.uid))) { + if (isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_MEDIUM)) { return Math.max(OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE, mMinAspectRatio); } return mMinAspectRatio; } + private boolean isChangeEnabled(long changeId) { + return CompatChanges.isChangeEnabled(changeId, applicationInfo.packageName, + UserHandle.getUserHandleForUid(applicationInfo.uid)); + } + /** @hide */ public float getManifestMinAspectRatio() { return mMinAspectRatio; @@ -1496,9 +1503,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { * @hide */ public boolean shouldCheckMinWidthHeightForMultiWindow() { - return CompatChanges.isChangeEnabled(CHECK_MIN_WIDTH_HEIGHT_FOR_MULTI_WINDOW, - applicationInfo.packageName, - UserHandle.getUserHandleForUid(applicationInfo.uid)); + return isChangeEnabled(CHECK_MIN_WIDTH_HEIGHT_FOR_MULTI_WINDOW); } public void dump(Printer pw, String prefix) { diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java index 0a10aaaa2060..e3a5de514b22 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java +++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java @@ -194,6 +194,8 @@ public class ParsingPackageUtils { public static final String METADATA_MAX_ASPECT_RATIO = "android.max_aspect"; public static final String METADATA_SUPPORTS_SIZE_CHANGES = "android.supports_size_changes"; + public static final String METADATA_CAN_DISPLAY_ON_REMOTE_DEVICES = + "android.can_display_on_remote_devices"; public static final String METADATA_ACTIVITY_WINDOW_LAYOUT_AFFINITY = "android.activity_window_layout_affinity"; public static final String METADATA_ACTIVITY_LAUNCH_MODE = "android.activity.launch_mode"; diff --git a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java index 45241b0cb280..2ddf923d7dd9 100644 --- a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java +++ b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java @@ -420,6 +420,21 @@ public class ParsedActivityUtils { } } + if (!isAlias) { + // Default allow the activity to be displayed on a remote device unless it explicitly + // set to false. + boolean canDisplayOnRemoteDevices = array.getBoolean( + R.styleable.AndroidManifestActivity_canDisplayOnRemoteDevices, true); + if (activity.getMetaData() != null && !activity.getMetaData().getBoolean( + ParsingPackageUtils.METADATA_CAN_DISPLAY_ON_REMOTE_DEVICES, true)) { + canDisplayOnRemoteDevices = false; + } + if (canDisplayOnRemoteDevices) { + activity.setFlags(activity.getFlags() + | ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES); + } + } + ParseResult<ActivityInfo.WindowLayout> layoutResult = resolveActivityWindowLayout(activity, input); if (layoutResult.isError()) { diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index c8c122da4ab8..6b5bec99e674 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -408,6 +408,19 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan } /** + * Flag to decide if authentication should ignore enrollment state. + * Defaults to false (not ignoring enrollment state) + * @param ignoreEnrollmentState + * @return This builder. + * @hide + */ + @NonNull + public Builder setIgnoreEnrollmentState(boolean ignoreEnrollmentState) { + mPromptInfo.setIgnoreEnrollmentState(ignoreEnrollmentState); + return this; + } + + /** * Creates a {@link BiometricPrompt}. * * @return An instance of {@link BiometricPrompt}. diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java index 339c654f4d2f..e6b762a64384 100644 --- a/core/java/android/hardware/biometrics/PromptInfo.java +++ b/core/java/android/hardware/biometrics/PromptInfo.java @@ -45,6 +45,7 @@ public class PromptInfo implements Parcelable { private boolean mReceiveSystemEvents; @NonNull private List<Integer> mAllowedSensorIds = new ArrayList<>(); private boolean mAllowBackgroundAuthentication; + private boolean mIgnoreEnrollmentState; public PromptInfo() { @@ -66,6 +67,7 @@ public class PromptInfo implements Parcelable { mReceiveSystemEvents = in.readBoolean(); mAllowedSensorIds = in.readArrayList(Integer.class.getClassLoader()); mAllowBackgroundAuthentication = in.readBoolean(); + mIgnoreEnrollmentState = in.readBoolean(); } public static final Creator<PromptInfo> CREATOR = new Creator<PromptInfo>() { @@ -102,6 +104,7 @@ public class PromptInfo implements Parcelable { dest.writeBoolean(mReceiveSystemEvents); dest.writeList(mAllowedSensorIds); dest.writeBoolean(mAllowBackgroundAuthentication); + dest.writeBoolean(mIgnoreEnrollmentState); } public boolean containsTestConfigurations() { @@ -192,6 +195,10 @@ public class PromptInfo implements Parcelable { mAllowBackgroundAuthentication = allow; } + public void setIgnoreEnrollmentState(boolean ignoreEnrollmentState) { + mIgnoreEnrollmentState = ignoreEnrollmentState; + } + // Getters public CharSequence getTitle() { @@ -261,4 +268,8 @@ public class PromptInfo implements Parcelable { public boolean isAllowBackgroundAuthentication() { return mAllowBackgroundAuthentication; } + + public boolean isIgnoreEnrollmentState() { + return mIgnoreEnrollmentState; + } } diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index a3d595c23095..fe04e5d35784 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -531,7 +531,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT}) public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel, int flags, @NonNull AuthenticationCallback callback, @Nullable Handler handler) { - authenticate(crypto, cancel, callback, handler, mContext.getUserId()); + authenticate(crypto, cancel, callback, handler, SENSOR_ID_ANY, mContext.getUserId(), flags); } /** @@ -541,7 +541,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT}) public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel, @NonNull AuthenticationCallback callback, Handler handler, int userId) { - authenticate(crypto, cancel, callback, handler, SENSOR_ID_ANY, userId); + authenticate(crypto, cancel, callback, handler, SENSOR_ID_ANY, userId, 0 /* flags */); } /** @@ -550,7 +550,8 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing */ @RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT}) public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel, - @NonNull AuthenticationCallback callback, Handler handler, int sensorId, int userId) { + @NonNull AuthenticationCallback callback, Handler handler, int sensorId, int userId, + int flags) { FrameworkStatsLog.write(FrameworkStatsLog.AUTH_DEPRECATED_API_USED, AUTH_DEPRECATED_APIUSED__DEPRECATED_API__API_FINGERPRINT_MANAGER_AUTHENTICATE, @@ -566,6 +567,8 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing return; } + final boolean ignoreEnrollmentState = flags == 0 ? false : true; + if (mService != null) { try { useHandler(handler); @@ -573,7 +576,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing mCryptoObject = crypto; final long operationId = crypto != null ? crypto.getOpId() : 0; final long authId = mService.authenticate(mToken, operationId, sensorId, userId, - mServiceReceiver, mContext.getOpPackageName()); + mServiceReceiver, mContext.getOpPackageName(), ignoreEnrollmentState); if (cancel != null) { cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId)); } diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index de94b2fbb5b5..ba1dc6da62a6 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -52,7 +52,8 @@ interface IFingerprintService { // permission. This is effectively deprecated, since it only comes through FingerprintManager // now. A requestId is returned that can be used to cancel this operation. long authenticate(IBinder token, long operationId, int sensorId, int userId, - IFingerprintServiceReceiver receiver, String opPackageName); + IFingerprintServiceReceiver receiver, String opPackageName, + boolean shouldIgnoreEnrollmentState); // Uses the fingerprint hardware to detect for the presence of a finger, without giving details // about accept/reject/lockout. A requestId is returned that can be used to cancel this diff --git a/core/java/android/inputmethodservice/RemoteInputConnection.java b/core/java/android/inputmethodservice/RemoteInputConnection.java index ae97fe7df729..ed617afab96e 100644 --- a/core/java/android/inputmethodservice/RemoteInputConnection.java +++ b/core/java/android/inputmethodservice/RemoteInputConnection.java @@ -31,6 +31,7 @@ import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputContentInfo; import android.view.inputmethod.SurroundingText; +import android.view.inputmethod.TextAttribute; import com.android.internal.inputmethod.CancellationGroup; import com.android.internal.inputmethod.CompletableFutureUtil; @@ -272,6 +273,17 @@ final class RemoteInputConnection implements InputConnection { } @AnyThread + public boolean commitText(@NonNull CharSequence text, int newCursorPosition, + @Nullable TextAttribute textAttribute) { + final boolean handled = + mInvoker.commitText(text, newCursorPosition, textAttribute); + if (handled) { + notifyUserActionIfNecessary(); + } + return handled; + } + + @AnyThread private void notifyUserActionIfNecessary() { final InputMethodServiceInternal imsInternal = mImsInternal.getAndWarnIfNull(); if (imsInternal == null) { @@ -311,6 +323,11 @@ final class RemoteInputConnection implements InputConnection { } @AnyThread + public boolean setComposingRegion(int start, int end, @Nullable TextAttribute textAttribute) { + return mInvoker.setComposingRegion(start, end, textAttribute); + } + + @AnyThread public boolean setComposingText(CharSequence text, int newCursorPosition) { final boolean handled = mInvoker.setComposingText(text, newCursorPosition); if (handled) { @@ -320,6 +337,16 @@ final class RemoteInputConnection implements InputConnection { } @AnyThread + public boolean setComposingText(CharSequence text, int newCursorPosition, + @Nullable TextAttribute textAttribute) { + final boolean handled = mInvoker.setComposingText(text, newCursorPosition, textAttribute); + if (handled) { + notifyUserActionIfNecessary(); + } + return handled; + } + + @AnyThread public boolean finishComposingText() { return mInvoker.finishComposingText(); } diff --git a/core/java/android/net/IpSecAlgorithm.java b/core/java/android/net/IpSecAlgorithm.java index 7ef5bac092f6..86052484eaf6 100644 --- a/core/java/android/net/IpSecAlgorithm.java +++ b/core/java/android/net/IpSecAlgorithm.java @@ -232,11 +232,10 @@ public final class IpSecAlgorithm implements Parcelable { ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_HMAC_SHA512, SDK_VERSION_ZERO); ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_CRYPT_AES_GCM, SDK_VERSION_ZERO); - // STOPSHIP: b/170424293 Use Build.VERSION_CODES.S when it is defined - ALGO_TO_REQUIRED_FIRST_SDK.put(CRYPT_AES_CTR, Build.VERSION_CODES.R + 1); - ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_AES_XCBC, Build.VERSION_CODES.R + 1); - ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_AES_CMAC, Build.VERSION_CODES.R + 1); - ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_CRYPT_CHACHA20_POLY1305, Build.VERSION_CODES.R + 1); + ALGO_TO_REQUIRED_FIRST_SDK.put(CRYPT_AES_CTR, Build.VERSION_CODES.S); + ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_AES_XCBC, Build.VERSION_CODES.S); + ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_AES_CMAC, Build.VERSION_CODES.S); + ALGO_TO_REQUIRED_FIRST_SDK.put(AUTH_CRYPT_CHACHA20_POLY1305, Build.VERSION_CODES.S); } private static final Set<String> ENABLED_ALGOS = diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index d1e671691897..09e5a8f7382c 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -62,6 +62,8 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.function.Function; +import java.util.function.IntFunction; import java.util.function.Supplier; /** @@ -178,8 +180,12 @@ import java.util.function.Supplier; * {@link #writeStrongInterface(IInterface)}, {@link #readStrongBinder()}, * {@link #writeBinderArray(IBinder[])}, {@link #readBinderArray(IBinder[])}, * {@link #createBinderArray()}, + * {@link #writeInterfaceArray(T[])}, {@link #readInterfaceArray(T[], Function)}, + * {@link #createInterfaceArray(IntFunction, Function)}, * {@link #writeBinderList(List)}, {@link #readBinderList(List)}, - * {@link #createBinderArrayList()}.</p> + * {@link #createBinderArrayList()}, + * {@link #writeInterfaceList(List)}, {@link #readInterfaceList(List, Function)}, + * {@link #createInterfaceArrayList(Function)}.</p> * * <p>FileDescriptor objects, representing raw Linux file descriptor identifiers, * can be written and {@link ParcelFileDescriptor} objects returned to operate @@ -1730,6 +1736,30 @@ public final class Parcel { } /** + * Flatten a homogeneous array containing an IInterface type into the parcel, + * at the current dataPosition() and growing dataCapacity() if needed. The + * type of the objects in the array must be one that implements IInterface. + * + * @param val The array of objects to be written. + * + * @see #createInterfaceArray + * @see #readInterfaceArray + * @see IInterface + */ + public final <T extends IInterface> void writeInterfaceArray( + @SuppressLint("ArrayReturn") @Nullable T[] val) { + if (val != null) { + int N = val.length; + writeInt(N); + for (int i=0; i<N; i++) { + writeStrongInterface(val[i]); + } + } else { + writeInt(-1); + } + } + + /** * @hide */ public final void writeCharSequenceArray(@Nullable CharSequence[] val) { @@ -1785,6 +1815,50 @@ public final class Parcel { } /** + * Read and return a new array of T (IInterface) from the parcel. + * + * @return the IInterface array of type T + * @param newArray a function to create an array of T with a given length + * @param asInterface a function to convert IBinder object into T (IInterface) + */ + @SuppressLint({"ArrayReturn", "NullableCollection", "SamShouldBeLast"}) + @Nullable + public final <T extends IInterface> T[] createInterfaceArray( + @NonNull IntFunction<T[]> newArray, @NonNull Function<IBinder, T> asInterface) { + int N = readInt(); + if (N >= 0) { + T[] val = newArray.apply(N); + for (int i=0; i<N; i++) { + val[i] = asInterface.apply(readStrongBinder()); + } + return val; + } else { + return null; + } + } + + /** + * Read an array of T (IInterface) from a parcel. + * + * @param asInterface a function to convert IBinder object into T (IInterface) + * + * @throws BadParcelableException Throws BadParcelableException if the length of `val` + * mismatches the number of items in the parcel. + */ + public final <T extends IInterface> void readInterfaceArray( + @SuppressLint("ArrayReturn") @NonNull T[] val, + @NonNull Function<IBinder, T> asInterface) { + int N = readInt(); + if (N == val.length) { + for (int i=0; i<N; i++) { + val[i] = asInterface.apply(readStrongBinder()); + } + } else { + throw new BadParcelableException("bad array lengths"); + } + } + + /** * Flatten a List containing a particular object type into the parcel, at * the current dataPosition() and growing dataCapacity() if needed. The * type of the objects in the list must be one that implements Parcelable. @@ -1898,6 +1972,28 @@ public final class Parcel { } /** + * Flatten a {@code List} containing T (IInterface) objects into this parcel + * at the current position. They can later be retrieved with + * {@link #createInterfaceArrayList} or {@link #readInterfaceList}. + * + * @see #createInterfaceArrayList + * @see #readInterfaceList + */ + public final <T extends IInterface> void writeInterfaceList(@Nullable List<T> val) { + if (val == null) { + writeInt(-1); + return; + } + int N = val.size(); + int i=0; + writeInt(N); + while (i < N) { + writeStrongInterface(val.get(i)); + i++; + } + } + + /** * Flatten a {@code List} containing arbitrary {@code Parcelable} objects into this parcel * at the current position. They can later be retrieved using * {@link #readParcelableList(List, ClassLoader)} if required. @@ -2896,8 +2992,24 @@ public final class Parcel { * from the parcel at the current dataPosition(). */ public final void readMap(@NonNull Map outVal, @Nullable ClassLoader loader) { - int N = readInt(); - readMapInternal(outVal, N, loader); + int n = readInt(); + readMapInternal(outVal, n, loader, /* clazzKey */ null, /* clazzValue */ null); + } + + /** + * Same as {@link #readMap(Map, ClassLoader)} but accepts {@code clazzKey} and + * {@code clazzValue} parameter as the types required for each key and value pair. + * + * @throws BadParcelableException If the item to be deserialized is not an instance of that + * class or any of its children class + */ + public <K, V> void readMap(@NonNull Map<? super K, ? super V> outVal, + @Nullable ClassLoader loader, @NonNull Class<K> clazzKey, + @NonNull Class<V> clazzValue) { + Objects.requireNonNull(clazzKey); + Objects.requireNonNull(clazzValue); + int n = readInt(); + readMapInternal(outVal, n, loader, clazzKey, clazzValue); } /** @@ -2935,16 +3047,38 @@ public final class Parcel { @Nullable public final HashMap readHashMap(@Nullable ClassLoader loader) { - int N = readInt(); - if (N < 0) { + int n = readInt(); + if (n < 0) { return null; } - HashMap m = new HashMap(N); - readMapInternal(m, N, loader); + HashMap m = new HashMap(n); + readMapInternal(m, n, loader, /* clazzKey */ null, /* clazzValue */ null); return m; } /** + * Same as {@link #readHashMap(ClassLoader)} but accepts {@code clazzKey} and + * {@code clazzValue} parameter as the types required for each key and value pair. + * + * @throws BadParcelableException if the item to be deserialized is not an instance of that + * class or any of its children class + */ + @SuppressLint({"ConcreteCollection", "NullableCollection"}) + @Nullable + public <K, V> HashMap<K, V> readHashMap(@Nullable ClassLoader loader, + @NonNull Class<? extends K> clazzKey, @NonNull Class<? extends V> clazzValue) { + Objects.requireNonNull(clazzKey); + Objects.requireNonNull(clazzValue); + int n = readInt(); + if (n < 0) { + return null; + } + HashMap<K, V> map = new HashMap<>(n); + readMapInternal(map, n, loader, clazzKey, clazzValue); + return map; + } + + /** * Read and return a new Bundle object from the parcel at the current * dataPosition(). Returns null if the previously written Bundle object was * null. @@ -3380,6 +3514,32 @@ public final class Parcel { } /** + * Read and return a new ArrayList containing T (IInterface) objects from + * the parcel that was written with {@link #writeInterfaceList} at the + * current dataPosition(). Returns null if the + * previously written list object was null. + * + * @return A newly created ArrayList containing T (IInterface) + * + * @see #writeInterfaceList + */ + @SuppressLint({"ConcreteCollection", "NullableCollection"}) + @Nullable + public final <T extends IInterface> ArrayList<T> createInterfaceArrayList( + @NonNull Function<IBinder, T> asInterface) { + int N = readInt(); + if (N < 0) { + return null; + } + ArrayList<T> l = new ArrayList<T>(N); + while (N > 0) { + l.add(asInterface.apply(readStrongBinder())); + N--; + } + return l; + } + + /** * Read into the given List items String objects that were written with * {@link #writeStringList} at the current dataPosition(). * @@ -3422,6 +3582,28 @@ public final class Parcel { } /** + * Read into the given List items IInterface objects that were written with + * {@link #writeInterfaceList} at the current dataPosition(). + * + * @see #writeInterfaceList + */ + public final <T extends IInterface> void readInterfaceList(@NonNull List<T> list, + @NonNull Function<IBinder, T> asInterface) { + int M = list.size(); + int N = readInt(); + int i = 0; + for (; i < M && i < N; i++) { + list.set(i, asInterface.apply(readStrongBinder())); + } + for (; i<N; i++) { + list.add(asInterface.apply(readStrongBinder())); + } + for (; i<M; i++) { + list.remove(N); + } + } + + /** * Read the list of {@code Parcelable} objects at the current data position into the * given {@code list}. The contents of the {@code list} are replaced. If the serialized * list was {@code null}, {@code list} is cleared. @@ -4328,13 +4510,23 @@ public final class Parcel { destroy(); } - /* package */ void readMapInternal(@NonNull Map outVal, int N, + /** + * To be replaced by {@link #readMapInternal(Map, int, ClassLoader, Class, Class)}, but keep + * the old API for compatibility usages. + */ + /* package */ void readMapInternal(@NonNull Map outVal, int n, @Nullable ClassLoader loader) { - while (N > 0) { - Object key = readValue(loader); - Object value = readValue(loader); + readMapInternal(outVal, n, loader, /* clazzKey */null, /* clazzValue */null); + } + + /* package */ <K, V> void readMapInternal(@NonNull Map<? super K, ? super V> outVal, int n, + @Nullable ClassLoader loader, @Nullable Class<K> clazzKey, + @Nullable Class<V> clazzValue) { + while (n > 0) { + K key = readValue(loader, clazzKey); + V value = readValue(loader, clazzValue); outVal.put(key, value); - N--; + n--; } } diff --git a/core/java/android/os/health/HealthStats.java b/core/java/android/os/health/HealthStats.java index 74ce5157a548..6c648f136183 100644 --- a/core/java/android/os/health/HealthStats.java +++ b/core/java/android/os/health/HealthStats.java @@ -32,7 +32,7 @@ import java.util.Map; * Each of the keys references data in one of five data types: * * <p> - * A <b>measurement</b> metric contains a sinlge {@code long} value. That value may + * A <b>measurement</b> metric contains a single {@code long} value. That value may * be a count, a time, or some other type of value. The unit for a measurement * (COUNT, MS, etc) will always be in the name of the constant for the key to * retrieve it. For example, the diff --git a/core/java/android/os/health/UidHealthStats.java b/core/java/android/os/health/UidHealthStats.java index afc9d78dcbd1..488a5422becc 100644 --- a/core/java/android/os/health/UidHealthStats.java +++ b/core/java/android/os/health/UidHealthStats.java @@ -43,14 +43,14 @@ public final class UidHealthStats { /** * How many milliseconds this statistics report covers in wall-clock time while the - * device was on battery including both screen-on and screen-off time. + * device was on battery including only screen-off time. */ @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT) public static final int MEASUREMENT_REALTIME_SCREEN_OFF_BATTERY_MS = HealthKeys.BASE_UID + 3; /** * How many milliseconds this statistics report covers that the CPU was running while the - * device was on battery including both screen-on and screen-off time. + * device was on battery including only screen-off time. */ @HealthKeys.Constant(type=HealthKeys.TYPE_MEASUREMENT) public static final int MEASUREMENT_UPTIME_SCREEN_OFF_BATTERY_MS = HealthKeys.BASE_UID + 4; @@ -65,7 +65,7 @@ public final class UidHealthStats { /** * Key for a TimerStat for the times a - * {@link android.os.PowerManager#PARTIAL_WAKE_LOCK full wake lock} + * {@link android.os.PowerManager#PARTIAL_WAKE_LOCK partial wake lock} * was acquired for this uid. */ @HealthKeys.Constant(type=HealthKeys.TYPE_TIMERS) diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index dd31e027997f..21c1feba662e 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -648,6 +648,14 @@ public final class DeviceConfig { public static final String NAMESPACE_GAME_OVERLAY = "game_overlay"; /** + * Namespace for Android Virtualization Framework related features accessible by native code. + * + * @hide + */ + public static final String NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE = + "virtualization_framework_native"; + + /** * Namespace for Constrain Display APIs related features. * * @hide diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 0348107e151d..295ed774f1e4 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -16609,30 +16609,6 @@ public final class Settings { public static final String WEAR_PLATFORM_MR_NUMBER = "wear_platform_mr_number"; /** - * The bluetooth settings storing duplicate address of companion device. - * @hide - */ - public static final String COMPANION_BT_ADDRESS_DUAL = "companion_bt_address_dual"; - - /** - * The offset of the visible screen from the display bottom (overscan bottom). - * @hide - */ - public static final String BOTTOM_OFFSET = "bottom_offset"; - - /** - * The shape of the display. - * @hide - */ - public static final String DISPLAY_SHAPE = "display_shape"; - - // Possible display shapes - /** @hide */ - public static final int DISPLAY_SHAPE_SQUARE = 0; - /** @hide */ - public static final int DISPLAY_SHAPE_ROUND = 1; - - /** * The different levels of screen brightness the user can select. * @hide */ @@ -16706,12 +16682,6 @@ public final class Settings { public static final String AMBIENT_PLUGGED_TIMEOUT_MIN = "ambient_plugged_timeout_min"; /** - * The companion device's bluetooth address. - * @hide - */ - public static final String COMPANION_ADDRESS = "companion_address"; - - /** * What OS does paired device has. * @hide */ @@ -16758,12 +16728,6 @@ public final class Settings { public static final int HFP_CLIENT_DISABLED = 2; /** - * The current HFP client profile setting. - * @hide - */ - public static final String HFP_CLIENT_PROFILE_ENABLED = "hfp_client_profile_enabled"; - - /** * The companion phone's android version. * @hide */ diff --git a/core/java/android/util/TimingsTraceLog.java b/core/java/android/util/TimingsTraceLog.java index 5370645d31bc..066709fd8744 100644 --- a/core/java/android/util/TimingsTraceLog.java +++ b/core/java/android/util/TimingsTraceLog.java @@ -61,13 +61,33 @@ public class TimingsTraceLog { mTraceTag = traceTag; mThreadId = Thread.currentThread().getId(); mMaxNestedCalls = maxNestedCalls; - if (maxNestedCalls > 0) { - mStartNames = new String[maxNestedCalls]; - mStartTimes = new long[maxNestedCalls]; - } else { - mStartNames = null; - mStartTimes = null; - } + this.mStartNames = createAndGetStartNamesArray(); + this.mStartTimes = createAndGetStartTimesArray(); + } + + /** + * Note: all fields will be copied except for {@code mStartNames} and {@code mStartTimes} + * in order to save memory. The copied object is only expected to be used at levels deeper than + * the value of {@code mCurrentLevel} when the object is copied. + * + * @param other object to be copied + */ + protected TimingsTraceLog(TimingsTraceLog other) { + this.mTag = other.mTag; + this.mTraceTag = other.mTraceTag; + this.mThreadId = Thread.currentThread().getId(); + this.mMaxNestedCalls = other.mMaxNestedCalls; + this.mStartNames = createAndGetStartNamesArray(); + this.mStartTimes = createAndGetStartTimesArray(); + this.mCurrentLevel = other.mCurrentLevel; + } + + private String[] createAndGetStartNamesArray() { + return mMaxNestedCalls > 0 ? new String[mMaxNestedCalls] : null; + } + + private long[] createAndGetStartTimesArray() { + return mMaxNestedCalls > 0 ? new long[mMaxNestedCalls] : null; } /** diff --git a/core/java/android/view/IRecentsAnimationRunner.aidl b/core/java/android/view/IRecentsAnimationRunner.aidl index aca17e448b82..c7fd38092ec7 100644 --- a/core/java/android/view/IRecentsAnimationRunner.aidl +++ b/core/java/android/view/IRecentsAnimationRunner.aidl @@ -63,5 +63,5 @@ oneway interface IRecentsAnimationRunner { * Called when the task of an activity that has been started while the recents animation * was running becomes ready for control. */ - void onTaskAppeared(in RemoteAnimationTarget app) = 3; + void onTasksAppeared(in RemoteAnimationTarget[] app) = 3; } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index be1596511432..ce96ecaf9c2f 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -217,6 +217,7 @@ import java.util.List; import java.util.Objects; import java.util.Queue; import java.util.concurrent.CountDownLatch; +import java.util.function.Consumer; /** * The top of a view hierarchy, implementing the needed protocol between View @@ -729,7 +730,7 @@ public final class ViewRootImpl implements ViewParent, /** * This is only used on the RenderThread when handling a blast sync. Specifically, it's only - * used when calling {@link BLASTBufferQueue#setNextTransaction(Transaction)} and then merged + * used when calling {@link BLASTBufferQueue#setSyncTransaction(Transaction)} and then merged * with a tmp transaction on the Render Thread. The tmp transaction is then merged into * {@link #mSurfaceChangedTransaction} on the UI Thread, avoiding any threading issues. */ @@ -742,6 +743,8 @@ public final class ViewRootImpl implements ViewParent, */ private long mRtLastAttemptedDrawFrameNum = 0; + private Consumer<SurfaceControl.Transaction> mBLASTDrawConsumer; + private HashSet<ScrollCaptureCallback> mRootScrollCaptureCallbacks; private long mScrollCaptureRequestTimeout = SCROLL_CAPTURE_REQUEST_TIMEOUT_MILLIS; @@ -3259,6 +3262,9 @@ public final class ViewRootImpl implements ViewParent, } boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible; + if (mBLASTDrawConsumer != null) { + useBlastSync = true; + } if (!cancelDraw) { if (mPendingTransitions != null && mPendingTransitions.size() > 0) { @@ -3977,6 +3983,9 @@ public final class ViewRootImpl implements ViewParent, Log.d(mTag, "Creating frameCompleteCallback"); } + final Consumer<SurfaceControl.Transaction> blastSyncConsumer = mBLASTDrawConsumer; + mBLASTDrawConsumer = null; + mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(() -> { long frameNr = mBlastBufferQueue.getLastAcquiredFrameNum(); if (DEBUG_BLAST) { @@ -3990,7 +3999,7 @@ public final class ViewRootImpl implements ViewParent, // draw attempt. The next transaction and transaction complete callback were only set // for the current draw attempt. if (frameWasNotDrawn) { - mBlastBufferQueue.setNextTransaction(null); + mBlastBufferQueue.setSyncTransaction(null); // Apply the transactions that were sent to mergeWithNextTransaction since the // frame didn't draw on this vsync. It's possible the frame will draw later, but // it's better to not be sync than to block on a frame that may never come. @@ -4002,6 +4011,9 @@ public final class ViewRootImpl implements ViewParent, mHandler.postAtFrontOfQueue(() -> { if (useBlastSync) { mSurfaceChangedTransaction.merge(tmpTransaction); + if (blastSyncConsumer != null) { + blastSyncConsumer.accept(mSurfaceChangedTransaction); + } } if (reportNextDraw) { @@ -4083,7 +4095,7 @@ public final class ViewRootImpl implements ViewParent, // We don't need to synchronize mRtBLASTSyncTransaction here since it's not // being modified and only sent to BlastBufferQueue. - mBlastBufferQueue.setNextTransaction(mRtBLASTSyncTransaction); + mBlastBufferQueue.setSyncTransaction(mRtBLASTSyncTransaction); } }; registerRtFrameCallback(frameDrawingCallback); @@ -10457,4 +10469,35 @@ public final class ViewRootImpl implements ViewParent, listener.onBufferTransformHintChanged(hint); } } + + /** + * Redirect the next draw of this ViewRoot (from the UI thread perspective) + * to the passed in consumer. This can be used to create P2P synchronization + * between ViewRoot's however it comes with many caveats. + * + * 1. You MUST consume the transaction, by either applying it immediately or + * merging it in to another transaction. The threading model doesn't + * allow you to hold in the passed transaction. + * 2. If you merge it in to another transaction, this ViewRootImpl will be + * paused until you finally apply that transaction and it receives + * the callback from SF. If you lose track of the transaction you will + * ANR the app. + * 3. Only one person can consume the transaction at a time, if you already + * have a pending consumer for this frame, the function will return false + * 4. Someone else may have requested to consume the next frame, in which case + * this function will return false and you will not receive a callback. + * 5. This function does not trigger drawing so even if it returns true you + * may not receive a callback unless there is some other UI thread work + * to trigger drawing. If it returns true, and a draw occurs, the callback + * will be called (Though again watch out for the null transaction case!) + * 6. This function must be called on the UI thread. The consumer will likewise + * be called on the UI thread. + */ + public boolean consumeNextDraw(Consumer<SurfaceControl.Transaction> consume) { + if (mBLASTDrawConsumer != null) { + return false; + } + mBLASTDrawConsumer = consume; + return true; + } } diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index d76f7894a9de..8287de22c49a 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -2442,6 +2442,20 @@ public interface WindowManager extends ViewManager { public static final int PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY = 0x00100000; /** + * Flag to indicate that this window will be excluded while computing the magnifiable region + * on the un-scaled screen coordinate, which could avoid the cutout on the magnification + * border. It should be used for unmagnifiable overlays. + * + * </p><p> + * Note unlike {@link #PRIVATE_FLAG_NOT_MAGNIFIABLE}, this flag doesn't affect the ability + * of magnification. If you want to the window to be unmagnifiable and doesn't lead to the + * cutout, you need to combine both of them. + * </p><p> + * @hide + */ + public static final int PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION = 0x00200000; + + /** * Flag to prevent the window from being magnified by the accessibility magnifier. * * TODO(b/190623172): This is a temporary solution and need to find out another way instead. @@ -2552,6 +2566,7 @@ public interface WindowManager extends ViewManager { PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE, SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY, + PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION, PRIVATE_FLAG_NOT_MAGNIFIABLE, PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION, PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC, @@ -2633,6 +2648,10 @@ public interface WindowManager extends ViewManager { equals = PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY, name = "IS_ROUNDED_CORNERS_OVERLAY"), @ViewDebug.FlagToString( + mask = PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION, + equals = PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION, + name = "EXCLUDE_FROM_SCREEN_MAGNIFICATION"), + @ViewDebug.FlagToString( mask = PRIVATE_FLAG_NOT_MAGNIFIABLE, equals = PRIVATE_FLAG_NOT_MAGNIFIABLE, name = "NOT_MAGNIFIABLE"), diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java index c3d7836a4786..3b15db2ded70 100644 --- a/core/java/android/view/inputmethod/InputConnection.java +++ b/core/java/android/view/inputmethod/InputConnection.java @@ -545,6 +545,33 @@ public interface InputConnection { boolean setComposingText(CharSequence text, int newCursorPosition); /** + * The variant of {@link #setComposingText(CharSequence, int)}. This method is + * used to allow the IME to provide extra information while setting up composing text. + * + * @param text The composing text with styles if necessary. If no style + * object attached to the text, the default style for composing text + * is used. See {@link android.text.Spanned} for how to attach style + * object to the text. {@link android.text.SpannableString} and + * {@link android.text.SpannableStringBuilder} are two + * implementations of the interface {@link android.text.Spanned}. + * @param newCursorPosition The new cursor position around the text. If + * > 0, this is relative to the end of the text - 1; if <= 0, this + * is relative to the start of the text. So a value of 1 will + * always advance you to the position after the full text being + * inserted. Note that this means you can't position the cursor + * within the text, because the editor can make modifications to + * the text you are providing so it is not possible to correctly + * specify locations there. + * @param textAttribute The extra information about the text. + * @return true on success, false if the input connection is no longer + * + */ + default boolean setComposingText(@NonNull CharSequence text, int newCursorPosition, + @Nullable TextAttribute textAttribute) { + return setComposingText(text, newCursorPosition); + } + + /** * Mark a certain region of text as composing text. If there was a * composing region, the characters are left as they were and the * composing span removed, as if {@link #finishComposingText()} @@ -579,6 +606,22 @@ public interface InputConnection { boolean setComposingRegion(int start, int end); /** + * The variant of {@link InputConnection#setComposingRegion(int, int)}. This method is + * used to allow the IME to provide extra information while setting up text. + * + * @param start the position in the text at which the composing region begins + * @param end the position in the text at which the composing region ends + * @param textAttribute The extra information about the text. + * @return {@code true} on success, {@code false} if the input connection is no longer valid. + * Since Android {@link android.os.Build.VERSION_CODES#N} until + * {@link android.os.Build.VERSION_CODES#TIRAMISU}, this API returned {@code false} when + * the target application does not implement this method. + */ + default boolean setComposingRegion(int start, int end, @Nullable TextAttribute textAttribute) { + return setComposingRegion(start, end); + } + + /** * Have the text editor finish whatever composing text is * currently active. This simply leaves the text as-is, removing * any special composing styling or other state that was around @@ -634,6 +677,28 @@ public interface InputConnection { boolean commitText(CharSequence text, int newCursorPosition); /** + * The variant of {@link InputConnection#commitText(CharSequence, int)}. This method is + * used to allow the IME to provide extra information while setting up text. + * + * @param text The text to commit. This may include styles. + * @param newCursorPosition The new cursor position around the text, + * in Java characters. If > 0, this is relative to the end + * of the text - 1; if <= 0, this is relative to the start + * of the text. So a value of 1 will always advance the cursor + * to the position after the full text being inserted. Note that + * this means you can't position the cursor within the text, + * because the editor can make modifications to the text + * you are providing so it is not possible to correctly specify + * locations there. + * @param textAttribute The extra information about the text. + * @return true on success, false if the input connection is no longer + */ + default boolean commitText(@NonNull CharSequence text, int newCursorPosition, + @Nullable TextAttribute textAttribute) { + return commitText(text, newCursorPosition); + } + + /** * Commit a completion the user has selected from the possible ones * previously reported to {@link InputMethodSession#displayCompletions * InputMethodSession#displayCompletions(CompletionInfo[])} or diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java index a99e9b8aab07..7a88a75f93ad 100644 --- a/core/java/android/view/inputmethod/InputConnectionWrapper.java +++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java @@ -17,6 +17,7 @@ package android.view.inputmethod; import android.annotation.IntRange; +import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Bundle; import android.os.Handler; @@ -158,6 +159,16 @@ public class InputConnectionWrapper implements InputConnection { * @throws NullPointerException if the target is {@code null}. */ @Override + public boolean setComposingText(@NonNull CharSequence text, + int newCursorPosition, @Nullable TextAttribute textAttribute) { + return mTarget.setComposingText(text, newCursorPosition, textAttribute); + } + + /** + * {@inheritDoc} + * @throws NullPointerException if the target is {@code null}. + */ + @Override public boolean setComposingRegion(int start, int end) { return mTarget.setComposingRegion(start, end); } @@ -167,6 +178,15 @@ public class InputConnectionWrapper implements InputConnection { * @throws NullPointerException if the target is {@code null}. */ @Override + public boolean setComposingRegion(int start, int end, @Nullable TextAttribute textAttribute) { + return mTarget.setComposingRegion(start, end, textAttribute); + } + + /** + * {@inheritDoc} + * @throws NullPointerException if the target is {@code null}. + */ + @Override public boolean finishComposingText() { return mTarget.finishComposingText(); } @@ -185,6 +205,16 @@ public class InputConnectionWrapper implements InputConnection { * @throws NullPointerException if the target is {@code null}. */ @Override + public boolean commitText(@NonNull CharSequence text, int newCursorPosition, + @Nullable TextAttribute textAttribute) { + return mTarget.commitText(text, newCursorPosition, textAttribute); + } + + /** + * {@inheritDoc} + * @throws NullPointerException if the target is {@code null}. + */ + @Override public boolean commitCompletion(CompletionInfo text) { return mTarget.commitCompletion(text); } diff --git a/core/java/android/view/inputmethod/TextAttribute.aidl b/core/java/android/view/inputmethod/TextAttribute.aidl new file mode 100644 index 000000000000..5f296d9cac74 --- /dev/null +++ b/core/java/android/view/inputmethod/TextAttribute.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.inputmethod; + +parcelable TextAttribute;
\ No newline at end of file diff --git a/core/java/android/view/inputmethod/TextAttribute.java b/core/java/android/view/inputmethod/TextAttribute.java new file mode 100644 index 000000000000..bc76e780a9e1 --- /dev/null +++ b/core/java/android/view/inputmethod/TextAttribute.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.inputmethod; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.PersistableBundle; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * The data class that IME can take extra information to applications when setting the text. + * + * See {@link InputConnection#commitText(CharSequence, int, TextAttribute)} and + * {@link InputConnection#setComposingRegion(int, int, TextAttribute)} and + * {@link InputConnection#setComposingText(CharSequence, int, TextAttribute)} + */ +public final class TextAttribute implements Parcelable { + private final @NonNull List<String> mTextConversionSuggestions; + private final @NonNull PersistableBundle mExtras; + + private TextAttribute(TextAttributeBuilder builder) { + mTextConversionSuggestions = builder.mTextConversionSuggestions; + mExtras = builder.mExtras; + } + + private TextAttribute(Parcel source) { + mTextConversionSuggestions = source.createStringArrayList(); + mExtras = source.readPersistableBundle(); + } + + /** + * Get the list of text conversion suggestions. More text conversion details in + * {@link TextAttributeBuilder#setTextConversionSuggestions(List)}. + * + * @return List of text conversion suggestions. If the list is empty, it means that IME not set + * this field or IME didn't have suggestions for applications. + */ + public @NonNull List<String> getTextConversionSuggestions() { + return mTextConversionSuggestions; + } + + /** + * Get the extras data. More extras data details in + * {@link TextAttributeBuilder#setExtras(PersistableBundle)}. + * + * @return Extras data. If the Bundle is empty, it means that IME not set this field or IME + * didn't have extras data. + */ + public @NonNull PersistableBundle getExtras() { + return mExtras; + } + + /** + * Builder for creating a {@link TextAttribute}. + */ + public static final class TextAttributeBuilder { + private List<String> mTextConversionSuggestions = new ArrayList<>(); + private PersistableBundle mExtras = new PersistableBundle(); + + /** + * Sets text conversion suggestions. + * + * <p>Text conversion suggestion is for some transliteration languages which has + * pronunciation characters and target characters. When the user is typing the pronunciation + * characters, the input method can insert possible target characters into this list so that + * the editor authors can provide suggestion before the user enters the complete + * pronunciation characters.</p> + * + * @param textConversionSuggestions The list of text conversion suggestions. + * @return This builder + */ + public @NonNull TextAttributeBuilder setTextConversionSuggestions( + @NonNull List<String> textConversionSuggestions) { + mTextConversionSuggestions = Collections.unmodifiableList(textConversionSuggestions); + return this; + } + + /** + * Sets extras data. + * + * <p>Any extra data to supply to the applications. This field is for extended communication + * with IME if there is data not defined in framework.</p> + * + * @return This builder. + */ + public @NonNull TextAttributeBuilder setExtras(@NonNull PersistableBundle extras) { + mExtras = extras; + return this; + } + + /** + * @return a new {@link TextAttribute}. + */ + public @NonNull TextAttribute build() { + return new TextAttribute(this); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeStringList(mTextConversionSuggestions); + dest.writePersistableBundle(mExtras); + } + + public static final @NonNull Parcelable.Creator<TextAttribute> CREATOR = + new Parcelable.Creator<TextAttribute>() { + @Override + public TextAttribute createFromParcel(Parcel source) { + return new TextAttribute(source); + } + + @Override + public TextAttribute[] newArray(int size) { + return new TextAttribute[size]; + } + }; +} diff --git a/core/java/android/view/inputmethod/TextSnapshot.java b/core/java/android/view/inputmethod/TextSnapshot.java index 33ce282780e5..977e6f49e301 100644 --- a/core/java/android/view/inputmethod/TextSnapshot.java +++ b/core/java/android/view/inputmethod/TextSnapshot.java @@ -129,8 +129,8 @@ public final class TextSnapshot { * <p>Values may be any combination of the following values:</p> * <ul> * <li>{@link android.text.TextUtils#CAP_MODE_CHARACTERS TextUtils.CAP_MODE_CHARACTERS}</li> - * <li>{@link android.text.TextUtils#CAP_MODE_CHARACTERS TextUtils.CAP_MODE_WORDS}</li> - * <li>{@link android.text.TextUtils#CAP_MODE_CHARACTERS TextUtils.CAP_MODE_SENTENCES}</li> + * <li>{@link android.text.TextUtils#CAP_MODE_WORDS TextUtils.CAP_MODE_WORDS}</li> + * <li>{@link android.text.TextUtils#CAP_MODE_SENTENCES TextUtils.CAP_MODE_SENTENCES}</li> * </ul> * * <p>You should generally just take a non-zero value to mean "start out in caps mode" though. diff --git a/core/java/android/window/TaskFragmentInfo.java b/core/java/android/window/TaskFragmentInfo.java index 165dcdf3a836..a118f9a8188f 100644 --- a/core/java/android/window/TaskFragmentInfo.java +++ b/core/java/android/window/TaskFragmentInfo.java @@ -213,6 +213,7 @@ public final class TaskFragmentInfo implements Parcelable { + " isEmpty=" + mIsEmpty + " runningActivityCount=" + mRunningActivityCount + " isVisible=" + mIsVisible + + " activities=" + mActivities + " positionInParent=" + mPositionInParent + " isTaskClearedForReuse=" + mIsTaskClearedForReuse + "}"; diff --git a/core/java/com/android/internal/compat/OWNERS b/core/java/com/android/internal/compat/OWNERS index cfd0a4b079ad..ee3086ab2fdb 100644 --- a/core/java/com/android/internal/compat/OWNERS +++ b/core/java/com/android/internal/compat/OWNERS @@ -1,6 +1 @@ -# Use this reviewer by default. -platform-compat-eng+reviews@google.com - -andreionea@google.com -mathewi@google.com -satayev@google.com +include tools/platform-compat:/OWNERS diff --git a/core/java/com/android/internal/inputmethod/IInputContextInvoker.java b/core/java/com/android/internal/inputmethod/IInputContextInvoker.java index efdf483563ec..4dbd941b3ae0 100644 --- a/core/java/com/android/internal/inputmethod/IInputContextInvoker.java +++ b/core/java/com/android/internal/inputmethod/IInputContextInvoker.java @@ -18,6 +18,7 @@ package com.android.internal.inputmethod; import android.annotation.AnyThread; import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Bundle; import android.os.RemoteException; import android.view.KeyEvent; @@ -27,6 +28,7 @@ import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputContentInfo; import android.view.inputmethod.SurroundingText; +import android.view.inputmethod.TextAttribute; import com.android.internal.infra.AndroidFuture; import com.android.internal.view.IInputContext; @@ -211,6 +213,28 @@ public final class IInputContextInvoker { } /** + * Invokes {@link IInputContext#commitTextWithTextAttribute(InputConnectionCommandHeader, int, + * CharSequence)}. + * + * @param text {@code text} parameter to be passed. + * @param newCursorPosition {@code newCursorPosition} parameter to be passed. + * @param textAttribute The extra information about the text. + * @return {@code true} if the invocation is completed without {@link RemoteException}. + * {@code false} otherwise. + */ + @AnyThread + public boolean commitText(CharSequence text, int newCursorPosition, + @Nullable TextAttribute textAttribute) { + try { + mIInputContext.commitTextWithTextAttribute( + createHeader(), text, newCursorPosition, textAttribute); + return true; + } catch (RemoteException e) { + return false; + } + } + + /** * Invokes {@link IInputContext#commitCompletion(InputConnectionCommandHeader, CompletionInfo)}. * * @param text {@code text} parameter to be passed. @@ -315,6 +339,27 @@ public final class IInputContextInvoker { } /** + * Invokes {@link IInputContext#setComposingRegionWithTextAttribute( + * InputConnectionCommandHeader, int, int, TextAttribute)}. + * + * @param start {@code id} parameter to be passed. + * @param end {@code id} parameter to be passed. + * @param textAttribute The extra information about the text. + * @return {@code true} if the invocation is completed without {@link RemoteException}. + * {@code false} otherwise. + */ + @AnyThread + public boolean setComposingRegion(int start, int end, @Nullable TextAttribute textAttribute) { + try { + mIInputContext.setComposingRegionWithTextAttribute( + createHeader(), start, end, textAttribute); + return true; + } catch (RemoteException e) { + return false; + } + } + + /** * Invokes * {@link IInputContext#setComposingText(InputConnectionCommandHeader, CharSequence, int)}. * @@ -334,6 +379,28 @@ public final class IInputContextInvoker { } /** + * Invokes {@link IInputContext#setComposingTextWithTextAttribute(InputConnectionCommandHeader, + * CharSequence, int, TextAttribute)}. + * + * @param text {@code text} parameter to be passed. + * @param newCursorPosition {@code newCursorPosition} parameter to be passed. + * @param textAttribute The extra information about the text. + * @return {@code true} if the invocation is completed without {@link RemoteException}. + * {@code false} otherwise. + */ + @AnyThread + public boolean setComposingText(CharSequence text, int newCursorPosition, + @Nullable TextAttribute textAttribute) { + try { + mIInputContext.setComposingTextWithTextAttribute( + createHeader(), text, newCursorPosition, textAttribute); + return true; + } catch (RemoteException e) { + return false; + } + } + + /** * Invokes {@link IInputContext#finishComposingText(InputConnectionCommandHeader)}. * * @return {@code true} if the invocation is completed without {@link RemoteException}. diff --git a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java index 21358abe243f..550322627794 100644 --- a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java +++ b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java @@ -42,6 +42,7 @@ import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputContentInfo; import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.TextAttribute; import com.android.internal.annotations.GuardedBy; import com.android.internal.infra.AndroidFuture; @@ -385,6 +386,23 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { @Dispatching(cancellable = true) @Override + public void commitTextWithTextAttribute(InputConnectionCommandHeader header, CharSequence text, + int newCursorPosition, @Nullable TextAttribute textAttribute) { + dispatchWithTracing("commitTextWithTextAttribute", () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return; // cancelled + } + InputConnection ic = getInputConnection(); + if (ic == null || !isActive()) { + Log.w(TAG, "commitText on inactive InputConnection"); + return; + } + ic.commitText(text, newCursorPosition, textAttribute); + }); + } + + @Dispatching(cancellable = true) + @Override public void commitCompletion(InputConnectionCommandHeader header, CompletionInfo text) { dispatchWithTracing("commitCompletion", () -> { if (header.mSessionId != mCurrentSessionId.get()) { @@ -489,6 +507,23 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { @Dispatching(cancellable = true) @Override + public void setComposingRegionWithTextAttribute(InputConnectionCommandHeader header, int start, + int end, @Nullable TextAttribute textAttribute) { + dispatchWithTracing("setComposingRegionWithTextAttribute", () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return; // cancelled + } + InputConnection ic = getInputConnection(); + if (ic == null || !isActive()) { + Log.w(TAG, "setComposingRegion on inactive InputConnection"); + return; + } + ic.setComposingRegion(start, end, textAttribute); + }); + } + + @Dispatching(cancellable = true) + @Override public void setComposingText(InputConnectionCommandHeader header, CharSequence text, int newCursorPosition) { dispatchWithTracing("setComposingText", () -> { @@ -504,6 +539,23 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { }); } + @Dispatching(cancellable = true) + @Override + public void setComposingTextWithTextAttribute(InputConnectionCommandHeader header, + CharSequence text, int newCursorPosition, @Nullable TextAttribute textAttribute) { + dispatchWithTracing("setComposingTextWithTextAttribute", () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return; // cancelled + } + InputConnection ic = getInputConnection(); + if (ic == null || !isActive()) { + Log.w(TAG, "setComposingText on inactive InputConnection"); + return; + } + ic.setComposingText(text, newCursorPosition, textAttribute); + }); + } + /** * Dispatches {@link InputConnection#finishComposingText()}. * diff --git a/core/java/com/android/internal/inputmethod/StartInputFlags.java b/core/java/com/android/internal/inputmethod/StartInputFlags.java index ac83987ef12c..dd4ff672c061 100644 --- a/core/java/com/android/internal/inputmethod/StartInputFlags.java +++ b/core/java/com/android/internal/inputmethod/StartInputFlags.java @@ -30,7 +30,9 @@ import java.lang.annotation.Retention; @IntDef(flag = true, value = { StartInputFlags.VIEW_HAS_FOCUS, StartInputFlags.IS_TEXT_EDITOR, - StartInputFlags.INITIAL_CONNECTION}) + StartInputFlags.INITIAL_CONNECTION, + StartInputFlags.WINDOW_GAINED_FOCUS, +}) public @interface StartInputFlags { /** * There is a focused view in the focused window. @@ -40,17 +42,17 @@ public @interface StartInputFlags { /** * The focused view is a text editor. */ - int IS_TEXT_EDITOR = 2; + int IS_TEXT_EDITOR = 1 << 1; /** * An internal concept to distinguish "start" and "restart". This concept doesn't look well * documented hence we probably need to revisit this though. */ - int INITIAL_CONNECTION = 4; + int INITIAL_CONNECTION = 1 << 2; /** * The start input happens when the window gained focus to call * {@code android.view.inputmethod.InputMethodManager#startInputAsyncOnWindowFocusGain}. */ - int WINDOW_GAINED_FOCUS = 8; + int WINDOW_GAINED_FOCUS = 1 << 3; } diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl index df55bebb35ef..7da0f116c358 100644 --- a/core/java/com/android/internal/view/IInputContext.aidl +++ b/core/java/com/android/internal/view/IInputContext.aidl @@ -22,6 +22,7 @@ import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputContentInfo; +import android.view.inputmethod.TextAttribute; import com.android.internal.infra.AndroidFuture; import com.android.internal.inputmethod.InputConnectionCommandHeader; @@ -52,10 +53,16 @@ import com.android.internal.inputmethod.InputConnectionCommandHeader; void setComposingText(in InputConnectionCommandHeader header, CharSequence text, int newCursorPosition); + void setComposingTextWithTextAttribute(in InputConnectionCommandHeader header, + CharSequence text, int newCursorPosition, in TextAttribute textAttribute); + void finishComposingText(in InputConnectionCommandHeader header); void commitText(in InputConnectionCommandHeader header, CharSequence text, - int newCursorPosition); + int newCursorPosition); + + void commitTextWithTextAttribute(in InputConnectionCommandHeader header, CharSequence text, + int newCursorPosition, in TextAttribute textAttribute); void commitCompletion(in InputConnectionCommandHeader header, in CompletionInfo completion); @@ -82,6 +89,9 @@ import com.android.internal.inputmethod.InputConnectionCommandHeader; void setComposingRegion(in InputConnectionCommandHeader header, int start, int end); + void setComposingRegionWithTextAttribute(in InputConnectionCommandHeader header, int start, + int end, in TextAttribute textAttribute); + void getSelectedText(in InputConnectionCommandHeader header, int flags, in AndroidFuture future /* T=CharSequence */); diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp index a7362ab3c3fa..cdfd08971905 100644 --- a/core/jni/android_graphics_BLASTBufferQueue.cpp +++ b/core/jni/android_graphics_BLASTBufferQueue.cpp @@ -61,10 +61,10 @@ static jobject nativeGetSurface(JNIEnv* env, jclass clazz, jlong ptr, queue->getSurface(includeSurfaceControlHandle)); } -static void nativeSetNextTransaction(JNIEnv* env, jclass clazz, jlong ptr, jlong transactionPtr) { +static void nativeSetSyncTransaction(JNIEnv* env, jclass clazz, jlong ptr, jlong transactionPtr) { sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr); auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionPtr); - queue->setNextTransaction(transaction); + queue->setSyncTransaction(transaction); } static void nativeUpdate(JNIEnv* env, jclass clazz, jlong ptr, jlong surfaceControl, jlong width, @@ -98,7 +98,7 @@ static const JNINativeMethod gMethods[] = { {"nativeCreate", "(Ljava/lang/String;JJJI)J", (void*)nativeCreate}, {"nativeGetSurface", "(JZ)Landroid/view/Surface;", (void*)nativeGetSurface}, {"nativeDestroy", "(J)V", (void*)nativeDestroy}, - {"nativeSetNextTransaction", "(JJ)V", (void*)nativeSetNextTransaction}, + {"nativeSetSyncTransaction", "(JJ)V", (void*)nativeSetSyncTransaction}, {"nativeUpdate", "(JJJJIJ)V", (void*)nativeUpdate}, {"nativeMergeWithNextTransaction", "(JJJ)V", (void*)nativeMergeWithNextTransaction}, {"nativeGetLastAcquiredFrameNum", "(J)J", (void*)nativeGetLastAcquiredFrameNum}, diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp index be82879c8411..ef6fd7dd6829 100644 --- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp +++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp @@ -36,6 +36,7 @@ #include <inttypes.h> #include <sys/stat.h> #include <sys/types.h> +#include <linux/fs.h> #include <memory> @@ -253,6 +254,16 @@ copyFileIfChanged(JNIEnv *env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntr return INSTALL_FAILED_CONTAINER_ERROR; } + // If a filesystem like f2fs supports per-file compression, set the compression bit before data + // writes + unsigned int flags; + if (ioctl(fd, FS_IOC_GETFLAGS, &flags) == -1) { + ALOGE("Failed to call FS_IOC_GETFLAGS on %s: %s\n", localTmpFileName, strerror(errno)); + } else if ((flags & FS_COMPR_FL) == 0) { + flags |= FS_COMPR_FL; + ioctl(fd, FS_IOC_SETFLAGS, &flags); + } + if (!zipFile->uncompressEntry(zipEntry, fd)) { ALOGE("Failed uncompressing %s to %s\n", fileName, localTmpFileName); close(fd); diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto index 3248cf513c84..8ef38254a38b 100644 --- a/core/proto/android/server/windowmanagerservice.proto +++ b/core/proto/android/server/windowmanagerservice.proto @@ -262,6 +262,7 @@ message DisplayRotationProto { optional int32 user_rotation = 3 [(.android.typedef) = "android.view.Surface.Rotation"]; optional int32 fixed_to_user_rotation_mode = 4; optional int32 last_orientation = 5 [(.android.typedef) = "android.content.pm.ActivityInfo.ScreenOrientation"]; + optional bool is_fixed_to_user_rotation = 6; } /* represents DockedTaskDividerController */ diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 240d3aa88c52..6d96784d0e0a 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1900,7 +1900,7 @@ @hide This should only be used by ManagedProvisioning app. --> <permission android:name="android.permission.NETWORK_MANAGED_PROVISIONING" - android:protectionLevel="signature" /> + android:protectionLevel="signature|role" /> <!-- Allows Carrier Provisioning to call methods in Networking services <p>Not for use by any other third-party or privileged applications. @@ -2356,10 +2356,10 @@ <permission android:name="android.permission.OEM_UNLOCK_STATE" android:protectionLevel="signature" /> - <!-- @hide Allows querying state of PersistentDataBlock + <!-- @SystemApi @hide Allows querying state of PersistentDataBlock <p>Not for use by third-party applications. --> <permission android:name="android.permission.ACCESS_PDB_STATE" - android:protectionLevel="signature" /> + android:protectionLevel="signature|role" /> <!-- Allows testing if a passwords is forbidden by the admins. @hide <p>Not for use by third-party applications. --> @@ -2417,7 +2417,7 @@ <!-- @SystemApi @TestApi Allows read access to privileged phone state. @hide Used internally. --> <permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" - android:protectionLevel="signature|privileged" /> + android:protectionLevel="signature|privileged|role" /> <!-- Allows to read device identifiers and use ICC based authentication like EAP-AKA. Often required in authentication to access the carrier's server and manage services @@ -2733,25 +2733,25 @@ user-targeted broadcasts. This permission is not available to third party applications. --> <permission android:name="android.permission.INTERACT_ACROSS_USERS" - android:protectionLevel="signature|privileged|development" /> + android:protectionLevel="signature|privileged|development|role" /> <!-- @SystemApi Fuller form of {@link android.Manifest.permission#INTERACT_ACROSS_USERS} that removes restrictions on where broadcasts can be sent and allows other types of interactions @hide --> <permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" - android:protectionLevel="signature|installer" /> + android:protectionLevel="signature|installer|role" /> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> <!-- Allows interaction across profiles in the same profile group. --> <permission android:name="android.permission.INTERACT_ACROSS_PROFILES" android:protectionLevel="signature|appop" /> - <!-- Allows configuring apps to have the INTERACT_ACROSS_PROFILES permission so that they can - interact across profiles in the same profile group. + <!-- @SystemApi Allows configuring apps to have the INTERACT_ACROSS_PROFILES permission so that + they can interact across profiles in the same profile group. @hide --> <permission android:name="android.permission.CONFIGURE_INTERACT_ACROSS_PROFILES" - android:protectionLevel="signature" /> + android:protectionLevel="signature|role" /> <!-- @SystemApi @hide Allows an application to call APIs that allow it to query and manage users on the device. This permission is not available to @@ -2772,10 +2772,10 @@ <permission android:name="android.permission.ACCESS_BLOBS_ACROSS_USERS" android:protectionLevel="signature|privileged|development|role" /> - <!-- @hide Allows an application to set the profile owners and the device owner. + <!-- @SystemApi @hide Allows an application to set the profile owners and the device owner. This permission is not available to third party applications.--> <permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS" - android:protectionLevel="signature" + android:protectionLevel="signature|role" android:label="@string/permlab_manageProfileAndDeviceOwners" android:description="@string/permdesc_manageProfileAndDeviceOwners" /> @@ -2828,7 +2828,7 @@ <!-- @SystemApi @hide Allows an application to start activities from background --> <permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND" - android:protectionLevel="signature|privileged|vendorPrivileged|oem|verifier" /> + android:protectionLevel="signature|privileged|vendorPrivileged|oem|verifier|role" /> <!-- Allows an application to start foreground services from the background at any time. <em>This permission is not for use by third-party applications</em>, @@ -2990,6 +2990,17 @@ <permission android:name="android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING" android:protectionLevel="signature|privileged" /> + <!-- Allows application to request to be associated with a vehicle head unit capable of + automotive projection + ({@link android.companion.AssociationRequest#DEVICE_PROFILE_AUTOMOTIVE_PROJECTION}) + by {@link android.companion.CompanionDeviceManager}. + <p>Not for use by third-party applications. + @hide + @SystemApi + --> + <permission android:name="android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION" + android:protectionLevel="internal|role" /> + <!-- Allows a companion app to associate to Wi-Fi. <p>Only for use by a single pre-approved app. @hide @@ -3054,7 +3065,7 @@ <!-- Allows applications to set the system time directly. <p>Not for use by third-party applications. --> <permission android:name="android.permission.SET_TIME" - android:protectionLevel="signature|privileged" /> + android:protectionLevel="signature|privileged|role" /> <!-- Allows applications to set the system time zone directly. <p>Not for use by third-party applications. @@ -3062,7 +3073,7 @@ <permission android:name="android.permission.SET_TIME_ZONE" android:label="@string/permlab_setTimeZone" android:description="@string/permdesc_setTimeZone" - android:protectionLevel="signature|privileged" /> + android:protectionLevel="signature|privileged|role" /> <!-- Allows telephony to suggest the time / time zone. <p>Not for use by third-party applications. @@ -3176,7 +3187,7 @@ as locale. <p>Protection level: signature|privileged|development --> <permission android:name="android.permission.CHANGE_CONFIGURATION" - android:protectionLevel="signature|privileged|development" /> + android:protectionLevel="signature|privileged|development|role" /> <!-- Allows an application to read or write the system settings. @@ -3193,7 +3204,7 @@ <permission android:name="android.permission.WRITE_SETTINGS" android:label="@string/permlab_writeSettings" android:description="@string/permdesc_writeSettings" - android:protectionLevel="signature|preinstalled|appop|pre23" /> + android:protectionLevel="signature|preinstalled|appop|pre23|role" /> <!-- Allows an application to modify the Google service map. <p>Not for use by third-party applications. --> @@ -3446,7 +3457,7 @@ <!-- Allows an application to read or write the secure system settings. <p>Not for use by third-party applications. --> <permission android:name="android.permission.WRITE_SECURE_SETTINGS" - android:protectionLevel="signature|privileged|development" /> + android:protectionLevel="signature|privileged|development|role" /> <!-- Allows an application to retrieve state dump information from system services. <p>Not for use by third-party applications. --> @@ -3578,6 +3589,13 @@ <permission android:name="android.permission.GET_APP_OPS_STATS" android:protectionLevel="signature|privileged|development" /> + <!-- @SystemApi @hide Allows an application to collect historical application operation + statistics. + <p>Not for use by third party applications. + --> + <permission android:name="android.permission.GET_HISTORICAL_APP_OPS_STATS" + android:protectionLevel="internal|role" /> + <!-- @SystemApi Allows an application to update application operation statistics. Not for use by third party apps. @hide --> @@ -3699,7 +3717,7 @@ to put the higher-level system there into a shutdown state. @hide --> <permission android:name="android.permission.SHUTDOWN" - android:protectionLevel="signature|privileged" /> + android:protectionLevel="signature|privileged|role" /> <!-- @SystemApi Allows an application to tell the activity manager to temporarily stop application switches, putting it into a special mode that @@ -4070,14 +4088,13 @@ <p>Protection level: signature --> <permission android:name="android.permission.BIND_DEVICE_ADMIN" - android:protectionLevel="signature" /> + android:protectionLevel="signature|role" /> <!-- @SystemApi Required to add or remove another application as a device admin. <p>Not for use by third-party applications. - @hide - @removed --> + @hide --> <permission android:name="android.permission.MANAGE_DEVICE_ADMINS" - android:protectionLevel="signature" /> + android:protectionLevel="signature|role" /> <!-- @SystemApi Allows an app to reset the device password. <p>Not for use by third-party applications. @@ -4190,14 +4207,14 @@ <permission android:name="android.permission.INSTALL_PACKAGE_UPDATES" android:protectionLevel="signature|privileged" /> - <!-- Allows an application to install existing system packages. This is a limited + <!-- @SystemApi Allows an application to install existing system packages. This is a limited version of {@link android.Manifest.permission#INSTALL_PACKAGES}. <p>Not for use by third-party applications. TODO(b/80204953): remove this permission once we have a long-term solution. @hide --> <permission android:name="com.android.permission.INSTALL_EXISTING_PACKAGES" - android:protectionLevel="signature|privileged" /> + android:protectionLevel="signature|privileged|role" /> <!-- Allows an application to use the package installer v2 APIs. <p>The package installer v2 APIs are still a work in progress and we're @@ -4292,7 +4309,7 @@ when the application deleting the package is not the same application that installed the package. --> <permission android:name="android.permission.DELETE_PACKAGES" - android:protectionLevel="signature|privileged" /> + android:protectionLevel="signature|privileged|role" /> <!-- @SystemApi Allows an application to move location of installed package. @hide --> @@ -4308,7 +4325,7 @@ enabled or not. <p>Not for use by third-party applications. --> <permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" - android:protectionLevel="signature|privileged" /> + android:protectionLevel="signature|privileged|role" /> <!-- @SystemApi Allows an application to grant specific permissions. @hide --> @@ -4760,7 +4777,7 @@ <!-- Not for use by third-party applications. --> <permission android:name="android.permission.MASTER_CLEAR" - android:protectionLevel="signature|privileged" /> + android:protectionLevel="signature|privileged|role" /> <!-- Allows an application to call any phone number, including emergency numbers, without going through the Dialer user interface for the user @@ -4771,7 +4788,7 @@ <!-- @SystemApi Allows an application to perform CDMA OTA provisioning @hide --> <permission android:name="android.permission.PERFORM_CDMA_PROVISIONING" - android:protectionLevel="signature|privileged" /> + android:protectionLevel="signature|privileged|role" /> <!-- @SystemApi Allows an application to perform SIM Activation @hide --> <permission android:name="android.permission.PERFORM_SIM_ACTIVATION" @@ -4998,7 +5015,7 @@ @hide --> <permission android:name="android.permission.CRYPT_KEEPER" - android:protectionLevel="signature|privileged" /> + android:protectionLevel="signature|privileged|role" /> <!-- @SystemApi Allows an application to read historical network usage for specific networks and applications. @hide --> @@ -5143,10 +5160,10 @@ android:protectionLevel="signature|installer" /> <uses-permission android:name="android.permission.MANAGE_NOTIFICATION_LISTENERS" /> - <!-- Allows notifications to be colorized + <!-- @SystemApi Allows notifications to be colorized <p>Not for use by third-party applications. @hide --> <permission android:name="android.permission.USE_COLORIZED_NOTIFICATIONS" - android:protectionLevel="signature|setup" /> + android:protectionLevel="signature|setup|role" /> <!-- Allows access to keyguard secure storage. Only allowed for system processes. @hide --> @@ -5387,7 +5404,7 @@ <!-- @SystemApi Allows access to MAC addresses of WiFi and Bluetooth peer devices. @hide --> <permission android:name="android.permission.PEERS_MAC_ADDRESS" - android:protectionLevel="signature|setup" /> + android:protectionLevel="signature|setup|role" /> <!-- Allows the Nfc stack to dispatch Nfc messages to applications. Applications can use this permission to ensure incoming Nfc messages are from the Nfc stack @@ -5692,11 +5709,11 @@ <permission android:name="android.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS" android:protectionLevel="signature" /> - <!-- Allows an app to mark a profile owner as managing an organization-owned device. + <!-- @SystemApi Allows an app to mark a profile owner as managing an organization-owned device. <p>Not for use by third-party applications. @hide --> <permission android:name="android.permission.MARK_DEVICE_ORGANIZATION_OWNED" - android:protectionLevel="signature" /> + android:protectionLevel="signature|role" /> <!-- Allows financial apps to read filtered sms messages. Protection level: signature|appop diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 755938eefdbd..7805d46188e9 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -2953,6 +2953,9 @@ usually TVs. <p>Requires permission {@code android.permission.DISABLE_SYSTEM_SOUND_EFFECTS}. --> <attr name="playHomeTransitionSound" format="boolean"/> + <!-- Indicates whether the activity can be displayed on a remote device which may or + may not be running Android. --> + <attr name="canDisplayOnRemoteDevices" format="boolean"/> </declare-styleable> <!-- The <code>activity-alias</code> tag declares a new diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 7f68bfd622cc..b924bd231361 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1956,8 +1956,9 @@ STREAM_MUSIC as if it's on TV platform. --> <bool name="config_single_volume">false</bool> - <!-- Flag indicating whether the volume panel should show remote sessions. --> - <bool name="config_volumeShowRemoteSessions">true</bool> + <!-- Flag indicating whether platform level volume adjustments are enabled for remote sessions + on grouped devices. --> + <bool name="config_volumeAdjustmentForRemoteGroupSessions">true</bool> <!-- Flag indicating that an outbound call must have a call capable phone account that has declared it can process the call's handle. --> @@ -2050,6 +2051,8 @@ <!-- The name of the package that will hold the television remote service role. TODO(b/189347385) make this a @SystemAPI --> <string name="config_systemTelevisionRemoteService" translatable="false">@string/config_tvRemoteServicePackage</string> + <!-- The name of the package that will hold the device management role --> + <string name="config_deviceManager" translatable="false"></string> <!-- The name of the package that will be allowed to change its components' label/icon. --> <string name="config_overrideComponentUiPackage" translatable="false">com.android.stk</string> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 366dccb67e72..2820f86c920a 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3303,6 +3303,7 @@ <public name="sharedUserMaxSdkVersion" /> <public name="requiredSplitTypes" /> <public name="splitTypes" /> + <public name="canDisplayOnRemoteDevices" /> </staging-public-group> <staging-public-group type="id" first-id="0x01de0000"> @@ -3312,12 +3313,14 @@ <public name="accessibilityActionSwipeDown" /> </staging-public-group> - <staging-public-group type="style" first-id="0x0dfd0000"> + <staging-public-group type="style" first-id="0x01dd0000"> </staging-public-group> <staging-public-group type="string" first-id="0x01dc0000"> <!-- @hide @SystemApi --> <public name="config_systemSupervision" /> + <!-- @hide @SystemApi --> + <public name="config_deviceManager" /> </staging-public-group> <staging-public-group type="dimen" first-id="0x01db0000"> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index f166b733042f..5208c4a9230d 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4557,7 +4557,7 @@ <java-symbol type="dimen" name="config_wallpaperDimAmount" /> - <java-symbol type="bool" name="config_volumeShowRemoteSessions" /> + <java-symbol type="bool" name="config_volumeAdjustmentForRemoteGroupSessions" /> <!-- List of shared library packages that should be loaded by the classloader after the code and resources provided by applications. --> diff --git a/core/tests/PlatformCompatFramework/OWNERS b/core/tests/PlatformCompatFramework/OWNERS index cfd0a4b079ad..ee3086ab2fdb 100644 --- a/core/tests/PlatformCompatFramework/OWNERS +++ b/core/tests/PlatformCompatFramework/OWNERS @@ -1,6 +1 @@ -# Use this reviewer by default. -platform-compat-eng+reviews@google.com - -andreionea@google.com -mathewi@google.com -satayev@google.com +include tools/platform-compat:/OWNERS diff --git a/data/etc/com.android.cellbroadcastreceiver.xml b/data/etc/com.android.cellbroadcastreceiver.xml index 01a28a8661cb..bc62bbc845f3 100644 --- a/data/etc/com.android.cellbroadcastreceiver.xml +++ b/data/etc/com.android.cellbroadcastreceiver.xml @@ -19,6 +19,7 @@ <permission name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS"/> <permission name="android.permission.INTERACT_ACROSS_USERS"/> <permission name="android.permission.MANAGE_USERS"/> + <permission name="android.permission.STATUS_BAR"/> <permission name="android.permission.MODIFY_PHONE_STATE"/> <permission name="android.permission.MODIFY_CELL_BROADCASTS"/> <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 33cc61b8f84f..6983aa4aafc9 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -49,6 +49,7 @@ applications that come with the platform <permission name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS"/> <permission name="android.permission.INTERACT_ACROSS_USERS"/> <permission name="android.permission.MANAGE_USERS"/> + <permission name="android.permission.STATUS_BAR"/> <permission name="android.permission.MODIFY_PHONE_STATE"/> <permission name="android.permission.MODIFY_CELL_BROADCASTS"/> <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/> diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java index 9af508a2d2c4..405f66dd5d56 100644 --- a/graphics/java/android/graphics/BLASTBufferQueue.java +++ b/graphics/java/android/graphics/BLASTBufferQueue.java @@ -31,7 +31,7 @@ public final class BLASTBufferQueue { long height, int format); private static native void nativeDestroy(long ptr); private static native Surface nativeGetSurface(long ptr, boolean includeSurfaceControlHandle); - private static native void nativeSetNextTransaction(long ptr, long transactionPtr); + private static native void nativeSetSyncTransaction(long ptr, long transactionPtr); private static native void nativeUpdate(long ptr, long surfaceControl, long width, long height, int format, long transactionPtr); private static native void nativeMergeWithNextTransaction(long ptr, long transactionPtr, @@ -70,8 +70,8 @@ public final class BLASTBufferQueue { * This gives the caller a chance to apply the transaction when it's ready. * @param t The transaction to add the frame to. This can be null to clear the transaction. */ - public void setNextTransaction(@Nullable SurfaceControl.Transaction t) { - nativeSetNextTransaction(mNativeObject, t == null ? 0 : t.mNativeObject); + public void setSyncTransaction(@Nullable SurfaceControl.Transaction t) { + nativeSetSyncTransaction(mNativeObject, t == null ? 0 : t.mNativeObject); } /** diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java index 06e7d1457417..44af1a9fd780 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java @@ -73,14 +73,61 @@ class SplitContainer { static boolean shouldFinishPrimaryWithSecondary(@NonNull SplitRule splitRule) { final boolean isPlaceholderContainer = splitRule instanceof SplitPlaceholderRule; final boolean shouldFinishPrimaryWithSecondary = (splitRule instanceof SplitPairRule) - && ((SplitPairRule) splitRule).shouldFinishPrimaryWithSecondary(); + && ((SplitPairRule) splitRule).getFinishPrimaryWithSecondary() + != SplitRule.FINISH_NEVER; return shouldFinishPrimaryWithSecondary || isPlaceholderContainer; } static boolean shouldFinishSecondaryWithPrimary(@NonNull SplitRule splitRule) { final boolean isPlaceholderContainer = splitRule instanceof SplitPlaceholderRule; final boolean shouldFinishSecondaryWithPrimary = (splitRule instanceof SplitPairRule) - && ((SplitPairRule) splitRule).shouldFinishSecondaryWithPrimary(); + && ((SplitPairRule) splitRule).getFinishSecondaryWithPrimary() + != SplitRule.FINISH_NEVER; return shouldFinishSecondaryWithPrimary || isPlaceholderContainer; } + + static boolean shouldFinishAssociatedContainerWhenStacked(int finishBehavior) { + return finishBehavior == SplitRule.FINISH_ALWAYS; + } + + static boolean shouldFinishAssociatedContainerWhenAdjacent(int finishBehavior) { + return finishBehavior == SplitRule.FINISH_ALWAYS + || finishBehavior == SplitRule.FINISH_ADJACENT; + } + + static int getFinishPrimaryWithSecondaryBehavior(@NonNull SplitRule splitRule) { + if (splitRule instanceof SplitPlaceholderRule) { + return ((SplitPlaceholderRule) splitRule).getFinishPrimaryWithSecondary(); + } + if (splitRule instanceof SplitPairRule) { + return ((SplitPairRule) splitRule).getFinishPrimaryWithSecondary(); + } + return SplitRule.FINISH_NEVER; + } + + static int getFinishSecondaryWithPrimaryBehavior(@NonNull SplitRule splitRule) { + if (splitRule instanceof SplitPlaceholderRule) { + return SplitRule.FINISH_ALWAYS; + } + if (splitRule instanceof SplitPairRule) { + return ((SplitPairRule) splitRule).getFinishSecondaryWithPrimary(); + } + return SplitRule.FINISH_NEVER; + } + + static boolean isStickyPlaceholderRule(@NonNull SplitRule splitRule) { + if (!(splitRule instanceof SplitPlaceholderRule)) { + return false; + } + return ((SplitPlaceholderRule) splitRule).isSticky(); + } + + @Override + public String toString() { + return "SplitContainer{" + + " primaryContainer=" + mPrimaryContainer + + " secondaryContainer=" + mSecondaryContainer + + " splitRule=" + mSplitRule + + "}"; + } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 20515e71a91b..68c19041940c 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -16,6 +16,12 @@ package androidx.window.extensions.embedding; +import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior; +import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior; +import static androidx.window.extensions.embedding.SplitContainer.isStickyPlaceholderRule; +import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenAdjacent; +import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; @@ -460,6 +466,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return false; } + if (isStickyPlaceholderRule(splitContainer.getSplitRule())) { + // The placeholder should remain after it was first shown. + return false; + } + if (mPresenter.shouldShowSideBySide(splitContainer)) { return false; } @@ -497,7 +508,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return; } List<SplitInfo> currentSplitStates = getActiveSplitStates(); - if (mLastReportedSplitStates.equals(currentSplitStates)) { + if (currentSplitStates == null || mLastReportedSplitStates.equals(currentSplitStates)) { return; } mLastReportedSplitStates.clear(); @@ -506,15 +517,19 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } /** - * Returns a list of descriptors for currently active split states. + * @return a list of descriptors for currently active split states. If the value returned is + * null, that indicates that the active split states are in an intermediate state and should + * not be reported. */ + @Nullable private List<SplitInfo> getActiveSplitStates() { List<SplitInfo> splitStates = new ArrayList<>(); for (SplitContainer container : mSplitContainers) { if (container.getPrimaryContainer().isEmpty() || container.getSecondaryContainer().isEmpty()) { - // Skipping containers that do not have any activities to report. - continue; + // We are in an intermediate state because either the split container is about to be + // removed or the primary or secondary container are about to receive an activity. + return null; } ActivityStack primaryContainer = container.getPrimaryContainer().toActivityStack(); ActivityStack secondaryContainer = container.getSecondaryContainer().toActivityStack(); @@ -639,6 +654,52 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return false; } + /** + * Checks whether the associated container should be destroyed together with a finishing + * container. There is a case when primary containers for placeholders should be retained + * despite the rule configuration to finish primary with secondary - if they are marked as + * 'sticky' and the placeholder was finished when fully overlapping the primary container. + * @return {@code true} if the associated container should be retained (and not be finished). + */ + boolean shouldRetainAssociatedContainer(@NonNull TaskFragmentContainer finishingContainer, + @NonNull TaskFragmentContainer associatedContainer) { + SplitContainer splitContainer = getActiveSplitForContainers(associatedContainer, + finishingContainer); + if (splitContainer == null) { + // Containers are not in the same split, no need to retain. + return false; + } + // Find the finish behavior for the associated container + int finishBehavior; + SplitRule splitRule = splitContainer.getSplitRule(); + if (finishingContainer == splitContainer.getPrimaryContainer()) { + finishBehavior = getFinishSecondaryWithPrimaryBehavior(splitRule); + } else { + finishBehavior = getFinishPrimaryWithSecondaryBehavior(splitRule); + } + // Decide whether the associated container should be retained based on the current + // presentation mode. + if (mPresenter.shouldShowSideBySide(splitContainer)) { + return !shouldFinishAssociatedContainerWhenAdjacent(finishBehavior); + } else { + return !shouldFinishAssociatedContainerWhenStacked(finishBehavior); + } + } + + /** + * @see #shouldRetainAssociatedContainer(TaskFragmentContainer, TaskFragmentContainer) + */ + boolean shouldRetainAssociatedActivity(@NonNull TaskFragmentContainer finishingContainer, + @NonNull Activity associatedActivity) { + TaskFragmentContainer associatedContainer = getContainerWithActivity( + associatedActivity.getActivityToken()); + if (associatedContainer == null) { + return false; + } + + return shouldRetainAssociatedContainer(finishingContainer, associatedContainer); + } + private final class LifecycleCallbacks implements ActivityLifecycleCallbacks { @Override diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index 80d9c2c1719c..a1a53bc93781 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -27,6 +27,7 @@ import android.window.TaskFragmentInfo; import android.window.WindowContainerTransaction; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; /** @@ -227,6 +228,9 @@ class TaskFragmentContainer { // Finish dependent containers for (TaskFragmentContainer container : mContainersToFinishOnExit) { + if (controller.shouldRetainAssociatedContainer(this, container)) { + continue; + } container.finish(true /* shouldFinishDependent */, presenter, wct, controller); } @@ -234,6 +238,9 @@ class TaskFragmentContainer { // Finish associated activities for (Activity activity : mActivitiesToFinishOnExit) { + if (controller.shouldRetainAssociatedActivity(this, activity)) { + continue; + } activity.finish(); } mActivitiesToFinishOnExit.clear(); @@ -267,4 +274,42 @@ class TaskFragmentContainer { mLastRequestedBounds.set(bounds); } } + + @Override + public String toString() { + return toString(true /* includeContainersToFinishOnExit */); + } + + /** + * @return string for this TaskFragmentContainer and includes containers to finish on exit + * based on {@code includeContainersToFinishOnExit}. If containers to finish on exit are always + * included in the string, then calling {@link #toString()} on a container that mutually + * finishes with another container would cause a stack overflow. + */ + private String toString(boolean includeContainersToFinishOnExit) { + return "TaskFragmentContainer{" + + " token=" + mToken + + " info=" + mInfo + + " topNonFinishingActivity=" + getTopNonFinishingActivity() + + " pendingAppearedActivities=" + mPendingAppearedActivities + + (includeContainersToFinishOnExit ? " containersToFinishOnExit=" + + containersToFinishOnExitToString() : "") + + " activitiesToFinishOnExit=" + mActivitiesToFinishOnExit + + " isFinished=" + mIsFinished + + " lastRequestedBounds=" + mLastRequestedBounds + + "}"; + } + + private String containersToFinishOnExitToString() { + StringBuilder sb = new StringBuilder("["); + Iterator<TaskFragmentContainer> containerIterator = mContainersToFinishOnExit.iterator(); + while (containerIterator.hasNext()) { + sb.append(containerIterator.next().toString( + false /* includeContainersToFinishOnExit */)); + if (containerIterator.hasNext()) { + sb.append(", "); + } + } + return sb.append("]").toString(); + } } diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar Binary files differindex 830d13dd6dc5..d6678bf9b320 100644 --- a/libs/WindowManager/Jetpack/window-extensions-release.aar +++ b/libs/WindowManager/Jetpack/window-extensions-release.aar diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index f7af4e1dd1d1..caa532761f1c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -1036,10 +1036,9 @@ public class BubbleController { // notification, so that the bubble will be re-created if shouldBubbleUp returns // true. mBubbleData.dismissBubbleWithKey(key, DISMISS_NO_BUBBLE_UP); - } else if (entry != null && mTmpRanking.isBubble() && !isActiveBubble - && !entry.getRanking().isSuspended()) { + } else if (entry != null && mTmpRanking.isBubble() && !isActiveBubble) { entry.setFlagBubble(true); - onEntryUpdated(entry, true /* shouldBubbleUp */); + onEntryUpdated(entry, shouldBubbleUp && !entry.getRanking().isSuspended()); } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index 836a6f610bbd..7cf3bafe499a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -41,10 +41,13 @@ import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.annotations.ExternalThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.util.GroupedRecentTaskInfo; +import com.android.wm.shell.util.StagedSplitBounds; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Manages the recent task list from the system, caching it as necessary. @@ -62,6 +65,13 @@ public class RecentTasksController implements TaskStackListenerCallback, // Mapping of split task ids, mappings are symmetrical (ie. if t1 is the taskid of a task in a // pair, then mSplitTasks[t1] = t2, and mSplitTasks[t2] = t1) private final SparseIntArray mSplitTasks = new SparseIntArray(); + /** + * Maps taskId to {@link StagedSplitBounds} for both taskIDs. + * Meaning there will be two taskId integers mapping to the same object. + * If there's any ordering to the pairing than we can probably just get away with only one + * taskID mapping to it, leaving both for consistency with {@link #mSplitTasks} for now. + */ + private final Map<Integer, StagedSplitBounds> mTaskSplitBoundsMap = new HashMap<>(); /** * Creates {@link RecentTasksController}, returns {@code null} if the feature is not @@ -97,15 +107,20 @@ public class RecentTasksController implements TaskStackListenerCallback, /** * Adds a split pair. This call does not validate the taskIds, only that they are not the same. */ - public void addSplitPair(int taskId1, int taskId2) { + public void addSplitPair(int taskId1, int taskId2, StagedSplitBounds splitBounds) { if (taskId1 == taskId2) { return; } // Remove any previous pairs removeSplitPair(taskId1); removeSplitPair(taskId2); + mTaskSplitBoundsMap.remove(taskId1); + mTaskSplitBoundsMap.remove(taskId2); + mSplitTasks.put(taskId1, taskId2); mSplitTasks.put(taskId2, taskId1); + mTaskSplitBoundsMap.put(taskId1, splitBounds); + mTaskSplitBoundsMap.put(taskId2, splitBounds); } /** @@ -116,6 +131,8 @@ public class RecentTasksController implements TaskStackListenerCallback, if (pairedTaskId != INVALID_TASK_ID) { mSplitTasks.delete(taskId); mSplitTasks.delete(pairedTaskId); + mTaskSplitBoundsMap.remove(taskId); + mTaskSplitBoundsMap.remove(pairedTaskId); } } @@ -203,7 +220,8 @@ public class RecentTasksController implements TaskStackListenerCallback, if (pairedTaskId != INVALID_TASK_ID) { final ActivityManager.RecentTaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId); rawMapping.remove(pairedTaskId); - recentTasks.add(new GroupedRecentTaskInfo(taskInfo, pairedTaskInfo)); + recentTasks.add(new GroupedRecentTaskInfo(taskInfo, pairedTaskInfo, + mTaskSplitBoundsMap.get(pairedTaskId))); } else { recentTasks.add(new GroupedRecentTaskInfo(taskInfo)); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 8471e1e58109..3589f7c14cd3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -96,6 +96,7 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.util.StagedSplitBounds; import java.io.PrintWriter; import java.util.ArrayList; @@ -691,11 +692,25 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, } mRecentTasks.ifPresent(recentTasks -> { + Rect topLeftBounds = mSplitLayout.getBounds1(); + Rect bottomRightBounds = mSplitLayout.getBounds2(); int mainStageTopTaskId = mMainStage.getTopVisibleChildTaskId(); int sideStageTopTaskId = mSideStage.getTopVisibleChildTaskId(); + boolean sideStageTopLeft = mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT; + int leftTopTaskId; + int rightBottomTaskId; + if (sideStageTopLeft) { + leftTopTaskId = sideStageTopTaskId; + rightBottomTaskId = mainStageTopTaskId; + } else { + leftTopTaskId = mainStageTopTaskId; + rightBottomTaskId = sideStageTopTaskId; + } + StagedSplitBounds splitBounds = new StagedSplitBounds(topLeftBounds, bottomRightBounds, + leftTopTaskId, rightBottomTaskId); if (mainStageTopTaskId != INVALID_TASK_ID && sideStageTopTaskId != INVALID_TASK_ID) { // Update the pair for the top tasks - recentTasks.addSplitPair(mainStageTopTaskId, sideStageTopTaskId); + recentTasks.addSplitPair(mainStageTopTaskId, sideStageTopTaskId, splitBounds); } }); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java index 0331ba19defe..603d05d78fc0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java @@ -30,25 +30,34 @@ import androidx.annotation.Nullable; public class GroupedRecentTaskInfo implements Parcelable { public @NonNull ActivityManager.RecentTaskInfo mTaskInfo1; public @Nullable ActivityManager.RecentTaskInfo mTaskInfo2; + public @Nullable StagedSplitBounds mStagedSplitBounds; public GroupedRecentTaskInfo(@NonNull ActivityManager.RecentTaskInfo task1) { - this(task1, null); + this(task1, null, null); } public GroupedRecentTaskInfo(@NonNull ActivityManager.RecentTaskInfo task1, - @Nullable ActivityManager.RecentTaskInfo task2) { + @Nullable ActivityManager.RecentTaskInfo task2, + @Nullable StagedSplitBounds stagedSplitBounds) { mTaskInfo1 = task1; mTaskInfo2 = task2; + mStagedSplitBounds = stagedSplitBounds; } GroupedRecentTaskInfo(Parcel parcel) { mTaskInfo1 = parcel.readTypedObject(ActivityManager.RecentTaskInfo.CREATOR); mTaskInfo2 = parcel.readTypedObject(ActivityManager.RecentTaskInfo.CREATOR); + mStagedSplitBounds = parcel.readTypedObject(StagedSplitBounds.CREATOR); } @Override public String toString() { - return "Task1: " + getTaskInfo(mTaskInfo1) + ", Task2: " + getTaskInfo(mTaskInfo2); + String taskString = "Task1: " + getTaskInfo(mTaskInfo1) + + ", Task2: " + getTaskInfo(mTaskInfo2); + if (mStagedSplitBounds != null) { + taskString += ", SplitBounds: " + mStagedSplitBounds.toString(); + } + return taskString; } private String getTaskInfo(ActivityManager.RecentTaskInfo taskInfo) { @@ -67,6 +76,7 @@ public class GroupedRecentTaskInfo implements Parcelable { public void writeToParcel(Parcel parcel, int flags) { parcel.writeTypedObject(mTaskInfo1, flags); parcel.writeTypedObject(mTaskInfo2, flags); + parcel.writeTypedObject(mStagedSplitBounds, flags); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/StagedSplitBounds.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/StagedSplitBounds.java new file mode 100644 index 000000000000..aadf792c572f --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/StagedSplitBounds.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.util; + +import android.graphics.Rect; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Container of various information needed to display split screen + * tasks/leashes/etc in Launcher + */ +public class StagedSplitBounds implements Parcelable { + public final Rect leftTopBounds; + public final Rect rightBottomBounds; + /** This rect represents the actual gap between the two apps */ + public final Rect visualDividerBounds; + // This class is orientation-agnostic, so we compute both for later use + public final float topTaskPercent; + public final float leftTaskPercent; + /** + * If {@code true}, that means at the time of creation of this object, the + * split-screened apps were vertically stacked. This is useful in scenarios like + * rotation where the bounds won't change, but this variable can indicate what orientation + * the bounds were originally in + */ + public final boolean appsStackedVertically; + public final int leftTopTaskId; + public final int rightBottomTaskId; + + public StagedSplitBounds(Rect leftTopBounds, Rect rightBottomBounds, + int leftTopTaskId, int rightBottomTaskId) { + this.leftTopBounds = leftTopBounds; + this.rightBottomBounds = rightBottomBounds; + this.leftTopTaskId = leftTopTaskId; + this.rightBottomTaskId = rightBottomTaskId; + + if (rightBottomBounds.top > leftTopBounds.top) { + // vertical apps, horizontal divider + this.visualDividerBounds = new Rect(leftTopBounds.left, leftTopBounds.bottom, + leftTopBounds.right, rightBottomBounds.top); + appsStackedVertically = true; + } else { + // horizontal apps, vertical divider + this.visualDividerBounds = new Rect(leftTopBounds.right, leftTopBounds.top, + rightBottomBounds.left, leftTopBounds.bottom); + appsStackedVertically = false; + } + + leftTaskPercent = this.leftTopBounds.width() / (float) rightBottomBounds.right; + topTaskPercent = this.leftTopBounds.height() / (float) rightBottomBounds.bottom; + } + + public StagedSplitBounds(Parcel parcel) { + leftTopBounds = parcel.readTypedObject(Rect.CREATOR); + rightBottomBounds = parcel.readTypedObject(Rect.CREATOR); + visualDividerBounds = parcel.readTypedObject(Rect.CREATOR); + topTaskPercent = parcel.readFloat(); + leftTaskPercent = parcel.readFloat(); + appsStackedVertically = parcel.readBoolean(); + leftTopTaskId = parcel.readInt(); + rightBottomTaskId = parcel.readInt(); + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeTypedObject(leftTopBounds, flags); + parcel.writeTypedObject(rightBottomBounds, flags); + parcel.writeTypedObject(visualDividerBounds, flags); + parcel.writeFloat(topTaskPercent); + parcel.writeFloat(leftTaskPercent); + parcel.writeBoolean(appsStackedVertically); + parcel.writeInt(leftTopTaskId); + parcel.writeInt(rightBottomTaskId); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "LeftTop: " + leftTopBounds + ", taskId: " + leftTopTaskId + "\n" + + "RightBottom: " + rightBottomBounds + ", taskId: " + rightBottomTaskId + "\n" + + "Divider: " + visualDividerBounds + "\n" + + "AppsVertical? " + appsStackedVertically; + } + + public static final Creator<StagedSplitBounds> CREATOR = new Creator<StagedSplitBounds>() { + @Override + public StagedSplitBounds createFromParcel(Parcel in) { + return new StagedSplitBounds(in); + } + + @Override + public StagedSplitBounds[] newArray(int size) { + return new StagedSplitBounds[size]; + } + }; +} diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS index d80699de8a2d..f49e80ae16b1 100644 --- a/libs/WindowManager/Shell/tests/OWNERS +++ b/libs/WindowManager/Shell/tests/OWNERS @@ -1,3 +1,4 @@ # Bug component: 909476 # includes OWNERS from parent directories natanieljr@google.com +pablogamito@google.com diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java index a1e12319ac70..19a5417aace6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java @@ -20,6 +20,8 @@ import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doReturn; @@ -31,10 +33,9 @@ import static org.mockito.Mockito.verify; import static java.lang.Integer.MAX_VALUE; import android.app.ActivityManager; -import android.app.WindowConfiguration; import android.content.Context; +import android.graphics.Rect; import android.view.SurfaceControl; -import android.window.TaskAppearedInfo; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -45,6 +46,7 @@ import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.util.GroupedRecentTaskInfo; +import com.android.wm.shell.util.StagedSplitBounds; import org.junit.Before; import org.junit.Test; @@ -106,8 +108,11 @@ public class RecentTasksControllerTest extends ShellTestCase { setRawList(t1, t2, t3, t4, t5, t6); // Mark a couple pairs [t2, t4], [t3, t5] - mRecentTasksController.addSplitPair(t2.taskId, t4.taskId); - mRecentTasksController.addSplitPair(t3.taskId, t5.taskId); + StagedSplitBounds pair1Bounds = new StagedSplitBounds(new Rect(), new Rect(), 2, 4); + StagedSplitBounds pair2Bounds = new StagedSplitBounds(new Rect(), new Rect(), 3, 5); + + mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds); + mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds); ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); @@ -126,7 +131,8 @@ public class RecentTasksControllerTest extends ShellTestCase { setRawList(t1, t2, t3); // Add a pair - mRecentTasksController.addSplitPair(t2.taskId, t3.taskId); + StagedSplitBounds pair1Bounds = new StagedSplitBounds(new Rect(), new Rect(), 2, 3); + mRecentTasksController.addSplitPair(t2.taskId, t3.taskId, pair1Bounds); reset(mRecentTasksController); // Remove one of the tasks and ensure the pair is removed @@ -201,10 +207,23 @@ public class RecentTasksControllerTest extends ShellTestCase { int[] flattenedTaskIds = new int[recentTasks.size() * 2]; for (int i = 0; i < recentTasks.size(); i++) { GroupedRecentTaskInfo pair = recentTasks.get(i); - flattenedTaskIds[2 * i] = pair.mTaskInfo1.taskId; + int taskId1 = pair.mTaskInfo1.taskId; + flattenedTaskIds[2 * i] = taskId1; flattenedTaskIds[2 * i + 1] = pair.mTaskInfo2 != null ? pair.mTaskInfo2.taskId : -1; + + if (pair.mTaskInfo2 != null) { + assertNotNull(pair.mStagedSplitBounds); + int leftTopTaskId = pair.mStagedSplitBounds.leftTopTaskId; + int bottomRightTaskId = pair.mStagedSplitBounds.rightBottomTaskId; + // Unclear if pairs are ordered by split position, most likely not. + assertTrue(leftTopTaskId == taskId1 || leftTopTaskId == pair.mTaskInfo2.taskId); + assertTrue(bottomRightTaskId == taskId1 + || bottomRightTaskId == pair.mTaskInfo2.taskId); + } else { + assertNull(pair.mStagedSplitBounds); + } } assertTrue("Expected: " + Arrays.toString(expectedTaskIds) + " Received: " + Arrays.toString(flattenedTaskIds), diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/StagedSplitBoundsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/StagedSplitBoundsTest.java new file mode 100644 index 000000000000..ad73c56950bd --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/StagedSplitBoundsTest.java @@ -0,0 +1,94 @@ +package com.android.wm.shell.recents; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.graphics.Rect; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.wm.shell.util.StagedSplitBounds; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class StagedSplitBoundsTest { + private static final int DEVICE_WIDTH = 100; + private static final int DEVICE_LENGTH = 200; + private static final int DIVIDER_SIZE = 20; + private static final int TASK_ID_1 = 4; + private static final int TASK_ID_2 = 9; + + // Bounds in screen space + private final Rect mTopRect = new Rect(); + private final Rect mBottomRect = new Rect(); + private final Rect mLeftRect = new Rect(); + private final Rect mRightRect = new Rect(); + + @Before + public void setup() { + mTopRect.set(0, 0, DEVICE_WIDTH, DEVICE_LENGTH / 2 - DIVIDER_SIZE / 2); + mBottomRect.set(0, DEVICE_LENGTH / 2 + DIVIDER_SIZE / 2, + DEVICE_WIDTH, DEVICE_LENGTH); + mLeftRect.set(0, 0, DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2, DEVICE_LENGTH); + mRightRect.set(DEVICE_WIDTH / 2 + DIVIDER_SIZE / 2, 0, + DEVICE_WIDTH, DEVICE_LENGTH); + } + + @Test + public void testVerticalStacked() { + StagedSplitBounds ssb = new StagedSplitBounds(mTopRect, mBottomRect, + TASK_ID_1, TASK_ID_2); + assertTrue(ssb.appsStackedVertically); + } + + @Test + public void testHorizontalStacked() { + StagedSplitBounds ssb = new StagedSplitBounds(mLeftRect, mRightRect, + TASK_ID_1, TASK_ID_2); + assertFalse(ssb.appsStackedVertically); + } + + @Test + public void testHorizontalDividerBounds() { + StagedSplitBounds ssb = new StagedSplitBounds(mTopRect, mBottomRect, + TASK_ID_1, TASK_ID_2); + Rect dividerBounds = ssb.visualDividerBounds; + assertEquals(0, dividerBounds.left); + assertEquals(DEVICE_LENGTH / 2 - DIVIDER_SIZE / 2, dividerBounds.top); + assertEquals(DEVICE_WIDTH, dividerBounds.right); + assertEquals(DEVICE_LENGTH / 2 + DIVIDER_SIZE / 2, dividerBounds.bottom); + } + + @Test + public void testVerticalDividerBounds() { + StagedSplitBounds ssb = new StagedSplitBounds(mLeftRect, mRightRect, + TASK_ID_1, TASK_ID_2); + Rect dividerBounds = ssb.visualDividerBounds; + assertEquals(DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2, dividerBounds.left); + assertEquals(0, dividerBounds.top); + assertEquals(DEVICE_WIDTH / 2 + DIVIDER_SIZE / 2, dividerBounds.right); + assertEquals(DEVICE_LENGTH, dividerBounds.bottom); + } + + @Test + public void testEqualVerticalTaskPercent() { + StagedSplitBounds ssb = new StagedSplitBounds(mTopRect, mBottomRect, + TASK_ID_1, TASK_ID_2); + float topPercentSpaceTaken = (float) (DEVICE_LENGTH / 2 - DIVIDER_SIZE / 2) / DEVICE_LENGTH; + assertEquals(topPercentSpaceTaken, ssb.topTaskPercent, 0.01); + } + + @Test + public void testEqualHorizontalTaskPercent() { + StagedSplitBounds ssb = new StagedSplitBounds(mLeftRect, mRightRect, + TASK_ID_1, TASK_ID_2); + float leftPercentSpaceTaken = (float) (DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2) / DEVICE_WIDTH; + assertEquals(leftPercentSpaceTaken, ssb.leftTaskPercent, 0.01); + } +} diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index cae2d0bc16b3..5e8a623d4205 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -2677,30 +2677,27 @@ bool ResTable_config::isBetterThan(const ResTable_config& o, // DENSITY_ANY is now dealt with. We should look to // pick a density bucket and potentially scale it. // Any density is potentially useful - // because the system will scale it. Scaling down - // is generally better than scaling up. + // because the system will scale it. Always prefer + // scaling down. int h = thisDensity; int l = otherDensity; bool bImBigger = true; if (l > h) { - int t = h; - h = l; - l = t; + std::swap(l, h); bImBigger = false; } - if (requestedDensity >= h) { - // requested value higher than both l and h, give h + if (h == requestedDensity) { + // This handles the case where l == h == requestedDensity. + // In that case, this and o are equally good so both + // true and false are valid. This preserves previous + // behavior. return bImBigger; - } - if (l >= requestedDensity) { + } else if (l >= requestedDensity) { // requested value lower than both l and h, give l return !bImBigger; - } - // saying that scaling down is 2x better than up - if (((2 * l) - requestedDensity) * h > requestedDensity * requestedDensity) { - return !bImBigger; } else { + // otherwise give h return bImBigger; } } diff --git a/libs/androidfw/tests/Config_test.cpp b/libs/androidfw/tests/Config_test.cpp index b54915f03c29..698c36f09301 100644 --- a/libs/androidfw/tests/Config_test.cpp +++ b/libs/androidfw/tests/Config_test.cpp @@ -75,6 +75,9 @@ TEST(ConfigTest, shouldSelectBestDensity) { configs.add(buildDensityConfig(int(ResTable_config::DENSITY_HIGH) + 20)); ASSERT_EQ(expectedBest, selectBest(deviceConfig, configs)); + configs.add(buildDensityConfig(int(ResTable_config::DENSITY_XHIGH) - 1)); + ASSERT_EQ(expectedBest, selectBest(deviceConfig, configs)); + expectedBest = buildDensityConfig(ResTable_config::DENSITY_XHIGH); configs.add(expectedBest); ASSERT_EQ(expectedBest, selectBest(deviceConfig, configs)); diff --git a/libs/hwui/pipeline/skia/FunctorDrawable.h b/libs/hwui/pipeline/skia/FunctorDrawable.h index 9bbd0a92600b..29ef2b82919d 100644 --- a/libs/hwui/pipeline/skia/FunctorDrawable.h +++ b/libs/hwui/pipeline/skia/FunctorDrawable.h @@ -34,6 +34,8 @@ namespace skiapipeline { */ class FunctorDrawable : public SkDrawable { public: + constexpr static const char* const TYPE_NAME = "FunctorDrawable"; + FunctorDrawable(int functor, SkCanvas* canvas) : mBounds(canvas->getLocalClipBounds()) , mWebViewHandle(WebViewFunctorManager::instance().handleFor(functor)) {} @@ -48,6 +50,8 @@ public: mWebViewHandle->onRemovedFromTree(); } + const char* getTypeName() const override { return TYPE_NAME; } + protected: virtual SkRect onGetBounds() override { return mBounds; } diff --git a/libs/hwui/pipeline/skia/TransformCanvas.cpp b/libs/hwui/pipeline/skia/TransformCanvas.cpp index 6777c00c4655..41e36874b862 100644 --- a/libs/hwui/pipeline/skia/TransformCanvas.cpp +++ b/libs/hwui/pipeline/skia/TransformCanvas.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ #include "TransformCanvas.h" + +#include "FunctorDrawable.h" #include "HolePunch.h" #include "SkData.h" #include "SkDrawable.h" @@ -35,7 +37,17 @@ void TransformCanvas::onDrawAnnotation(const SkRect& rect, const char* key, SkDa } void TransformCanvas::onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) { - drawable->draw(this, matrix); + // TransformCanvas filters all drawing commands while maintaining the current + // clip stack and transformation. We need to draw most SkDrawables, since their + // draw calls may call methods that affect the clip stack and transformation. (Any + // actual draw commands will then be filtered out.) But FunctorDrawables are used + // as leaf nodes which issue self-contained OpenGL/Vulkan commands. These won't + // affect the clip stack + transformation, and in some cases cause problems (e.g. if + // the surface only has an alpha channel). See b/203960959 + const auto* drawableName = drawable->getTypeName(); + if (drawableName == nullptr || strcmp(drawableName, FunctorDrawable::TYPE_NAME) != 0) { + drawable->draw(this, matrix); + } } bool TransformCanvas::onFilter(SkPaint& paint) const { diff --git a/media/jni/tuner/DvrClient.cpp b/media/jni/tuner/DvrClient.cpp index 052b465f7b84..30278382c8ec 100644 --- a/media/jni/tuner/DvrClient.cpp +++ b/media/jni/tuner/DvrClient.cpp @@ -278,8 +278,12 @@ Result DvrClient::flush() { Result DvrClient::close() { if (mDvrMQEventFlag != nullptr) { EventFlag::deleteEventFlag(&mDvrMQEventFlag); + mDvrMQEventFlag = nullptr; + } + if (mDvrMQ != nullptr) { + delete mDvrMQ; + mDvrMQ = nullptr; } - mDvrMQ = nullptr; if (mTunerDvr != nullptr) { Status s = mTunerDvr->close(); diff --git a/media/jni/tuner/FilterClient.cpp b/media/jni/tuner/FilterClient.cpp index fe746fa7971f..e8b3de82f284 100644 --- a/media/jni/tuner/FilterClient.cpp +++ b/media/jni/tuner/FilterClient.cpp @@ -177,11 +177,14 @@ Result FilterClient::setDataSource(sp<FilterClient> filterClient){ } Result FilterClient::close() { - if (mFilterMQEventFlag) { + if (mFilterMQEventFlag != nullptr) { EventFlag::deleteEventFlag(&mFilterMQEventFlag); + mFilterMQEventFlag = nullptr; + } + if (mFilterMQ != nullptr) { + delete mFilterMQ; + mFilterMQ = nullptr; } - mFilterMQEventFlag = nullptr; - mFilterMQ = nullptr; if (mTunerFilter != nullptr) { Status s = mTunerFilter->close(); diff --git a/packages/SettingsLib/AndroidManifest.xml b/packages/SettingsLib/AndroidManifest.xml index ad62f6e23ef0..a3473459948d 100644 --- a/packages/SettingsLib/AndroidManifest.xml +++ b/packages/SettingsLib/AndroidManifest.xml @@ -18,6 +18,4 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.settingslib"> - <uses-sdk android:minSdkVersion="29" /> - </manifest> diff --git a/packages/SettingsLib/MainSwitchPreference/AndroidManifest.xml b/packages/SettingsLib/MainSwitchPreference/AndroidManifest.xml index 5817f77eb6d9..6e0d82768148 100644 --- a/packages/SettingsLib/MainSwitchPreference/AndroidManifest.xml +++ b/packages/SettingsLib/MainSwitchPreference/AndroidManifest.xml @@ -18,8 +18,4 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.settingslib.widget"> - <uses-sdk - android:minSdkVersion="28" - android:targetSdkVersion="31"/> - </manifest> diff --git a/packages/SettingsLib/Utils/AndroidManifest.xml b/packages/SettingsLib/Utils/AndroidManifest.xml index fd89676ab6d7..e9957a9efd38 100644 --- a/packages/SettingsLib/Utils/AndroidManifest.xml +++ b/packages/SettingsLib/Utils/AndroidManifest.xml @@ -18,6 +18,4 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.settingslib.utils"> - <uses-sdk android:minSdkVersion="21" /> - </manifest> diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index aede665fe9a7..7e47f4561e78 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java @@ -71,6 +71,7 @@ public class InfoMediaManager extends MediaManager { MediaRouter2Manager mRouterManager; @VisibleForTesting String mPackageName; + private final boolean mVolumeAdjustmentForRemoteGroupSessions; private MediaDevice mCurrentConnectedDevice; private LocalBluetoothManager mBluetoothManager; @@ -84,6 +85,9 @@ public class InfoMediaManager extends MediaManager { if (!TextUtils.isEmpty(packageName)) { mPackageName = packageName; } + + mVolumeAdjustmentForRemoteGroupSessions = context.getResources().getBoolean( + com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions); } @Override @@ -386,7 +390,9 @@ public class InfoMediaManager extends MediaManager { @TargetApi(Build.VERSION_CODES.R) boolean shouldEnableVolumeSeekBar(RoutingSessionInfo sessionInfo) { - return false; + return sessionInfo.isSystemSession() // System sessions are not remote + || mVolumeAdjustmentForRemoteGroupSessions + || sessionInfo.getSelectedRoutes().size() <= 1; } private void refreshDevices() { diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SimStatusImeiInfoPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SimStatusImeiInfoPreferenceControllerTest.java index 5252c6c82754..52d243a14e2f 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SimStatusImeiInfoPreferenceControllerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SimStatusImeiInfoPreferenceControllerTest.java @@ -20,9 +20,8 @@ import static com.google.common.truth.Truth.assertThat; import static org.robolectric.shadow.api.Shadow.extract; -import android.net.ConnectivityManager; import android.os.UserManager; -import android.util.SparseBooleanArray; +import android.telephony.TelephonyManager; import org.junit.Before; import org.junit.Test; @@ -35,7 +34,7 @@ import org.robolectric.annotation.Implements; @RunWith(RobolectricTestRunner.class) @Config(shadows = {SimStatusImeiInfoPreferenceControllerTest.ShadowUserManager.class, - SimStatusImeiInfoPreferenceControllerTest.ShadowConnectivityManager.class}) + SimStatusImeiInfoPreferenceControllerTest.ShadowTelephonyManager.class}) public class SimStatusImeiInfoPreferenceControllerTest { private AbstractSimStatusImeiInfoPreferenceController mController; @@ -56,9 +55,9 @@ public class SimStatusImeiInfoPreferenceControllerTest { ShadowUserManager userManager = extract(RuntimeEnvironment.application.getSystemService(UserManager.class)); userManager.setIsAdminUser(true); - ShadowConnectivityManager connectivityManager = - extract(RuntimeEnvironment.application.getSystemService(ConnectivityManager.class)); - connectivityManager.setNetworkSupported(ConnectivityManager.TYPE_MOBILE, true); + ShadowTelephonyManager telephonyManager = + extract(RuntimeEnvironment.application.getSystemService(TelephonyManager.class)); + telephonyManager.setDataCapable(true); assertThat(mController.isAvailable()).isTrue(); } @@ -68,9 +67,9 @@ public class SimStatusImeiInfoPreferenceControllerTest { ShadowUserManager userManager = extract(RuntimeEnvironment.application.getSystemService(UserManager.class)); userManager.setIsAdminUser(true); - ShadowConnectivityManager connectivityManager = - extract(RuntimeEnvironment.application.getSystemService(ConnectivityManager.class)); - connectivityManager.setNetworkSupported(ConnectivityManager.TYPE_MOBILE, false); + ShadowTelephonyManager telephonyManager = + extract(RuntimeEnvironment.application.getSystemService(TelephonyManager.class)); + telephonyManager.setDataCapable(false); assertThat(mController.isAvailable()).isFalse(); } @@ -99,19 +98,17 @@ public class SimStatusImeiInfoPreferenceControllerTest { } } - @Implements(ConnectivityManager.class) - public static class ShadowConnectivityManager - extends org.robolectric.shadows.ShadowConnectivityManager { - - private final SparseBooleanArray mSupportedNetworkTypes = new SparseBooleanArray(); - - private void setNetworkSupported(int networkType, boolean supported) { - mSupportedNetworkTypes.put(networkType, supported); + @Implements(TelephonyManager.class) + public static class ShadowTelephonyManager + extends org.robolectric.shadows.ShadowTelephonyManager { + private boolean mDataCapable = false; + private void setDataCapable(boolean capable) { + mDataCapable = capable; } @Implementation - public boolean isNetworkSupported(int networkType) { - return mSupportedNetworkTypes.get(networkType); + public boolean isDataCapable() { + return mDataCapable; } } } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java index 93f900d9bda0..2bd5bdc1e7a3 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java @@ -257,14 +257,6 @@ public class GlobalSettingsValidators { VALIDATORS.put(Global.Wearable.SYSTEM_CAPABILITIES, ANY_INTEGER_VALIDATOR); VALIDATORS.put(Global.Wearable.SYSTEM_EDITION, ANY_INTEGER_VALIDATOR); VALIDATORS.put(Global.Wearable.WEAR_PLATFORM_MR_NUMBER, ANY_INTEGER_VALIDATOR); - VALIDATORS.put(Global.Wearable.BOTTOM_OFFSET, ANY_INTEGER_VALIDATOR); - VALIDATORS.put( - Global.Wearable.DISPLAY_SHAPE, - new DiscreteValueValidator( - new String[] { - String.valueOf(Global.Wearable.DISPLAY_SHAPE_ROUND), - String.valueOf(Global.Wearable.DISPLAY_SHAPE_SQUARE) - })); VALIDATORS.put(Global.Wearable.MOBILE_SIGNAL_DETECTOR, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.Wearable.AMBIENT_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.Wearable.AMBIENT_TILT_TO_WAKE, BOOLEAN_VALIDATOR); @@ -299,7 +291,6 @@ public class GlobalSettingsValidators { String.valueOf(Global.Wearable.HFP_CLIENT_ENABLED), String.valueOf(Global.Wearable.HFP_CLIENT_DISABLED) })); - VALIDATORS.put(Global.Wearable.HFP_CLIENT_PROFILE_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.Wearable.COMPANION_OS_VERSION, ANY_INTEGER_VALIDATOR); VALIDATORS.put(Global.Wearable.ENABLE_ALL_LANGUAGES, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.Wearable.OEM_SETUP_VERSION, ANY_INTEGER_VALIDATOR); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index ea46ef1220cf..cd4047bf48b2 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -5294,11 +5294,6 @@ public class SettingsProvider extends ContentProvider { Global.Wearable.WEAR_PLATFORM_MR_NUMBER, SystemProperties.getInt("ro.cw_build.platform_mr", 0)); initGlobalSettingsDefaultValForWearLocked( - Settings.Global.Wearable.BOTTOM_OFFSET, 0); - initGlobalSettingsDefaultValForWearLocked( - Settings.Global.Wearable.DISPLAY_SHAPE, - Settings.Global.Wearable.DISPLAY_SHAPE_SQUARE); - initGlobalSettingsDefaultValForWearLocked( Settings.Global.Wearable.SCREEN_BRIGHTNESS_LEVEL, getContext() .getResources() @@ -5345,8 +5340,6 @@ public class SettingsProvider extends ContentProvider { Settings.Global.Wearable.AMBIENT_PLUGGED_TIMEOUT_MIN, SystemProperties.getInt("ro.ambient.plugged_timeout_min", -1)); initGlobalSettingsDefaultValForWearLocked( - Settings.Global.Wearable.COMPANION_ADDRESS, ""); - initGlobalSettingsDefaultValForWearLocked( Settings.Global.Wearable.PAIRED_DEVICE_OS_TYPE, Settings.Global.Wearable.PAIRED_DEVICE_OS_TYPE_UNKNOWN); initGlobalSettingsDefaultValForWearLocked( @@ -5359,12 +5352,6 @@ public class SettingsProvider extends ContentProvider { disabledProfileSetting.isNull() ? 0 : Long.parseLong(disabledProfileSetting.getValue()); - final boolean isHfpClientProfileEnabled = - (disabledProfileSettingValue & (1 << BluetoothProfile.HEADSET_CLIENT)) - == 0; - initGlobalSettingsDefaultValForWearLocked( - Settings.Global.Wearable.HFP_CLIENT_PROFILE_ENABLED, - isHfpClientProfileEnabled); initGlobalSettingsDefaultValForWearLocked( Settings.Global.Wearable.COMPANION_OS_VERSION, Settings.Global.Wearable.COMPANION_OS_VERSION_UNDEFINED); diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 82012d99bf63..c05e01dda1a5 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -632,9 +632,6 @@ public class SettingsBackupTest { Settings.Global.Wearable.SYSTEM_CAPABILITIES, Settings.Global.Wearable.SYSTEM_EDITION, Settings.Global.Wearable.WEAR_PLATFORM_MR_NUMBER, - Settings.Global.Wearable.COMPANION_BT_ADDRESS_DUAL, - Settings.Global.Wearable.DISPLAY_SHAPE, - Settings.Global.Wearable.BOTTOM_OFFSET, Settings.Global.Wearable.SCREEN_BRIGHTNESS_LEVEL, Settings.Global.Wearable.MOBILE_SIGNAL_DETECTOR, Settings.Global.Wearable.AMBIENT_ENABLED, @@ -647,12 +644,10 @@ public class SettingsBackupTest { Settings.Global.Wearable.AMBIENT_GESTURE_SENSOR_ID, Settings.Global.Wearable.AMBIENT_LOW_BIT_ENABLED, Settings.Global.Wearable.AMBIENT_PLUGGED_TIMEOUT_MIN, - Settings.Global.Wearable.COMPANION_ADDRESS, Settings.Global.Wearable.PAIRED_DEVICE_OS_TYPE, Settings.Global.Wearable.COMPANION_BLE_ROLE, Settings.Global.Wearable.COMPANION_NAME, Settings.Global.Wearable.USER_HFP_CLIENT_SETTING, - Settings.Global.Wearable.HFP_CLIENT_PROFILE_ENABLED, Settings.Global.Wearable.COMPANION_OS_VERSION, Settings.Global.Wearable.ENABLE_ALL_LANGUAGES, Settings.Global.Wearable.SETUP_LOCALE, diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS index 1cf14f2362de..ce23a8bc09ca 100644 --- a/packages/SystemUI/OWNERS +++ b/packages/SystemUI/OWNERS @@ -22,6 +22,7 @@ hwwang@google.com hyunyoungs@google.com jaggies@google.com jamesoleary@google.com +jbolinger@google.com jdemeulenaere@google.com jeffdq@google.com jjaggi@google.com diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt index 1844288796cc..0b3eccfd3a91 100644 --- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt +++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt @@ -85,9 +85,10 @@ public class ColorScheme(@ColorInt seed: Int, val darkTheme: Boolean) { val camSeed = Cam.fromInt(seedArgb) val hue = camSeed.hue val chroma = camSeed.chroma.coerceAtLeast(ACCENT1_CHROMA) + val tertiaryHue = wrapDegrees((hue + ACCENT3_HUE_SHIFT).toInt()) accent1 = Shades.of(hue, chroma).toList() accent2 = Shades.of(hue, ACCENT2_CHROMA).toList() - accent3 = Shades.of(hue + ACCENT3_HUE_SHIFT, ACCENT3_CHROMA).toList() + accent3 = Shades.of(tertiaryHue.toFloat(), ACCENT3_CHROMA).toList() neutral1 = Shades.of(hue, NEUTRAL1_CHROMA).toList() neutral2 = Shades.of(hue, NEUTRAL2_CHROMA).toList() } diff --git a/packages/SystemUI/plugin/AndroidManifest.xml b/packages/SystemUI/plugin/AndroidManifest.xml index 7c057dc78ac7..811595ade979 100644 --- a/packages/SystemUI/plugin/AndroidManifest.xml +++ b/packages/SystemUI/plugin/AndroidManifest.xml @@ -18,7 +18,4 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.systemui.plugins"> - <uses-sdk - android:minSdkVersion="21" /> - </manifest> diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags index a110413700b2..07c2ac426391 100644 --- a/packages/SystemUI/proguard.flags +++ b/packages/SystemUI/proguard.flags @@ -5,8 +5,8 @@ -keep class com.android.systemui.car.CarSystemUIFactory -keep class com.android.systemui.SystemUIFactory -keep class com.android.systemui.tv.TvSystemUIFactory --keep class * extends com.android.systemui.SystemUI --keep class * implements com.android.systemui.SystemUI$Injector +-keep class * extends com.android.systemui.CoreStartable +-keep class * implements com.android.systemui.CoreStartable$Injector -keepclasseswithmembers class * { public <init>(android.content.Context, android.util.AttributeSet); @@ -22,7 +22,7 @@ } -keep class androidx.core.app.CoreComponentFactory --keep public class * extends com.android.systemui.SystemUI { +-keep public class * extends com.android.systemui.CoreStartable { public <init>(android.content.Context); } diff --git a/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml b/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml index c415ecd4f0a8..88914ded15c8 100644 --- a/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml +++ b/packages/SystemUI/res/drawable/stat_sys_managed_profile_status.xml @@ -20,5 +20,5 @@ android:viewportHeight="24"> <path android:fillColor="@android:color/white" - android:pathData="M20,6h-4V4c0-1.1-0.9-2-2-2h-4C8.9,2,8,2.9,8,4v2H4C2.9,6,2,6.9,2,8l0,11c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V8 C22,6.9,21.1,6,20,6z M10,4h4v2h-4V4z M20,19H4V8h16V19z" /> + android:pathData="@*android:string/config_work_badge_path_24" /> </vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/notif_half_shelf.xml b/packages/SystemUI/res/layout/notif_half_shelf.xml index 6bc01389e3c3..b2ff46e0de29 100644 --- a/packages/SystemUI/res/layout/notif_half_shelf.xml +++ b/packages/SystemUI/res/layout/notif_half_shelf.xml @@ -78,12 +78,6 @@ android:layout_gravity="center_vertical" android:padding="8dp" /> </com.android.systemui.statusbar.notification.row.AppControlView> - <!-- divider view --> - <View - android:layout_width="match_parent" - android:layout_height="1dp" - android:background="@*android:color/background_device_default_light" - /> <!-- ChannelRows get added dynamically --> diff --git a/packages/SystemUI/res/layout/notif_half_shelf_row.xml b/packages/SystemUI/res/layout/notif_half_shelf_row.xml index 245d1579d5a3..d03cd7e87a2d 100644 --- a/packages/SystemUI/res/layout/notif_half_shelf_row.xml +++ b/packages/SystemUI/res/layout/notif_half_shelf_row.xml @@ -87,10 +87,4 @@ android:padding="8dp" /> </LinearLayout> - <!-- divider view --> - <View - android:layout_width="match_parent" - android:layout_height=".5dp" - android:background="@*android:color/background_device_default_light" - /> </com.android.systemui.statusbar.notification.row.ChannelRow> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index d2ea78978ba2..af5d85de0562 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -77,7 +77,7 @@ <!-- The color of the gear shown behind a notification --> <color name="notification_gear_color">@color/GM2_grey_700</color> - <color name="notification_guts_link_icon_tint">@color/GM2_grey_700</color> + <color name="notification_guts_link_icon_tint">@*android:color/accent_device_default</color> <color name="notification_guts_sub_text_color">@color/GM2_grey_700</color> <color name="notification_guts_header_text_color">@color/GM2_grey_900</color> <color name="notification_guts_priority_button_content_color">@color/GM2_grey_700</color> diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagReader.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagReader.kt index ee6dea5364f4..91a391272be7 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagReader.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagReader.kt @@ -20,6 +20,11 @@ package com.android.systemui.flags */ interface FlagReader { /** Returns a boolean value for the given flag. */ + fun isEnabled(flag: BooleanFlag): Boolean { + return flag.default + } + + /** Returns a boolean value for the given flag. */ fun isEnabled(id: Int, def: Boolean): Boolean { return def } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/GroupTask.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/GroupTask.java deleted file mode 100644 index 323b20e41a5c..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/GroupTask.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.shared.recents.model; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -/** - * A group task in the recent tasks list. - * TODO: Move this into Launcher - */ -public class GroupTask { - public @NonNull Task task1; - public @Nullable Task task2; - - public GroupTask(@NonNull Task t1, @Nullable Task t2) { - task1 = t1; - task2 = t2; - } - - public GroupTask(@NonNull GroupTask group) { - task1 = new Task(group.task1); - task2 = group.task2 != null - ? new Task(group.task2) - : null; - } - - public boolean containsTask(int taskId) { - return task1.key.id == taskId || (task2 != null && task2.key.id == taskId); - } -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java index b95123d2fa41..38eded878014 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java @@ -195,8 +195,13 @@ public class ActivityManagerWrapper { } @Override - public void onTaskAppeared(RemoteAnimationTarget app) { - animationHandler.onTaskAppeared(new RemoteAnimationTargetCompat(app)); + public void onTasksAppeared(RemoteAnimationTarget[] apps) { + final RemoteAnimationTargetCompat[] compats = + new RemoteAnimationTargetCompat[apps.length]; + for (int i = 0; i < apps.length; ++i) { + compats[i] = new RemoteAnimationTargetCompat(apps[i]); + } + animationHandler.onTasksAppeared(compats); } }; } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java index a74de2e0c085..48f1b76c1d50 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java @@ -39,5 +39,5 @@ public interface RecentsAnimationListener { * Called when the task of an activity that has been started while the recents animation * was running becomes ready for control. */ - void onTaskAppeared(RemoteAnimationTargetCompat app); + void onTasksAppeared(RemoteAnimationTargetCompat[] app); } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java index 99b6aed497cc..954cf9fd81a8 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java @@ -53,6 +53,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DataClass; import com.android.systemui.shared.recents.model.ThumbnailData; +import java.util.ArrayList; import java.util.concurrent.Executor; /** @@ -127,7 +128,7 @@ public class RemoteTransitionCompat implements Parcelable { mToken = transition; // This transition is for opening recents, so recents is on-top. We want to draw // the current going-away task on top of recents, though, so move it to front - WindowContainerToken pausingTask = null; + final ArrayList<WindowContainerToken> pausingTasks = new ArrayList<>(); WindowContainerToken pipTask = null; for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); @@ -138,7 +139,8 @@ public class RemoteTransitionCompat implements Parcelable { if (taskInfo == null) { continue; } - pausingTask = taskInfo.token; + // Add to front since we are iterating backwards. + pausingTasks.add(0, taskInfo.token); if (taskInfo.pictureInPictureParams != null && taskInfo.pictureInPictureParams.isAutoEnterEnabled()) { pipTask = taskInfo.token; @@ -150,7 +152,7 @@ public class RemoteTransitionCompat implements Parcelable { t.setAlpha(wallpapers[i].leash.mSurfaceControl, 1); } t.apply(); - mRecentsSession.setup(controller, info, finishedCallback, pausingTask, pipTask, + mRecentsSession.setup(controller, info, finishedCallback, pausingTasks, pipTask, leashMap, mToken); recents.onAnimationStart(mRecentsSession, apps, wallpapers, new Rect(0, 0, 0, 0), new Rect()); @@ -198,18 +200,18 @@ public class RemoteTransitionCompat implements Parcelable { static class RecentsControllerWrap extends RecentsAnimationControllerCompat { private RecentsAnimationControllerCompat mWrapped = null; private IRemoteTransitionFinishedCallback mFinishCB = null; - private WindowContainerToken mPausingTask = null; + private ArrayList<WindowContainerToken> mPausingTasks = null; private WindowContainerToken mPipTask = null; private TransitionInfo mInfo = null; - private SurfaceControl mOpeningLeash = null; + private ArrayList<SurfaceControl> mOpeningLeashes = null; private ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = null; private PictureInPictureSurfaceTransaction mPipTransaction = null; private IBinder mTransition = null; void setup(RecentsAnimationControllerCompat wrapped, TransitionInfo info, - IRemoteTransitionFinishedCallback finishCB, WindowContainerToken pausingTask, - WindowContainerToken pipTask, ArrayMap<SurfaceControl, SurfaceControl> leashMap, - IBinder transition) { + IRemoteTransitionFinishedCallback finishCB, + ArrayList<WindowContainerToken> pausingTasks, WindowContainerToken pipTask, + ArrayMap<SurfaceControl, SurfaceControl> leashMap, IBinder transition) { if (mInfo != null) { throw new IllegalStateException("Trying to run a new recents animation while" + " recents is already active."); @@ -217,7 +219,7 @@ public class RemoteTransitionCompat implements Parcelable { mWrapped = wrapped; mInfo = info; mFinishCB = finishCB; - mPausingTask = pausingTask; + mPausingTasks = pausingTasks; mPipTask = pipTask; mLeashMap = leashMap; mTransition = transition; @@ -226,36 +228,57 @@ public class RemoteTransitionCompat implements Parcelable { @SuppressLint("NewApi") boolean merge(TransitionInfo info, SurfaceControl.Transaction t, RecentsAnimationListener recents) { - TransitionInfo.Change openingTask = null; + ArrayList<TransitionInfo.Change> openingTasks = null; for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); if (change.getMode() == TRANSIT_OPEN || change.getMode() == TRANSIT_TO_FRONT) { if (change.getTaskInfo() != null) { - if (openingTask != null) { - Log.w(TAG, " Expecting to merge a task-open, but got >1 opening " - + "tasks"); + if (openingTasks == null) { + openingTasks = new ArrayList<>(); } - openingTask = change; + openingTasks.add(change); } } } - if (openingTask == null) return false; - mOpeningLeash = openingTask.getLeash(); - if (openingTask.getContainer().equals(mPausingTask)) { - // In this case, we are "returning" to the already running app, so just consume + if (openingTasks == null) return false; + int pauseMatches = 0; + for (int i = 0; i < openingTasks.size(); ++i) { + if (mPausingTasks.contains(openingTasks.get(i).getContainer())) { + ++pauseMatches; + } + if (openingTasks.get(i).getContainer().equals(mPausingTasks.get(i))) { + // In this case, we are "returning" to an already running app, so just consume + // the merge and do nothing. + } + } + if (pauseMatches > 0) { + if (pauseMatches != mPausingTasks.size()) { + // We are not really "returning" properly... something went wrong. + throw new IllegalStateException("\"Concelling\" a recents transitions by " + + "unpausing " + pauseMatches + " apps after pausing " + + mPausingTasks.size() + " apps."); + } + // In this case, we are "returning" to an already running app, so just consume // the merge and do nothing. return true; } - // We are receiving a new opening task, so convert to onTaskAppeared. final int layer = mInfo.getChanges().size() * 3; - final RemoteAnimationTargetCompat target = new RemoteAnimationTargetCompat( - openingTask, layer, mInfo, t); - mLeashMap.put(mOpeningLeash, target.leash.mSurfaceControl); - t.reparent(target.leash.mSurfaceControl, mInfo.getRootLeash()); - t.setLayer(target.leash.mSurfaceControl, layer); - t.hide(target.leash.mSurfaceControl); - t.apply(); - recents.onTaskAppeared(target); + mOpeningLeashes = new ArrayList<>(); + final RemoteAnimationTargetCompat[] targets = + new RemoteAnimationTargetCompat[openingTasks.size()]; + for (int i = 0; i < openingTasks.size(); ++i) { + mOpeningLeashes.add(openingTasks.get(i).getLeash()); + // We are receiving new opening tasks, so convert to onTasksAppeared. + final RemoteAnimationTargetCompat target = new RemoteAnimationTargetCompat( + openingTasks.get(i), layer, mInfo, t); + mLeashMap.put(mOpeningLeashes.get(i), target.leash.mSurfaceControl); + t.reparent(target.leash.mSurfaceControl, mInfo.getRootLeash()); + t.setLayer(target.leash.mSurfaceControl, layer); + t.hide(target.leash.mSurfaceControl); + t.apply(); + targets[i] = target; + } + recents.onTasksAppeared(targets); return true; } @@ -292,21 +315,26 @@ public class RemoteTransitionCompat implements Parcelable { } if (mWrapped != null) mWrapped.finish(toHome, sendUserLeaveHint); try { - if (!toHome && mPausingTask != null && mOpeningLeash == null) { + if (!toHome && mPausingTasks != null && mOpeningLeashes == null) { // The gesture went back to opening the app rather than continuing with // recents, so end the transition by moving the app back to the top (and also // re-showing it's task). final WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.reorder(mPausingTask, true /* onTop */); final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - t.show(mInfo.getChange(mPausingTask).getLeash()); + for (int i = mPausingTasks.size() - 1; i >= 0; ++i) { + // reverse order so that index 0 ends up on top + wct.reorder(mPausingTasks.get(i), true /* onTop */); + t.show(mInfo.getChange(mPausingTasks.get(i)).getLeash()); + } mFinishCB.onTransitionFinished(wct, t); } else { - if (mOpeningLeash != null) { + if (mOpeningLeashes != null) { // TODO: the launcher animation should handle this final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - t.show(mOpeningLeash); - t.setAlpha(mOpeningLeash, 1.f); + for (int i = 0; i < mOpeningLeashes.size(); ++i) { + t.show(mOpeningLeashes.get(i)); + t.setAlpha(mOpeningLeashes.get(i), 1.f); + } t.apply(); } if (mPipTask != null && mPipTransaction != null) { @@ -339,9 +367,9 @@ public class RemoteTransitionCompat implements Parcelable { // Reset all members. mWrapped = null; mFinishCB = null; - mPausingTask = null; + mPausingTasks = null; mInfo = null; - mOpeningLeash = null; + mOpeningLeashes = null; mLeashMap = null; mTransition = null; } diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java b/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java index ef046193473d..acfa3c84a4ba 100644 --- a/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java +++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FeatureFlagManager.java @@ -26,13 +26,16 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.res.Resources; import android.os.Bundle; import android.util.Log; +import androidx.annotation.BoolRes; import androidx.annotation.NonNull; import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.util.settings.SecureSettings; @@ -62,14 +65,19 @@ public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable { private final FlagManager mFlagManager; private final SecureSettings mSecureSettings; + private final Resources mResources; private final Map<Integer, Boolean> mBooleanFlagCache = new HashMap<>(); @Inject - public FeatureFlagManager(FlagManager flagManager, - SecureSettings secureSettings, Context context, + public FeatureFlagManager( + FlagManager flagManager, + Context context, + SecureSettings secureSettings, + @Main Resources resources, DumpManager dumpManager) { mFlagManager = flagManager; mSecureSettings = secureSettings; + mResources = resources; IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_SET_FLAG); filter.addAction(ACTION_GET_FLAGS); @@ -77,17 +85,32 @@ public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable { dumpManager.registerDumpable(TAG, this); } - /** Return a {@link BooleanFlag}'s value. */ @Override - public boolean isEnabled(int id, boolean defaultValue) { + public boolean isEnabled(BooleanFlag flag) { + int id = flag.getId(); if (!mBooleanFlagCache.containsKey(id)) { - Boolean result = isEnabledInternal(id); - mBooleanFlagCache.put(id, result == null ? defaultValue : result); + boolean def = flag.getDefault(); + if (flag.hasResourceOverride()) { + try { + def = isEnabledInOverlay(flag.getResourceOverride()); + } catch (Resources.NotFoundException e) { + // no-op + } + } + + mBooleanFlagCache.put(id, isEnabled(id, def)); } return mBooleanFlagCache.get(id); } + /** Return a {@link BooleanFlag}'s value. */ + @Override + public boolean isEnabled(int id, boolean defaultValue) { + Boolean result = isEnabledInternal(id); + return result == null ? defaultValue : result; + } + /** Returns the stored value or null if not set. */ private Boolean isEnabledInternal(int id) { try { @@ -98,6 +121,10 @@ public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable { return null; } + private boolean isEnabledInOverlay(@BoolRes int resId) { + return mResources.getBoolean(resId); + } + /** Set whether a given {@link BooleanFlag} is enabled or not. */ @Override public void setEnabled(int id, boolean value) { diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FeatureFlagManager.java b/packages/SystemUI/src-release/com/android/systemui/flags/FeatureFlagManager.java index 6ff175f589a6..0934b32a71e4 100644 --- a/packages/SystemUI/src-release/com/android/systemui/flags/FeatureFlagManager.java +++ b/packages/SystemUI/src-release/com/android/systemui/flags/FeatureFlagManager.java @@ -16,7 +16,6 @@ package com.android.systemui.flags; -import android.content.Context; import android.util.SparseBooleanArray; import androidx.annotation.NonNull; @@ -24,7 +23,6 @@ import androidx.annotation.NonNull; import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; -import com.android.systemui.util.settings.SecureSettings; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -41,8 +39,7 @@ import javax.inject.Inject; public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable { SparseBooleanArray mAccessedFlags = new SparseBooleanArray(); @Inject - public FeatureFlagManager( - SecureSettings secureSettings, Context context, DumpManager dumpManager) { + public FeatureFlagManager(DumpManager dumpManager) { dumpManager.registerDumpable("SysUIFlags", this); } @@ -53,6 +50,11 @@ public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable { public void removeListener(Listener run) {} @Override + public boolean isEnabled(BooleanFlag flag) { + return isEnabled(flag.getId(), flag.getDefault()); + } + + @Override public boolean isEnabled(int key, boolean defaultValue) { mAccessedFlags.append(key, defaultValue); return defaultValue; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 85bc8f7c70a2..d27bc675ecb8 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -2044,17 +2044,17 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } /** - * @return true if there's at least one udfps enrolled + * @return true if there's at least one udfps enrolled for the current user. */ public boolean isUdfpsEnrolled() { return mIsUdfpsEnrolled; } /** - * @return if udfps is available on this device. will return true even if the user hasn't - * enrolled udfps. This may be false if called before onAllAuthenticatorsRegistered. + * @return true if udfps HW is supported on this device. Can return true even if the user has + * not enrolled udfps. This may be false if called before onAllAuthenticatorsRegistered. */ - public boolean isUdfpsAvailable() { + public boolean isUdfpsSupported() { return mAuthController.getUdfpsProps() != null && !mAuthController.getUdfpsProps().isEmpty(); } @@ -2102,7 +2102,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } updateUdfpsEnrolled(getCurrentUser()); - final boolean shouldListenForFingerprint = shouldListenForFingerprint(isUdfpsEnrolled()); + final boolean shouldListenForFingerprint = shouldListenForFingerprint(isUdfpsSupported()); final boolean runningOrRestarting = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING || mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING; if (runningOrRestarting && !shouldListenForFingerprint) { @@ -2407,7 +2407,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } else { mFpm.authenticate(null /* crypto */, mFingerprintCancelSignal, mFingerprintAuthenticationCallback, null /* handler */, - FingerprintManager.SENSOR_ID_ANY, userId); + FingerprintManager.SENSOR_ID_ANY, userId, 0 /* flags */); } setFingerprintRunningState(BIOMETRIC_STATE_RUNNING); } @@ -2990,7 +2990,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab /** * Register to receive notifications about general keyguard information - * (see {@link InfoCallback}. + * (see {@link KeyguardUpdateMonitorCallback}. * * @param callback The callback to register */ @@ -3388,11 +3388,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab + " expected=" + (shouldListenForFingerprint(isUdfpsEnrolled()) ? 1 : 0)); pw.println(" strongAuthFlags=" + Integer.toHexString(strongAuthFlags)); pw.println(" trustManaged=" + getUserTrustIsManaged(userId)); - pw.println(" udfpsEnrolled=" + isUdfpsEnrolled()); pw.println(" mFingerprintLockedOut=" + mFingerprintLockedOut); pw.println(" mFingerprintLockedOutPermanent=" + mFingerprintLockedOutPermanent); pw.println(" enabledByUser=" + mBiometricEnabledForUser.get(userId)); - if (isUdfpsEnrolled()) { + if (isUdfpsSupported()) { + pw.println(" udfpsEnrolled=" + isUdfpsEnrolled()); pw.println(" shouldListenForUdfps=" + shouldListenForFingerprint(true)); pw.println(" bouncerVisible=" + mBouncer); pw.println(" mStatusBarState=" diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java index 23438a957b48..a382b5331d26 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java @@ -136,6 +136,13 @@ public class KeyguardVisibilityHelper { .setStartDelay(delay); } animator.start(); + } else if (mUnlockedScreenOffAnimationController.shouldAnimateInKeyguard()) { + mKeyguardViewVisibilityAnimating = true; + + // Ask the screen off animation controller to animate the keyguard visibility for us + // since it may need to be cancelled due to keyguard lifecycle events. + mUnlockedScreenOffAnimationController.animateInKeyguard( + mView, mAnimateKeyguardStatusViewVisibleEndRunnable); } else if (mLastOccludedState && !isOccluded) { // An activity was displayed over the lock screen, and has now gone away mView.setVisibility(View.VISIBLE); @@ -147,13 +154,6 @@ public class KeyguardVisibilityHelper { .alpha(1f) .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable) .start(); - } else if (mUnlockedScreenOffAnimationController.shouldAnimateInKeyguard()) { - mKeyguardViewVisibilityAnimating = true; - - // Ask the screen off animation controller to animate the keyguard visibility for us - // since it may need to be cancelled due to keyguard lifecycle events. - mUnlockedScreenOffAnimationController.animateInKeyguard( - mView, mAnimateKeyguardStatusViewVisibleEndRunnable); } else { mView.setVisibility(View.VISIBLE); mView.setAlpha(1f); diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 8a0b5b8704e6..c7be3ce01c54 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -435,7 +435,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme boolean wasUdfpsSupported = mUdfpsSupported; boolean wasUdfpsEnrolled = mUdfpsEnrolled; - mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null; + mUdfpsSupported = mKeyguardUpdateMonitor.isUdfpsSupported(); mView.setUseBackground(mUdfpsSupported); mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled(); diff --git a/packages/SystemUI/src/com/android/systemui/SystemUI.java b/packages/SystemUI/src/com/android/systemui/CoreStartable.java index c6a750af862c..0df8e0228984 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUI.java +++ b/packages/SystemUI/src/com/android/systemui/CoreStartable.java @@ -30,17 +30,18 @@ import java.io.PrintWriter; /** * A top-level module of system UI code (sometimes called "system UI services" elsewhere in code). - * Which SystemUI modules are loaded can be controlled via a config resource. + * Which CoreStartable modules are loaded can be controlled via a config resource. * * @see SystemUIApplication#startServicesIfNeeded() */ -public abstract class SystemUI implements Dumpable { +public abstract class CoreStartable implements Dumpable { protected final Context mContext; - public SystemUI(Context context) { + public CoreStartable(Context context) { mContext = context; } + /** Main entry point for implementations. Called shortly after app startup. */ public abstract void start(); protected void onConfigurationChanged(Configuration newConfig) { @@ -54,6 +55,7 @@ public abstract class SystemUI implements Dumpable { protected void onBootCompleted() { } + /** TODO(b/205725937): Move this. SystemUIApplication? */ public static void overrideNotificationAppName(Context context, Notification.Builder n, boolean system) { final Bundle extras = new Bundle(); diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java index d325b92cf89f..bc2a1ff24235 100644 --- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java +++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java @@ -35,7 +35,7 @@ import javax.inject.Inject; * system that are used for testing the latency. */ @SysUISingleton -public class LatencyTester extends SystemUI { +public class LatencyTester extends CoreStartable { private static final String ACTION_FINGERPRINT_WAKE = diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index b2fae9dd8469..e84024d4f88f 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -107,7 +107,7 @@ import javax.inject.Inject; * for antialiasing and emulation purposes. */ @SysUISingleton -public class ScreenDecorations extends SystemUI implements Tunable { +public class ScreenDecorations extends CoreStartable implements Tunable { private static final boolean DEBUG = false; private static final String TAG = "ScreenDecorations"; diff --git a/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java b/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java index 0b997d0e0955..d7da63b9e262 100644 --- a/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java +++ b/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java @@ -38,7 +38,7 @@ import javax.inject.Inject; * @see SliceBroadcastRelay */ @SysUISingleton -public class SliceBroadcastRelayHandler extends SystemUI { +public class SliceBroadcastRelayHandler extends CoreStartable { private static final String TAG = "SliceBroadcastRelay"; private static final boolean DEBUG = false; diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index 4fd270131466..61d8c25f63ff 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -60,7 +60,7 @@ public class SystemUIApplication extends Application implements /** * Hold a reference on the stuff we start. */ - private SystemUI[] mServices; + private CoreStartable[] mServices; private boolean mServicesStarted; private SystemUIAppComponentFactory.ContextAvailableCallback mContextAvailableCallback; private GlobalRootComponent mRootComponent; @@ -190,7 +190,7 @@ public class SystemUIApplication extends Application implements if (mServicesStarted) { return; } - mServices = new SystemUI[services.length]; + mServices = new CoreStartable[services.length]; if (!mBootCompleteCache.isBootComplete()) { // check to see if maybe it was already completed long before we began @@ -217,10 +217,10 @@ public class SystemUIApplication extends Application implements log.traceBegin(metricsPrefix + clsName); long ti = System.currentTimeMillis(); try { - SystemUI obj = mComponentHelper.resolveSystemUI(clsName); + CoreStartable obj = mComponentHelper.resolveCoreStartable(clsName); if (obj == null) { Constructor constructor = Class.forName(clsName).getConstructor(Context.class); - obj = (SystemUI) constructor.newInstance(this); + obj = (CoreStartable) constructor.newInstance(this); } mServices[i] = obj; } catch (ClassNotFoundException @@ -265,7 +265,7 @@ public class SystemUIApplication extends Application implements } } - public SystemUI[] getServices() { + public CoreStartable[] getServices() { return mServices; } diff --git a/packages/SystemUI/src/com/android/systemui/VendorServices.java b/packages/SystemUI/src/com/android/systemui/VendorServices.java index 13d847bf93c4..139448c0fab4 100644 --- a/packages/SystemUI/src/com/android/systemui/VendorServices.java +++ b/packages/SystemUI/src/com/android/systemui/VendorServices.java @@ -21,7 +21,7 @@ import android.content.Context; /** * Placeholder for any vendor-specific services. */ -public class VendorServices extends SystemUI { +public class VendorServices extends CoreStartable { public VendorServices(Context context) { super(context); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java index e521c90961fb..052ec86d8398 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java @@ -28,7 +28,6 @@ import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.Rect; import android.os.Bundle; -import android.os.UserHandle; import android.provider.Settings; import android.util.MathUtils; import android.view.Gravity; @@ -75,6 +74,7 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider; private int mMagnificationMode = ACCESSIBILITY_MAGNIFICATION_MODE_NONE; private final LayoutParams mParams; + private final SwitchListener mSwitchListener; @VisibleForTesting final Rect mDraggableWindowBounds = new Rect(); private boolean mIsVisible = false; @@ -82,17 +82,29 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL private boolean mSingleTapDetected = false; private boolean mToLeftScreenEdge = false; - MagnificationModeSwitch(@UiContext Context context) { - this(context, createView(context), new SfVsyncFrameCallbackProvider()); + public interface SwitchListener { + /** + * Called when the switch is clicked to change the magnification mode. + * @param displayId the display id of the display to which the view's window has been + * attached + * @param magnificationMode the magnification mode + */ + void onSwitch(int displayId, int magnificationMode); + } + + MagnificationModeSwitch(@UiContext Context context, + SwitchListener switchListener) { + this(context, createView(context), new SfVsyncFrameCallbackProvider(), switchListener); } @VisibleForTesting MagnificationModeSwitch(Context context, @NonNull ImageView imageView, - SfVsyncFrameCallbackProvider sfVsyncFrameProvider) { + SfVsyncFrameCallbackProvider sfVsyncFrameProvider, SwitchListener switchListener) { mContext = context; mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class); mWindowManager = mContext.getSystemService(WindowManager.class); mSfVsyncFrameProvider = sfVsyncFrameProvider; + mSwitchListener = switchListener; mParams = createLayoutParams(context); mImageView = imageView; mImageView.setOnTouchListener(this::onTouch); @@ -364,11 +376,7 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL mMagnificationMode ^ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL; mMagnificationMode = newMode; mImageView.setImageResource(getIconResId(newMode)); - Settings.Secure.putIntForUser( - mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, - newMode, - UserHandle.USER_CURRENT); + mSwitchListener.onSwitch(mContext.getDisplayId(), newMode); } private void handleSingleTap() { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java b/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java index 1a01ad85fccc..5e48ee3d2366 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java @@ -18,6 +18,8 @@ package com.android.systemui.accessibility; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY; +import static com.android.systemui.accessibility.MagnificationModeSwitch.SwitchListener; + import android.annotation.MainThread; import android.content.Context; import android.hardware.display.DisplayManager; @@ -27,21 +29,24 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.dagger.SysUISingleton; /** - * A class to control {@link MagnificationModeSwitch}. It should show the button UI with following + * A class to control {@link MagnificationModeSwitch}. It shows the button UI with following * conditions: * <ol> * <li> Both full-screen and window magnification mode are capable.</li> * <li> The magnification scale is changed by a user.</li> * <ol> + * The switch action will be handled by {@link #mSwitchListenerDelegate} which informs the system + * server about the changed mode. */ @SysUISingleton -public class ModeSwitchesController { +public class ModeSwitchesController implements SwitchListener { private final DisplayIdIndexSupplier<MagnificationModeSwitch> mSwitchSupplier; + private SwitchListener mSwitchListenerDelegate; public ModeSwitchesController(Context context) { mSwitchSupplier = new SwitchSupplier(context, - context.getSystemService(DisplayManager.class)); + context.getSystemService(DisplayManager.class), this::onSwitch); } @VisibleForTesting @@ -50,8 +55,8 @@ public class ModeSwitchesController { } /** - * Shows a button that a user can click the button to switch magnification mode. And the - * button would be dismissed automatically after the button is displayed for a period of time. + * Shows a button that a user can click to switch magnification mode. And the button + * would be dismissed automatically after the button is displayed for a period of time. * * @param displayId The logical display id * @param mode The magnification mode @@ -93,24 +98,41 @@ public class ModeSwitchesController { switchController -> switchController.onConfigurationChanged(configDiff)); } + @Override + public void onSwitch(int displayId, int magnificationMode) { + if (mSwitchListenerDelegate != null) { + mSwitchListenerDelegate.onSwitch(displayId, magnificationMode); + } + } + + public void setSwitchListenerDelegate(SwitchListener switchListenerDelegate) { + mSwitchListenerDelegate = switchListenerDelegate; + } + private static class SwitchSupplier extends DisplayIdIndexSupplier<MagnificationModeSwitch> { private final Context mContext; + private final SwitchListener mSwitchListener; /** + * Supplies the switch for the given display. + * * @param context Context * @param displayManager DisplayManager + * @param switchListener The callback that will run when the switch is clicked */ - SwitchSupplier(Context context, DisplayManager displayManager) { + SwitchSupplier(Context context, DisplayManager displayManager, + SwitchListener switchListener) { super(displayManager); mContext = context; + mSwitchListener = switchListener; } @Override protected MagnificationModeSwitch createInstance(Display display) { final Context uiContext = mContext.createWindowContext(display, TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, /* options */ null); - return new MagnificationModeSwitch(uiContext); + return new MagnificationModeSwitch(uiContext, mSwitchListener); } } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java index 294d1f42305a..20d6e3247cd1 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java @@ -49,7 +49,7 @@ import android.view.accessibility.AccessibilityManager; import com.android.internal.R; import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity; import com.android.internal.util.ScreenshotHelper; -import com.android.systemui.SystemUI; +import com.android.systemui.CoreStartable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.recents.Recents; import com.android.systemui.statusbar.CommandQueue; @@ -69,7 +69,7 @@ import dagger.Lazy; * Class to register system actions with accessibility framework. */ @SysUISingleton -public class SystemActions extends SystemUI { +public class SystemActions extends CoreStartable { private static final String TAG = "SystemActions"; /** diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java index 32813479dc0a..33ce20686e66 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java @@ -35,7 +35,7 @@ import android.view.accessibility.IWindowMagnificationConnection; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; -import com.android.systemui.SystemUI; +import com.android.systemui.CoreStartable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.model.SysUiState; @@ -54,7 +54,7 @@ import javax.inject.Inject; * when {@code IStatusBar#requestWindowMagnificationConnection(boolean)} is called. */ @SysUISingleton -public class WindowMagnification extends SystemUI implements WindowMagnifierCallback, +public class WindowMagnification extends CoreStartable implements WindowMagnifierCallback, CommandQueue.Callbacks { private static final String TAG = "WindowMagnification"; @@ -243,12 +243,15 @@ public class WindowMagnification extends SystemUI implements WindowMagnifierCall mWindowMagnificationConnectionImpl = new WindowMagnificationConnectionImpl(this, mHandler, mModeSwitchesController); } + mModeSwitchesController.setSwitchListenerDelegate( + mWindowMagnificationConnectionImpl::onChangeMagnificationMode); mAccessibilityManager.setWindowMagnificationConnection( mWindowMagnificationConnectionImpl); } private void clearWindowMagnificationConnection() { mAccessibilityManager.setWindowMagnificationConnection(null); + mModeSwitchesController.setSwitchListenerDelegate(null); //TODO: destroy controllers. } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java index 2d620ab9e3c9..92cd8b183b62 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationConnectionImpl.java @@ -130,4 +130,14 @@ class WindowMagnificationConnectionImpl extends IWindowMagnificationConnection.S } } } + + void onChangeMagnificationMode(int displayId, int mode) { + if (mConnectionCallback != null) { + try { + mConnectionCallback.onChangeMagnificationMode(displayId, mode); + } catch (RemoteException e) { + Log.e(TAG, "Failed to inform changing magnification mode", e); + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java index 59d9aff2ef46..d2703f5e73a2 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java @@ -22,6 +22,7 @@ import static android.util.MathUtils.sq; import static android.view.WindowInsets.Type.displayCutout; import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.systemBars; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION; import static java.util.Objects.requireNonNull; @@ -659,6 +660,7 @@ public class AccessibilityFloatingMenuView extends FrameLayout | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, PixelFormat.TRANSLUCENT); params.receiveInsetsIgnoringZOrder = true; + params.privateFlags |= PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION; params.windowAnimations = android.R.style.Animation_Translucent; params.gravity = Gravity.START | Gravity.TOP; params.x = (mAlignment == Alignment.RIGHT) ? getMaxWindowX() : getMinWindowX(); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricUdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricUdfpsView.java index 376368fbf9d4..d80d9cc9d62d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricUdfpsView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricUdfpsView.java @@ -21,12 +21,19 @@ import android.annotation.Nullable; import android.content.Context; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.util.AttributeSet; +import android.util.Log; +import android.widget.FrameLayout; +import android.widget.TextView; + +import com.android.systemui.R; /** * Manages the layout for under-display fingerprint sensors (UDFPS). Ensures that UI elements * do not overlap with */ public class AuthBiometricUdfpsView extends AuthBiometricFingerprintView { + private static final String TAG = "AuthBiometricUdfpsView"; + @Nullable private UdfpsDialogMeasureAdapter mMeasureAdapter; public AuthBiometricUdfpsView(Context context) { @@ -51,4 +58,23 @@ public class AuthBiometricUdfpsView extends AuthBiometricFingerprintView { ? mMeasureAdapter.onMeasureInternal(width, height, layoutParams) : layoutParams; } + + @Override + void onLayoutInternal() { + super.onLayoutInternal(); + + // Move the UDFPS icon and indicator text if necessary. This probably only needs to happen + // for devices where the UDFPS sensor is too low. + // TODO(b/201510778): Update this logic to support cases where the sensor or text overlap + // the button bar area. + final int bottomSpacerHeight = mMeasureAdapter.getBottomSpacerHeight(); + Log.w(TAG, "bottomSpacerHeight: " + bottomSpacerHeight); + if (bottomSpacerHeight < 0) { + FrameLayout iconFrame = findViewById(R.id.biometric_icon_frame); + iconFrame.setTranslationY(-bottomSpacerHeight); + + TextView indicator = findViewById(R.id.indicator); + indicator.setTranslationY(-bottomSpacerHeight); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 7215736330ed..29e5574830a8 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -58,7 +58,7 @@ import android.view.WindowManager; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.SomeArgs; -import com.android.systemui.SystemUI; +import com.android.systemui.CoreStartable; import com.android.systemui.assist.ui.DisplayUtils; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; @@ -84,7 +84,7 @@ import kotlin.Unit; * {@link com.android.keyguard.KeyguardUpdateMonitor} */ @SysUISingleton -public class AuthController extends SystemUI implements CommandQueue.Callbacks, +public class AuthController extends CoreStartable implements CommandQueue.Callbacks, AuthDialogCallback, DozeReceiver { private static final String TAG = "AuthController"; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index ec17d4e5cd03..90a1e5e64daf 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -16,6 +16,8 @@ package com.android.systemui.biometrics +import android.animation.Animator +import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator import android.content.Context import android.graphics.PointF @@ -29,7 +31,9 @@ import com.android.settingslib.Utils import com.android.systemui.R import com.android.systemui.animation.Interpolators import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.CircleReveal +import com.android.systemui.statusbar.LiftReveal import com.android.systemui.statusbar.LightRevealEffect import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.commandline.Command @@ -41,13 +45,10 @@ import com.android.systemui.statusbar.phone.dagger.StatusBarComponent.StatusBarS import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.ViewController +import com.android.systemui.util.leak.RotationUtils import java.io.PrintWriter import javax.inject.Inject import javax.inject.Provider -import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.util.leak.RotationUtils - -private const val WAKE_AND_UNLOCK_FADE_DURATION = 180L /*** * Controls the ripple effect that shows when authentication is successful. @@ -141,11 +142,12 @@ class AuthRippleController @Inject constructor( private fun showUnlockedRipple() { notificationShadeWindowController.setForcePluginOpen(true, this) - val useCircleReveal = circleReveal != null && biometricUnlockController.isWakeAndUnlock val lightRevealScrim = statusBar.lightRevealScrim - if (useCircleReveal) { - lightRevealScrim?.revealEffect = circleReveal!! - startLightRevealScrimOnKeyguardFadingAway = true + if (statusBarStateController.isDozing || biometricUnlockController.isWakeAndUnlock) { + circleReveal?.let { + lightRevealScrim?.revealEffect = it + startLightRevealScrimOnKeyguardFadingAway = true + } } mView.startUnlockedRipple( @@ -160,19 +162,29 @@ class AuthRippleController @Inject constructor( if (keyguardStateController.isKeyguardFadingAway) { val lightRevealScrim = statusBar.lightRevealScrim if (startLightRevealScrimOnKeyguardFadingAway && lightRevealScrim != null) { - val revealAnimator = ValueAnimator.ofFloat(.1f, 1f).apply { + ValueAnimator.ofFloat(.1f, 1f).apply { interpolator = Interpolators.LINEAR_OUT_SLOW_IN duration = RIPPLE_ANIMATION_DURATION startDelay = keyguardStateController.keyguardFadingAwayDelay addUpdateListener { animator -> if (lightRevealScrim.revealEffect != circleReveal) { - // if the something else took over the reveal, let's do nothing. + // if something else took over the reveal, let's do nothing. return@addUpdateListener } lightRevealScrim.revealAmount = animator.animatedValue as Float } + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator?) { + // Reset light reveal scrim to the default, so the StatusBar + // can handle any subsequent light reveal changes + // (ie: from dozing changes) + if (lightRevealScrim.revealEffect == circleReveal) { + lightRevealScrim.revealEffect = LiftReveal + } + } + }) + start() } - revealAnimator.start() startLightRevealScrimOnKeyguardFadingAway = false } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java index 6cc8acf07c50..1624d5f68c35 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java @@ -46,6 +46,7 @@ public class UdfpsDialogMeasureAdapter { @NonNull private final FingerprintSensorPropertiesInternal mSensorProps; @Nullable private WindowManager mWindowManager; + private int mBottomSpacerHeight; public UdfpsDialogMeasureAdapter( @NonNull ViewGroup view, @NonNull FingerprintSensorPropertiesInternal sensorProps) { @@ -75,6 +76,16 @@ public class UdfpsDialogMeasureAdapter { } } + /** + * @return the actual (and possibly negative) bottom spacer height. If negative, this indicates + * that the UDFPS sensor is too low. Our current xml and custom measurement logic is very hard + * too cleanly support this case. So, let's have the onLayout code translate the sensor location + * instead. + */ + int getBottomSpacerHeight() { + return mBottomSpacerHeight; + } + @NonNull private AuthDialog.LayoutParams onMeasureInternalPortrait(int width, int height) { // Get the height of the everything below the icon. Currently, that's the indicator and @@ -87,7 +98,7 @@ public class UdfpsDialogMeasureAdapter { final int dialogMargin = getDialogMarginPx(); final int displayHeight = getWindowBounds().height(); final Insets navbarInsets = getNavbarInsets(); - final int bottomSpacerHeight = calculateBottomSpacerHeightForPortrait( + mBottomSpacerHeight = calculateBottomSpacerHeightForPortrait( mSensorProps, displayHeight, textIndicatorHeight, buttonBarHeight, dialogMargin, navbarInsets.bottom); @@ -123,9 +134,10 @@ public class UdfpsDialogMeasureAdapter { MeasureSpec.EXACTLY)); } else if (child.getId() == R.id.space_below_icon) { // Set the spacer height so the fingerprint icon is on the physical sensor area + final int clampedSpacerHeight = Math.max(mBottomSpacerHeight, 0); child.measure( MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(bottomSpacerHeight, MeasureSpec.EXACTLY)); + MeasureSpec.makeMeasureSpec(clampedSpacerHeight, MeasureSpec.EXACTLY)); } else { child.measure( MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentHelper.java b/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentHelper.java index 1e7449c64182..f53221c959a5 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentHelper.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentHelper.java @@ -20,7 +20,7 @@ import android.app.Activity; import android.app.Service; import android.content.BroadcastReceiver; -import com.android.systemui.SystemUI; +import com.android.systemui.CoreStartable; import com.android.systemui.recents.RecentsImplementation; /** @@ -37,7 +37,7 @@ public interface ContextComponentHelper { Service resolveService(String className); /** Turns a classname into an instance of the class or returns null. */ - SystemUI resolveSystemUI(String className); + CoreStartable resolveCoreStartable(String className); /** Turns a classname into an instance of the class or returns null. */ BroadcastReceiver resolveBroadcastReceiver(String className); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java b/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java index b41915bc2547..fba8d351e990 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/ContextComponentResolver.java @@ -20,7 +20,7 @@ import android.app.Activity; import android.app.Service; import android.content.BroadcastReceiver; -import com.android.systemui.SystemUI; +import com.android.systemui.CoreStartable; import com.android.systemui.recents.RecentsImplementation; import java.util.Map; @@ -35,14 +35,14 @@ import javax.inject.Provider; public class ContextComponentResolver implements ContextComponentHelper { private final Map<Class<?>, Provider<Activity>> mActivityCreators; private final Map<Class<?>, Provider<Service>> mServiceCreators; - private final Map<Class<?>, Provider<SystemUI>> mSystemUICreators; + private final Map<Class<?>, Provider<CoreStartable>> mSystemUICreators; private final Map<Class<?>, Provider<RecentsImplementation>> mRecentsCreators; private final Map<Class<?>, Provider<BroadcastReceiver>> mBroadcastReceiverCreators; @Inject ContextComponentResolver(Map<Class<?>, Provider<Activity>> activityCreators, Map<Class<?>, Provider<Service>> serviceCreators, - Map<Class<?>, Provider<SystemUI>> systemUICreators, + Map<Class<?>, Provider<CoreStartable>> systemUICreators, Map<Class<?>, Provider<RecentsImplementation>> recentsCreators, Map<Class<?>, Provider<BroadcastReceiver>> broadcastReceiverCreators) { mActivityCreators = activityCreators; @@ -88,7 +88,7 @@ public class ContextComponentResolver implements ContextComponentHelper { * Looks up the SystemUI class name to see if Dagger has an instance of it. */ @Override - public SystemUI resolveSystemUI(String className) { + public CoreStartable resolveCoreStartable(String className) { return resolve(className, mSystemUICreators); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java index a5d4d80598c4..e5c6ab547999 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java @@ -16,10 +16,10 @@ package com.android.systemui.dagger; +import com.android.systemui.CoreStartable; import com.android.systemui.LatencyTester; import com.android.systemui.ScreenDecorations; import com.android.systemui.SliceBroadcastRelayHandler; -import com.android.systemui.SystemUI; import com.android.systemui.accessibility.SystemActions; import com.android.systemui.accessibility.WindowMagnification; import com.android.systemui.biometrics.AuthController; @@ -63,145 +63,145 @@ public abstract class SystemUIBinder { @Binds @IntoMap @ClassKey(AuthController.class) - public abstract SystemUI bindAuthController(AuthController service); + public abstract CoreStartable bindAuthController(AuthController service); /** Inject into GarbageMonitor.Service. */ @Binds @IntoMap @ClassKey(GarbageMonitor.Service.class) - public abstract SystemUI bindGarbageMonitorService(GarbageMonitor.Service sysui); + public abstract CoreStartable bindGarbageMonitorService(GarbageMonitor.Service sysui); /** Inject into GlobalActionsComponent. */ @Binds @IntoMap @ClassKey(GlobalActionsComponent.class) - public abstract SystemUI bindGlobalActionsComponent(GlobalActionsComponent sysui); + public abstract CoreStartable bindGlobalActionsComponent(GlobalActionsComponent sysui); /** Inject into InstantAppNotifier. */ @Binds @IntoMap @ClassKey(InstantAppNotifier.class) - public abstract SystemUI bindInstantAppNotifier(InstantAppNotifier sysui); + public abstract CoreStartable bindInstantAppNotifier(InstantAppNotifier sysui); /** Inject into KeyguardViewMediator. */ @Binds @IntoMap @ClassKey(KeyguardViewMediator.class) - public abstract SystemUI bindKeyguardViewMediator(KeyguardViewMediator sysui); + public abstract CoreStartable bindKeyguardViewMediator(KeyguardViewMediator sysui); /** Inject into LatencyTests. */ @Binds @IntoMap @ClassKey(LatencyTester.class) - public abstract SystemUI bindLatencyTester(LatencyTester sysui); + public abstract CoreStartable bindLatencyTester(LatencyTester sysui); /** Inject into PowerUI. */ @Binds @IntoMap @ClassKey(PowerUI.class) - public abstract SystemUI bindPowerUI(PowerUI sysui); + public abstract CoreStartable bindPowerUI(PowerUI sysui); /** Inject into Recents. */ @Binds @IntoMap @ClassKey(Recents.class) - public abstract SystemUI bindRecents(Recents sysui); + public abstract CoreStartable bindRecents(Recents sysui); /** Inject into ScreenDecorations. */ @Binds @IntoMap @ClassKey(ScreenDecorations.class) - public abstract SystemUI bindScreenDecorations(ScreenDecorations sysui); + public abstract CoreStartable bindScreenDecorations(ScreenDecorations sysui); /** Inject into ShortcutKeyDispatcher. */ @Binds @IntoMap @ClassKey(ShortcutKeyDispatcher.class) - public abstract SystemUI bindsShortcutKeyDispatcher(ShortcutKeyDispatcher sysui); + public abstract CoreStartable bindsShortcutKeyDispatcher(ShortcutKeyDispatcher sysui); /** Inject into SliceBroadcastRelayHandler. */ @Binds @IntoMap @ClassKey(SliceBroadcastRelayHandler.class) - public abstract SystemUI bindSliceBroadcastRelayHandler(SliceBroadcastRelayHandler sysui); + public abstract CoreStartable bindSliceBroadcastRelayHandler(SliceBroadcastRelayHandler sysui); /** Inject into StatusBar. */ @Binds @IntoMap @ClassKey(StatusBar.class) - public abstract SystemUI bindsStatusBar(StatusBar sysui); + public abstract CoreStartable bindsStatusBar(StatusBar sysui); /** Inject into SystemActions. */ @Binds @IntoMap @ClassKey(SystemActions.class) - public abstract SystemUI bindSystemActions(SystemActions sysui); + public abstract CoreStartable bindSystemActions(SystemActions sysui); /** Inject into ThemeOverlayController. */ @Binds @IntoMap @ClassKey(ThemeOverlayController.class) - public abstract SystemUI bindThemeOverlayController(ThemeOverlayController sysui); + public abstract CoreStartable bindThemeOverlayController(ThemeOverlayController sysui); /** Inject into ToastUI. */ @Binds @IntoMap @ClassKey(ToastUI.class) - public abstract SystemUI bindToastUI(ToastUI service); + public abstract CoreStartable bindToastUI(ToastUI service); /** Inject into TvStatusBar. */ @Binds @IntoMap @ClassKey(TvStatusBar.class) - public abstract SystemUI bindsTvStatusBar(TvStatusBar sysui); + public abstract CoreStartable bindsTvStatusBar(TvStatusBar sysui); /** Inject into TvNotificationPanel. */ @Binds @IntoMap @ClassKey(TvNotificationPanel.class) - public abstract SystemUI bindsTvNotificationPanel(TvNotificationPanel sysui); + public abstract CoreStartable bindsTvNotificationPanel(TvNotificationPanel sysui); /** Inject into TvOngoingPrivacyChip. */ @Binds @IntoMap @ClassKey(TvOngoingPrivacyChip.class) - public abstract SystemUI bindsTvOngoingPrivacyChip(TvOngoingPrivacyChip sysui); + public abstract CoreStartable bindsTvOngoingPrivacyChip(TvOngoingPrivacyChip sysui); /** Inject into VolumeUI. */ @Binds @IntoMap @ClassKey(VolumeUI.class) - public abstract SystemUI bindVolumeUI(VolumeUI sysui); + public abstract CoreStartable bindVolumeUI(VolumeUI sysui); /** Inject into WindowMagnification. */ @Binds @IntoMap @ClassKey(WindowMagnification.class) - public abstract SystemUI bindWindowMagnification(WindowMagnification sysui); + public abstract CoreStartable bindWindowMagnification(WindowMagnification sysui); /** Inject into WMShell. */ @Binds @IntoMap @ClassKey(WMShell.class) - public abstract SystemUI bindWMShell(WMShell sysui); + public abstract CoreStartable bindWMShell(WMShell sysui); /** Inject into HomeSoundEffectController. */ @Binds @IntoMap @ClassKey(HomeSoundEffectController.class) - public abstract SystemUI bindHomeSoundEffectController(HomeSoundEffectController sysui); + public abstract CoreStartable bindHomeSoundEffectController(HomeSoundEffectController sysui); /** Inject into DreamOverlay. */ @Binds @IntoMap @ClassKey(DreamOverlayRegistrant.class) - public abstract SystemUI bindDreamOverlayRegistrant( + public abstract CoreStartable bindDreamOverlayRegistrant( DreamOverlayRegistrant dreamOverlayRegistrant); /** Inject into AppWidgetOverlayPrimer. */ @Binds @IntoMap @ClassKey(AppWidgetOverlayPrimer.class) - public abstract SystemUI bindAppWidgetOverlayPrimer( + public abstract CoreStartable bindAppWidgetOverlayPrimer( AppWidgetOverlayPrimer appWidgetOverlayPrimer); } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java index 20c46da14e63..994c63002ac2 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java @@ -30,8 +30,8 @@ import android.service.dreams.DreamService; import android.service.dreams.IDreamManager; import android.util.Log; +import com.android.systemui.CoreStartable; import com.android.systemui.R; -import com.android.systemui.SystemUI; import com.android.systemui.dagger.qualifiers.Main; import javax.inject.Inject; @@ -40,7 +40,7 @@ import javax.inject.Inject; * {@link DreamOverlayRegistrant} is responsible for telling system server that SystemUI should be * the designated dream overlay component. */ -public class DreamOverlayRegistrant extends SystemUI { +public class DreamOverlayRegistrant extends CoreStartable { private static final String TAG = "DreamOverlayRegistrant"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final IDreamManager mDreamManager; diff --git a/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/AppWidgetOverlayPrimer.java b/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/AppWidgetOverlayPrimer.java index a0c7c29e0191..563f70776209 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/AppWidgetOverlayPrimer.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/AppWidgetOverlayPrimer.java @@ -23,8 +23,8 @@ import android.view.Gravity; import androidx.constraintlayout.widget.ConstraintSet; +import com.android.systemui.CoreStartable; import com.android.systemui.R; -import com.android.systemui.SystemUI; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.dreams.OverlayHostView; @@ -36,7 +36,7 @@ import javax.inject.Inject; * {@link AppWidgetOverlayPrimer} reads the configured App Widget Overlay from resources on start * and populates them into the {@link DreamOverlayStateController}. */ -public class AppWidgetOverlayPrimer extends SystemUI { +public class AppWidgetOverlayPrimer extends CoreStartable { private final Resources mResources; private final DreamOverlayStateController mDreamOverlayStateController; private final AppWidgetOverlayComponent.Factory mComponentFactory; diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java index 6880674fd8f0..27e254f176fd 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java @@ -17,22 +17,11 @@ package com.android.systemui.flags; import android.content.Context; -import android.content.res.Resources; import android.util.FeatureFlagUtils; import android.util.Log; -import android.util.SparseArray; import android.widget.Toast; -import androidx.annotation.BoolRes; - -import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Main; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import javax.inject.Inject; @@ -43,31 +32,13 @@ import javax.inject.Inject; */ @SysUISingleton public class FeatureFlags { - private final Resources mResources; private final FlagReader mFlagReader; private final Context mContext; - private final Map<Integer, Flag<?>> mFlagMap = new HashMap<>(); - private final Map<Integer, List<Listener>> mListeners = new HashMap<>(); - private final SparseArray<Boolean> mCachedFlags = new SparseArray<>(); @Inject - public FeatureFlags(@Main Resources resources, FlagReader flagReader, Context context) { - mResources = resources; + public FeatureFlags(FlagReader flagReader, Context context) { mFlagReader = flagReader; mContext = context; - - flagReader.addListener(mListener); - } - - private final FlagReader.Listener mListener = id -> { - if (mListeners.containsKey(id) && mFlagMap.containsKey(id)) { - mListeners.get(id).forEach(listener -> listener.onFlagChanged(mFlagMap.get(id))); - } - }; - - @VisibleForTesting - void addFlag(Flag<?> flag) { - mFlagMap.put(flag.getId(), flag); } /** @@ -75,32 +46,7 @@ public class FeatureFlags { * @return The value of the flag. */ public boolean isEnabled(BooleanFlag flag) { - boolean def = flag.getDefault(); - if (flag.hasResourceOverride()) { - try { - def = isEnabledInOverlay(flag.getResourceOverride()); - } catch (Resources.NotFoundException e) { - // no-op - } - } - return mFlagReader.isEnabled(flag.getId(), def); - } - - /** - * @param flag The {@link IntFlag} of interest. - - /** Add a listener for a specific flag. */ - public void addFlagListener(Flag<?> flag, Listener listener) { - mListeners.putIfAbsent(flag.getId(), new ArrayList<>()); - mListeners.get(flag.getId()).add(listener); - mFlagMap.putIfAbsent(flag.getId(), flag); - } - - /** Remove a listener for a specific flag. */ - public void removeFlagListener(Flag<?> flag, Listener listener) { - if (mListeners.containsKey(flag.getId())) { - mListeners.get(flag.getId()).remove(listener); - } + return mFlagReader.isEnabled(flag); } public void assertLegacyPipelineEnabled() { @@ -209,20 +155,4 @@ public class FeatureFlags { public static boolean isProviderModelSettingEnabled(Context context) { return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL); } - - private boolean isEnabledInOverlay(@BoolRes int resId) { - synchronized (mCachedFlags) { - if (!mCachedFlags.contains(resId)) { - mCachedFlags.put(resId, mResources.getBoolean(resId)); - } - - return mCachedFlags.get(resId); - } - } - - /** Simple interface for beinga alerted when a specific flag changes value. */ - public interface Listener { - /** */ - void onFlagChanged(Flag<?> flag); - } } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java index 86c8565bf8ef..e746cafb5ea0 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java @@ -19,7 +19,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import com.android.internal.statusbar.IStatusBarService; -import com.android.systemui.SystemUI; +import com.android.systemui.CoreStartable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.plugins.GlobalActions; import com.android.systemui.plugins.GlobalActions.GlobalActionsManager; @@ -36,7 +36,8 @@ import javax.inject.Provider; * Manages power menu plugins and communicates power menu actions to the StatusBar. */ @SysUISingleton -public class GlobalActionsComponent extends SystemUI implements Callbacks, GlobalActionsManager { +public class GlobalActionsComponent extends CoreStartable + implements Callbacks, GlobalActionsManager { private final CommandQueue mCommandQueue; private final ExtensionController mExtensionController; diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java index 42f455af7df4..1c0b104b6945 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java +++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java @@ -49,9 +49,9 @@ import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.LocalBluetoothAdapter; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; +import com.android.systemui.CoreStartable; import com.android.systemui.Dependency; import com.android.systemui.R; -import com.android.systemui.SystemUI; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -60,7 +60,7 @@ import java.util.Collection; import java.util.List; import java.util.Set; -public class KeyboardUI extends SystemUI implements InputManager.OnTabletModeChangedListener { +public class KeyboardUI extends CoreStartable implements InputManager.OnTabletModeChangedListener { private static final String TAG = "KeyboardUI"; private static final boolean DEBUG = false; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index 2cc564bf8452..a6455e625afb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -30,8 +30,8 @@ import com.android.keyguard.KeyguardClockSwitchController import com.android.keyguard.KeyguardViewController import com.android.systemui.animation.Interpolators import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController import com.android.systemui.flags.FeatureFlags +import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController import com.android.systemui.statusbar.policy.KeyguardStateController import dagger.Lazy import javax.inject.Inject @@ -88,7 +88,8 @@ const val DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD = 0.4f class KeyguardUnlockAnimationController @Inject constructor( context: Context, private val keyguardStateController: KeyguardStateController, - private val keyguardViewMediator: Lazy<KeyguardViewMediator>, + private val + keyguardViewMediator: Lazy<KeyguardViewMediator>, private val keyguardViewController: KeyguardViewController, private val smartspaceTransitionController: SmartspaceTransitionController, private val featureFlags: FeatureFlags diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 7bb7cc477693..e97e7622c272 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -99,8 +99,8 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.KeyguardViewController; import com.android.keyguard.ViewMediatorCallback; +import com.android.systemui.CoreStartable; import com.android.systemui.Dumpable; -import com.android.systemui.SystemUI; import com.android.systemui.animation.Interpolators; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.classifier.FalsingCollector; @@ -176,7 +176,7 @@ import dagger.Lazy; * directly to the keyguard UI is posted to a {@link android.os.Handler} to ensure it is taken on the UI * thread of the keyguard. */ -public class KeyguardViewMediator extends SystemUI implements Dumpable, +public class KeyguardViewMediator extends CoreStartable implements Dumpable, StatusBarStateController.StateListener { private static final int KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000; private static final long KEYGUARD_DONE_PENDING_TIMEOUT_MS = 3000; diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java index 553b6d8679db..ae5f9b63fb3d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java +++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java @@ -37,7 +37,7 @@ import android.os.UserHandle; import android.provider.MediaStore; import android.util.Log; -import com.android.systemui.SystemUI; +import com.android.systemui.CoreStartable; import java.io.FileDescriptor; import java.io.IOException; @@ -48,7 +48,7 @@ import java.util.HashMap; * Service that offers to play ringtones by {@link Uri}, since our process has * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE}. */ -public class RingtonePlayer extends SystemUI { +public class RingtonePlayer extends CoreStartable { private static final String TAG = "RingtonePlayer"; private static final boolean LOGD = false; diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java index 42dd8862576b..19812697719c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -79,6 +79,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { private final DialogLaunchAnimator mDialogLaunchAnimator; private final List<MediaDevice> mGroupMediaDevices = new CopyOnWriteArrayList<>(); private final boolean mAboveStatusbar; + private final boolean mVolumeAdjustmentForRemoteGroupSessions; private final NotificationEntryManager mNotificationEntryManager; @VisibleForTesting final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>(); @@ -111,6 +112,8 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName); mUiEventLogger = uiEventLogger; mDialogLaunchAnimator = dialogLaunchAnimator; + mVolumeAdjustmentForRemoteGroupSessions = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions); } void start(@NonNull Callback cb) { @@ -477,7 +480,9 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { } boolean isVolumeControlEnabled(@NonNull MediaDevice device) { - return !isActiveRemoteDevice(device); + // TODO(b/202500642): Also enable volume control for remote non-group sessions. + return !isActiveRemoteDevice(device) + || mVolumeAdjustmentForRemoteGroupSessions; } private final MediaController.Callback mCb = new MediaController.Callback() { diff --git a/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java b/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java index 31e49390b9b8..d60172a17988 100644 --- a/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java +++ b/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java @@ -25,8 +25,8 @@ import android.content.pm.PackageManager; import android.media.AudioManager; import android.util.Slog; +import com.android.systemui.CoreStartable; import com.android.systemui.R; -import com.android.systemui.SystemUI; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.TaskStackChangeListener; @@ -40,7 +40,7 @@ import javax.inject.Inject; * documented at {@link #handleTaskStackChanged} apply. */ @SysUISingleton -public class HomeSoundEffectController extends SystemUI { +public class HomeSoundEffectController extends CoreStartable { private static final String TAG = "HomeSoundEffectController"; private final AudioManager mAudioManager; diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index c351d13e9b16..0e6e8a433799 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -24,9 +24,6 @@ import static android.app.StatusBarManager.WindowType; import static android.app.StatusBarManager.WindowVisibleState; import static android.app.StatusBarManager.windowStateToString; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; -import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; -import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE; -import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.containsType; @@ -613,8 +610,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mDeviceProvisionedController.addCallback(mUserSetupListener); mNotificationShadeDepthController.addListener(mDepthListener); - updateAccessibilityButtonModeIfNeeded(); - return barView; } @@ -1405,34 +1400,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener, updateSystemUiStateFlags(a11yFlags); } - private void updateAccessibilityButtonModeIfNeeded() { - final int mode = Settings.Secure.getIntForUser(mContentResolver, - Settings.Secure.ACCESSIBILITY_BUTTON_MODE, - ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_CURRENT); - - // ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU is compatible under gestural or non-gestural - // mode, so we don't need to update it. - if (mode == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU) { - return; - } - - // ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR is incompatible under gestural mode. Need to - // force update to ACCESSIBILITY_BUTTON_MODE_GESTURE. - if (QuickStepContract.isGesturalMode(mNavBarMode) - && mode == ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR) { - Settings.Secure.putIntForUser(mContentResolver, - Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_GESTURE, - UserHandle.USER_CURRENT); - // ACCESSIBILITY_BUTTON_MODE_GESTURE is incompatible under non gestural mode. Need to - // force update to ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR. - } else if (!QuickStepContract.isGesturalMode(mNavBarMode) - && mode == ACCESSIBILITY_BUTTON_MODE_GESTURE) { - Settings.Secure.putIntForUser(mContentResolver, - Settings.Secure.ACCESSIBILITY_BUTTON_MODE, - ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_CURRENT); - } - } - public void updateSystemUiStateFlags(int a11yFlags) { if (a11yFlags < 0) { a11yFlags = mNavigationBarA11yHelper.getA11yButtonState(); @@ -1550,6 +1517,9 @@ public class NavigationBar implements View.OnAttachStateChangeListener, @Override public void onNavigationModeChanged(int mode) { mNavBarMode = mode; + // update assistant entry points on system navigation radio button click + updateAssistantEntrypoints(); + if (!QuickStepContract.isGesturalMode(mode)) { // Reset the override alpha if (getBarTransitions() != null) { @@ -1557,7 +1527,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener, } } updateScreenPinningGestures(); - updateAccessibilityButtonModeIfNeeded(); if (!canShowSecondaryHandle()) { resetSecondaryHandle(); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java index 4959c7d6808d..3dc79c43d894 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java @@ -16,10 +16,14 @@ package com.android.systemui.navigationbar; +import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; +import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE; +import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR; import static android.view.Display.DEFAULT_DISPLAY; import static com.android.systemui.shared.recents.utilities.Utilities.isTablet; +import android.content.ContentResolver; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Configuration; @@ -27,6 +31,8 @@ import android.hardware.display.DisplayManager; import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; +import android.os.UserHandle; +import android.provider.Settings; import android.util.Log; import android.util.SparseArray; import android.view.Display; @@ -46,6 +52,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.model.SysUiState; import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CommandQueue.Callbacks; import com.android.systemui.statusbar.phone.AutoHideController; @@ -142,6 +149,8 @@ public class NavigationBarController implements } final int oldMode = mNavMode; mNavMode = mode; + updateAccessibilityButtonModeIfNeeded(); + mHandler.post(() -> { // create/destroy nav bar based on nav mode only in unfolded state if (oldMode != mNavMode) { @@ -157,6 +166,35 @@ public class NavigationBarController implements }); } + private void updateAccessibilityButtonModeIfNeeded() { + ContentResolver contentResolver = mContext.getContentResolver(); + final int mode = Settings.Secure.getIntForUser(contentResolver, + Settings.Secure.ACCESSIBILITY_BUTTON_MODE, + ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_CURRENT); + + // ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU is compatible under gestural or non-gestural + // mode, so we don't need to update it. + if (mode == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU) { + return; + } + + // ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR is incompatible under gestural mode. Need to + // force update to ACCESSIBILITY_BUTTON_MODE_GESTURE. + if (QuickStepContract.isGesturalMode(mNavMode) + && mode == ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR) { + Settings.Secure.putIntForUser(contentResolver, + Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_GESTURE, + UserHandle.USER_CURRENT); + // ACCESSIBILITY_BUTTON_MODE_GESTURE is incompatible under non gestural mode. Need to + // force update to ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR. + } else if (!QuickStepContract.isGesturalMode(mNavMode) + && mode == ACCESSIBILITY_BUTTON_MODE_GESTURE) { + Settings.Secure.putIntForUser(contentResolver, + Settings.Secure.ACCESSIBILITY_BUTTON_MODE, + ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, UserHandle.USER_CURRENT); + } + } + /** @see #initializeTaskbarIfNecessary() */ private boolean updateNavbarForTaskbar() { boolean taskbarShown = initializeTaskbarIfNecessary(); @@ -222,6 +260,8 @@ public class NavigationBarController implements */ public void createNavigationBars(final boolean includeDefaultDisplay, RegisterStatusBarResult result) { + updateAccessibilityButtonModeIfNeeded(); + // Don't need to create nav bar on the default display if we initialize TaskBar. final boolean shouldCreateDefaultNavbar = includeDefaultDisplay && !initializeTaskbarIfNecessary(); diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java index 4e1e1cd23c14..b483e59b4716 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java @@ -58,9 +58,9 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.settingslib.Utils; import com.android.settingslib.fuelgauge.BatterySaverUtils; import com.android.settingslib.utils.PowerUtil; +import com.android.systemui.CoreStartable; import com.android.systemui.Dependency; import com.android.systemui.R; -import com.android.systemui.SystemUI; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.statusbar.phone.SystemUIDialog; @@ -247,7 +247,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { .setContentText(mContext.getString(R.string.invalid_charger_text)) .setColor(mContext.getColor( com.android.internal.R.color.system_notification_accent_color)); - SystemUI.overrideNotificationAppName(mContext, nb, false); + CoreStartable.overrideNotificationAppName(mContext, nb, false); final Notification n = nb.build(); mNoMan.cancelAsUser(TAG_BATTERY, SystemMessage.NOTE_POWER_LOW, UserHandle.ALL); mNoMan.notifyAsUser(TAG_BATTERY, SystemMessage.NOTE_BAD_CHARGER, n, UserHandle.ALL); @@ -298,7 +298,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { } nb.setOnlyAlertOnce(!mPlaySound); mPlaySound = false; - SystemUI.overrideNotificationAppName(mContext, nb, false); + CoreStartable.overrideNotificationAppName(mContext, nb, false); final Notification n = nb.build(); mNoMan.cancelAsUser(TAG_BATTERY, SystemMessage.NOTE_BAD_CHARGER, UserHandle.ALL); mNoMan.notifyAsUser(TAG_BATTERY, SystemMessage.NOTE_POWER_LOW, n, UserHandle.ALL); @@ -320,7 +320,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { mContext.getString(R.string.no_auto_saver_action), pendingBroadcast(ACTION_AUTO_SAVER_NO_THANKS)); - SystemUI.overrideNotificationAppName(mContext, nb, false); + CoreStartable.overrideNotificationAppName(mContext, nb, false); final Notification n = nb.build(); mNoMan.notifyAsUser( @@ -397,7 +397,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { .setDeleteIntent(pendingBroadcast(ACTION_DISMISSED_TEMP_WARNING)) .setColor(Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorError)); - SystemUI.overrideNotificationAppName(mContext, nb, false); + CoreStartable.overrideNotificationAppName(mContext, nb, false); final Notification n = nb.build(); mNoMan.notifyAsUser(TAG_TEMPERATURE, SystemMessage.NOTE_HIGH_TEMP, n, UserHandle.ALL); } @@ -484,7 +484,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { pendingBroadcast(ACTION_DISMISSED_THERMAL_SHUTDOWN_WARNING)) .setColor(Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorError)); - SystemUI.overrideNotificationAppName(mContext, nb, false); + CoreStartable.overrideNotificationAppName(mContext, nb, false); final Notification n = nb.build(); mNoMan.notifyAsUser( TAG_TEMPERATURE, SystemMessage.NOTE_THERMAL_SHUTDOWN, n, UserHandle.ALL); diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java index 625265485f6d..37a0f5949e16 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java @@ -42,9 +42,9 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.fuelgauge.Estimate; import com.android.settingslib.utils.ThreadUtils; +import com.android.systemui.CoreStartable; import com.android.systemui.Dependency; import com.android.systemui.R; -import com.android.systemui.SystemUI; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.statusbar.CommandQueue; @@ -62,7 +62,7 @@ import javax.inject.Inject; import dagger.Lazy; @SysUISingleton -public class PowerUI extends SystemUI implements CommandQueue.Callbacks { +public class PowerUI extends CoreStartable implements CommandQueue.Callbacks { static final String TAG = "PowerUI"; static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); diff --git a/packages/SystemUI/src/com/android/systemui/privacy/television/TvOngoingPrivacyChip.java b/packages/SystemUI/src/com/android/systemui/privacy/television/TvOngoingPrivacyChip.java index 875097625a6f..5510eb172cd7 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/television/TvOngoingPrivacyChip.java +++ b/packages/SystemUI/src/com/android/systemui/privacy/television/TvOngoingPrivacyChip.java @@ -45,8 +45,8 @@ import android.widget.LinearLayout; import androidx.annotation.NonNull; +import com.android.systemui.CoreStartable; import com.android.systemui.R; -import com.android.systemui.SystemUI; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.privacy.PrivacyChipBuilder; import com.android.systemui.privacy.PrivacyItem; @@ -67,7 +67,7 @@ import javax.inject.Inject; * recording audio, accessing the camera or accessing the location. */ @SysUISingleton -public class TvOngoingPrivacyChip extends SystemUI implements PrivacyItemController.Callback, +public class TvOngoingPrivacyChip extends CoreStartable implements PrivacyItemController.Callback, PrivacyChipDrawable.PrivacyChipDrawableListener { private static final String TAG = "TvOngoingPrivacyChip"; private static final boolean DEBUG = false; diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java index 6f6dd9cb22c7..b7a44a43f4b7 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java @@ -21,7 +21,7 @@ import android.content.Context; import android.content.res.Configuration; import android.provider.Settings; -import com.android.systemui.SystemUI; +import com.android.systemui.CoreStartable; import com.android.systemui.statusbar.CommandQueue; import java.io.FileDescriptor; @@ -30,7 +30,7 @@ import java.io.PrintWriter; /** * A proxy to a Recents implementation. */ -public class Recents extends SystemUI implements CommandQueue.Callbacks { +public class Recents extends CoreStartable implements CommandQueue.Callbacks { private final RecentsImplementation mImpl; private final CommandQueue mCommandQueue; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java index 58a54f6ce0ed..28bdd5358956 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java @@ -30,8 +30,8 @@ import android.util.DisplayMetrics; import android.view.WindowManager; import com.android.internal.messages.nano.SystemMessageProto; +import com.android.systemui.CoreStartable; import com.android.systemui.R; -import com.android.systemui.SystemUI; import com.android.systemui.util.NotificationChannels; import javax.inject.Inject; @@ -86,7 +86,7 @@ public class ScreenshotNotificationsController { b.setContentIntent(pendingIntent); } - SystemUI.overrideNotificationAppName(mContext, b, true); + CoreStartable.overrideNotificationAppName(mContext, b, true); Notification n = new Notification.BigTextStyle(b) .bigText(errorMsg) diff --git a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java index 993343808ee9..10aa12bc97ac 100644 --- a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java +++ b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java @@ -24,7 +24,7 @@ import android.view.KeyEvent; import android.view.WindowManagerGlobal; import com.android.internal.policy.DividerSnapAlgorithm; -import com.android.systemui.SystemUI; +import com.android.systemui.CoreStartable; import com.android.systemui.dagger.SysUISingleton; import com.android.wm.shell.legacysplitscreen.DividerView; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; @@ -37,7 +37,7 @@ import javax.inject.Inject; * Dispatches shortcut to System UI components */ @SysUISingleton -public class ShortcutKeyDispatcher extends SystemUI +public class ShortcutKeyDispatcher extends CoreStartable implements ShortcutKeyServiceProxy.Callbacks { private static final String TAG = "ShortcutKeyDispatcher"; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 74ebfe5ad5e4..1c0088709f14 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -818,7 +818,7 @@ public class KeyguardIndicationController { } private void showTryFingerprintMsg(int msgId, String a11yString) { - if (mKeyguardUpdateMonitor.isUdfpsAvailable()) { + if (mKeyguardUpdateMonitor.isUdfpsSupported()) { // if udfps available, there will always be a tappable affordance to unlock // For example, the lock icon if (mKeyguardBypassController.getUserHasDeviceEntryIntent()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java index 464b2b69c58e..ff3e97af72a7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java @@ -35,7 +35,7 @@ import com.android.systemui.statusbar.notification.DynamicChildBindController; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.inflation.LowPriorityInflationHelper; +import com.android.systemui.statusbar.notification.collection.legacy.LowPriorityInflationHelper; import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy; import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java index cbb3aba5cc64..da2b85ee0b61 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java @@ -173,7 +173,7 @@ public class StatusBarStateControllerImpl implements } // Record the to-be mState and mLastState - recordHistoricalState(state, mState); + recordHistoricalState(state /* newState */, mState /* lastState */, false); // b/139259891 if (mState == StatusBarState.SHADE && state == StatusBarState.SHADE_LOCKED) { @@ -206,6 +206,7 @@ public class StatusBarStateControllerImpl implements @Override public void setUpcomingState(int nextState) { mUpcomingState = nextState; + recordHistoricalState(mUpcomingState /* newState */, mState /* lastState */, true); } @Override @@ -505,31 +506,36 @@ public class StatusBarStateControllerImpl implements } } - private void recordHistoricalState(int currentState, int lastState) { + private void recordHistoricalState(int newState, int lastState, boolean upcoming) { mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE; HistoricalState state = mHistoricalRecords[mHistoryIndex]; - state.mState = currentState; + state.mNewState = newState; state.mLastState = lastState; state.mTimestamp = System.currentTimeMillis(); + state.mUpcoming = upcoming; } /** * For keeping track of our previous state to help with debugging */ private static class HistoricalState { - int mState; + int mNewState; int mLastState; long mTimestamp; + boolean mUpcoming; @Override public String toString() { if (mTimestamp != 0) { StringBuilder sb = new StringBuilder(); - sb.append("state=").append(mState) - .append(" (").append(describe(mState)).append(")"); - sb.append("lastState=").append(mLastState).append(" (").append(describe(mLastState)) + if (mUpcoming) { + sb.append("upcoming-"); + } + sb.append("newState=").append(mNewState) + .append("(").append(describe(mNewState)).append(")"); + sb.append(" lastState=").append(mLastState).append("(").append(describe(mLastState)) .append(")"); - sb.append("timestamp=") + sb.append(" timestamp=") .append(DateFormat.format("MM-dd HH:mm:ss", mTimestamp)); return sb.toString(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java index aa86daaae125..d5cba72f99d3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java @@ -55,7 +55,7 @@ import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifPipeline; -import com.android.systemui.statusbar.notification.collection.inflation.LowPriorityInflationHelper; +import com.android.systemui.statusbar.notification.collection.legacy.LowPriorityInflationHelper; import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy; import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; @@ -73,8 +73,6 @@ import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLogger; import com.android.systemui.statusbar.policy.RemoteInputUriController; import com.android.systemui.statusbar.window.StatusBarWindowController; -import com.android.systemui.statusbar.window.StatusBarWindowModule; -import com.android.systemui.statusbar.window.StatusBarWindowView; import com.android.systemui.tracing.ProtoTracer; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.time.SystemClock; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java index e58ea7b8b5f3..60d13170b6db 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java @@ -51,9 +51,9 @@ import android.util.Pair; import com.android.internal.messages.nano.SystemMessageProto; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; +import com.android.systemui.CoreStartable; import com.android.systemui.Dependency; import com.android.systemui.R; -import com.android.systemui.SystemUI; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.statusbar.CommandQueue; @@ -71,7 +71,7 @@ import javax.inject.Inject; * splitted screen. */ @SysUISingleton -public class InstantAppNotifier extends SystemUI +public class InstantAppNotifier extends CoreStartable implements CommandQueue.Callbacks, KeyguardStateController.Callback { private static final String TAG = "InstantAppNotifier"; public static final int NUM_TASKS_FOR_INSTANT_APP_INFO = 5; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index 82f35a814d22..2437415d0c7e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -638,7 +638,7 @@ public class NotificationEntryManager implements // Construct the expanded view. if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { - mNotificationRowBinderLazy.get().inflateViews(entry, mInflationCallback); + mNotificationRowBinderLazy.get().inflateViews(entry, null, mInflationCallback); } mPendingNotifications.put(key, entry); @@ -695,7 +695,7 @@ public class NotificationEntryManager implements } if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { - mNotificationRowBinderLazy.get().inflateViews(entry, mInflationCallback); + mNotificationRowBinderLazy.get().inflateViews(entry, null, mInflationCallback); } updateNotifications("updateNotificationInternal"); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java index 8562a2e55a4f..4f3c287d5f1e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java @@ -16,7 +16,8 @@ package com.android.systemui.statusbar.notification.collection; -import com.android.internal.statusbar.IStatusBarService; +import androidx.annotation.NonNull; + import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.statusbar.notification.InflationException; import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater; @@ -34,23 +35,13 @@ import javax.inject.Inject; @SysUISingleton public class NotifInflaterImpl implements NotifInflater { - private final IStatusBarService mStatusBarService; - private final NotifCollection mNotifCollection; private final NotifInflationErrorManager mNotifErrorManager; - private final NotifPipeline mNotifPipeline; private NotificationRowBinderImpl mNotificationRowBinder; @Inject - public NotifInflaterImpl( - IStatusBarService statusBarService, - NotifCollection notifCollection, - NotifInflationErrorManager errorManager, - NotifPipeline notifPipeline) { - mStatusBarService = statusBarService; - mNotifCollection = notifCollection; + public NotifInflaterImpl(NotifInflationErrorManager errorManager) { mNotifErrorManager = errorManager; - mNotifPipeline = notifPipeline; } /** @@ -61,8 +52,9 @@ public class NotifInflaterImpl implements NotifInflater { } @Override - public void rebindViews(NotificationEntry entry, InflationCallback callback) { - inflateViews(entry, callback); + public void rebindViews(@NonNull NotificationEntry entry, @NonNull Params params, + @NonNull InflationCallback callback) { + inflateViews(entry, params, callback); } /** @@ -70,10 +62,12 @@ public class NotifInflaterImpl implements NotifInflater { * views are bound. */ @Override - public void inflateViews(NotificationEntry entry, InflationCallback callback) { + public void inflateViews(@NonNull NotificationEntry entry, @NonNull Params params, + @NonNull InflationCallback callback) { try { requireBinder().inflateViews( entry, + params, wrapInflationCallback(callback)); } catch (InflationException e) { mNotifErrorManager.setInflationError(entry, e); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt index 50d7324df2b4..9ae9fe508944 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt @@ -58,12 +58,13 @@ import javax.inject.Inject * appropriately). * 3. OnBeforeTransformGroupListeners are fired ([.addOnBeforeTransformGroupsListener]) * 4. NotifPromoters are called on each notification with a parent ([.addPromoter]) - * 5. Finalize filters are fired on each notification ([.addFinalizeFilter]) - * 6. OnBeforeSortListeners are fired ([.addOnBeforeSortListener]) - * 7. Top-level entries are assigned sections by NotifSections ([.setSections]) - * 8. Top-level entries within the same section are sorted by NotifComparators ([.setComparators]) - * 9. OnBeforeRenderListListeners are fired ([.addOnBeforeRenderListListener]) - * 10. The list is handed off to the view layer to be rendered + * 5. OnBeforeSortListeners are fired ([.addOnBeforeSortListener]) + * 6. Top-level entries are assigned sections by NotifSections ([.setSections]) + * 7. Top-level entries within the same section are sorted by NotifComparators ([.setComparators]) + * 8. OnBeforeFinalizeFilterListeners are fired ([.addOnBeforeFinalizeFilterListener]) + * 9. Finalize filters are fired on each notification ([.addFinalizeFilter]) + * 10. OnBeforeRenderListListeners are fired ([.addOnBeforeRenderListListener]) + * 11. The list is handed off to the view layer to be rendered */ @SysUISingleton class NotifPipeline @Inject constructor( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index 15872da14416..72cd95128779 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java @@ -28,12 +28,15 @@ import static com.android.systemui.statusbar.notification.collection.listbuilder import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_SORTING; import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_TRANSFORMING; +import static java.util.Objects.requireNonNull; + import android.annotation.MainThread; import android.annotation.Nullable; import android.os.Trace; import android.util.ArrayMap; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; @@ -64,6 +67,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; @@ -363,22 +367,24 @@ public class ShadeListBuilder implements Dumpable { mPipelineState.incrementTo(STATE_GROUP_STABILIZING); stabilizeGroupingNotifs(mNotifList); + // Step 5: Section & Sort + // Assign each top-level entry a section, and copy to all of its children + dispatchOnBeforeSort(mReadOnlyNotifList); + mPipelineState.incrementTo(STATE_SORTING); + assignSections(); + notifySectionEntriesUpdated(); + // Sort the list by section and then within section by our list of custom comparators + sortListAndGroups(); - // Step 5: Filter out entries after pre-group filtering, grouping and promoting - // Now filters can see grouping information to determine whether to filter or not. + // Step 6: Filter out entries after pre-group filtering, grouping, promoting, and sorting + // Now filters can see grouping, sectioning, and order information to determine whether + // to filter or not. dispatchOnBeforeFinalizeFilter(mReadOnlyNotifList); mPipelineState.incrementTo(STATE_FINALIZE_FILTERING); filterNotifs(mNotifList, mNewNotifList, mNotifFinalizeFilters); applyNewNotifList(); pruneIncompleteGroups(mNotifList); - // Step 6: Sort - // Assign each top-level entry a section, then sort the list by section and then within - // section by our list of custom comparators - dispatchOnBeforeSort(mReadOnlyNotifList); - mPipelineState.incrementTo(STATE_SORTING); - sortListAndNotifySections(); - // Step 7: Lock in our group structure and log anything that's changed since the last run mPipelineState.incrementTo(STATE_FINALIZING); logChanges(); @@ -408,18 +414,15 @@ public class ShadeListBuilder implements Dumpable { private void notifySectionEntriesUpdated() { Trace.beginSection("ShadeListBuilder.notifySectionEntriesUpdated"); - NotifSection currentSection = null; mTempSectionMembers.clear(); - for (int i = 0; i < mNotifList.size(); i++) { - ListEntry currentEntry = mNotifList.get(i); - if (currentSection != currentEntry.getSection()) { - if (currentSection != null) { - currentSection.getSectioner().onEntriesUpdated(mTempSectionMembers); - mTempSectionMembers.clear(); + for (NotifSection section : mNotifSections) { + for (ListEntry entry : mNotifList) { + if (section == entry.getSection()) { + mTempSectionMembers.add(entry); } - currentSection = currentEntry.getSection(); } - mTempSectionMembers.add(currentEntry); + section.getSectioner().onEntriesUpdated(mTempSectionMembers); + mTempSectionMembers.clear(); } Trace.endSection(); } @@ -653,16 +656,15 @@ public class ShadeListBuilder implements Dumpable { final List<NotificationEntry> children = group.getRawChildren(); if (group.getSummary() != null && children.size() == 0) { - shadeList.remove(i); - i--; - NotificationEntry summary = group.getSummary(); summary.setParent(ROOT_ENTRY); - shadeList.add(summary); + // The list may be sorted; replace the group with the summary, in its place + shadeList.set(i, summary); group.setSummary(null); annulAddition(group, shadeList); + i--; // The node we visited is gone, so be sure to visit this index again. } else if (group.getSummary() == null || children.size() < MIN_CHILDREN_FOR_GROUP) { @@ -680,7 +682,6 @@ public class ShadeListBuilder implements Dumpable { // its children (if any) directly to top-level. shadeList.remove(i); - i--; if (group.getSummary() != null) { final NotificationEntry summary = group.getSummary(); @@ -691,11 +692,14 @@ public class ShadeListBuilder implements Dumpable { for (int j = 0; j < children.size(); j++) { final NotificationEntry child = children.get(j); child.setParent(ROOT_ENTRY); - shadeList.add(child); + // The list may be sorted, so add the children in order where the group was. + shadeList.add(i + j, child); } children.clear(); annulAddition(group, shadeList); + + i--; // The node we visited is gone, so be sure to visit this index again. } } } @@ -765,9 +769,9 @@ public class ShadeListBuilder implements Dumpable { } } - private void sortListAndNotifySections() { - Trace.beginSection("ShadeListBuilder.sortListAndNotifySections"); - // Assign sections to top-level elements and sort their children + private void assignSections() { + Trace.beginSection("ShadeListBuilder.assignSections"); + // Assign sections to top-level elements and their children for (ListEntry entry : mNotifList) { NotifSection section = applySections(entry); if (entry instanceof GroupEntry) { @@ -775,27 +779,64 @@ public class ShadeListBuilder implements Dumpable { for (NotificationEntry child : parent.getChildren()) { setEntrySection(child, section); } + } + } + Trace.endSection(); + } + + private void sortListAndGroups() { + Trace.beginSection("ShadeListBuilder.sortListAndGroups"); + // Assign sections to top-level elements and sort their children + for (ListEntry entry : mNotifList) { + if (entry instanceof GroupEntry) { + GroupEntry parent = (GroupEntry) entry; parent.sortChildren(mGroupChildrenComparator); } } mNotifList.sort(mTopLevelComparator); assignIndexes(mNotifList); - notifySectionEntriesUpdated(); + // Check for suppressed order changes + if (!mNotifStabilityManager.isEveryChangeAllowed()) { + mForceReorderable = true; + boolean isSorted = isSorted(mNotifList, mTopLevelComparator); + mForceReorderable = false; + if (!isSorted) { + mNotifStabilityManager.onEntryReorderSuppressed(); + } + } Trace.endSection(); } + /** Determine whether the items in the list are sorted according to the comparator */ + @VisibleForTesting + public static <T> boolean isSorted(List<T> items, Comparator<T> comparator) { + if (items.size() <= 1) { + return true; + } + Iterator<T> iterator = items.iterator(); + T previous = iterator.next(); + T current; + while (iterator.hasNext()) { + current = iterator.next(); + if (comparator.compare(previous, current) > 0) { + return false; + } + previous = current; + } + return true; + } + /** * Assign the index of each notification relative to the total order - * @param notifList */ - private void assignIndexes(List<ListEntry> notifList) { + private static void assignIndexes(List<ListEntry> notifList) { if (notifList.size() == 0) return; - NotifSection currentSection = notifList.get(0).getSection(); + NotifSection currentSection = requireNonNull(notifList.get(0).getSection()); int sectionMemberIndex = 0; for (int i = 0; i < notifList.size(); i++) { ListEntry entry = notifList.get(i); - NotifSection section = entry.getSection(); + NotifSection section = requireNonNull(entry.getSection()); if (section.getIndex() != currentSection.getIndex()) { sectionMemberIndex = 0; currentSection = section; @@ -960,8 +1001,14 @@ public class ShadeListBuilder implements Dumpable { return cmp; }; + /** + * A flag that is set to true when we want to run the comparators as if all reordering is + * allowed. This is used to check if the list is "out of order" after the sort is complete. + */ + private boolean mForceReorderable = false; + private boolean canReorder(ListEntry entry) { - return mNotifStabilityManager.isEntryReorderingAllowed(entry); + return mForceReorderable || mNotifStabilityManager.isEntryReorderingAllowed(entry); } private boolean applyFilters(NotificationEntry entry, long now, List<NotifFilter> filters) { @@ -1031,8 +1078,11 @@ public class ShadeListBuilder implements Dumpable { private void setEntrySection(ListEntry entry, NotifSection finalSection) { entry.getAttachState().setSection(finalSection); NotificationEntry representativeEntry = entry.getRepresentativeEntry(); - if (representativeEntry != null && finalSection != null) { - representativeEntry.setBucket(finalSection.getBucket()); + if (representativeEntry != null) { + representativeEntry.getAttachState().setSection(finalSection); + if (finalSection != null) { + representativeEntry.setBucket(finalSection.getBucket()); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java index afdfb3bdeef6..644f248fca00 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java @@ -26,6 +26,9 @@ import android.service.notification.StatusBarNotification; import android.util.ArrayMap; import android.util.ArraySet; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.dagger.SysUISingleton; @@ -35,6 +38,8 @@ import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.ShadeListBuilder; import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater; +import com.android.systemui.statusbar.notification.collection.inflation.NotifUiAdjustment; +import com.android.systemui.statusbar.notification.collection.inflation.NotifUiAdjustmentProvider; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; @@ -46,7 +51,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; import java.util.Map; -import java.util.Set; import javax.inject.Inject; @@ -66,14 +70,24 @@ public class PreparationCoordinator implements Coordinator { private final NotifInflater mNotifInflater; private final NotifInflationErrorManager mNotifErrorManager; private final NotifViewBarn mViewBarn; - private final Map<NotificationEntry, Integer> mInflationStates = new ArrayMap<>(); + private final NotifUiAdjustmentProvider mAdjustmentProvider; + private final ArrayMap<NotificationEntry, Integer> mInflationStates = new ArrayMap<>(); + + /** + * The map of notifications to the NotifUiAdjustment (i.e. parameters) that were calculated + * when the inflation started. If an update of any kind results in the adjustment changing, + * then the row must be reinflated. If the row is being inflated, then the inflation must be + * aborted and restarted. + */ + private final ArrayMap<NotificationEntry, NotifUiAdjustment> mInflationAdjustments = + new ArrayMap<>(); /** * The set of notifications that are currently inflating something. Note that this is * separate from inflation state as a view could either be uninflated or inflated and still be * inflating something. */ - private final Set<NotificationEntry> mInflatingNotifs = new ArraySet<>(); + private final ArraySet<NotificationEntry> mInflatingNotifs = new ArraySet<>(); private final IStatusBarService mStatusBarService; @@ -92,12 +106,14 @@ public class PreparationCoordinator implements Coordinator { NotifInflater notifInflater, NotifInflationErrorManager errorManager, NotifViewBarn viewBarn, + NotifUiAdjustmentProvider adjustmentProvider, IStatusBarService service) { this( logger, notifInflater, errorManager, viewBarn, + adjustmentProvider, service, CHILD_BIND_CUTOFF, MAX_GROUP_INFLATION_DELAY); @@ -109,6 +125,7 @@ public class PreparationCoordinator implements Coordinator { NotifInflater notifInflater, NotifInflationErrorManager errorManager, NotifViewBarn viewBarn, + NotifUiAdjustmentProvider adjustmentProvider, IStatusBarService service, int childBindCutoff, long maxGroupInflationDelay) { @@ -116,6 +133,7 @@ public class PreparationCoordinator implements Coordinator { mNotifInflater = notifInflater; mNotifErrorManager = errorManager; mViewBarn = viewBarn; + mAdjustmentProvider = adjustmentProvider; mStatusBarService = service; mChildBindCutoff = childBindCutoff; mMaxGroupInflationDelay = maxGroupInflationDelay; @@ -160,6 +178,7 @@ public class PreparationCoordinator implements Coordinator { public void onEntryCleanUp(NotificationEntry entry) { mInflationStates.remove(entry); mViewBarn.removeViewForEntry(entry); + mInflationAdjustments.remove(entry); } }; @@ -269,39 +288,78 @@ public class PreparationCoordinator implements Coordinator { } private void inflateRequiredNotifViews(NotificationEntry entry) { + NotifUiAdjustment newAdjustment = mAdjustmentProvider.calculateAdjustment(entry); if (mInflatingNotifs.contains(entry)) { // Already inflating this entry + String errorIfNoOldAdjustment = "Inflating notification has no adjustments"; + if (needToReinflate(entry, newAdjustment, errorIfNoOldAdjustment)) { + inflateEntry(entry, newAdjustment, "adjustment changed while inflating"); + } return; } @InflationState int state = mInflationStates.get(entry); switch (state) { case STATE_UNINFLATED: - inflateEntry(entry, "entryAdded"); + inflateEntry(entry, newAdjustment, "entryAdded"); break; case STATE_INFLATED_INVALID: - rebind(entry, "entryUpdated"); + rebind(entry, newAdjustment, "entryUpdated"); break; case STATE_INFLATED: + String errorIfNoOldAdjustment = "Fully inflated notification has no adjustments"; + if (needToReinflate(entry, newAdjustment, errorIfNoOldAdjustment)) { + rebind(entry, newAdjustment, "adjustment changed after inflated"); + } + break; case STATE_ERROR: + if (needToReinflate(entry, newAdjustment, null)) { + inflateEntry(entry, newAdjustment, "adjustment changed after error"); + } + break; default: // Nothing to do. } } - private void inflateEntry(NotificationEntry entry, String reason) { + private boolean needToReinflate(@NonNull NotificationEntry entry, + @NonNull NotifUiAdjustment newAdjustment, @Nullable String oldAdjustmentMissingError) { + NotifUiAdjustment oldAdjustment = mInflationAdjustments.get(entry); + if (oldAdjustment == null) { + if (oldAdjustmentMissingError == null) { + return true; + } else { + throw new IllegalStateException(oldAdjustmentMissingError); + } + } + return NotifUiAdjustment.needReinflate(oldAdjustment, newAdjustment); + } + + private void inflateEntry(NotificationEntry entry, + NotifUiAdjustment newAdjustment, + String reason) { abortInflation(entry, reason); + mInflationAdjustments.put(entry, newAdjustment); mInflatingNotifs.add(entry); - mNotifInflater.inflateViews(entry, this::onInflationFinished); + NotifInflater.Params params = getInflaterParams(newAdjustment, reason); + mNotifInflater.inflateViews(entry, params, this::onInflationFinished); } - private void rebind(NotificationEntry entry, String reason) { + private void rebind(NotificationEntry entry, + NotifUiAdjustment newAdjustment, + String reason) { + mInflationAdjustments.put(entry, newAdjustment); mInflatingNotifs.add(entry); - mNotifInflater.rebindViews(entry, this::onInflationFinished); + NotifInflater.Params params = getInflaterParams(newAdjustment, reason); + mNotifInflater.rebindViews(entry, params, this::onInflationFinished); + } + + NotifInflater.Params getInflaterParams(NotifUiAdjustment adjustment, String reason) { + return new NotifInflater.Params(adjustment.isMinimized(), reason); } private void abortInflation(NotificationEntry entry, String reason) { mLogger.logInflationAborted(entry.getKey(), reason); - entry.abortTask(); + mNotifInflater.abortInflation(entry); mInflatingNotifs.remove(entry); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java index 79eb0898bc0b..c60ebcdc5fd1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java @@ -19,12 +19,12 @@ package com.android.systemui.statusbar.notification.collection.coordinator; import android.annotation.NonNull; import android.annotation.Nullable; -import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope; +import com.android.systemui.statusbar.notification.collection.inflation.NotifUiAdjustmentProvider; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; @@ -34,6 +34,7 @@ import com.android.systemui.statusbar.notification.dagger.AlertingHeader; import com.android.systemui.statusbar.notification.dagger.SilentHeader; import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt; +import java.util.Collections; import java.util.List; import javax.inject.Inject; @@ -50,6 +51,7 @@ public class RankingCoordinator implements Coordinator { public static final boolean SHOW_ALL_SECTIONS = false; private final StatusBarStateController mStatusBarStateController; private final HighPriorityProvider mHighPriorityProvider; + private final NotifUiAdjustmentProvider mAdjustmentProvider; private final NodeController mSilentNodeController; private final SectionHeaderController mSilentHeaderController; private final NodeController mAlertingHeaderController; @@ -60,11 +62,13 @@ public class RankingCoordinator implements Coordinator { public RankingCoordinator( StatusBarStateController statusBarStateController, HighPriorityProvider highPriorityProvider, + NotifUiAdjustmentProvider adjustmentProvider, @AlertingHeader NodeController alertingHeaderController, @SilentHeader SectionHeaderController silentHeaderController, @SilentHeader NodeController silentNodeController) { mStatusBarStateController = statusBarStateController; mHighPriorityProvider = highPriorityProvider; + mAdjustmentProvider = adjustmentProvider; mAlertingHeaderController = alertingHeaderController; mSilentNodeController = silentNodeController; mSilentHeaderController = silentHeaderController; @@ -73,10 +77,10 @@ public class RankingCoordinator implements Coordinator { @Override public void attach(NotifPipeline pipeline) { mStatusBarStateController.addCallback(mStatusBarStateCallback); + mAdjustmentProvider.setLowPrioritySections(Collections.singleton(mMinimizedNotifSectioner)); pipeline.addPreGroupFilter(mSuspendedFilter); pipeline.addPreGroupFilter(mDndVisualEffectsFilter); - pipeline.addOnBeforeSortListener(entries -> resetClearAllFlags()); } public NotifSectioner getAlertingSectioner() { @@ -126,6 +130,7 @@ public class RankingCoordinator implements Coordinator { @Nullable @Override public void onEntriesUpdated(@NonNull List<ListEntry> entries) { + mHasSilentEntries = false; for (int i = 0; i < entries.size(); i++) { if (entries.get(i).getRepresentativeEntry().getSbn().isClearable()) { mHasSilentEntries = true; @@ -154,6 +159,7 @@ public class RankingCoordinator implements Coordinator { @Nullable @Override public void onEntriesUpdated(@NonNull List<ListEntry> entries) { + mHasMinimizedEntries = false; for (int i = 0; i < entries.size(); i++) { if (entries.get(i).getRepresentativeEntry().getSbn().isClearable()) { mHasMinimizedEntries = true; @@ -189,12 +195,6 @@ public class RankingCoordinator implements Coordinator { } }; - @VisibleForTesting - protected void resetClearAllFlags() { - mHasSilentEntries = false; - mHasMinimizedEntries = false; - } - private final StatusBarStateController.StateListener mStatusBarStateCallback = new StatusBarStateController.StateListener() { @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java index 32b1cf6bfdca..75489b1faadb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java @@ -19,11 +19,12 @@ package com.android.systemui.statusbar.notification.collection.coordinator; import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE; import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING; -import android.annotation.NonNull; - +import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; +import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationViewHierarchyManager; @@ -34,6 +35,8 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.util.concurrency.DelayableExecutor; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -53,7 +56,7 @@ import javax.inject.Inject; */ // TODO(b/204468557): Move to @CoordinatorScope @SysUISingleton -public class VisualStabilityCoordinator implements Coordinator { +public class VisualStabilityCoordinator implements Coordinator, Dumpable { private final DelayableExecutor mDelayableExecutor; private final WakefulnessLifecycle mWakefulnessLifecycle; private final StatusBarStateController mStatusBarStateController; @@ -66,6 +69,7 @@ public class VisualStabilityCoordinator implements Coordinator { private boolean mReorderingAllowed; private boolean mIsSuppressingGroupChange = false; private final Set<String> mEntriesWithSuppressedSectionChange = new HashSet<>(); + private boolean mIsSuppressingEntryReorder = false; // key: notification key that can temporarily change its section // value: runnable that when run removes its associated RemoveOverrideSuppressionRunnable @@ -77,6 +81,7 @@ public class VisualStabilityCoordinator implements Coordinator { @Inject public VisualStabilityCoordinator( + DumpManager dumpManager, HeadsUpManager headsUpManager, WakefulnessLifecycle wakefulnessLifecycle, StatusBarStateController statusBarStateController, @@ -86,6 +91,8 @@ public class VisualStabilityCoordinator implements Coordinator { mWakefulnessLifecycle = wakefulnessLifecycle; mStatusBarStateController = statusBarStateController; mDelayableExecutor = delayableExecutor; + + dumpManager.registerDumpable(this); } @Override @@ -99,7 +106,6 @@ public class VisualStabilityCoordinator implements Coordinator { pipeline.setVisualStabilityManager(mNotifStabilityManager); } - // TODO(b/203828145): Ensure stability manager handles minimized state changes // TODO(b/203826051): Ensure stability manager can allow reordering off-screen // HUNs to the top of the shade private final NotifStabilityManager mNotifStabilityManager = @@ -108,6 +114,7 @@ public class VisualStabilityCoordinator implements Coordinator { public void onBeginRun() { mIsSuppressingGroupChange = false; mEntriesWithSuppressedSectionChange.clear(); + mIsSuppressingEntryReorder = false; } @Override @@ -124,7 +131,7 @@ public class VisualStabilityCoordinator implements Coordinator { mReorderingAllowed || mHeadsUpManager.isAlerting(entry.getKey()) || mEntriesThatCanChangeSection.containsKey(entry.getKey()); - if (isSectionChangeAllowedForEntry) { + if (!isSectionChangeAllowedForEntry) { mEntriesWithSuppressedSectionChange.add(entry.getKey()); } return isSectionChangeAllowedForEntry; @@ -134,11 +141,22 @@ public class VisualStabilityCoordinator implements Coordinator { public boolean isEntryReorderingAllowed(ListEntry section) { return mReorderingAllowed; } + + @Override + public boolean isEveryChangeAllowed() { + return mReorderingAllowed; + } + + @Override + public void onEntryReorderSuppressed() { + mIsSuppressingEntryReorder = true; + } }; private void updateAllowedStates() { mReorderingAllowed = isReorderingAllowed(); - if (mReorderingAllowed && (mIsSuppressingGroupChange || isSuppressingSectionChange())) { + if (mReorderingAllowed && (mIsSuppressingGroupChange || isSuppressingSectionChange() + || mIsSuppressingEntryReorder)) { mNotifStabilityManager.invalidateList(); } } @@ -211,4 +229,23 @@ public class VisualStabilityCoordinator implements Coordinator { updateAllowedStates(); } }; + + @Override + public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { + pw.println("reorderingAllowed: " + mReorderingAllowed); + pw.println(" screenOn: " + mScreenOn); + pw.println(" panelExpanded: " + mPanelExpanded); + pw.println(" pulsing: " + mPulsing); + pw.println("isSuppressingGroupChange: " + mIsSuppressingGroupChange); + pw.println("isSuppressingEntryReorder: " + mIsSuppressingEntryReorder); + pw.println("entriesWithSuppressedSectionChange: " + + mEntriesWithSuppressedSectionChange.size()); + for (String key : mEntriesWithSuppressedSectionChange) { + pw.println(" " + key); + } + pw.println("entriesThatCanChangeSection: " + mEntriesThatCanChangeSection.size()); + for (String key : mEntriesThatCanChangeSection.keySet()) { + pw.println(" " + key); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt index e3d76113d537..c59f18436b74 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt @@ -14,22 +14,22 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.collection.inflation; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.coordinator.PreparationCoordinator; +package com.android.systemui.statusbar.notification.collection.inflation + +import com.android.systemui.statusbar.notification.collection.NotificationEntry /** - * Used by the {@link PreparationCoordinator}. When notifications are added or updated, the + * Used by the [PreparationCoordinator]. When notifications are added or updated, the * NotifInflater is asked to (re)inflated and prepare their views. This inflation occurs off the * main thread. When the inflation is finished, NotifInflater will trigger its InflationCallback. */ -public interface NotifInflater { +interface NotifInflater { /** * Called to rebind the entry's views. * * @param callback callback called after inflation finishes */ - void rebindViews(NotificationEntry entry, InflationCallback callback); + fun rebindViews(entry: NotificationEntry, params: Params, callback: InflationCallback) /** * Called to inflate the views of an entry. Views are not considered inflated until all of its @@ -37,18 +37,23 @@ public interface NotifInflater { * * @param callback callback called after inflation finishes */ - void inflateViews(NotificationEntry entry, InflationCallback callback); + fun inflateViews(entry: NotificationEntry, params: Params, callback: InflationCallback) /** * Request to stop the inflation of an entry. For example, called when a notification is * removed and no longer needs to be inflated. */ - void abortInflation(NotificationEntry entry); + fun abortInflation(entry: NotificationEntry) /** * Callback once all the views are inflated and bound for a given NotificationEntry. */ interface InflationCallback { - void onInflationFinished(NotificationEntry entry); + fun onInflationFinished(entry: NotificationEntry) } -} + + /** + * A class holding parameters used when inflating the notification row + */ + class Params(val isLowPriority: Boolean, val reason: String) +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt new file mode 100644 index 000000000000..9d86b7831f18 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt @@ -0,0 +1,95 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.inflation + +import android.app.Notification +import android.app.RemoteInput +import android.graphics.drawable.Icon +import android.text.TextUtils + +/** + * An immutable object which contains minimal state extracted from an entry that represents state + * which can change without a direct app update (e.g. with a ranking update). + * Diffing two entries determines if view re-inflation is needed. + */ +class NotifUiAdjustment internal constructor( + val key: String, + val smartActions: List<Notification.Action>, + val smartReplies: List<CharSequence>, + val isConversation: Boolean, + val isMinimized: Boolean +) { + companion object { + @JvmStatic + fun needReinflate( + oldAdjustment: NotifUiAdjustment, + newAdjustment: NotifUiAdjustment + ): Boolean = when { + oldAdjustment === newAdjustment -> false + oldAdjustment.isConversation != newAdjustment.isConversation -> true + oldAdjustment.isMinimized != newAdjustment.isMinimized -> true + areDifferent(oldAdjustment.smartActions, newAdjustment.smartActions) -> true + newAdjustment.smartReplies != oldAdjustment.smartReplies -> true + else -> false + } + + private fun areDifferent( + first: List<Notification.Action>, + second: List<Notification.Action> + ): Boolean = when { + first === second -> false + first.size != second.size -> true + else -> first.asSequence().zip(second.asSequence()).any { + (!TextUtils.equals(it.first.title, it.second.title)) || + (areDifferent(it.first.getIcon(), it.second.getIcon())) || + (it.first.actionIntent != it.second.actionIntent) || + (areDifferent(it.first.remoteInputs, it.second.remoteInputs)) + } + } + + private fun areDifferent(first: Icon?, second: Icon?): Boolean = when { + first === second -> false + first == null || second == null -> true + else -> !first.sameAs(second) + } + + private fun areDifferent( + first: Array<RemoteInput>?, + second: Array<RemoteInput>? + ): Boolean = when { + first === second -> false + first == null || second == null -> true + first.size != second.size -> true + else -> first.asSequence().zip(second.asSequence()).any { + (!TextUtils.equals(it.first.label, it.second.label)) || + (areDifferent(it.first.choices, it.second.choices)) + } + } + + private fun areDifferent( + first: Array<CharSequence>?, + second: Array<CharSequence>? + ): Boolean = when { + first === second -> false + first == null || second == null -> true + first.size != second.size -> true + else -> first.asSequence().zip(second.asSequence()).any { + !TextUtils.equals(it.first, it.second) + } + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt new file mode 100644 index 000000000000..3290cdffdceb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.inflation + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.collection.GroupEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner +import javax.inject.Inject + +/** + * A class which provides an adjustment object to the preparation coordinator which is uses + * to ensure that notifications are reinflated when ranking-derived information changes. + */ +@SysUISingleton +open class NotifUiAdjustmentProvider @Inject constructor() { + + private lateinit var lowPrioritySections: Set<NotifSectioner> + + /** + * Feed the provider the information it needs about which sections should have minimized top + * level views, so that it can calculate the correct minimized value in the adjustment. + */ + fun setLowPrioritySections(sections: Collection<NotifSectioner>) { + lowPrioritySections = sections.toSet() + } + + private fun isEntryMinimized(entry: NotificationEntry): Boolean { + val section = entry.section ?: error("Entry must have a section to determine if minimized") + val parent = entry.parent ?: error("Entry must have a parent to determine if minimized") + val isLowPrioritySection = lowPrioritySections.contains(section.sectioner) + val isTopLevelEntry = parent == GroupEntry.ROOT_ENTRY + val isGroupSummary = parent.summary == entry + return isLowPrioritySection && (isTopLevelEntry || isGroupSummary) + } + + /** + * Returns a adjustment object for the given entry. This can be compared to a previous instance + * from the same notification using [NotifUiAdjustment.needReinflate] to determine if it + * should be reinflated. + */ + fun calculateAdjustment(entry: NotificationEntry) = NotifUiAdjustment( + key = entry.key, + smartActions = entry.ranking.smartActions, + smartReplies = entry.ranking.smartReplies, + isConversation = entry.ranking.isConversation, + isMinimized = isEntryMinimized(entry) + ) +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java index 1215ade2abb1..3a4701c9ac76 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java @@ -32,12 +32,10 @@ public interface NotificationRowBinder { /** * Called when a notification has been added or updated. The binder must asynchronously inflate * and bind the views associated with the notification. - * - * TODO: The caller is notified when the inflation completes, but this is currently a very - * roundabout business. Add an explicit completion/failure callback to this method. */ void inflateViews( NotificationEntry entry, + NotifInflater.Params params, NotificationRowContentBinder.InflationCallback callback) throws InflationException; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java index e5425cfc8c93..5c8e8b244abb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java @@ -25,6 +25,7 @@ import android.view.ViewGroup; import com.android.internal.util.NotificationMessagingUtil; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationRemoteInputManager; @@ -32,6 +33,7 @@ import com.android.systemui.statusbar.NotificationUiAdjustment; import com.android.systemui.statusbar.notification.InflationException; import com.android.systemui.statusbar.notification.NotificationClicker; import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.legacy.LowPriorityInflationHelper; import com.android.systemui.statusbar.notification.icon.IconManager; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController; @@ -53,6 +55,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { private static final String TAG = "NotificationViewManager"; private final Context mContext; + private final FeatureFlags mFeatureFlags; private final NotificationMessagingUtil mMessagingUtil; private final NotificationRemoteInputManager mNotificationRemoteInputManager; private final NotificationLockscreenUserManager mNotificationLockscreenUserManager; @@ -72,6 +75,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { @Inject public NotificationRowBinderImpl( Context context, + FeatureFlags featureFlags, NotificationMessagingUtil notificationMessagingUtil, NotificationRemoteInputManager notificationRemoteInputManager, NotificationLockscreenUserManager notificationLockscreenUserManager, @@ -82,6 +86,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { IconManager iconManager, LowPriorityInflationHelper lowPriorityInflationHelper) { mContext = context; + mFeatureFlags = featureFlags; mNotifBindPipeline = notifBindPipeline; mRowContentBindStage = rowContentBindStage; mMessagingUtil = notificationMessagingUtil; @@ -116,8 +121,13 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { @Override public void inflateViews( NotificationEntry entry, + NotifInflater.Params params, NotificationRowContentBinder.InflationCallback callback) throws InflationException { + if (params == null) { + // weak assert that the params should always be passed in the new pipeline + mFeatureFlags.checkLegacyPipelineEnabled(); + } ViewGroup parent = mListContainer.getViewParentForNotification(entry); if (entry.rowExists()) { @@ -125,7 +135,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { ExpandableNotificationRow row = entry.getRow(); row.reset(); updateRow(entry, row); - inflateContentViews(entry, row, callback); + inflateContentViews(entry, params, row, callback); } else { mIconManager.createIcons(entry); mRowInflaterTaskProvider.get().inflate(mContext, parent, entry, @@ -144,7 +154,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { entry.setRowController(rowController); bindRow(entry, row); updateRow(entry, row); - inflateContentViews(entry, row, callback); + inflateContentViews(entry, params, row, callback); }); } } @@ -177,12 +187,13 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { NotificationUiAdjustment oldAdjustment, NotificationUiAdjustment newAdjustment, NotificationRowContentBinder.InflationCallback callback) { + mFeatureFlags.checkLegacyPipelineEnabled(); if (NotificationUiAdjustment.needReinflate(oldAdjustment, newAdjustment)) { if (entry.rowExists()) { ExpandableNotificationRow row = entry.getRow(); row.reset(); updateRow(entry, row); - inflateContentViews(entry, row, callback); + inflateContentViews(entry, null, row, callback); } else { // Once the RowInflaterTask is done, it will pick up the updated entry, so // no-op here. @@ -216,15 +227,24 @@ public class NotificationRowBinderImpl implements NotificationRowBinder { */ private void inflateContentViews( NotificationEntry entry, + NotifInflater.Params inflaterParams, ExpandableNotificationRow row, @Nullable NotificationRowContentBinder.InflationCallback inflationCallback) { final boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(entry.getSbn(), entry.getImportance()); - // If this is our first time inflating, we don't actually know the groupings for real - // yet, so we might actually inflate a low priority content view incorrectly here and have - // to correct it later in the pipeline. On subsequent inflations (i.e. updates), this - // should inflate the correct view. - final boolean isLowPriority = mLowPriorityInflationHelper.shouldUseLowPriorityView(entry); + final boolean isLowPriority; + if (inflaterParams != null) { + // NEW pipeline + isLowPriority = inflaterParams.isLowPriority(); + } else { + // LEGACY pipeline + mFeatureFlags.checkLegacyPipelineEnabled(); + // If this is our first time inflating, we don't actually know the groupings for real + // yet, so we might actually inflate a low priority content view incorrectly here and + // have to correct it later in the pipeline. On subsequent inflations (i.e. updates), + // this should inflate the correct view. + isLowPriority = mLowPriorityInflationHelper.shouldUseLowPriorityView(entry); + } RowContentBindParams params = mRowContentBindStage.getStageParams(entry); params.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/LowPriorityInflationHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LowPriorityInflationHelper.java index 518c3f1d1948..dd1f9485a4f3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/LowPriorityInflationHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LowPriorityInflationHelper.java @@ -14,13 +14,11 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.collection.inflation; +package com.android.systemui.statusbar.notification.collection.legacy; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.RowContentBindParams; import com.android.systemui.statusbar.notification.row.RowContentBindStage; @@ -61,6 +59,7 @@ public class LowPriorityInflationHelper { public void recheckLowPriorityViewAndInflate( NotificationEntry entry, ExpandableNotificationRow row) { + mFeatureFlags.checkLegacyPipelineEnabled(); RowContentBindParams params = mRowContentBindStage.getStageParams(entry); final boolean shouldBeLowPriority = shouldUseLowPriorityView(entry); if (!row.isRemoved() && row.isLowPriority() != shouldBeLowPriority) { @@ -74,12 +73,7 @@ public class LowPriorityInflationHelper { * Whether the notification should inflate a low priority version of its content views. */ public boolean shouldUseLowPriorityView(NotificationEntry entry) { - boolean isGroupChild; - if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { - isGroupChild = (entry.getParent() != GroupEntry.ROOT_ENTRY); - } else { - isGroupChild = mGroupManager.isChildInGroup(entry); - } - return entry.isAmbient() && !isGroupChild; + mFeatureFlags.checkLegacyPipelineEnabled(); + return entry.isAmbient() && !mGroupManager.isChildInGroup(entry); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt index 6424e37ad328..8444287cbf60 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt @@ -27,8 +27,7 @@ data class NotifSection( val label: String get() = "Section($index, $bucket, \"${sectioner.name}\")" - val headerController: NodeController? - get() = sectioner.headerNodeController + val headerController: NodeController? = sectioner.headerNodeController @PriorityBucket val bucket: Int = sectioner.bucket } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java index 027ac0f66b35..798bfe7f39d0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java @@ -82,8 +82,8 @@ public class PipelineState { public static final int STATE_GROUPING = 4; public static final int STATE_TRANSFORMING = 5; public static final int STATE_GROUP_STABILIZING = 6; - public static final int STATE_FINALIZE_FILTERING = 7; - public static final int STATE_SORTING = 8; + public static final int STATE_SORTING = 7; + public static final int STATE_FINALIZE_FILTERING = 8; public static final int STATE_FINALIZING = 9; @IntDef(prefix = { "STATE_" }, value = { @@ -94,8 +94,8 @@ public class PipelineState { STATE_GROUPING, STATE_TRANSFORMING, STATE_GROUP_STABILIZING, - STATE_FINALIZE_FILTERING, STATE_SORTING, + STATE_FINALIZE_FILTERING, STATE_FINALIZING, }) @Retention(RetentionPolicy.SOURCE) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.java index 520791c91803..cb2d3cb97468 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.java @@ -60,4 +60,19 @@ public abstract class NotifStabilityManager extends Pluggable<NotifStabilityMana * @return if can re-order */ public abstract boolean isEntryReorderingAllowed(ListEntry entry); + + /** + * Called by the pipeline to determine if every call to the other stability methods would + * return true, regardless of parameters. This allows the pipeline to skip any pieces of + * work related to stability. + * + * @return true if all other methods will return true for any parameters. + */ + public abstract boolean isEveryChangeAllowed(); + + /** + * Called by the pipeline to inform the stability manager that an entry reordering was indeed + * suppressed as the result of a previous call to {@link #isEntryReorderingAllowed(ListEntry)}. + */ + public abstract void onEntryReorderSuppressed(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index bc79db36c3ea..a97a54a50da5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -4631,6 +4631,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable public void addContainerViewAt(View v, int index) { Assert.isMainThread(); + ensureRemovedFromTransientContainer(v); addView(v, index); if (v instanceof ExpandableNotificationRow && mController.isShowingEmptyShadeView()) { mController.updateShowEmptyShadeView(); @@ -4640,6 +4641,22 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable updateSpeedBumpIndex(); } + private void ensureRemovedFromTransientContainer(View v) { + if (v.getParent() == this && v instanceof SectionHeaderView) { + ExpandableView expandableView = (ExpandableView) v; + ViewGroup transientContainer = expandableView.getTransientContainer(); + // If the child is animating away, it will still have a parent, so + // detach it first + // TODO: We should really cancel the active animations here. This will + // happen automatically when the view's intro animation starts, but + // it's a fragile link. + if (transientContainer != null) { + transientContainer.removeTransientView(v); + expandableView.setTransientContainer(null); + } + } + } + @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void runAfterAnimationFinished(Runnable runnable) { mAnimationFinishedRunnables.add(runnable); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index e26e69f9381a..395369898e91 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -2342,7 +2342,7 @@ public class NotificationPanelViewController extends PanelViewController { mQs.setExpanded(mQsExpanded); } - private void setQsExpansion(float height) { + void setQsExpansion(float height) { height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight); mQsFullyExpanded = height == mQsMaxExpansionHeight && mQsMaxExpansionHeight != 0; if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling @@ -2386,7 +2386,13 @@ public class NotificationPanelViewController extends PanelViewController { int qsPanelBottomY = calculateQsBottomPosition(qsExpansionFraction); mScrimController.setQsPosition(qsExpansionFraction, qsPanelBottomY); setQSClippingBounds(); - mNotificationStackScrollLayoutController.setQsExpansionFraction(qsExpansionFraction); + + // Only need to notify the notification stack when we're not in split screen mode. If we + // do, then the notification panel starts scrolling along with the QS. + if (!mShouldUseSplitNotificationShade) { + mNotificationStackScrollLayoutController.setQsExpansionFraction(qsExpansionFraction); + } + mDepthController.setQsPanelExpansion(qsExpansionFraction); if (mCommunalViewController != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index afa333ac68b3..1108f5ef1337 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -128,12 +128,12 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.ActivityIntentHelper; import com.android.systemui.AutoReinflateContainer; +import com.android.systemui.CoreStartable; import com.android.systemui.DejankUtils; import com.android.systemui.EventLogTags; import com.android.systemui.InitController; import com.android.systemui.Prefs; import com.android.systemui.R; -import com.android.systemui.SystemUI; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.animation.DelegateLaunchAnimatorController; import com.android.systemui.assist.AssistManager; @@ -255,7 +255,7 @@ import javax.inject.Named; import dagger.Lazy; /** */ -public class StatusBar extends SystemUI implements +public class StatusBar extends CoreStartable implements ActivityStarter, LifecycleOwner { public static final boolean MULTIUSER_DEBUG = false; @@ -2385,6 +2385,8 @@ public class StatusBar extends SystemUI implements if (mLightRevealScrim != null) { pw.println( + "mLightRevealScrim.getRevealEffect(): " + mLightRevealScrim.getRevealEffect()); + pw.println( "mLightRevealScrim.getRevealAmount(): " + mLightRevealScrim.getRevealAmount()); } @@ -3370,17 +3372,24 @@ public class StatusBar extends SystemUI implements return; } - if (wakingUp && mWakefulnessLifecycle.getLastWakeReason() - == PowerManager.WAKE_REASON_POWER_BUTTON - || !wakingUp && mWakefulnessLifecycle.getLastSleepReason() - == PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON) { + final boolean wakingUpFromPowerButton = wakingUp + && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal) + && mWakefulnessLifecycle.getLastWakeReason() + == PowerManager.WAKE_REASON_POWER_BUTTON; + final boolean sleepingFromPowerButton = !wakingUp + && mWakefulnessLifecycle.getLastSleepReason() + == PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON; + + if (wakingUpFromPowerButton || sleepingFromPowerButton) { mLightRevealScrim.setRevealEffect(mPowerButtonReveal); + mLightRevealScrim.setRevealAmount(1f - mStatusBarStateController.getDozeAmount()); } else if (!wakingUp || !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) { // If we're going to sleep, but it's not from the power button, use the default reveal. // If we're waking up, only use the default reveal if the biometric controller didn't // already set it to the circular reveal because we're waking up from a fingerprint/face // auth. mLightRevealScrim.setRevealEffect(LiftReveal.INSTANCE); + mLightRevealScrim.setRevealAmount(1f - mStatusBarStateController.getDozeAmount()); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java index 37a763b740e1..8ea7d62c9c74 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java @@ -22,7 +22,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import com.android.internal.statusbar.IStatusBarService; -import com.android.systemui.SystemUI; +import com.android.systemui.CoreStartable; import com.android.systemui.assist.AssistManager; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.statusbar.CommandQueue; @@ -36,7 +36,7 @@ import dagger.Lazy; * Serves as a collection of UI components, rather than showing its own UI. */ @SysUISingleton -public class TvStatusBar extends SystemUI implements CommandQueue.Callbacks { +public class TvStatusBar extends CoreStartable implements CommandQueue.Callbacks { private final CommandQueue mCommandQueue; private final Lazy<AssistManager> mAssistManagerLazy; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/VpnStatusObserver.kt b/packages/SystemUI/src/com/android/systemui/statusbar/tv/VpnStatusObserver.kt index 7c6f86aceeb0..e25a10507825 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/VpnStatusObserver.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/VpnStatusObserver.kt @@ -24,9 +24,9 @@ import android.app.NotificationManager import android.content.Context import com.android.internal.messages.nano.SystemMessageProto import com.android.internal.net.VpnConfig +import com.android.systemui.CoreStartable import com.android.systemui.Dependency import com.android.systemui.R -import com.android.systemui.SystemUI import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.policy.SecurityController import javax.inject.Inject @@ -35,7 +35,7 @@ import javax.inject.Inject * Observes if a vpn connection is active and displays a notification to the user */ @SysUISingleton -class VpnStatusObserver @Inject constructor(context: Context) : SystemUI(context), +class VpnStatusObserver @Inject constructor(context: Context) : CoreStartable(context), SecurityController.SecurityControllerCallback { private var vpnConnected = false diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationHandler.java index d985803c2b39..8026ba517820 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationHandler.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationHandler.java @@ -24,7 +24,7 @@ import android.service.notification.StatusBarNotification; import android.util.Log; import android.util.SparseArray; -import com.android.systemui.SystemUI; +import com.android.systemui.CoreStartable; import com.android.systemui.statusbar.NotificationListener; import javax.inject.Inject; @@ -32,7 +32,7 @@ import javax.inject.Inject; /** * Keeps track of the notifications on TV. */ -public class TvNotificationHandler extends SystemUI implements +public class TvNotificationHandler extends CoreStartable implements NotificationListener.NotificationHandler { private static final String TAG = "TvNotificationHandler"; private final NotificationListener mNotificationListener; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java index c9bc7e6c8e5c..892fedcc8ce2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/notifications/TvNotificationPanel.java @@ -25,7 +25,7 @@ import android.content.pm.ResolveInfo; import android.os.UserHandle; import android.util.Log; -import com.android.systemui.SystemUI; +import com.android.systemui.CoreStartable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.statusbar.CommandQueue; @@ -35,7 +35,7 @@ import javax.inject.Inject; * Offers control methods for the notification panel handler on TV devices. */ @SysUISingleton -public class TvNotificationPanel extends SystemUI implements CommandQueue.Callbacks { +public class TvNotificationPanel extends CoreStartable implements CommandQueue.Callbacks { private static final String TAG = "TvNotificationPanel"; private final CommandQueue mCommandQueue; private final String mNotificationHandlerPackage; diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index c5f463f82bba..adfff4af2775 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -53,8 +53,8 @@ import android.util.TypedValue; import androidx.annotation.NonNull; import com.android.internal.graphics.ColorUtils; +import com.android.systemui.CoreStartable; import com.android.systemui.Dumpable; -import com.android.systemui.SystemUI; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; @@ -93,7 +93,7 @@ import javax.inject.Inject; * associated work profiles */ @SysUISingleton -public class ThemeOverlayController extends SystemUI implements Dumpable { +public class ThemeOverlayController extends CoreStartable implements Dumpable { protected static final String TAG = "ThemeOverlayController"; private static final boolean DEBUG = true; diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java index 42f66875e7a1..0758f8fc4fab 100644 --- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java +++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java @@ -37,7 +37,7 @@ import android.widget.ToastPresenter; import androidx.annotation.VisibleForTesting; -import com.android.systemui.SystemUI; +import com.android.systemui.CoreStartable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.statusbar.CommandQueue; @@ -49,7 +49,7 @@ import javax.inject.Inject; * Controls display of text toasts. */ @SysUISingleton -public class ToastUI extends SystemUI implements CommandQueue.Callbacks { +public class ToastUI extends CoreStartable implements CommandQueue.Callbacks { // values from NotificationManagerService#LONG_DELAY and NotificationManagerService#SHORT_DELAY private static final int TOAST_LONG_TIME = 3500; // 3.5 seconds private static final int TOAST_SHORT_TIME = 2000; // 2 seconds diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java index 353333f714d4..e59d2f233804 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java +++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java @@ -16,7 +16,7 @@ package com.android.systemui.tv; -import com.android.systemui.SystemUI; +import com.android.systemui.CoreStartable; import com.android.systemui.dagger.GlobalRootComponent; import com.android.systemui.statusbar.tv.notifications.TvNotificationHandler; @@ -33,5 +33,5 @@ interface TvSystemUIBinder { @Binds @IntoMap @ClassKey(TvNotificationHandler.class) - SystemUI bindTvNotificationHandler(TvNotificationHandler systemui); + CoreStartable bindTvNotificationHandler(TvNotificationHandler systemui); } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt index cc2c2083cbab..65106f1df93c 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt @@ -41,7 +41,9 @@ annotation class SysUIUnfoldScope * no objects will get constructed if these parameters are empty. */ @Module(subcomponents = [SysUIUnfoldComponent::class]) -object SysUIUnfoldModule { +class SysUIUnfoldModule { + constructor() {} + @Provides @SysUISingleton fun provideSysUIUnfoldComponent( diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java index 755372b4b0f5..881ac08c541c 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java +++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java @@ -44,12 +44,12 @@ import android.util.SparseArray; import com.android.internal.R; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; -import com.android.systemui.SystemUI; +import com.android.systemui.CoreStartable; import com.android.systemui.util.NotificationChannels; import java.util.List; -public class StorageNotification extends SystemUI { +public class StorageNotification extends CoreStartable { private static final String TAG = "StorageNotification"; private static final String ACTION_SNOOZE_VOLUME = "com.android.systemui.action.SNOOZE_VOLUME"; @@ -224,7 +224,7 @@ public class StorageNotification extends SystemUI { .setCategory(Notification.CATEGORY_SYSTEM) .setDeleteIntent(buildSnoozeIntent(fsUuid)) .extend(new Notification.TvExtender()); - SystemUI.overrideNotificationAppName(mContext, builder, false); + CoreStartable.overrideNotificationAppName(mContext, builder, false); mNotificationManager.notifyAsUser(fsUuid, SystemMessage.NOTE_STORAGE_PRIVATE, builder.build(), UserHandle.ALL); @@ -252,7 +252,7 @@ public class StorageNotification extends SystemUI { .setLocalOnly(true) .setCategory(Notification.CATEGORY_ERROR) .extend(new Notification.TvExtender()); - SystemUI.overrideNotificationAppName(mContext, builder, false); + CoreStartable.overrideNotificationAppName(mContext, builder, false); mNotificationManager.notifyAsUser(disk.getId(), SystemMessage.NOTE_STORAGE_DISK, builder.build(), UserHandle.ALL); @@ -527,7 +527,7 @@ public class StorageNotification extends SystemUI { .setCategory(Notification.CATEGORY_PROGRESS) .setProgress(100, status, false) .setOngoing(true); - SystemUI.overrideNotificationAppName(mContext, builder, false); + CoreStartable.overrideNotificationAppName(mContext, builder, false); mNotificationManager.notifyAsUser(move.packageName, SystemMessage.NOTE_STORAGE_MOVE, builder.build(), UserHandle.ALL); @@ -577,7 +577,7 @@ public class StorageNotification extends SystemUI { .setLocalOnly(true) .setCategory(Notification.CATEGORY_SYSTEM) .setAutoCancel(true); - SystemUI.overrideNotificationAppName(mContext, builder, false); + CoreStartable.overrideNotificationAppName(mContext, builder, false); mNotificationManager.notifyAsUser(move.packageName, SystemMessage.NOTE_STORAGE_MOVE, builder.build(), UserHandle.ALL); diff --git a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java index f97f4d0edc24..ce7e4cf82081 100644 --- a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java +++ b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java @@ -23,13 +23,13 @@ import android.net.Uri; import android.provider.Settings; import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.CoreStartable; import com.android.systemui.R; -import com.android.systemui.SystemUI; import com.android.wm.shell.pip.tv.TvPipNotificationController; import java.util.Arrays; -public class NotificationChannels extends SystemUI { +public class NotificationChannels extends CoreStartable { public static String ALERTS = "ALR"; public static String SCREENSHOTS_HEADSUP = "SCN_HEADSUP"; public static String GENERAL = "GEN"; diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java index 431899460586..612b7cb10007 100644 --- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java +++ b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java @@ -45,9 +45,9 @@ import android.util.LongSparseArray; import android.view.View; import com.android.internal.logging.MetricsLogger; +import com.android.systemui.CoreStartable; import com.android.systemui.Dumpable; import com.android.systemui.R; -import com.android.systemui.SystemUI; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; @@ -565,7 +565,7 @@ public class GarbageMonitor implements Dumpable { /** */ @SysUISingleton - public static class Service extends SystemUI implements Dumpable { + public static class Service extends CoreStartable implements Dumpable { private final GarbageMonitor mGarbageMonitor; @Inject diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java index 5aa3617b1651..11725ef4867e 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java @@ -33,7 +33,11 @@ import android.media.AudioManager; import android.media.AudioSystem; import android.media.IAudioService; import android.media.IVolumeController; +import android.media.MediaRoute2Info; +import android.media.MediaRouter2Manager; +import android.media.RoutingSessionInfo; import android.media.VolumePolicy; +import android.media.session.MediaController; import android.media.session.MediaController.PlaybackInfo; import android.media.session.MediaSession.Token; import android.net.Uri; @@ -71,6 +75,7 @@ import com.android.systemui.util.concurrency.ThreadFactory; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -118,6 +123,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa private final Context mContext; private final Looper mWorkerLooper; private final PackageManager mPackageManager; + private final MediaRouter2Manager mRouter2Manager; private final WakefulnessLifecycle mWakefulnessLifecycle; private AudioManager mAudio; private IAudioService mAudioService; @@ -179,6 +185,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa mWorkerLooper = theadFactory.buildLooperOnNewThread( VolumeDialogControllerImpl.class.getSimpleName()); mWorker = new W(mWorkerLooper); + mRouter2Manager = MediaRouter2Manager.getInstance(mContext); mMediaSessionsCallbacksW = new MediaSessionsCallbacks(mContext); mMediaSessions = createMediaSessions(mContext, mWorkerLooper, mMediaSessionsCallbacksW); mAudio = audioManager; @@ -1150,16 +1157,16 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa private final HashMap<Token, Integer> mRemoteStreams = new HashMap<>(); private int mNextStream = DYNAMIC_STREAM_START_INDEX; - private final boolean mShowRemoteSessions; + private final boolean mVolumeAdjustmentForRemoteGroupSessions; public MediaSessionsCallbacks(Context context) { - mShowRemoteSessions = context.getResources().getBoolean( - com.android.internal.R.bool.config_volumeShowRemoteSessions); + mVolumeAdjustmentForRemoteGroupSessions = context.getResources().getBoolean( + com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions); } @Override public void onRemoteUpdate(Token token, String name, PlaybackInfo pi) { - if (mShowRemoteSessions) { + if (showForSession(token)) { addStream(token, "onRemoteUpdate"); int stream = 0; @@ -1191,7 +1198,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa @Override public void onRemoteVolumeChanged(Token token, int flags) { - if (mShowRemoteSessions) { + if (showForSession(token)) { addStream(token, "onRemoteVolumeChanged"); int stream = 0; synchronized (mRemoteStreams) { @@ -1215,7 +1222,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa @Override public void onRemoteRemoved(Token token) { - if (mShowRemoteSessions) { + if (showForSession(token)) { int stream = 0; synchronized (mRemoteStreams) { if (!mRemoteStreams.containsKey(token)) { @@ -1234,14 +1241,41 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa } public void setStreamVolume(int stream, int level) { - if (mShowRemoteSessions) { - final Token t = findToken(stream); - if (t == null) { - Log.w(TAG, "setStreamVolume: No token found for stream: " + stream); - return; + final Token token = findToken(stream); + if (token == null) { + Log.w(TAG, "setStreamVolume: No token found for stream: " + stream); + return; + } + if (showForSession(token)) { + mMediaSessions.setVolume(token, level); + } + } + + private boolean showForSession(Token token) { + if (mVolumeAdjustmentForRemoteGroupSessions) { + return true; + } + MediaController ctr = new MediaController(mContext, token); + String packageName = ctr.getPackageName(); + List<RoutingSessionInfo> sessions = + mRouter2Manager.getRoutingSessions(packageName); + boolean foundNonSystemSession = false; + boolean isGroup = false; + for (RoutingSessionInfo session : sessions) { + if (!session.isSystemSession()) { + foundNonSystemSession = true; + int selectedRouteCount = session.getSelectedRoutes().size(); + if (selectedRouteCount > 1) { + isGroup = true; + break; + } } - mMediaSessions.setVolume(t, level); } + if (!foundNonSystemSession) { + Log.d(TAG, "No routing session for " + packageName); + return false; + } + return !isGroup; } private Token findToken(int stream) { diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java index c378e3bd76c9..8007ed3b866a 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java @@ -21,8 +21,8 @@ import android.content.res.Configuration; import android.os.Handler; import android.util.Log; +import com.android.systemui.CoreStartable; import com.android.systemui.R; -import com.android.systemui.SystemUI; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.qs.tiles.DndTile; @@ -32,7 +32,7 @@ import java.io.PrintWriter; import javax.inject.Inject; @SysUISingleton -public class VolumeUI extends SystemUI { +public class VolumeUI extends CoreStartable { private static final String TAG = "VolumeUI"; private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG); diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index 74611cce6f87..66c70edbc3fc 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -40,8 +40,8 @@ import android.view.KeyEvent; import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; +import com.android.systemui.CoreStartable; import com.android.systemui.Dependency; -import com.android.systemui.SystemUI; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.WMComponent; import com.android.systemui.dagger.qualifiers.Main; @@ -92,7 +92,7 @@ import javax.inject.Inject; * -> WMShell starts and binds SysUI with Shell components via exported Shell interfaces */ @SysUISingleton -public final class WMShell extends SystemUI +public final class WMShell extends CoreStartable implements CommandQueue.Callbacks, ProtoTraceable<SystemUiTraceProto> { private static final String TAG = WMShell.class.getName(); private static final int INVALID_SYSUI_STATE_MASK = diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 1ee6f70ec7fc..ff5960bc33ce 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -472,7 +472,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */); mTestableLooper.processAllMessages(); - verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt()); + verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(), + anyInt()); verify(mFingerprintManager, never()).detectFingerprint(any(), any(), anyInt()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java index fe5633eb9df8..6ddfbb2f430f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java @@ -55,10 +55,9 @@ import android.graphics.Insets; import android.graphics.Rect; import android.os.Handler; import android.os.SystemClock; -import android.os.UserHandle; -import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.util.SparseIntArray; import android.view.Choreographer; import android.view.MotionEvent; import android.view.View; @@ -101,6 +100,7 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { private AccessibilityManager mAccessibilityManager; @Mock private SfVsyncFrameCallbackProvider mSfVsyncFrameProvider; + private SwitchListenerStub mSwitchListener; private TestableWindowManager mWindowManager; private ViewPropertyAnimator mViewPropertyAnimator; private MagnificationModeSwitch mMagnificationModeSwitch; @@ -112,6 +112,7 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); final WindowManager wm = mContext.getSystemService(WindowManager.class); + mSwitchListener = new SwitchListenerStub(); mWindowManager = spy(new TestableWindowManager(wm)); mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager); mContext.addMockSystemService(Context.ACCESSIBILITY_SERVICE, mAccessibilityManager); @@ -130,7 +131,7 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { }).when(mSfVsyncFrameProvider).postFrameCallback( any(Choreographer.FrameCallback.class)); mMagnificationModeSwitch = new MagnificationModeSwitch(mContext, mSpyImageView, - mSfVsyncFrameProvider); + mSfVsyncFrameProvider, mSwitchListener); assertNotNull(mTouchListener); } @@ -326,8 +327,6 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { public void performDragging_showMagnificationButton_updateViewLayout() { mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); resetAndStubMockImageViewAndAnimator(); - final int previousMode = Settings.Secure.getIntForUser(mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, 0, UserHandle.USER_CURRENT); // Perform dragging final int offset = ViewConfiguration.get(mContext).getScaledTouchSlop() + 10; @@ -345,7 +344,7 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { mTouchListener.onTouch(mSpyImageView, obtainMotionEvent( downTime, downTime, ACTION_UP, 100 + offset, 100)); - assertModeUnchanged(previousMode); + assertModeUnchanged(); assertShowFadingAnimation(FADE_OUT_ALPHA); } @@ -353,8 +352,6 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { public void performSingleTapActionCanceled_showButtonAnimation() { mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); resetAndStubMockImageViewAndAnimator(); - final int previousMode = Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, 0); final long downTime = SystemClock.uptimeMillis(); mTouchListener.onTouch(mSpyImageView, obtainMotionEvent( @@ -363,7 +360,7 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { mTouchListener.onTouch(mSpyImageView, obtainMotionEvent( downTime, downTime, ACTION_CANCEL, 100, 100)); - assertModeUnchanged(previousMode); + assertModeUnchanged(); assertShowFadingAnimation(FADE_OUT_ALPHA); } @@ -371,8 +368,6 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { public void performDraggingActionCanceled_showButtonAnimation() { mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); resetAndStubMockImageViewAndAnimator(); - final int previousMode = Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, 0); // Perform dragging final long downTime = SystemClock.uptimeMillis(); @@ -385,7 +380,7 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { mTouchListener.onTouch(mSpyImageView, obtainMotionEvent( downTime, downTime, ACTION_CANCEL, 100 + offset, 100)); - assertModeUnchanged(previousMode); + assertModeUnchanged(); assertShowFadingAnimation(FADE_OUT_ALPHA); } @@ -529,10 +524,9 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { assertEquals(expectedY, mWindowManager.getLayoutParamsFromAttachedView().y); } - private void assertModeUnchanged(int expectedMode) { - final int actualMode = Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, 0); - assertEquals(expectedMode, actualMode); + private void assertModeUnchanged() { + assertEquals(SwitchListenerStub.MODE_INVALID, + mSwitchListener.getChangedMode(mContext.getDisplayId())); } private void assertShowFadingAnimation(float alpha) { @@ -594,9 +588,8 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { verify(mSpyImageView).setImageResource( getIconResId(expectedMode)); verify(mWindowManager).removeView(mSpyImageView); - final int actualMode = Settings.Secure.getIntForUser(mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, 0, UserHandle.USER_CURRENT); - assertEquals(expectedMode, actualMode); + final int changedMode = mSwitchListener.getChangedMode(mContext.getDisplayId()); + assertEquals(expectedMode, changedMode); } private MotionEvent obtainMotionEvent(long downTime, long eventTime, int action, float x, @@ -621,4 +614,20 @@ public class MagnificationModeSwitchTest extends SysuiTestCase { assertEquals(expectedX, layoutParams.x); assertEquals(expectedY, layoutParams.y); } + + private static class SwitchListenerStub implements MagnificationModeSwitch.SwitchListener { + + private static final int MODE_INVALID = -1; + + private final SparseIntArray mModes = new SparseIntArray(); + + @Override + public void onSwitch(int displayId, int magnificationMode) { + mModes.put(displayId, magnificationMode); + } + + int getChangedMode(int displayId) { + return mModes.get(displayId, MODE_INVALID); + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java index 9fa5b87af3b4..216f63fce885 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java @@ -22,27 +22,32 @@ import android.content.pm.ActivityInfo; import android.hardware.display.DisplayManager; import android.provider.Settings; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.view.Display; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidTestingRunner.class) /** Tests the ModeSwitchesController. */ +@TestableLooper.RunWithLooper(setAsMainLooper = true) public class ModeSwitchesControllerTest extends SysuiTestCase { private FakeSwitchSupplier mSupplier; - @Mock private MagnificationModeSwitch mModeSwitch; private ModeSwitchesController mModeSwitchesController; + @Mock + private MagnificationModeSwitch.SwitchListener mListener; @Before @@ -50,6 +55,13 @@ public class ModeSwitchesControllerTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mSupplier = new FakeSwitchSupplier(mContext.getSystemService(DisplayManager.class)); mModeSwitchesController = new ModeSwitchesController(mSupplier); + mModeSwitchesController.setSwitchListenerDelegate(mListener); + mModeSwitch = Mockito.spy(new MagnificationModeSwitch(mContext, mModeSwitchesController)); + } + + @After + public void tearDown() { + mModeSwitchesController.removeButton(Display.DEFAULT_DISPLAY); } @Test @@ -79,6 +91,18 @@ public class ModeSwitchesControllerTest extends SysuiTestCase { verify(mModeSwitch).onConfigurationChanged(ActivityInfo.CONFIG_DENSITY); } + + @Test + public void testOnSwitchClick_showWindowModeButton_invokeListener() { + mModeSwitchesController.showButton(Display.DEFAULT_DISPLAY, + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); + + mModeSwitch.onSingleTap(); + + verify(mListener).onSwitch(mContext.getDisplayId(), + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); + } + private class FakeSwitchSupplier extends DisplayIdIndexSupplier<MagnificationModeSwitch> { FakeSwitchSupplier(DisplayManager displayManager) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java index 6ef7cc3f0af8..fb1716aed474 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java @@ -22,6 +22,8 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_M import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -99,17 +101,19 @@ public class WindowMagnificationTest extends SysuiTestCase { } @Test - public void requestWindowMagnificationConnection_setWindowMagnificationConnection() { + public void requestWindowMagnificationConnection_setConnectionAndListener() { mCommandQueue.requestWindowMagnificationConnection(true); waitForIdleSync(); verify(mAccessibilityManager).setWindowMagnificationConnection(any( IWindowMagnificationConnection.class)); + verify(mModeSwitchesController).setSwitchListenerDelegate(notNull()); mCommandQueue.requestWindowMagnificationConnection(false); waitForIdleSync(); - verify(mAccessibilityManager).setWindowMagnificationConnection(null); + verify(mAccessibilityManager).setWindowMagnificationConnection(isNull()); + verify(mModeSwitchesController).setSwitchListenerDelegate(isNull()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java index 2fa32ba1fe75..634763866d02 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagManagerTest.java @@ -53,8 +53,6 @@ import java.io.StringWriter; public class FeatureFlagManagerTest extends SysuiTestCase { FeatureFlagManager mFeatureFlagManager; - @Mock private FlagManager mFlagManager; - @Mock private SecureSettings mSecureSettings; @Mock private Context mContext; @Mock private DumpManager mDumpManager; @@ -62,14 +60,11 @@ public class FeatureFlagManagerTest extends SysuiTestCase { public void setup() { MockitoAnnotations.initMocks(this); - mFeatureFlagManager = new FeatureFlagManager(mSecureSettings, mContext, mDumpManager); + mFeatureFlagManager = new FeatureFlagManager(mDumpManager); } @After public void onFinished() { - // SecureSettings and Context are provided for constructor consistency with the - // debug version of the FeatureFlagManager, but should never be used. - verifyZeroInteractions(mSecureSettings, mContext); // The dump manager should be registered with even for the release version, but that's it. verify(mDumpManager).registerDumpable(anyString(), any()); verifyNoMoreInteractions(mDumpManager); diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java deleted file mode 100644 index 30e9b51ea47e..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsTest.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.flags; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.res.Resources; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.stubbing.Answer; - -@SmallTest -public class FeatureFlagsTest extends SysuiTestCase { - - @Mock Resources mResources; - @Mock FlagReader mFeatureFlagReader; - - private FeatureFlags mFeatureFlags; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - - when(mFeatureFlagReader.isEnabled(anyInt(), anyBoolean())).thenAnswer( - (Answer<Boolean>) invocation -> invocation.getArgument(1)); - - mFeatureFlags = new FeatureFlags(mResources, mFeatureFlagReader, getContext()); - } - - @Test - public void testAddListener() { - Flag<?> flag = new BooleanFlag(1); - mFeatureFlags.addFlag(flag); - - // Assert and capture that a plugin listener was added. - ArgumentCaptor<FlagReader.Listener> pluginListenerCaptor = - ArgumentCaptor.forClass(FlagReader.Listener.class); - verify(mFeatureFlagReader).addListener(pluginListenerCaptor.capture()); - FlagReader.Listener pluginListener = pluginListenerCaptor.getValue(); - - // Signal a change. No listeners, so no real effect. - pluginListener.onFlagChanged(flag.getId()); - - // Add a listener for the flag - final Flag<?>[] changedFlag = {null}; - FeatureFlags.Listener listener = f -> changedFlag[0] = f; - mFeatureFlags.addFlagListener(flag, listener); - - // No changes seen yet. - assertThat(changedFlag[0]).isNull(); - - // Signal a change. - pluginListener.onFlagChanged(flag.getId()); - - // Assert that the change was for the correct flag. - assertThat(changedFlag[0]).isEqualTo(flag); - } - - @Test - public void testRemoveListener() { - Flag<?> flag = new BooleanFlag(1); - mFeatureFlags.addFlag(flag); - - // Assert and capture that a plugin listener was added. - ArgumentCaptor<FlagReader.Listener> pluginListenerCaptor = - ArgumentCaptor.forClass(FlagReader.Listener.class); - verify(mFeatureFlagReader).addListener(pluginListenerCaptor.capture()); - FlagReader.Listener pluginListener = pluginListenerCaptor.getValue(); - - // Add a listener for the flag - final Flag<?>[] changedFlag = {null}; - FeatureFlags.Listener listener = f -> changedFlag[0] = f; - mFeatureFlags.addFlagListener(flag, listener); - - // Signal a change. - pluginListener.onFlagChanged(flag.getId()); - - // Assert that the change was for the correct flag. - assertThat(changedFlag[0]).isEqualTo(flag); - - changedFlag[0] = null; - - // Now remove the listener. - mFeatureFlags.removeFlagListener(flag, listener); - // Signal a change. - pluginListener.onFlagChanged(flag.getId()); - // Assert that the change was not triggered - assertThat(changedFlag[0]).isNull(); - } - - @Test - public void testBooleanDefault() { - BooleanFlag flag = new BooleanFlag(1, true); - - mFeatureFlags.addFlag(flag); - - assertThat(mFeatureFlags.isEnabled(flag)).isTrue(); - } - - @Test - public void testBooleanResourceOverlay() { - int resourceId = 12; - BooleanFlag flag = new BooleanFlag(1, false, resourceId); - when(mResources.getBoolean(resourceId)).thenReturn(true); - when(mResources.getResourceEntryName(resourceId)).thenReturn("flag"); - - mFeatureFlags.addFlag(flag); - - assertThat(mFeatureFlags.isEnabled(flag)).isTrue(); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java index 5e73dbcbc95d..d64319b278b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java @@ -212,6 +212,7 @@ public class LockIconViewControllerTest extends SysuiTestCase { @Test public void testUpdateFingerprintLocationOnAuthenticatorsRegistered() { // GIVEN fp sensor location is not available pre-init + when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false); when(mAuthController.getFingerprintSensorLocation()).thenReturn(null); when(mAuthController.getUdfpsProps()).thenReturn(null); mLockIconViewController.init(); @@ -232,7 +233,7 @@ public class LockIconViewControllerTest extends SysuiTestCase { } @Test - public void testLockIconViewBackgroundEnabledWhenUdfpsIsAvailable() { + public void testLockIconViewBackgroundEnabledWhenUdfpsIsSupported() { // GIVEN Udpfs sensor location is available setupUdfps(); @@ -247,9 +248,9 @@ public class LockIconViewControllerTest extends SysuiTestCase { } @Test - public void testLockIconViewBackgroundDisabledWhenUdfpsIsUnavailable() { - // GIVEN Udfps sensor location is not available - when(mAuthController.getUdfpsSensorLocation()).thenReturn(null); + public void testLockIconViewBackgroundDisabledWhenUdfpsIsNotSupported() { + // GIVEN Udfps sensor location is not supported + when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false); mLockIconViewController.init(); captureAttachListener(); @@ -365,6 +366,7 @@ public class LockIconViewControllerTest extends SysuiTestCase { } private Pair<Integer, PointF> setupUdfps() { + when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true); final PointF udfpsLocation = new PointF(50, 75); final int radius = 33; final FingerprintSensorPropertiesInternal fpProps = diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java index bc86ef98c6fe..8cd7d94d8952 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java @@ -22,6 +22,7 @@ import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; +import com.android.internal.graphics.cam.Cam; import com.android.systemui.SysuiTestCase; import org.junit.Assert; @@ -90,4 +91,13 @@ public class ColorSchemeTest extends SysuiTestCase { List<Integer> rankedSeedColors = ColorScheme.getSeedColors(wallpaperColors); Assert.assertEquals(rankedSeedColors, List.of(0xffaec00a, 0xffbe0000, 0xffcc040f)); } + + @Test + public void testTertiaryHueWrapsProperly() { + int colorInt = 0xffB3588A; // H350 C50 T50 + ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */); + int tertiaryMid = colorScheme.getAccent3().get(colorScheme.getAccent3().size() / 2); + Cam cam = Cam.fromInt(tertiaryMid); + Assert.assertEquals(cam.getHue(), 50.0, 10.0); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index 01f7fae05f76..cb0d87a20f89 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -701,7 +701,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { // GIVEN fingerprint is also running (not udfps) when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); - when(mKeyguardUpdateMonitor.isUdfpsAvailable()).thenReturn(false); + when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false); mController.setVisible(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java index cf58c63e3d26..acd62b2fabe7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java @@ -44,7 +44,7 @@ import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.inflation.LowPriorityInflationHelper; +import com.android.systemui.statusbar.notification.collection.legacy.LowPriorityInflationHelper; import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy; import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager; import com.android.systemui.statusbar.notification.logging.NotificationLogger; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java index fc94262465b1..b91f7e6b6169 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java @@ -30,6 +30,7 @@ import android.service.notification.StatusBarNotification; import com.android.internal.logging.InstanceId; import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.SbnBuilder; +import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection; import com.android.systemui.util.time.FakeSystemClock; import java.util.ArrayList; @@ -53,6 +54,7 @@ public class NotificationEntryBuilder { /* ListEntry properties */ private GroupEntry mParent; + private NotifSection mNotifSection; /* If set, use this creation time instead of mClock.uptimeMillis */ private long mCreationTime = -1; @@ -71,6 +73,11 @@ public class NotificationEntryBuilder { mCreationTime = source.getCreationTime(); } + /** Update an the parent on an existing entry */ + public static void setNewParent(NotificationEntry entry, GroupEntry parent) { + entry.setParent(parent); + } + /** Build a new instance of NotificationEntry */ public NotificationEntry build() { return buildOrApply(null); @@ -103,6 +110,7 @@ public class NotificationEntryBuilder { /* ListEntry properties */ entry.setParent(mParent); + entry.getAttachState().setSection(mNotifSection); entry.getAttachState().setStableIndex(mStableIndex); return entry; } @@ -116,6 +124,14 @@ public class NotificationEntryBuilder { } /** + * Sets the parent. + */ + public NotificationEntryBuilder setSection(@Nullable NotifSection section) { + mNotifSection = section; + return this; + } + + /** * Sets the SBN directly. If set, causes all calls to delegated SbnBuilder methods to be * ignored. */ diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java index 913ffd44437a..b254ed4e3f2e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java @@ -18,6 +18,8 @@ package com.android.systemui.statusbar.notification.collection; import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpTree; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -44,7 +46,7 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.ArrayMap; -import androidx.annotation.Nullable; +import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -79,10 +81,11 @@ import org.mockito.Spy; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; @SmallTest @@ -124,7 +127,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { mListBuilder.attach(mNotifCollection); - mStabilityManager = new TestableStabilityManager(); + mStabilityManager = spy(new TestableStabilityManager()); mListBuilder.setNotifStabilityManager(mStabilityManager); Mockito.verify(mNotifCollection).setBuildListener(mBuildListenerCaptor.capture()); @@ -618,26 +621,53 @@ public class ShadeListBuilderTest extends SysuiTestCase { @Test public void testNotifSectionsChildrenUpdated() { - AtomicBoolean validChildren = new AtomicBoolean(false); + ArrayList<ListEntry> pkg1Entries = new ArrayList<>(); + ArrayList<ListEntry> pkg2Entries = new ArrayList<>(); + ArrayList<ListEntry> pkg3Entries = new ArrayList<>(); final NotifSectioner pkg1Sectioner = spy(new PackageSectioner(PACKAGE_1) { - @Nullable @Override public void onEntriesUpdated(List<ListEntry> entries) { super.onEntriesUpdated(entries); - validChildren.set(entries.size() == 2); + pkg1Entries.addAll(entries); + } + }); + final NotifSectioner pkg2Sectioner = spy(new PackageSectioner(PACKAGE_2) { + @Override + public void onEntriesUpdated(List<ListEntry> entries) { + super.onEntriesUpdated(entries); + pkg2Entries.addAll(entries); + } + }); + final NotifSectioner pkg3Sectioner = spy(new PackageSectioner(PACKAGE_3) { + @Override + public void onEntriesUpdated(List<ListEntry> entries) { + super.onEntriesUpdated(entries); + pkg3Entries.addAll(entries); } }); - mListBuilder.setSectioners(asList(pkg1Sectioner)); + mListBuilder.setSectioners(asList(pkg1Sectioner, pkg2Sectioner, pkg3Sectioner)); - addNotif(0, PACKAGE_4); + addNotif(0, PACKAGE_1); addNotif(1, PACKAGE_1); - addNotif(2, PACKAGE_1); + addNotif(2, PACKAGE_3); addNotif(3, PACKAGE_3); + addNotif(4, PACKAGE_3); dispatchBuild(); - verify(pkg1Sectioner, times(1)).onEntriesUpdated(any()); - assertTrue(validChildren.get()); + verify(pkg1Sectioner).onEntriesUpdated(any()); + verify(pkg2Sectioner).onEntriesUpdated(any()); + verify(pkg3Sectioner).onEntriesUpdated(any()); + assertThat(pkg1Entries).containsExactly( + mEntrySet.get(0), + mEntrySet.get(1) + ).inOrder(); + assertThat(pkg2Entries).isEmpty(); + assertThat(pkg3Entries).containsExactly( + mEntrySet.get(2), + mEntrySet.get(3), + mEntrySet.get(4) + ).inOrder(); } @Test @@ -835,13 +865,13 @@ public class ShadeListBuilderTest extends SysuiTestCase { .onBeforeTransformGroups(anyList()); inOrder.verify(promoter, atLeastOnce()) .shouldPromoteToTopLevel(any(NotificationEntry.class)); - inOrder.verify(mOnBeforeFinalizeFilterListener).onBeforeFinalizeFilter(anyList()); - inOrder.verify(preRenderFilter, atLeastOnce()) - .shouldFilterOut(any(NotificationEntry.class), anyLong()); inOrder.verify(mOnBeforeSortListener).onBeforeSort(anyList()); inOrder.verify(section, atLeastOnce()).isInSection(any(ListEntry.class)); inOrder.verify(comparator, atLeastOnce()) .compare(any(ListEntry.class), any(ListEntry.class)); + inOrder.verify(mOnBeforeFinalizeFilterListener).onBeforeFinalizeFilter(anyList()); + inOrder.verify(preRenderFilter, atLeastOnce()) + .shouldFilterOut(any(NotificationEntry.class), anyLong()); inOrder.verify(mOnBeforeRenderListListener).onBeforeRenderList(anyList()); inOrder.verify(mOnRenderListListener).onRenderList(anyList()); } @@ -947,13 +977,11 @@ public class ShadeListBuilderTest extends SysuiTestCase { ); // THEN all the new notifs, including the new GroupEntry, are passed to the listener - assertEquals( - asList( - mEntrySet.get(0), - mBuiltList.get(1), - mEntrySet.get(4)), - listener.mEntriesReceived - ); + assertThat(listener.mEntriesReceived).containsExactly( + mEntrySet.get(0), + mBuiltList.get(1), + mEntrySet.get(4) + ).inOrder(); // Order is a bonus because this listener is before sort } @Test @@ -993,14 +1021,12 @@ public class ShadeListBuilderTest extends SysuiTestCase { ); // THEN all the new notifs, including the new GroupEntry, are passed to the listener - assertEquals( - asList( - mEntrySet.get(0), - mBuiltList.get(2), - mEntrySet.get(7), - mEntrySet.get(1)), - listener.mEntriesReceived - ); + assertThat(listener.mEntriesReceived).containsExactly( + mEntrySet.get(0), + mEntrySet.get(1), + mBuiltList.get(2), + mEntrySet.get(7) + ).inOrder(); // Order is a bonus because this listener is before sort } @Test @@ -1091,9 +1117,93 @@ public class ShadeListBuilderTest extends SysuiTestCase { } @Test + public void testFinalizeFilteringGroupSummaryDoesNotBreakSort() { + // GIVEN children from 3 packages, with one in the middle of the sort order being a group + addNotif(0, PACKAGE_1); + addNotif(1, PACKAGE_2); + addNotif(2, PACKAGE_3); + addNotif(3, PACKAGE_1); + addNotif(4, PACKAGE_2); + addNotif(5, PACKAGE_3); + addGroupSummary(6, PACKAGE_2, GROUP_1); + addGroupChild(7, PACKAGE_2, GROUP_1); + addGroupChild(8, PACKAGE_2, GROUP_1); + + // GIVEN that they should be sorted by package + mListBuilder.setComparators(asList( + new HypeComparator(PACKAGE_1), + new HypeComparator(PACKAGE_2), + new HypeComparator(PACKAGE_3) + )); + + // WHEN a finalize filter removes the summary + mListBuilder.addFinalizeFilter(new NotifFilter("Test") { + @Override + public boolean shouldFilterOut(@NonNull NotificationEntry entry, long now) { + return entry == notif(6).entry; + } + }); + + dispatchBuild(); + + // THEN the notifications remain ordered by package, even though the children were promoted + verifyBuiltList( + notif(0), + notif(3), + notif(1), + notif(4), + notif(7), // promoted child + notif(8), // promoted child + notif(2), + notif(5) + ); + } + + @Test + public void testFinalizeFilteringGroupChildDoesNotBreakSort() { + // GIVEN children from 3 packages, with one in the middle of the sort order being a group + addNotif(0, PACKAGE_1); + addNotif(1, PACKAGE_2); + addNotif(2, PACKAGE_3); + addNotif(3, PACKAGE_1); + addNotif(4, PACKAGE_2); + addNotif(5, PACKAGE_3); + addGroupSummary(6, PACKAGE_2, GROUP_1); + addGroupChild(7, PACKAGE_2, GROUP_1); + addGroupChild(8, PACKAGE_2, GROUP_1); + + // GIVEN that they should be sorted by package + mListBuilder.setComparators(asList( + new HypeComparator(PACKAGE_1), + new HypeComparator(PACKAGE_2), + new HypeComparator(PACKAGE_3) + )); + + // WHEN a finalize filter one of the 2 children from a group + mListBuilder.addFinalizeFilter(new NotifFilter("Test") { + @Override + public boolean shouldFilterOut(@NonNull NotificationEntry entry, long now) { + return entry == notif(7).entry; + } + }); + + dispatchBuild(); + + // THEN the notifications remain ordered by package, even though the children were promoted + verifyBuiltList( + notif(0), + notif(3), + notif(1), + notif(4), + notif(8), // promoted child + notif(2), + notif(5) + ); + } + + @Test public void testBrokenGroupNotificationOrdering() { // GIVEN two group children with different sections & without a summary yet - addGroupChild(0, PACKAGE_2, GROUP_1); addNotif(1, PACKAGE_1); addGroupChild(2, PACKAGE_2, GROUP_1); @@ -1224,13 +1334,11 @@ public class ShadeListBuilderTest extends SysuiTestCase { dispatchBuild(); // THEN all the new notifs are passed to the listener out of order - assertEquals( - asList( - mEntrySet.get(0), - mEntrySet.get(1), - mEntrySet.get(2)), - listener.mEntriesReceived - ); + assertThat(listener.mEntriesReceived).containsExactly( + mEntrySet.get(0), + mEntrySet.get(1), + mEntrySet.get(2) + ).inOrder(); // Checking out-of-order input to validate sorted output // THEN the final list is in order verifyBuiltList( @@ -1256,13 +1364,11 @@ public class ShadeListBuilderTest extends SysuiTestCase { dispatchBuild(); // THEN all the new notifs are passed to the listener - assertEquals( - asList( - mEntrySet.get(0), - mEntrySet.get(1), - mEntrySet.get(2)), - listener.mEntriesReceived - ); + assertThat(listener.mEntriesReceived).containsExactly( + mEntrySet.get(0), + mEntrySet.get(1), + mEntrySet.get(2) + ).inOrder(); } @Test @@ -1365,6 +1471,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { assertOrder("ABCDEFG", "ACDEFBG", "ABCDEFG"); // no change assertOrder("ABCDEFG", "ACDEFBXZG", "XZABCDEFG"); // Z and X assertOrder("ABCDEFG", "AXCDEZFBG", "XZABCDEFG"); // Z and X + gap + verify(mStabilityManager, times(4)).onEntryReorderSuppressed(); } @Test @@ -1373,6 +1480,7 @@ public class ShadeListBuilderTest extends SysuiTestCase { assertOrder("ABCDEFG", "ACDEFBG", "ACDEFBG"); // no change assertOrder("ABCDEFG", "ACDEFBXZG", "ACDEFBXZG"); // Z and X assertOrder("ABCDEFG", "AXCDEZFBG", "AXCDEZFBG"); // Z and X + gap + verify(mStabilityManager, never()).onEntryReorderSuppressed(); } @Test @@ -1410,6 +1518,26 @@ public class ShadeListBuilderTest extends SysuiTestCase { // THEN no exception thrown } + @Test + public void testIsSorted() { + Comparator<Integer> intCmp = Integer::compare; + assertTrue(ShadeListBuilder.isSorted(Collections.emptyList(), intCmp)); + assertTrue(ShadeListBuilder.isSorted(Collections.singletonList(1), intCmp)); + assertTrue(ShadeListBuilder.isSorted(Arrays.asList(1, 2), intCmp)); + assertTrue(ShadeListBuilder.isSorted(Arrays.asList(1, 2, 3), intCmp)); + assertTrue(ShadeListBuilder.isSorted(Arrays.asList(1, 2, 3, 4), intCmp)); + assertTrue(ShadeListBuilder.isSorted(Arrays.asList(1, 2, 3, 4, 5), intCmp)); + assertTrue(ShadeListBuilder.isSorted(Arrays.asList(1, 1, 1, 1, 1), intCmp)); + assertTrue(ShadeListBuilder.isSorted(Arrays.asList(1, 1, 2, 2, 3, 3), intCmp)); + + assertFalse(ShadeListBuilder.isSorted(Arrays.asList(2, 1), intCmp)); + assertFalse(ShadeListBuilder.isSorted(Arrays.asList(2, 1, 2), intCmp)); + assertFalse(ShadeListBuilder.isSorted(Arrays.asList(1, 2, 1), intCmp)); + assertFalse(ShadeListBuilder.isSorted(Arrays.asList(1, 2, 3, 2, 5), intCmp)); + assertFalse(ShadeListBuilder.isSorted(Arrays.asList(5, 2, 3, 4, 5), intCmp)); + assertFalse(ShadeListBuilder.isSorted(Arrays.asList(1, 2, 3, 4, 1), intCmp)); + } + /** * Adds a notif to the collection that will be passed to the list builder when * {@link #dispatchBuild()}s is called. @@ -1815,6 +1943,15 @@ public class ShadeListBuilderTest extends SysuiTestCase { public boolean isEntryReorderingAllowed(ListEntry entry) { return mAllowEntryReodering; } + + @Override + public boolean isEveryChangeAllowed() { + return mAllowEntryReodering && mAllowGroupChanges && mAllowSectionChanges; + } + + @Override + public void onEntryReorderSuppressed() { + } } private static final String PACKAGE_1 = "com.test1"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java index bec5174aceba..c3e10aa3178f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import static java.util.Objects.requireNonNull; @@ -33,10 +34,12 @@ import android.os.RemoteException; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder; import com.android.systemui.statusbar.notification.collection.ListEntry; @@ -44,8 +47,11 @@ import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater; +import com.android.systemui.statusbar.notification.collection.inflation.NotifUiAdjustmentProvider; +import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection; import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.render.NotifViewBarn; import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager; @@ -60,6 +66,7 @@ import org.mockito.MockitoAnnotations; import org.mockito.Spy; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -79,24 +86,36 @@ public class PreparationCoordinatorTest extends SysuiTestCase { @Captor private ArgumentCaptor<NotifCollectionListener> mCollectionListenerCaptor; @Captor private ArgumentCaptor<OnBeforeFinalizeFilterListener> mBeforeFilterListenerCaptor; @Captor private ArgumentCaptor<NotifInflater.InflationCallback> mCallbackCaptor; + @Captor private ArgumentCaptor<NotifInflater.Params> mParamsCaptor; + @Mock private NotifSectioner mNotifSectioner; + @Mock private NotifSection mNotifSection; @Mock private NotifPipeline mNotifPipeline; @Mock private IStatusBarService mService; @Spy private FakeNotifInflater mNotifInflater = new FakeNotifInflater(); + private final TestableAdjustmentProvider mAdjustmentProvider = new TestableAdjustmentProvider(); + + @NonNull + private NotificationEntryBuilder getNotificationEntryBuilder() { + return new NotificationEntryBuilder().setSection(mNotifSection); + } @Before public void setUp() { MockitoAnnotations.initMocks(this); - mEntry = new NotificationEntryBuilder().setParent(ROOT_ENTRY).build(); + mEntry = getNotificationEntryBuilder().setParent(ROOT_ENTRY).build(); mInflationError = new Exception(TEST_MESSAGE); mErrorManager = new NotifInflationErrorManager(); + when(mNotifSection.getSectioner()).thenReturn(mNotifSectioner); + mAdjustmentProvider.setSectionIsLowPriority(false); PreparationCoordinator coordinator = new PreparationCoordinator( mock(PreparationCoordinatorLogger.class), mNotifInflater, mErrorManager, mock(NotifViewBarn.class), + mAdjustmentProvider, mService, TEST_CHILD_BIND_CUTOFF, TEST_MAX_GROUP_DELAY); @@ -150,7 +169,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase { mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry)); // THEN we inflate it - verify(mNotifInflater).inflateViews(eq(mEntry), any()); + verify(mNotifInflater).inflateViews(eq(mEntry), any(), any()); // THEN we filter it out until it's done inflating. assertTrue(mUninflatedFilter.shouldFilterOut(mEntry, 0)); @@ -161,7 +180,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase { // GIVEN an inflated notification mCollectionListener.onEntryAdded(mEntry); mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry)); - verify(mNotifInflater).inflateViews(eq(mEntry), mCallbackCaptor.capture()); + verify(mNotifInflater).inflateViews(eq(mEntry), any(), mCallbackCaptor.capture()); mCallbackCaptor.getValue().onInflationFinished(mEntry); // WHEN notification is updated @@ -169,7 +188,90 @@ public class PreparationCoordinatorTest extends SysuiTestCase { mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry)); // THEN we rebind it - verify(mNotifInflater).rebindViews(eq(mEntry), any()); + verify(mNotifInflater).rebindViews(eq(mEntry), any(), any()); + + // THEN we do not filter it because it's not the first inflation. + assertFalse(mUninflatedFilter.shouldFilterOut(mEntry, 0)); + } + + @Test + public void testEntrySmartReplyAdditionWillRebindViews() { + // GIVEN an inflated notification + mCollectionListener.onEntryAdded(mEntry); + mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry)); + verify(mNotifInflater).inflateViews(eq(mEntry), any(), mCallbackCaptor.capture()); + mCallbackCaptor.getValue().onInflationFinished(mEntry); + + // WHEN notification ranking now has smart replies + mEntry.setRanking(new RankingBuilder(mEntry.getRanking()).setSmartReplies("yes").build()); + mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry)); + + // THEN we rebind it + verify(mNotifInflater).rebindViews(eq(mEntry), any(), any()); + + // THEN we do not filter it because it's not the first inflation. + assertFalse(mUninflatedFilter.shouldFilterOut(mEntry, 0)); + } + + @Test + public void testEntryChangedToMinimizedSectionWillRebindViews() { + // GIVEN an inflated notification + mCollectionListener.onEntryAdded(mEntry); + mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry)); + verify(mNotifInflater).inflateViews(eq(mEntry), + mParamsCaptor.capture(), mCallbackCaptor.capture()); + assertFalse(mParamsCaptor.getValue().isLowPriority()); + mCallbackCaptor.getValue().onInflationFinished(mEntry); + + // WHEN notification moves to a min priority section + mAdjustmentProvider.setSectionIsLowPriority(true); + mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry)); + + // THEN we rebind it + verify(mNotifInflater).rebindViews(eq(mEntry), mParamsCaptor.capture(), any()); + assertTrue(mParamsCaptor.getValue().isLowPriority()); + + // THEN we do not filter it because it's not the first inflation. + assertFalse(mUninflatedFilter.shouldFilterOut(mEntry, 0)); + } + + @Test + public void testMinimizedEntryMovedIntoGroupWillRebindViews() { + // GIVEN an inflated, minimized notification + mAdjustmentProvider.setSectionIsLowPriority(true); + mCollectionListener.onEntryAdded(mEntry); + mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry)); + verify(mNotifInflater).inflateViews(eq(mEntry), + mParamsCaptor.capture(), mCallbackCaptor.capture()); + assertTrue(mParamsCaptor.getValue().isLowPriority()); + mCallbackCaptor.getValue().onInflationFinished(mEntry); + + // WHEN notification is moved under a parent + NotificationEntryBuilder.setNewParent(mEntry, mock(GroupEntry.class)); + mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry)); + + // THEN we rebind it as not-minimized + verify(mNotifInflater).rebindViews(eq(mEntry), mParamsCaptor.capture(), any()); + assertFalse(mParamsCaptor.getValue().isLowPriority()); + + // THEN we do not filter it because it's not the first inflation. + assertFalse(mUninflatedFilter.shouldFilterOut(mEntry, 0)); + } + + @Test + public void testEntryRankChangeWillNotRebindViews() { + // GIVEN an inflated notification + mCollectionListener.onEntryAdded(mEntry); + mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry)); + verify(mNotifInflater).inflateViews(eq(mEntry), any(), mCallbackCaptor.capture()); + mCallbackCaptor.getValue().onInflationFinished(mEntry); + + // WHEN notification ranking changes rank, which does not affect views + mEntry.setRanking(new RankingBuilder(mEntry.getRanking()).setRank(100).build()); + mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry)); + + // THEN we do not rebind it + verify(mNotifInflater, never()).rebindViews(eq(mEntry), any(), any()); // THEN we do not filter it because it's not the first inflation. assertFalse(mUninflatedFilter.shouldFilterOut(mEntry, 0)); @@ -180,7 +282,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase { // GIVEN an inflated notification mCollectionListener.onEntryAdded(mEntry); mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry)); - verify(mNotifInflater).inflateViews(eq(mEntry), mCallbackCaptor.capture()); + verify(mNotifInflater).inflateViews(eq(mEntry), any(), mCallbackCaptor.capture()); mCallbackCaptor.getValue().onInflationFinished(mEntry); // THEN it isn't filtered from shade list @@ -191,13 +293,13 @@ public class PreparationCoordinatorTest extends SysuiTestCase { public void testCutoffGroupChildrenNotInflated() { // WHEN there is a new notification group is posted int id = 0; - NotificationEntry summary = new NotificationEntryBuilder() + NotificationEntry summary = getNotificationEntryBuilder() .setOverrideGroupKey(TEST_GROUP_KEY) .setId(id++) .build(); List<NotificationEntry> children = new ArrayList<>(); for (int i = 0; i < TEST_CHILD_BIND_CUTOFF + 1; i++) { - NotificationEntry child = new NotificationEntryBuilder() + NotificationEntry child = getNotificationEntryBuilder() .setOverrideGroupKey(TEST_GROUP_KEY) .setId(id++) .build(); @@ -224,9 +326,9 @@ public class PreparationCoordinatorTest extends SysuiTestCase { // THEN we inflate up to the cut-off only for (int i = 0; i < children.size(); i++) { if (i < TEST_CHILD_BIND_CUTOFF) { - verify(mNotifInflater).inflateViews(eq(children.get(i)), any()); + verify(mNotifInflater).inflateViews(eq(children.get(i)), any(), any()); } else { - verify(mNotifInflater, never()).inflateViews(eq(children.get(i)), any()); + verify(mNotifInflater, never()).inflateViews(eq(children.get(i)), any(), any()); } } } @@ -236,9 +338,9 @@ public class PreparationCoordinatorTest extends SysuiTestCase { // GIVEN a newly-posted group with a summary and two children final GroupEntry group = new GroupEntryBuilder() .setCreationTime(400) - .setSummary(new NotificationEntryBuilder().setId(1).build()) - .addChild(new NotificationEntryBuilder().setId(2).build()) - .addChild(new NotificationEntryBuilder().setId(3).build()) + .setSummary(getNotificationEntryBuilder().setId(1).build()) + .addChild(getNotificationEntryBuilder().setId(2).build()) + .addChild(getNotificationEntryBuilder().setId(3).build()) .build(); fireAddEvents(List.of(group)); final NotificationEntry child0 = group.getChildren().get(0); @@ -256,9 +358,9 @@ public class PreparationCoordinatorTest extends SysuiTestCase { // GIVEN a newly-posted group with a summary and two children final GroupEntry group = new GroupEntryBuilder() .setCreationTime(400) - .setSummary(new NotificationEntryBuilder().setId(1).build()) - .addChild(new NotificationEntryBuilder().setId(2).build()) - .addChild(new NotificationEntryBuilder().setId(3).build()) + .setSummary(getNotificationEntryBuilder().setId(1).build()) + .addChild(getNotificationEntryBuilder().setId(2).build()) + .addChild(getNotificationEntryBuilder().setId(3).build()) .build(); fireAddEvents(List.of(group)); final NotificationEntry summary = group.getSummary(); @@ -281,9 +383,9 @@ public class PreparationCoordinatorTest extends SysuiTestCase { // GIVEN a newly-posted group with a summary and two children final GroupEntry group = new GroupEntryBuilder() .setCreationTime(400) - .setSummary(new NotificationEntryBuilder().setId(1).build()) - .addChild(new NotificationEntryBuilder().setId(2).build()) - .addChild(new NotificationEntryBuilder().setId(3).build()) + .setSummary(getNotificationEntryBuilder().setId(1).build()) + .addChild(getNotificationEntryBuilder().setId(2).build()) + .addChild(getNotificationEntryBuilder().setId(3).build()) .build(); fireAddEvents(List.of(group)); final NotificationEntry summary = group.getSummary(); @@ -307,9 +409,9 @@ public class PreparationCoordinatorTest extends SysuiTestCase { // GIVEN a newly-posted group with a summary and two children final GroupEntry group = new GroupEntryBuilder() .setCreationTime(400) - .setSummary(new NotificationEntryBuilder().setId(1).build()) - .addChild(new NotificationEntryBuilder().setId(2).build()) - .addChild(new NotificationEntryBuilder().setId(3).build()) + .setSummary(getNotificationEntryBuilder().setId(1).build()) + .addChild(getNotificationEntryBuilder().setId(2).build()) + .addChild(getNotificationEntryBuilder().setId(3).build()) .build(); fireAddEvents(List.of(group)); final NotificationEntry child0 = group.getChildren().get(0); @@ -324,19 +426,21 @@ public class PreparationCoordinatorTest extends SysuiTestCase { } private static class FakeNotifInflater implements NotifInflater { - private Map<NotificationEntry, InflationCallback> mInflateCallbacks = new HashMap<>(); + private final Map<NotificationEntry, InflationCallback> mInflateCallbacks = new HashMap<>(); @Override - public void inflateViews(NotificationEntry entry, InflationCallback callback) { + public void inflateViews(@NonNull NotificationEntry entry, @NonNull Params params, + @NonNull InflationCallback callback) { mInflateCallbacks.put(entry, callback); } @Override - public void rebindViews(NotificationEntry entry, InflationCallback callback) { + public void rebindViews(@NonNull NotificationEntry entry, @NonNull Params params, + @NonNull InflationCallback callback) { } @Override - public void abortInflation(NotificationEntry entry) { + public void abortInflation(@NonNull NotificationEntry entry) { } public InflationCallback getInflateCallback(NotificationEntry entry) { @@ -365,4 +469,12 @@ public class PreparationCoordinatorTest extends SysuiTestCase { private static final String TEST_GROUP_KEY = "TEST_GROUP_KEY"; private static final int TEST_CHILD_BIND_CUTOFF = 9; private static final int TEST_MAX_GROUP_DELAY = 100; + + private class TestableAdjustmentProvider extends NotifUiAdjustmentProvider { + private void setSectionIsLowPriority(boolean lowPriority) { + setLowPrioritySections(lowPriority + ? Collections.singleton(mNotifSection.getSectioner()) + : Collections.emptyList()); + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java index 2091cf8cb028..abe33aae7fc6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; @@ -28,8 +29,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Notification; -import android.service.notification.NotificationListenerService; -import android.service.notification.StatusBarNotification; +import android.app.NotificationManager; import android.testing.AndroidTestingRunner; import androidx.annotation.Nullable; @@ -38,10 +38,12 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.RankingBuilder; +import com.android.systemui.statusbar.SbnBuilder; import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.statusbar.notification.collection.inflation.NotifUiAdjustmentProvider; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; @@ -54,7 +56,6 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.ArrayList; @@ -66,12 +67,11 @@ public class RankingCoordinatorTest extends SysuiTestCase { @Mock private StatusBarStateController mStatusBarStateController; @Mock private HighPriorityProvider mHighPriorityProvider; + @Mock private NotifUiAdjustmentProvider mAdjustmentProvider; @Mock private NotifPipeline mNotifPipeline; @Mock private NodeController mAlertingHeaderController; @Mock private NodeController mSilentNodeController; @Mock private SectionHeaderController mSilentHeaderController; - @Mock private NotificationListenerService.Ranking mRanking; - @Mock private StatusBarNotification mSbn; @Captor private ArgumentCaptor<NotifFilter> mNotifFilterCaptor; @@ -89,12 +89,14 @@ public class RankingCoordinatorTest extends SysuiTestCase { public void setup() { MockitoAnnotations.initMocks(this); mRankingCoordinator = new RankingCoordinator( - mStatusBarStateController, mHighPriorityProvider, mAlertingHeaderController, - mSilentHeaderController, mSilentNodeController); + mStatusBarStateController, + mHighPriorityProvider, + mAdjustmentProvider, + mAlertingHeaderController, + mSilentHeaderController, + mSilentNodeController); mEntry = spy(new NotificationEntryBuilder().build()); - mRanking = spy(getRankingForUnfilteredNotif().build()); - mEntry.setRanking(mRanking); - when(mEntry.getSbn()).thenReturn(mSbn); + mEntry.setRanking(getRankingForUnfilteredNotif().build()); mRankingCoordinator.attach(mNotifPipeline); verify(mNotifPipeline, times(2)).addPreGroupFilter(mNotifFilterCaptor.capture()); @@ -109,23 +111,19 @@ public class RankingCoordinatorTest extends SysuiTestCase { @Test public void testSilentHeaderClearableChildrenUpdate() { - StatusBarNotification sbn = Mockito.mock(StatusBarNotification.class); - Mockito.doReturn("key").when(sbn).getKey(); - Mockito.doReturn(Mockito.mock(Notification.class)).when(sbn).getNotification(); - NotificationEntry entry = new NotificationEntryBuilder().setSbn(sbn).build(); - ListEntry listEntry = new ListEntry("key", 0L) { + ListEntry listEntry = new ListEntry(mEntry.getKey(), 0L) { @Nullable @Override public NotificationEntry getRepresentativeEntry() { - return entry; + return mEntry; } }; - Mockito.doReturn(true).when(sbn).isClearable(); + setRankingAmbient(false); + setSbnClearable(true); mSilentSectioner.onEntriesUpdated(Arrays.asList(listEntry)); - when(mRanking.isAmbient()).thenReturn(false); verify(mSilentHeaderController).setClearSectionEnabled(eq(true)); - mRankingCoordinator.resetClearAllFlags(); - Mockito.doReturn(false).when(sbn).isClearable(); + + setSbnClearable(false); mSilentSectioner.onEntriesUpdated(Arrays.asList(listEntry)); verify(mSilentHeaderController).setClearSectionEnabled(eq(false)); } @@ -204,7 +202,7 @@ public class RankingCoordinatorTest extends SysuiTestCase { public void testIncludeInSectionSilent() { // GIVEN the entry isn't high priority when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false); - when(mRanking.isAmbient()).thenReturn(false); + setRankingAmbient(false); // THEN entry is in the silent section assertFalse(mAlertingSectioner.isInSection(mEntry)); @@ -213,24 +211,23 @@ public class RankingCoordinatorTest extends SysuiTestCase { @Test public void testMinSection() { - when(mEntry.getRanking()).thenReturn(mRanking); when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false); - when(mRanking.isAmbient()).thenReturn(true); + setRankingAmbient(true); assertInSection(mEntry, mMinimizedSectioner); } @Test public void testSilentSection() { when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false); - when(mRanking.isAmbient()).thenReturn(false); + setRankingAmbient(false); assertInSection(mEntry, mSilentSectioner); } @Test public void testClearableSilentSection() { when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false); - when(mSbn.isClearable()).thenReturn(true); - when(mRanking.isAmbient()).thenReturn(false); + setSbnClearable(true); + setRankingAmbient(false); mSilentSectioner.onEntriesUpdated(Arrays.asList(mEntry)); verify(mSilentHeaderController).setClearSectionEnabled(eq(true)); } @@ -238,17 +235,17 @@ public class RankingCoordinatorTest extends SysuiTestCase { @Test public void testClearableMinimizedSection() { when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false); - when(mSbn.isClearable()).thenReturn(true); - when(mRanking.isAmbient()).thenReturn(true); + setSbnClearable(true); + setRankingAmbient(true); mMinimizedSectioner.onEntriesUpdated(Arrays.asList(mEntry)); verify(mSilentHeaderController).setClearSectionEnabled(eq(true)); } @Test public void testNotClearableSilentSection() { - when(mSbn.isClearable()).thenReturn(false); + setSbnClearable(false); when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false); - when(mRanking.isAmbient()).thenReturn(false); + setRankingAmbient(false); mSilentSectioner.onEntriesUpdated(Arrays.asList(mEntry)); mMinimizedSectioner.onEntriesUpdated(Arrays.asList(mEntry)); mAlertingSectioner.onEntriesUpdated(Arrays.asList(mEntry)); @@ -257,9 +254,9 @@ public class RankingCoordinatorTest extends SysuiTestCase { @Test public void testNotClearableMinimizedSection() { - when(mSbn.isClearable()).thenReturn(false); + setSbnClearable(false); when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false); - when(mRanking.isAmbient()).thenReturn(true); + setRankingAmbient(true); mSilentSectioner.onEntriesUpdated(Arrays.asList(mEntry)); mMinimizedSectioner.onEntriesUpdated(Arrays.asList(mEntry)); mAlertingSectioner.onEntriesUpdated(Arrays.asList(mEntry)); @@ -277,9 +274,24 @@ public class RankingCoordinatorTest extends SysuiTestCase { } private RankingBuilder getRankingForUnfilteredNotif() { - return new RankingBuilder() - .setKey(mEntry.getKey()) + return new RankingBuilder(mEntry.getRanking()) .setSuppressedVisualEffects(0) .setSuspended(false); } + + private void setSbnClearable(boolean clearable) { + mEntry.setSbn(new SbnBuilder(mEntry.getSbn()) + .setFlag(mContext, Notification.FLAG_NO_CLEAR, !clearable) + .build()); + assertEquals(clearable, mEntry.getSbn().isClearable()); + } + + private void setRankingAmbient(boolean ambient) { + mEntry.setRanking(new RankingBuilder(mEntry.getRanking()) + .setImportance(ambient + ? NotificationManager.IMPORTANCE_MIN + : NotificationManager.IMPORTANCE_DEFAULT) + .build()); + assertEquals(ambient, mEntry.getRanking().isAmbient()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java index 4edca7dd43d9..5df1d28073fc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java @@ -19,8 +19,10 @@ package com.android.systemui.statusbar.notification.collection.coordinator; import static junit.framework.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -30,6 +32,7 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.notification.collection.NotifPipeline; @@ -37,7 +40,6 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable; -import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; @@ -57,9 +59,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { private VisualStabilityCoordinator mCoordinator; - // captured listeners and pluggables: - private NotifCollectionListener mCollectionListener; - + @Mock private DumpManager mDumpManager; @Mock private NotifPipeline mNotifPipeline; @Mock private WakefulnessLifecycle mWakefulnessLifecycle; @Mock private StatusBarStateController mStatusBarStateController; @@ -69,7 +69,6 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { @Captor private ArgumentCaptor<WakefulnessLifecycle.Observer> mWakefulnessObserverCaptor; @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mSBStateListenerCaptor; @Captor private ArgumentCaptor<NotifStabilityManager> mNotifStabilityManagerCaptor; - @Captor private ArgumentCaptor<NotifCollectionListener> mNotifCollectionListenerCaptor; private FakeSystemClock mFakeSystemClock = new FakeSystemClock(); private FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock); @@ -84,6 +83,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mCoordinator = new VisualStabilityCoordinator( + mDumpManager, mHeadsUpManager, mWakefulnessLifecycle, mStatusBarStateController, @@ -107,6 +107,12 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { .build(); when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(false); + + // Whenever we invalidate, the pipeline runs again, so we invalidate the state + doAnswer(i -> { + mNotifStabilityManager.onBeginRun(); + return null; + }).when(mInvalidateListener).onPluggableInvalidated(eq(mNotifStabilityManager)); } @Test @@ -211,7 +217,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { mCoordinator.temporarilyAllowSectionChanges(mEntry, mFakeSystemClock.uptimeMillis()); // THEN the notification list is invalidated - verifyInvalidateCalled(true); + verify(mInvalidateListener, times(1)).onPluggableInvalidated(mNotifStabilityManager); } @Test @@ -225,7 +231,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { mCoordinator.temporarilyAllowSectionChanges(mEntry, mFakeSystemClock.currentTimeMillis()); // THEN invalidate is not called because this entry was never suppressed from reordering - verifyInvalidateCalled(false); + verify(mInvalidateListener, never()).onPluggableInvalidated(mNotifStabilityManager); } @Test @@ -241,7 +247,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { // THEN invalidate is not called because this entry was never suppressed from reordering; // THEN section changes are allowed for this notification - verifyInvalidateCalled(false); + verify(mInvalidateListener, never()).onPluggableInvalidated(mNotifStabilityManager); assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry)); // WHEN we're pulsing (now disallowing reordering) @@ -268,13 +274,14 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { // WHEN we temporarily allow section changes for this notification entry mCoordinator.temporarilyAllowSectionChanges(mEntry, mFakeSystemClock.currentTimeMillis()); - verifyInvalidateCalled(true); // can now reorder, so invalidates + // can now reorder, so invalidates + verify(mInvalidateListener, times(1)).onPluggableInvalidated(mNotifStabilityManager); // WHEN reordering is now allowed because device isn't pulsing anymore setPulsing(false); - // THEN invalidate isn't called since reordering was already allowed - verifyInvalidateCalled(false); + // THEN invalidate isn't called a second time since reordering was already allowed + verify(mInvalidateListener, times(1)).onPluggableInvalidated(mNotifStabilityManager); } @Test @@ -292,7 +299,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { // THEN we never see any calls to invalidate since there weren't any notifications that // were being suppressed from grouping or section changes - verifyInvalidateCalled(false); + verify(mInvalidateListener, never()).onPluggableInvalidated(mNotifStabilityManager); } @Test @@ -308,7 +315,41 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { setPanelExpanded(false); // invalidate is called because we were previously suppressing a group change - verifyInvalidateCalled(true); + verify(mInvalidateListener, times(1)).onPluggableInvalidated(mNotifStabilityManager); + } + + @Test + public void testNotSuppressingEntryReorderingAnymoreWillInvalidate() { + // GIVEN visual stability is being maintained b/c panel is expanded + setPulsing(false); + setScreenOn(true); + setPanelExpanded(true); + + assertFalse(mNotifStabilityManager.isEntryReorderingAllowed(mEntry)); + // The pipeline still has to report back that entry reordering was suppressed + mNotifStabilityManager.onEntryReorderSuppressed(); + + // WHEN the panel isn't expanded anymore + setPanelExpanded(false); + + // invalidate is called because we were previously suppressing an entry reorder + verify(mInvalidateListener, times(1)).onPluggableInvalidated(mNotifStabilityManager); + } + + @Test + public void testQueryingEntryReorderingButNotReportingReorderSuppressedDoesNotInvalidate() { + // GIVEN visual stability is being maintained b/c panel is expanded + setPulsing(false); + setScreenOn(true); + setPanelExpanded(true); + + assertFalse(mNotifStabilityManager.isEntryReorderingAllowed(mEntry)); + + // WHEN the panel isn't expanded anymore + setPanelExpanded(false); + + // invalidate is not called because we were not told that an entry reorder was suppressed + verify(mInvalidateListener, never()).onPluggableInvalidated(mNotifStabilityManager); } @Test @@ -345,13 +386,4 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { mStatusBarStateListener.onExpandedChanged(expanded); } - private void verifyInvalidateCalled(boolean invalidateCalled) { - if (invalidateCalled) { - verify(mInvalidateListener).onPluggableInvalidated(mNotifStabilityManager); - } else { - verify(mInvalidateListener, never()).onPluggableInvalidated(mNotifStabilityManager); - } - - reset(mInvalidateListener); - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java index ed42ac3efe80..0d996025392f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java @@ -70,8 +70,8 @@ import com.android.systemui.statusbar.notification.NotificationFilter; import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationRankingManager; -import com.android.systemui.statusbar.notification.collection.inflation.LowPriorityInflationHelper; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl; +import com.android.systemui.statusbar.notification.collection.legacy.LowPriorityInflationHelper; import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.notification.icon.IconBuilder; @@ -283,6 +283,7 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase { mRowBinder = new NotificationRowBinderImpl( mContext, + mFeatureFlags, new NotificationMessagingUtil(mContext), mRemoteInputManager, mLockscreenUserManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java index 5c0efd36fcd1..c9462d651bc0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java @@ -101,6 +101,11 @@ public class VolumeDialogControllerImplTest extends SysuiTestCase { // Initial non-set value when(mRingerModeLiveData.getValue()).thenReturn(-1); when(mRingerModeInternalLiveData.getValue()).thenReturn(-1); + // Enable group volume adjustments + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions, + true); + mCallback = mock(VolumeDialogControllerImpl.C.class); mThreadFactory.setLooper(TestableLooper.get(this).getLooper()); mVolumeController = new TestableVolumeDialogControllerImpl(mContext, diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 214769bbd9c9..0e3932799123 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -272,6 +272,27 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return getUserStateLocked(mCurrentUserId); } + /** + * Changes the magnification mode on the given display. + * + * @param displayId the logical display + * @param magnificationMode the target magnification mode + */ + public void changeMagnificationMode(int displayId, int magnificationMode) { + synchronized (mLock) { + if (displayId == Display.DEFAULT_DISPLAY) { + persistMagnificationModeSettingsLocked(magnificationMode); + } else { + final AccessibilityUserState userState = getCurrentUserStateLocked(); + final int currentMode = userState.getMagnificationModeLocked(displayId); + if (magnificationMode != currentMode) { + userState.setMagnificationModeLocked(displayId, magnificationMode); + updateMagnificationModeChangeSettingsLocked(userState, displayId); + } + } + } + } + public static final class Lifecycle extends SystemService { private final AccessibilityManagerService mService; @@ -299,7 +320,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub SystemActionPerformer systemActionPerformer, AccessibilityWindowManager a11yWindowManager, AccessibilityDisplayListener a11yDisplayListener, - MagnificationController magnificationController) { + MagnificationController magnificationController, + @Nullable AccessibilityInputFilter inputFilter) { mContext = context; mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mWindowManagerService = LocalServices.getService(WindowManagerInternal.class); @@ -314,6 +336,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mA11yDisplayListener = a11yDisplayListener; mMagnificationController = magnificationController; mMagnificationProcessor = new MagnificationProcessor(mMagnificationController); + if (inputFilter != null) { + mInputFilter = inputFilter; + mHasInputFilter = true; + } init(); } @@ -1794,47 +1820,51 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return relevantEventTypes; } - private void updateMagnificationModeChangeSettingsLocked(AccessibilityUserState userState) { + private void updateMagnificationModeChangeSettingsLocked(AccessibilityUserState userState, + int displayId) { if (userState.mUserId != mCurrentUserId) { return; } // New mode is invalid, so ignore and restore it. - if (fallBackMagnificationModeSettingsLocked(userState)) { + if (fallBackMagnificationModeSettingsLocked(userState, displayId)) { return; } mMagnificationController.transitionMagnificationModeLocked( - Display.DEFAULT_DISPLAY, userState.getMagnificationModeLocked(), + displayId, userState.getMagnificationModeLocked(displayId), this::onMagnificationTransitionEndedLocked); } /** - * Called when the magnification mode transition is completed. + * Called when the magnification mode transition is completed. If the given display is default + * display, we also need to fall back the mode in user settings. */ - void onMagnificationTransitionEndedLocked(boolean success) { + void onMagnificationTransitionEndedLocked(int displayId, boolean success) { final AccessibilityUserState userState = getCurrentUserStateLocked(); - final int previousMode = userState.getMagnificationModeLocked() + final int previousMode = userState.getMagnificationModeLocked(displayId) ^ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL; if (!success && previousMode != 0) { - userState.setMagnificationModeLocked(previousMode); - persistMagnificationModeSettingLocked(previousMode); + userState.setMagnificationModeLocked(displayId, previousMode); + if (displayId == Display.DEFAULT_DISPLAY) { + persistMagnificationModeSettingsLocked(previousMode); + } } else { mMainHandler.sendMessage(obtainMessage( AccessibilityManagerService::notifyRefreshMagnificationModeToInputFilter, - this)); + this, displayId)); } } - private void notifyRefreshMagnificationModeToInputFilter() { + private void notifyRefreshMagnificationModeToInputFilter(int displayId) { synchronized (mLock) { if (!mHasInputFilter) { return; } - // TODO: notify the mode change on specified display. final ArrayList<Display> displays = getValidDisplayList(); for (int i = 0; i < displays.size(); i++) { final Display display = displays.get(i); - if (display != null) { + if (display != null && display.getDisplayId() == displayId) { mInputFilter.refreshMagnificationMode(display); + return; } } } @@ -2245,12 +2275,22 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub scheduleUpdateClientsIfNeededLocked(userState); updateAccessibilityShortcutKeyTargetsLocked(userState); updateAccessibilityButtonTargetsLocked(userState); - // Update the capabilities before the mode. + // Update the capabilities before the mode because we will check the current mode is + // invalid or not.. updateMagnificationCapabilitiesSettingsChangeLocked(userState); - updateMagnificationModeChangeSettingsLocked(userState); + updateMagnificationModeChangeSettingsForAllDisplaysLocked(userState); updateFocusAppearanceDataLocked(userState); } + private void updateMagnificationModeChangeSettingsForAllDisplaysLocked( + AccessibilityUserState userState) { + final ArrayList<Display> displays = getValidDisplayList(); + for (int i = 0; i < displays.size(); i++) { + final int displayId = displays.get(i).getDisplayId(); + updateMagnificationModeChangeSettingsLocked(userState, displayId); + } + } + private void updateWindowsForAccessibilityCallbackLocked(AccessibilityUserState userState) { // We observe windows for accessibility only if there is at least // one bound service that can retrieve window content that specified @@ -2348,7 +2388,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub somethingChanged |= readAccessibilityButtonTargetsLocked(userState); somethingChanged |= readAccessibilityButtonTargetComponentLocked(userState); somethingChanged |= readUserRecommendedUiTimeoutSettingsLocked(userState); - somethingChanged |= readMagnificationModeLocked(userState); + somethingChanged |= readMagnificationModeForDefaultDisplayLocked(userState); somethingChanged |= readMagnificationCapabilitiesLocked(userState); return somethingChanged; } @@ -3878,8 +3918,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub || mUserInteractiveUiTimeoutUri.equals(uri)) { readUserRecommendedUiTimeoutSettingsLocked(userState); } else if (mMagnificationModeUri.equals(uri)) { - if (readMagnificationModeLocked(userState)) { - updateMagnificationModeChangeSettingsLocked(userState); + if (readMagnificationModeForDefaultDisplayLocked(userState)) { + updateMagnificationModeChangeSettingsLocked(userState, + Display.DEFAULT_DISPLAY); } } else if (mMagnificationCapabilityUri.equals(uri)) { if (readMagnificationCapabilitiesLocked(userState)) { @@ -3892,8 +3933,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private void updateMagnificationCapabilitiesSettingsChangeLocked( AccessibilityUserState userState) { - if (fallBackMagnificationModeSettingsLocked(userState)) { - updateMagnificationModeChangeSettingsLocked(userState); + final ArrayList<Display> displays = getValidDisplayList(); + for (int i = 0; i < displays.size(); i++) { + final int displayId = displays.get(i).getDisplayId(); + if (fallBackMagnificationModeSettingsLocked(userState, displayId)) { + updateMagnificationModeChangeSettingsLocked(userState, displayId); + } } updateWindowMagnificationConnectionIfNeeded(userState); // Remove magnification button UI when the magnification capability is not all mode or @@ -3902,7 +3947,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub || userState.isShortcutMagnificationEnabledLocked()) || userState.getMagnificationCapabilitiesLocked() != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL) { - final ArrayList<Display> displays = getValidDisplayList(); + for (int i = 0; i < displays.size(); i++) { final int displayId = displays.get(i).getDisplayId(); getWindowMagnificationMgr().removeMagnificationButton(displayId); @@ -3910,18 +3955,22 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - private boolean fallBackMagnificationModeSettingsLocked(AccessibilityUserState userState) { - if (userState.isValidMagnificationModeLocked()) { + private boolean fallBackMagnificationModeSettingsLocked(AccessibilityUserState userState, + int displayId) { + if (userState.isValidMagnificationModeLocked(displayId)) { return false; } - Slog.w(LOG_TAG, "invalid magnification mode:" + userState.getMagnificationModeLocked()); + Slog.w(LOG_TAG, "displayId " + displayId + ", invalid magnification mode:" + + userState.getMagnificationModeLocked(displayId)); final int capabilities = userState.getMagnificationCapabilitiesLocked(); - userState.setMagnificationModeLocked(capabilities); - persistMagnificationModeSettingLocked(capabilities); + userState.setMagnificationModeLocked(displayId, capabilities); + if (displayId == Display.DEFAULT_DISPLAY) { + persistMagnificationModeSettingsLocked(capabilities); + } return true; } - private void persistMagnificationModeSettingLocked(int mode) { + private void persistMagnificationModeSettingsLocked(int mode) { BackgroundThread.getHandler().post(() -> { final long identity = Binder.clearCallingIdentity(); try { @@ -3933,7 +3982,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub }); } - //TODO: support multi-display. /** * Gets the magnification mode of the specified display. * @@ -3943,17 +3991,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ public int getMagnificationMode(int displayId) { synchronized (mLock) { - return getCurrentUserStateLocked().getMagnificationModeLocked(); + return getCurrentUserStateLocked().getMagnificationModeLocked(displayId); } } - private boolean readMagnificationModeLocked(AccessibilityUserState userState) { + // Only the value of the default display is from user settings because not each of displays has + // a unique id. + private boolean readMagnificationModeForDefaultDisplayLocked(AccessibilityUserState userState) { final int magnificationMode = Settings.Secure.getIntForUser( mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN, userState.mUserId); - if (magnificationMode != userState.getMagnificationModeLocked()) { - userState.setMagnificationModeLocked(magnificationMode); + if (magnificationMode != userState.getMagnificationModeLocked(Display.DEFAULT_DISPLAY)) { + userState.setMagnificationModeLocked(Display.DEFAULT_DISPLAY, magnificationMode); return true; } return false; diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java index c70bf73fc7f5..8c3ca3430a3a 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java @@ -23,6 +23,7 @@ import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HIDDEN import static android.accessibilityservice.AccessibilityService.SHOW_MODE_IGNORE_HARD_KEYBOARD; import static android.accessibilityservice.AccessibilityService.SHOW_MODE_MASK; import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; +import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE; import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON; import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY; import static android.view.accessibility.AccessibilityManager.ShortcutType; @@ -42,6 +43,7 @@ import android.provider.Settings; import android.text.TextUtils; import android.util.ArraySet; import android.util.Slog; +import android.util.SparseIntArray; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.IAccessibilityManagerClient; @@ -122,8 +124,8 @@ class AccessibilityUserState { /** {@code true} if the device config supports magnification area. */ private final boolean mSupportMagnificationArea; - // The magnification mode of default display. - private int mMagnificationMode = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; + // The magnification modes on displays. + private final SparseIntArray mMagnificationModes = new SparseIntArray(); // The magnification capabilities used to know magnification mode could be switched. private int mMagnificationCapabilities = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; @@ -141,12 +143,13 @@ class AccessibilityUserState { @SoftKeyboardShowMode private int mSoftKeyboardShowMode = SHOW_MODE_AUTO; - boolean isValidMagnificationModeLocked() { + boolean isValidMagnificationModeLocked(int displayId) { + final int mode = getMagnificationModeLocked(displayId); if (!mSupportMagnificationArea - && mMagnificationMode == Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) { + && mode == Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) { return false; } - return (mMagnificationCapabilities & mMagnificationMode) != 0; + return (mMagnificationCapabilities & mode) != 0; } interface ServiceInfoChangeListener { @@ -203,7 +206,7 @@ class AccessibilityUserState { mIsAutoclickEnabled = false; mUserNonInteractiveUiTimeout = 0; mUserInteractiveUiTimeout = 0; - mMagnificationMode = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; + mMagnificationModes.clear(); mFocusStrokeWidth = mFocusStrokeWidthDefaultValue; mFocusColor = mFocusColorDefaultValue; } @@ -500,7 +503,7 @@ class AccessibilityUserState { pw.append(", nonInteractiveUiTimeout=").append(String.valueOf(mNonInteractiveUiTimeout)); pw.append(", interactiveUiTimeout=").append(String.valueOf(mInteractiveUiTimeout)); pw.append(", installedServiceCount=").append(String.valueOf(mInstalledServices.size())); - pw.append(", magnificationMode=").append(String.valueOf(mMagnificationMode)); + pw.append(", magnificationModes=").append(String.valueOf(mMagnificationModes)); pw.append(", magnificationCapabilities=") .append(String.valueOf(mMagnificationCapabilities)); pw.append("}"); @@ -635,14 +638,19 @@ class AccessibilityUserState { } /** - * Gets the magnification mode of default display. + * Gets the magnification mode for the given display. * @return magnification mode * * @see Settings.Secure#ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN * @see Settings.Secure#ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW */ - public int getMagnificationModeLocked() { - return mMagnificationMode; + public int getMagnificationModeLocked(int displayId) { + int mode = mMagnificationModes.get(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_NONE); + if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_NONE) { + mode = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; + setMagnificationModeLocked(displayId, mode); + } + return mode; } @@ -671,11 +679,13 @@ class AccessibilityUserState { } /** - * Sets the magnification mode of default display. + * Sets the magnification mode to the given display. + * + * @param displayId The display id. * @param mode The magnification mode. */ - public void setMagnificationModeLocked(int mode) { - mMagnificationMode = mode; + public void setMagnificationModeLocked(int displayId, int mode) { + mMagnificationModes.put(displayId, mode); } /** diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java index 935df9934dcb..966d887d11f7 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java @@ -208,8 +208,8 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } mPromptController.onDestroy(); // Check if need to reset when MagnificationGestureHandler is the last magnifying service. - mFullScreenMagnificationController.resetAllIfNeeded( - AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); + mFullScreenMagnificationController.resetIfNeeded( + mDisplayId, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); clearAndTransitionToStateDetecting(); } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java index 3708c7a422a0..6473bf5ffc3e 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java @@ -92,14 +92,16 @@ public class MagnificationController implements WindowMagnificationManager.Callb private long mFullScreenModeEnabledTime = 0; /** - * A callback to inform the magnification transition result. + * A callback to inform the magnification transition result on the given display. */ public interface TransitionCallBack { /** * Invoked when the transition ends. + * + * @param displayId The display id. * @param success {@code true} if the transition success. */ - void onResult(boolean success); + void onResult(int displayId, boolean success); } public MagnificationController(AccessibilityManagerService ams, Object lock, @@ -179,7 +181,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb final DisableMagnificationCallback animationCallback = getDisableMagnificationEndRunnableLocked(displayId); if (magnificationCenter == null && animationCallback == null) { - transitionCallBack.onResult(true); + transitionCallBack.onResult(displayId, true); return; } @@ -195,7 +197,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb if (magnificationCenter == null) { Slog.w(TAG, "Invalid center, ignore it"); - transitionCallBack.onResult(true); + transitionCallBack.onResult(displayId, true); return; } final FullScreenMagnificationController screenMagnificationController = @@ -251,6 +253,11 @@ public class MagnificationController implements WindowMagnificationManager.Callb updateMagnificationButton(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); } + @Override + public void onChangeMagnificationMode(int displayId, int magnificationMode) { + mAms.changeMagnificationMode(displayId, magnificationMode); + } + private void disableFullScreenMagnificationIfNeeded(int displayId) { final FullScreenMagnificationController fullScreenMagnificationController = getFullScreenMagnificationController(); @@ -514,7 +521,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb applyMagnificationModeLocked(mTargetMode); } updateMagnificationButton(mDisplayId, mTargetMode); - mTransitionCallBack.onResult(success); + mTransitionCallBack.onResult(mDisplayId, success); } } @@ -539,7 +546,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb setExpiredAndRemoveFromListLocked(); applyMagnificationModeLocked(mCurrentMode); updateMagnificationButton(mDisplayId, mCurrentMode); - mTransitionCallBack.onResult(true); + mTransitionCallBack.onResult(mDisplayId, true); } } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java index ce7ba7568b6e..bfaab9a0aa70 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java @@ -106,6 +106,15 @@ public class WindowMagnificationManager implements * @param activated {@code true} if the magnification is activated, otherwise {@code false}. */ void onWindowMagnificationActivationState(int displayId, boolean activated); + + /** + * Called from {@link IWindowMagnificationConnection} to request changing the magnification + * mode on the given display. + * + * @param displayId the logical display id + * @param magnificationMode the target magnification mode + */ + void onChangeMagnificationMode(int displayId, int magnificationMode); } private final Callback mCallback; @@ -535,7 +544,7 @@ public class WindowMagnificationManager implements FLAGS_WINDOW_MAGNIFICATION_CONNECTION_CALLBACK, "displayId=" + displayId + ";mode=" + magnificationMode); } - //TODO: Uses this method to change the magnification mode on non-default display. + mCallback.onChangeMagnificationMode(displayId, magnificationMode); } @Override diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java index 1fd795128fad..2645f3f9d9f5 100644 --- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java +++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java @@ -17,6 +17,7 @@ package com.android.server.companion; import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING; +import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION; import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH; import static android.content.pm.PackageManager.PERMISSION_GRANTED; @@ -32,6 +33,7 @@ import static java.util.Collections.unmodifiableMap; import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.role.RoleManager; import android.companion.AssociationInfo; import android.companion.AssociationRequest; import android.companion.CompanionDeviceManager; @@ -41,8 +43,10 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.Signature; +import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; +import android.os.UserHandle; import android.util.ArrayMap; import android.util.PackageUtils; import android.util.Slog; @@ -56,6 +60,7 @@ import com.android.server.FgThread; import java.io.PrintWriter; import java.util.Arrays; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -69,6 +74,8 @@ class AssociationRequestsProcessor { map.put(DEVICE_PROFILE_WATCH, Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH); map.put(DEVICE_PROFILE_APP_STREAMING, Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING); + map.put(DEVICE_PROFILE_AUTOMOTIVE_PROJECTION, + Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION); DEVICE_PROFILE_TO_PERMISSION = unmodifiableMap(map); } @@ -87,12 +94,14 @@ class AssociationRequestsProcessor { private IFindDeviceCallback mFindDeviceCallback; private String mCallingPackage; private AndroidFuture<?> mOngoingDeviceDiscovery; + private RoleManager mRoleManager; private PerUser<ServiceConnector<ICompanionDeviceDiscoveryService>> mServiceConnectors; - AssociationRequestsProcessor(CompanionDeviceManagerService service) { + AssociationRequestsProcessor(CompanionDeviceManagerService service, RoleManager roleManager) { mContext = service.getContext(); mService = service; + mRoleManager = roleManager; final Intent serviceIntent = new Intent().setComponent(SERVICE_TO_BIND_TO); mServiceConnectors = new PerUser<ServiceConnector<ICompanionDeviceDiscoveryService>>() { @@ -165,6 +174,16 @@ class AssociationRequestsProcessor { })); } + private boolean isRoleHolder(int userId, String packageName, String role) { + final long identity = Binder.clearCallingIdentity(); + try { + List<String> holders = mRoleManager.getRoleHoldersAsUser(role, UserHandle.of(userId)); + return holders.contains(packageName); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + void stopScan(AssociationRequest request, IFindDeviceCallback callback, String callingPackage) { if (DEBUG) { Slog.d(TAG, "stopScan(request = " + request + ")"); @@ -186,6 +205,12 @@ class AssociationRequestsProcessor { "DEVICE_PROFILE_APP_STREAMING is not fully supported yet."); } + if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) { + // TODO: remove, when properly supporting this profile. + throw new UnsupportedOperationException( + "DEVICE_PROFILE_AUTOMOTIVE_PROJECTION is not fully supported yet."); + } + if (!DEVICE_PROFILE_TO_PERMISSION.containsKey(deviceProfile)) { throw new IllegalArgumentException("Unsupported device profile: " + deviceProfile); } @@ -217,6 +242,12 @@ class AssociationRequestsProcessor { } private boolean mayAssociateWithoutPrompt(String packageName, int userId) { + if (mRequest.getDeviceProfile() != null + && isRoleHolder(userId, packageName, mRequest.getDeviceProfile())) { + // Don't need to collect user's consent since app already holds the role. + return true; + } + String[] sameOemPackages = mContext.getResources() .getStringArray(com.android.internal.R.array.config_companionDevicePackages); if (!ArrayUtils.contains(sameOemPackages, packageName)) { diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index cebabcb20402..d5357dc1dc5e 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -200,7 +200,6 @@ public class CompanionDeviceManagerService extends SystemService { super(context); mImpl = new CompanionDeviceManagerImpl(); mPersistentDataStore = new PersistentDataStore(); - mAssociationRequestsProcessor = new AssociationRequestsProcessor(this); mPowerWhitelistManager = context.getSystemService(PowerWhitelistManager.class); mRoleManager = context.getSystemService(RoleManager.class); @@ -213,6 +212,7 @@ public class CompanionDeviceManagerService extends SystemService { context.getSystemService(PermissionControllerManager.class)); mUserManager = context.getSystemService(UserManager.class); mCompanionDevicePresenceController = new CompanionDevicePresenceController(); + mAssociationRequestsProcessor = new AssociationRequestsProcessor(this, mRoleManager); registerPackageMonitor(); } @@ -757,7 +757,12 @@ public class CompanionDeviceManagerService extends SystemService { android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)) { mPowerWhitelistManager.addToWhitelist(packageInfo.packageName); } else { - mPowerWhitelistManager.removeFromWhitelist(packageInfo.packageName); + try { + mPowerWhitelistManager.removeFromWhitelist(packageInfo.packageName); + } catch (UnsupportedOperationException e) { + Slog.w(LOG_TAG, packageInfo.packageName + " can't be removed from power save" + + " whitelist. It might due to the package is whitelisted by the system."); + } } NetworkPolicyManager networkPolicyManager = NetworkPolicyManager.from(getContext()); diff --git a/services/core/Android.bp b/services/core/Android.bp index 16343e59bce9..9f22489f9305 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -149,9 +149,11 @@ java_library_static { "android.hardware.boot-V1.1-java", "android.hardware.boot-V1.2-java", "android.hardware.broadcastradio-V2.0-java", - "android.hardware.health-V1.0-java", - "android.hardware.health-V2.0-java", - "android.hardware.health-V2.1-java", + "android.hardware.health-V1.0-java", // HIDL + "android.hardware.health-V2.0-java", // HIDL + "android.hardware.health-V2.1-java", // HIDL + "android.hardware.health-V1-java", // AIDL + "android.hardware.health-translate-java", "android.hardware.light-V1-java", "android.hardware.tv.cec-V1.1-java", "android.hardware.weaver-V1.0-java", diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index 0146aa82a217..844ac86e8eb5 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -17,6 +17,7 @@ package com.android.server; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import static com.android.server.health.Utils.copyV1Battery; import android.annotation.Nullable; import android.app.ActivityManager; @@ -25,14 +26,8 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.database.ContentObserver; -import android.hardware.health.V1_0.HealthInfo; -import android.hardware.health.V2_0.IHealth; -import android.hardware.health.V2_0.Result; +import android.hardware.health.HealthInfo; import android.hardware.health.V2_1.BatteryCapacityLevel; -import android.hardware.health.V2_1.Constants; -import android.hardware.health.V2_1.IHealthInfoCallback; -import android.hidl.manager.V1_0.IServiceManager; -import android.hidl.manager.V1_0.IServiceNotification; import android.metrics.LogMaker; import android.os.BatteryManager; import android.os.BatteryManagerInternal; @@ -44,7 +39,6 @@ import android.os.Bundle; import android.os.DropBoxManager; import android.os.FileUtils; import android.os.Handler; -import android.os.HandlerThread; import android.os.IBatteryPropertiesRegistrar; import android.os.IBinder; import android.os.OsProtoEnums; @@ -62,15 +56,14 @@ import android.provider.Settings; import android.service.battery.BatteryServiceDumpProto; import android.sysprop.PowerProperties; import android.util.EventLog; -import android.util.MutableInt; import android.util.Slog; import android.util.proto.ProtoOutputStream; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.logging.MetricsLogger; import com.android.internal.util.DumpUtils; import com.android.server.am.BatteryStatsService; +import com.android.server.health.HealthServiceWrapper; import com.android.server.lights.LightsManager; import com.android.server.lights.LogicalLight; @@ -82,8 +75,6 @@ import java.io.PrintWriter; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.NoSuchElementException; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicReference; /** * <p>BatteryService monitors the charging status, and charge level of the device @@ -147,7 +138,6 @@ public final class BatteryService extends SystemService { private HealthInfo mHealthInfo; private final HealthInfo mLastHealthInfo = new HealthInfo(); - private android.hardware.health.V2_1.HealthInfo mHealthInfo2p1; private boolean mBatteryLevelCritical; private int mLastBatteryStatus; private int mLastBatteryHealth; @@ -191,7 +181,6 @@ public final class BatteryService extends SystemService { private ActivityManagerInternal mActivityManagerInternal; private HealthServiceWrapper mHealthServiceWrapper; - private HealthHalCallback mHealthHalCallback; private BatteryPropertiesRegistrar mBatteryPropertiesRegistrar; private ArrayDeque<Bundle> mBatteryLevelsEventQueue; private long mLastBatteryLevelChangedSentMs; @@ -274,13 +263,9 @@ public final class BatteryService extends SystemService { private void registerHealthCallback() { traceBegin("HealthInitWrapper"); - mHealthServiceWrapper = new HealthServiceWrapper(); - mHealthHalCallback = new HealthHalCallback(); // IHealth is lazily retrieved. try { - mHealthServiceWrapper.init(mHealthHalCallback, - new HealthServiceWrapper.IServiceManagerSupplier() {}, - new HealthServiceWrapper.IHealthSupplier() {}); + mHealthServiceWrapper = HealthServiceWrapper.create(this::update); } catch (RemoteException ex) { Slog.e(TAG, "health: cannot register callback. (RemoteException)"); throw ex.rethrowFromSystemServer(); @@ -368,8 +353,8 @@ public final class BatteryService extends SystemService { } private boolean shouldShutdownLocked() { - if (mHealthInfo2p1.batteryCapacityLevel != BatteryCapacityLevel.UNSUPPORTED) { - return (mHealthInfo2p1.batteryCapacityLevel == BatteryCapacityLevel.CRITICAL); + if (mHealthInfo.batteryCapacityLevel != BatteryCapacityLevel.UNSUPPORTED) { + return (mHealthInfo.batteryCapacityLevel == BatteryCapacityLevel.CRITICAL); } if (mHealthInfo.batteryLevel > 0) { return false; @@ -411,7 +396,7 @@ public final class BatteryService extends SystemService { // shut down gracefully if temperature is too high (> 68.0C by default) // wait until the system has booted before attempting to display the // shutdown dialog. - if (mHealthInfo.batteryTemperature > mShutdownBatteryTemperature) { + if (mHealthInfo.batteryTemperatureTenthsCelsius > mShutdownBatteryTemperature) { mHandler.post(new Runnable() { @Override public void run() { @@ -428,51 +413,28 @@ public final class BatteryService extends SystemService { } } - private void update(android.hardware.health.V2_1.HealthInfo info) { + private void update(android.hardware.health.HealthInfo info) { traceBegin("HealthInfoUpdate"); - Trace.traceCounter(Trace.TRACE_TAG_POWER, "BatteryChargeCounter", - info.legacy.legacy.batteryChargeCounter); - Trace.traceCounter(Trace.TRACE_TAG_POWER, "BatteryCurrent", - info.legacy.legacy.batteryCurrent); - Trace.traceCounter(Trace.TRACE_TAG_POWER, "PlugType", - plugType(info.legacy.legacy)); - Trace.traceCounter(Trace.TRACE_TAG_POWER, "BatteryStatus", - info.legacy.legacy.batteryStatus); + Trace.traceCounter( + Trace.TRACE_TAG_POWER, "BatteryChargeCounter", info.batteryChargeCounterUah); + Trace.traceCounter(Trace.TRACE_TAG_POWER, "BatteryCurrent", info.batteryCurrentMicroamps); + Trace.traceCounter(Trace.TRACE_TAG_POWER, "PlugType", plugType(info)); + Trace.traceCounter(Trace.TRACE_TAG_POWER, "BatteryStatus", info.batteryStatus); synchronized (mLock) { if (!mUpdatesStopped) { - mHealthInfo = info.legacy.legacy; - mHealthInfo2p1 = info; + mHealthInfo = info; // Process the new values. processValuesLocked(false); mLock.notifyAll(); // for any waiters on new info } else { - copy(mLastHealthInfo, info.legacy.legacy); + copyV1Battery(mLastHealthInfo, info); } } traceEnd(); } - private static void copy(HealthInfo dst, HealthInfo src) { - dst.chargerAcOnline = src.chargerAcOnline; - dst.chargerUsbOnline = src.chargerUsbOnline; - dst.chargerWirelessOnline = src.chargerWirelessOnline; - dst.maxChargingCurrent = src.maxChargingCurrent; - dst.maxChargingVoltage = src.maxChargingVoltage; - dst.batteryStatus = src.batteryStatus; - dst.batteryHealth = src.batteryHealth; - dst.batteryPresent = src.batteryPresent; - dst.batteryLevel = src.batteryLevel; - dst.batteryVoltage = src.batteryVoltage; - dst.batteryTemperature = src.batteryTemperature; - dst.batteryCurrent = src.batteryCurrent; - dst.batteryCycleCount = src.batteryCycleCount; - dst.batteryFullCharge = src.batteryFullCharge; - dst.batteryChargeCounter = src.batteryChargeCounter; - dst.batteryTechnology = src.batteryTechnology; - } - private static int plugType(HealthInfo healthInfo) { if (healthInfo.chargerAcOnline) { return BatteryManager.BATTERY_PLUGGED_AC; @@ -503,11 +465,16 @@ public final class BatteryService extends SystemService { // Let the battery stats keep track of the current level. try { - mBatteryStats.setBatteryState(mHealthInfo.batteryStatus, mHealthInfo.batteryHealth, - mPlugType, mHealthInfo.batteryLevel, mHealthInfo.batteryTemperature, - mHealthInfo.batteryVoltage, mHealthInfo.batteryChargeCounter, - mHealthInfo.batteryFullCharge, - mHealthInfo2p1.batteryChargeTimeToFullNowSeconds); + mBatteryStats.setBatteryState( + mHealthInfo.batteryStatus, + mHealthInfo.batteryHealth, + mPlugType, + mHealthInfo.batteryLevel, + mHealthInfo.batteryTemperatureTenthsCelsius, + mHealthInfo.batteryVoltageMillivolts, + mHealthInfo.batteryChargeCounterUah, + mHealthInfo.batteryFullChargeUah, + mHealthInfo.batteryChargeTimeToFullNowSeconds); } catch (RemoteException e) { // Should never happen. } @@ -515,17 +482,18 @@ public final class BatteryService extends SystemService { shutdownIfNoPowerLocked(); shutdownIfOverTempLocked(); - if (force || (mHealthInfo.batteryStatus != mLastBatteryStatus || - mHealthInfo.batteryHealth != mLastBatteryHealth || - mHealthInfo.batteryPresent != mLastBatteryPresent || - mHealthInfo.batteryLevel != mLastBatteryLevel || - mPlugType != mLastPlugType || - mHealthInfo.batteryVoltage != mLastBatteryVoltage || - mHealthInfo.batteryTemperature != mLastBatteryTemperature || - mHealthInfo.maxChargingCurrent != mLastMaxChargingCurrent || - mHealthInfo.maxChargingVoltage != mLastMaxChargingVoltage || - mHealthInfo.batteryChargeCounter != mLastChargeCounter || - mInvalidCharger != mLastInvalidCharger)) { + if (force + || (mHealthInfo.batteryStatus != mLastBatteryStatus + || mHealthInfo.batteryHealth != mLastBatteryHealth + || mHealthInfo.batteryPresent != mLastBatteryPresent + || mHealthInfo.batteryLevel != mLastBatteryLevel + || mPlugType != mLastPlugType + || mHealthInfo.batteryVoltageMillivolts != mLastBatteryVoltage + || mHealthInfo.batteryTemperatureTenthsCelsius != mLastBatteryTemperature + || mHealthInfo.maxChargingCurrentMicroamps != mLastMaxChargingCurrent + || mHealthInfo.maxChargingVoltageMicrovolts != mLastMaxChargingVoltage + || mHealthInfo.batteryChargeCounterUah != mLastChargeCounter + || mInvalidCharger != mLastInvalidCharger)) { if (mPlugType != mLastPlugType) { if (mLastPlugType == BATTERY_PLUGGED_NONE) { @@ -582,8 +550,11 @@ public final class BatteryService extends SystemService { if (mHealthInfo.batteryLevel != mLastBatteryLevel) { // Don't do this just from voltage or temperature changes, that is // too noisy. - EventLog.writeEvent(EventLogTags.BATTERY_LEVEL, - mHealthInfo.batteryLevel, mHealthInfo.batteryVoltage, mHealthInfo.batteryTemperature); + EventLog.writeEvent( + EventLogTags.BATTERY_LEVEL, + mHealthInfo.batteryLevel, + mHealthInfo.batteryVoltageMillivolts, + mHealthInfo.batteryTemperatureTenthsCelsius); } if (mBatteryLevelCritical && !mLastBatteryLevelCritical && mPlugType == BATTERY_PLUGGED_NONE) { @@ -689,11 +660,11 @@ public final class BatteryService extends SystemService { mLastBatteryPresent = mHealthInfo.batteryPresent; mLastBatteryLevel = mHealthInfo.batteryLevel; mLastPlugType = mPlugType; - mLastBatteryVoltage = mHealthInfo.batteryVoltage; - mLastBatteryTemperature = mHealthInfo.batteryTemperature; - mLastMaxChargingCurrent = mHealthInfo.maxChargingCurrent; - mLastMaxChargingVoltage = mHealthInfo.maxChargingVoltage; - mLastChargeCounter = mHealthInfo.batteryChargeCounter; + mLastBatteryVoltage = mHealthInfo.batteryVoltageMillivolts; + mLastBatteryTemperature = mHealthInfo.batteryTemperatureTenthsCelsius; + mLastMaxChargingCurrent = mHealthInfo.maxChargingCurrentMicroamps; + mLastMaxChargingVoltage = mHealthInfo.maxChargingVoltageMicrovolts; + mLastChargeCounter = mHealthInfo.batteryChargeCounterUah; mLastBatteryLevelCritical = mBatteryLevelCritical; mLastInvalidCharger = mInvalidCharger; } @@ -716,13 +687,17 @@ public final class BatteryService extends SystemService { intent.putExtra(BatteryManager.EXTRA_SCALE, BATTERY_SCALE); intent.putExtra(BatteryManager.EXTRA_ICON_SMALL, icon); intent.putExtra(BatteryManager.EXTRA_PLUGGED, mPlugType); - intent.putExtra(BatteryManager.EXTRA_VOLTAGE, mHealthInfo.batteryVoltage); - intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mHealthInfo.batteryTemperature); + intent.putExtra(BatteryManager.EXTRA_VOLTAGE, mHealthInfo.batteryVoltageMillivolts); + intent.putExtra( + BatteryManager.EXTRA_TEMPERATURE, mHealthInfo.batteryTemperatureTenthsCelsius); intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mHealthInfo.batteryTechnology); intent.putExtra(BatteryManager.EXTRA_INVALID_CHARGER, mInvalidCharger); - intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_CURRENT, mHealthInfo.maxChargingCurrent); - intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE, mHealthInfo.maxChargingVoltage); - intent.putExtra(BatteryManager.EXTRA_CHARGE_COUNTER, mHealthInfo.batteryChargeCounter); + intent.putExtra( + BatteryManager.EXTRA_MAX_CHARGING_CURRENT, mHealthInfo.maxChargingCurrentMicroamps); + intent.putExtra( + BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE, + mHealthInfo.maxChargingVoltageMicrovolts); + intent.putExtra(BatteryManager.EXTRA_CHARGE_COUNTER, mHealthInfo.batteryChargeCounterUah); if (DEBUG) { Slog.d(TAG, "Sending ACTION_BATTERY_CHANGED. scale:" + BATTERY_SCALE + ", info:" + mHealthInfo.toString()); @@ -742,9 +717,9 @@ public final class BatteryService extends SystemService { event.putBoolean(BatteryManager.EXTRA_BATTERY_LOW, mSentLowBatteryBroadcast); event.putInt(BatteryManager.EXTRA_SCALE, BATTERY_SCALE); event.putInt(BatteryManager.EXTRA_PLUGGED, mPlugType); - event.putInt(BatteryManager.EXTRA_VOLTAGE, mHealthInfo.batteryVoltage); - event.putInt(BatteryManager.EXTRA_TEMPERATURE, mHealthInfo.batteryTemperature); - event.putInt(BatteryManager.EXTRA_CHARGE_COUNTER, mHealthInfo.batteryChargeCounter); + event.putInt(BatteryManager.EXTRA_VOLTAGE, mHealthInfo.batteryVoltageMillivolts); + event.putInt(BatteryManager.EXTRA_TEMPERATURE, mHealthInfo.batteryTemperatureTenthsCelsius); + event.putInt(BatteryManager.EXTRA_CHARGE_COUNTER, mHealthInfo.batteryChargeCounterUah); event.putLong(BatteryManager.EXTRA_EVENT_TIMESTAMP, now); boolean queueWasEmpty = mBatteryLevelsEventQueue.isEmpty(); @@ -936,7 +911,7 @@ public final class BatteryService extends SystemService { } try { if (!mUpdatesStopped) { - copy(mLastHealthInfo, mHealthInfo); + copyV1Battery(mLastHealthInfo, mHealthInfo); } boolean update = true; switch (key) { @@ -959,10 +934,10 @@ public final class BatteryService extends SystemService { mHealthInfo.batteryLevel = Integer.parseInt(value); break; case "counter": - mHealthInfo.batteryChargeCounter = Integer.parseInt(value); + mHealthInfo.batteryChargeCounterUah = Integer.parseInt(value); break; case "temp": - mHealthInfo.batteryTemperature = Integer.parseInt(value); + mHealthInfo.batteryTemperatureTenthsCelsius = Integer.parseInt(value); break; case "invalid": mInvalidCharger = Integer.parseInt(value); @@ -1006,7 +981,7 @@ public final class BatteryService extends SystemService { private void setChargerAcOnline(boolean online, boolean forceUpdate) { if (!mUpdatesStopped) { - copy(mLastHealthInfo, mHealthInfo); + copyV1Battery(mLastHealthInfo, mHealthInfo); } mHealthInfo.chargerAcOnline = online; mUpdatesStopped = true; @@ -1015,7 +990,7 @@ public final class BatteryService extends SystemService { private void setBatteryLevel(int level, boolean forceUpdate) { if (!mUpdatesStopped) { - copy(mLastHealthInfo, mHealthInfo); + copyV1Battery(mLastHealthInfo, mHealthInfo); } mHealthInfo.batteryLevel = level; mUpdatesStopped = true; @@ -1024,7 +999,7 @@ public final class BatteryService extends SystemService { private void unplugBattery(boolean forceUpdate, PrintWriter pw) { if (!mUpdatesStopped) { - copy(mLastHealthInfo, mHealthInfo); + copyV1Battery(mLastHealthInfo, mHealthInfo); } mHealthInfo.chargerAcOnline = false; mHealthInfo.chargerUsbOnline = false; @@ -1036,7 +1011,7 @@ public final class BatteryService extends SystemService { private void resetBattery(boolean forceUpdate, @Nullable PrintWriter pw) { if (mUpdatesStopped) { mUpdatesStopped = false; - copy(mHealthInfo, mLastHealthInfo); + copyV1Battery(mHealthInfo, mLastHealthInfo); Binder.withCleanCallingIdentity(() -> processValuesLocked(forceUpdate, pw)); } if (mBatteryInputSuspended) { @@ -1071,16 +1046,16 @@ public final class BatteryService extends SystemService { pw.println(" AC powered: " + mHealthInfo.chargerAcOnline); pw.println(" USB powered: " + mHealthInfo.chargerUsbOnline); pw.println(" Wireless powered: " + mHealthInfo.chargerWirelessOnline); - pw.println(" Max charging current: " + mHealthInfo.maxChargingCurrent); - pw.println(" Max charging voltage: " + mHealthInfo.maxChargingVoltage); - pw.println(" Charge counter: " + mHealthInfo.batteryChargeCounter); + pw.println(" Max charging current: " + mHealthInfo.maxChargingCurrentMicroamps); + pw.println(" Max charging voltage: " + mHealthInfo.maxChargingVoltageMicrovolts); + pw.println(" Charge counter: " + mHealthInfo.batteryChargeCounterUah); pw.println(" status: " + mHealthInfo.batteryStatus); pw.println(" health: " + mHealthInfo.batteryHealth); pw.println(" present: " + mHealthInfo.batteryPresent); pw.println(" level: " + mHealthInfo.batteryLevel); pw.println(" scale: " + BATTERY_SCALE); - pw.println(" voltage: " + mHealthInfo.batteryVoltage); - pw.println(" temperature: " + mHealthInfo.batteryTemperature); + pw.println(" voltage: " + mHealthInfo.batteryVoltageMillivolts); + pw.println(" temperature: " + mHealthInfo.batteryTemperatureTenthsCelsius); pw.println(" technology: " + mHealthInfo.batteryTechnology); } else { Shell shell = new Shell(); @@ -1103,16 +1078,23 @@ public final class BatteryService extends SystemService { batteryPluggedValue = OsProtoEnums.BATTERY_PLUGGED_WIRELESS; } proto.write(BatteryServiceDumpProto.PLUGGED, batteryPluggedValue); - proto.write(BatteryServiceDumpProto.MAX_CHARGING_CURRENT, mHealthInfo.maxChargingCurrent); - proto.write(BatteryServiceDumpProto.MAX_CHARGING_VOLTAGE, mHealthInfo.maxChargingVoltage); - proto.write(BatteryServiceDumpProto.CHARGE_COUNTER, mHealthInfo.batteryChargeCounter); + proto.write( + BatteryServiceDumpProto.MAX_CHARGING_CURRENT, + mHealthInfo.maxChargingCurrentMicroamps); + proto.write( + BatteryServiceDumpProto.MAX_CHARGING_VOLTAGE, + mHealthInfo.maxChargingVoltageMicrovolts); + proto.write( + BatteryServiceDumpProto.CHARGE_COUNTER, mHealthInfo.batteryChargeCounterUah); proto.write(BatteryServiceDumpProto.STATUS, mHealthInfo.batteryStatus); proto.write(BatteryServiceDumpProto.HEALTH, mHealthInfo.batteryHealth); proto.write(BatteryServiceDumpProto.IS_PRESENT, mHealthInfo.batteryPresent); proto.write(BatteryServiceDumpProto.LEVEL, mHealthInfo.batteryLevel); proto.write(BatteryServiceDumpProto.SCALE, BATTERY_SCALE); - proto.write(BatteryServiceDumpProto.VOLTAGE, mHealthInfo.batteryVoltage); - proto.write(BatteryServiceDumpProto.TEMPERATURE, mHealthInfo.batteryTemperature); + proto.write(BatteryServiceDumpProto.VOLTAGE, mHealthInfo.batteryVoltageMillivolts); + proto.write( + BatteryServiceDumpProto.TEMPERATURE, + mHealthInfo.batteryTemperatureTenthsCelsius); proto.write(BatteryServiceDumpProto.TECHNOLOGY, mHealthInfo.batteryTechnology); } proto.flush(); @@ -1184,64 +1166,6 @@ public final class BatteryService extends SystemService { } } - private final class HealthHalCallback extends IHealthInfoCallback.Stub - implements HealthServiceWrapper.Callback { - @Override public void healthInfoChanged(android.hardware.health.V2_0.HealthInfo props) { - android.hardware.health.V2_1.HealthInfo propsLatest = - new android.hardware.health.V2_1.HealthInfo(); - propsLatest.legacy = props; - - propsLatest.batteryCapacityLevel = BatteryCapacityLevel.UNSUPPORTED; - propsLatest.batteryChargeTimeToFullNowSeconds = - Constants.BATTERY_CHARGE_TIME_TO_FULL_NOW_SECONDS_UNSUPPORTED; - - BatteryService.this.update(propsLatest); - } - - @Override public void healthInfoChanged_2_1(android.hardware.health.V2_1.HealthInfo props) { - BatteryService.this.update(props); - } - - // on new service registered - @Override public void onRegistration(IHealth oldService, IHealth newService, - String instance) { - if (newService == null) return; - - traceBegin("HealthUnregisterCallback"); - try { - if (oldService != null) { - int r = oldService.unregisterCallback(this); - if (r != Result.SUCCESS) { - Slog.w(TAG, "health: cannot unregister previous callback: " + - Result.toString(r)); - } - } - } catch (RemoteException ex) { - Slog.w(TAG, "health: cannot unregister previous callback (transaction error): " - + ex.getMessage()); - } finally { - traceEnd(); - } - - traceBegin("HealthRegisterCallback"); - try { - int r = newService.registerCallback(this); - if (r != Result.SUCCESS) { - Slog.w(TAG, "health: cannot register callback: " + Result.toString(r)); - return; - } - // registerCallback does NOT guarantee that update is called - // immediately, so request a manual update here. - newService.update(); - } catch (RemoteException ex) { - Slog.e(TAG, "health: cannot register callback (transaction error): " - + ex.getMessage()); - } finally { - traceEnd(); - } - } - } - private final class BinderService extends Binder { @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; @@ -1265,71 +1189,11 @@ public final class BatteryService extends SystemService { private final class BatteryPropertiesRegistrar extends IBatteryPropertiesRegistrar.Stub { @Override public int getProperty(int id, final BatteryProperty prop) throws RemoteException { - traceBegin("HealthGetProperty"); - try { - IHealth service = mHealthServiceWrapper.getLastService(); - if (service == null) throw new RemoteException("no health service"); - final MutableInt outResult = new MutableInt(Result.NOT_SUPPORTED); - switch(id) { - case BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER: - service.getChargeCounter((int result, int value) -> { - outResult.value = result; - if (result == Result.SUCCESS) prop.setLong(value); - }); - break; - case BatteryManager.BATTERY_PROPERTY_CURRENT_NOW: - service.getCurrentNow((int result, int value) -> { - outResult.value = result; - if (result == Result.SUCCESS) prop.setLong(value); - }); - break; - case BatteryManager.BATTERY_PROPERTY_CURRENT_AVERAGE: - service.getCurrentAverage((int result, int value) -> { - outResult.value = result; - if (result == Result.SUCCESS) prop.setLong(value); - }); - break; - case BatteryManager.BATTERY_PROPERTY_CAPACITY: - service.getCapacity((int result, int value) -> { - outResult.value = result; - if (result == Result.SUCCESS) prop.setLong(value); - }); - break; - case BatteryManager.BATTERY_PROPERTY_STATUS: - service.getChargeStatus((int result, int value) -> { - outResult.value = result; - if (result == Result.SUCCESS) prop.setLong(value); - }); - break; - case BatteryManager.BATTERY_PROPERTY_ENERGY_COUNTER: - service.getEnergyCounter((int result, long value) -> { - outResult.value = result; - if (result == Result.SUCCESS) prop.setLong(value); - }); - break; - } - return outResult.value; - } finally { - traceEnd(); - } + return mHealthServiceWrapper.getProperty(id, prop); } @Override public void scheduleUpdate() throws RemoteException { - mHealthServiceWrapper.getHandlerThread().getThreadHandler().post(() -> { - traceBegin("HealthScheduleUpdate"); - try { - IHealth service = mHealthServiceWrapper.getLastService(); - if (service == null) { - Slog.e(TAG, "no health service"); - return; - } - service.update(); - } catch (RemoteException ex) { - Slog.e(TAG, "Cannot call update on health HAL", ex); - } finally { - traceEnd(); - } - }); + mHealthServiceWrapper.scheduleUpdate(); } } @@ -1358,14 +1222,14 @@ public final class BatteryService extends SystemService { @Override public int getBatteryChargeCounter() { synchronized (mLock) { - return mHealthInfo.batteryChargeCounter; + return mHealthInfo.batteryChargeCounterUah; } } @Override public int getBatteryFullCharge() { synchronized (mLock) { - return mHealthInfo.batteryFullCharge; + return mHealthInfo.batteryFullChargeUah; } } @@ -1418,184 +1282,4 @@ public final class BatteryService extends SystemService { BatteryService.this.suspendBatteryInput(); } } - - /** - * HealthServiceWrapper wraps the internal IHealth service and refreshes the service when - * necessary. - * - * On new registration of IHealth service, {@link #onRegistration onRegistration} is called and - * the internal service is refreshed. - * On death of an existing IHealth service, the internal service is NOT cleared to avoid - * race condition between death notification and new service notification. Hence, - * a caller must check for transaction errors when calling into the service. - * - * @hide Should only be used internally. - */ - public static final class HealthServiceWrapper { - private static final String TAG = "HealthServiceWrapper"; - public static final String INSTANCE_VENDOR = "default"; - - private final IServiceNotification mNotification = new Notification(); - private final HandlerThread mHandlerThread = new HandlerThread("HealthServiceHwbinder"); - // These variables are fixed after init. - private Callback mCallback; - private IHealthSupplier mHealthSupplier; - private String mInstanceName; - - // Last IHealth service received. - private final AtomicReference<IHealth> mLastService = new AtomicReference<>(); - - /** - * init should be called after constructor. For testing purposes, init is not called by - * constructor. - */ - public HealthServiceWrapper() { - } - - public IHealth getLastService() { - return mLastService.get(); - } - - /** - * See {@link #init(Callback, IServiceManagerSupplier, IHealthSupplier)} - */ - public void init() throws RemoteException, NoSuchElementException { - init(/* callback= */null, new HealthServiceWrapper.IServiceManagerSupplier() {}, - new HealthServiceWrapper.IHealthSupplier() {}); - } - - /** - * Start monitoring registration of new IHealth services. Only instance - * {@link #INSTANCE_VENDOR} and in device / framework manifest are used. This function should - * only be called once. - * - * mCallback.onRegistration() is called synchronously (aka in init thread) before - * this method returns if callback is not null. - * - * @throws RemoteException transaction error when talking to IServiceManager - * @throws NoSuchElementException if one of the following cases: - * - No service manager; - * - {@link #INSTANCE_VENDOR} is not in manifests (i.e. not - * available on this device), or none of these instances are available to current - * process. - * @throws NullPointerException when supplier is null - */ - void init(@Nullable Callback callback, - IServiceManagerSupplier managerSupplier, - IHealthSupplier healthSupplier) - throws RemoteException, NoSuchElementException, NullPointerException { - if (managerSupplier == null || healthSupplier == null) { - throw new NullPointerException(); - } - IServiceManager manager; - - mHealthSupplier = healthSupplier; - - // Initialize mLastService and call callback for the first time (in init thread) - IHealth newService = null; - traceBegin("HealthInitGetService_" + INSTANCE_VENDOR); - try { - newService = healthSupplier.get(INSTANCE_VENDOR); - } catch (NoSuchElementException ex) { - /* ignored, handled below */ - } finally { - traceEnd(); - } - if (newService != null) { - mInstanceName = INSTANCE_VENDOR; - mLastService.set(newService); - } - - if (mInstanceName == null || newService == null) { - throw new NoSuchElementException(String.format( - "IHealth service instance %s isn't available. Perhaps no permission?", - INSTANCE_VENDOR)); - } - - if (callback != null) { - mCallback = callback; - mCallback.onRegistration(null, newService, mInstanceName); - } - - // Register for future service registrations - traceBegin("HealthInitRegisterNotification"); - mHandlerThread.start(); - try { - managerSupplier.get().registerForNotifications( - IHealth.kInterfaceName, mInstanceName, mNotification); - } finally { - traceEnd(); - } - Slog.i(TAG, "health: HealthServiceWrapper listening to instance " + mInstanceName); - } - - @VisibleForTesting - HandlerThread getHandlerThread() { - return mHandlerThread; - } - - interface Callback { - /** - * This function is invoked asynchronously when a new and related IServiceNotification - * is received. - * @param service the recently retrieved service from IServiceManager. - * Can be a dead service before service notification of a new service is delivered. - * Implementation must handle cases for {@link RemoteException}s when calling - * into service. - * @param instance instance name. - */ - void onRegistration(IHealth oldService, IHealth newService, String instance); - } - - /** - * Supplier of services. - * Must not return null; throw {@link NoSuchElementException} if a service is not available. - */ - interface IServiceManagerSupplier { - default IServiceManager get() throws NoSuchElementException, RemoteException { - return IServiceManager.getService(); - } - } - /** - * Supplier of services. - * Must not return null; throw {@link NoSuchElementException} if a service is not available. - */ - interface IHealthSupplier { - default IHealth get(String name) throws NoSuchElementException, RemoteException { - return IHealth.getService(name, true /* retry */); - } - } - - private class Notification extends IServiceNotification.Stub { - @Override - public final void onRegistration(String interfaceName, String instanceName, - boolean preexisting) { - if (!IHealth.kInterfaceName.equals(interfaceName)) return; - if (!mInstanceName.equals(instanceName)) return; - - // This runnable only runs on mHandlerThread and ordering is ensured, hence - // no locking is needed inside the runnable. - mHandlerThread.getThreadHandler().post(new Runnable() { - @Override - public void run() { - try { - IHealth newService = mHealthSupplier.get(mInstanceName); - IHealth oldService = mLastService.getAndSet(newService); - - // preexisting may be inaccurate (race). Check for equality here. - if (Objects.equals(newService, oldService)) return; - - Slog.i(TAG, "health: new instance registered " + mInstanceName); - // #init() may be called with null callback. Skip null callbacks. - if (mCallback == null) return; - mCallback.onRegistration(oldService, newService, mInstanceName); - } catch (NoSuchElementException | RemoteException ex) { - Slog.e(TAG, "health: Cannot get instance '" + mInstanceName - + "': " + ex.getMessage() + ". Perhaps no permission?"); - } - } - }); - } - } - } } diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java index cb6e73af56ac..c5b23790c918 100644 --- a/services/core/java/com/android/server/SystemServiceManager.java +++ b/services/core/java/com/android/server/SystemServiceManager.java @@ -19,12 +19,14 @@ package com.android.server; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.app.ActivityManager; import android.content.Context; import android.content.pm.UserInfo; import android.os.Build; import android.os.Environment; import android.os.SystemClock; import android.os.Trace; +import android.os.UserHandle; import android.util.ArrayMap; import android.util.EventLog; import android.util.IndentingPrintWriter; @@ -45,6 +47,9 @@ import java.io.File; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; /** * Manages creating, starting, and other lifecycle events of @@ -67,6 +72,19 @@ public final class SystemServiceManager implements Dumpable { private static final String USER_STOPPING = "Stop"; // Logged as onStopUser private static final String USER_STOPPED = "Cleanup"; // Logged as onCleanupUser + // Whether to use multiple threads to run user lifecycle phases in parallel. + private static boolean sUseLifecycleThreadPool = false; + // The default number of threads to use if lifecycle thread pool is enabled. + private static final int DEFAULT_MAX_USER_POOL_THREADS = 3; + // The number of threads to use if lifecycle thread pool is enabled, dependent on the number of + // available cores on the device. + private final int mNumUserPoolThreads; + // Maximum time to wait for a particular lifecycle phase to finish. + private static final long USER_POOL_SHUTDOWN_TIMEOUT_SECONDS = 30; + // Indirectly indicates how many services belong in the bootstrap and core service categories. + // This is used to decide which services the user lifecycle phases should be parallelized for. + private static volatile int sOtherServicesStartIndex; + private static File sSystemDir; private final Context mContext; private boolean mSafeMode; @@ -100,6 +118,11 @@ public final class SystemServiceManager implements Dumpable { SystemServiceManager(Context context) { mContext = context; + // Disable using the thread pool for low ram devices + sUseLifecycleThreadPool = sUseLifecycleThreadPool + && !ActivityManager.isLowRamDeviceStatic(); + mNumUserPoolThreads = Math.min(Runtime.getRuntime().availableProcessors(), + DEFAULT_MAX_USER_POOL_THREADS); } /** @@ -261,6 +284,18 @@ public final class SystemServiceManager implements Dumpable { } /** + * Called from SystemServer to indicate that services in the other category are now starting. + * This is used to keep track of how many services are in the bootstrap and core service + * categories for the purposes of user lifecycle parallelization. + */ + public void updateOtherServicesStartIndex() { + // Only update the index if the boot phase has not been completed yet + if (!isBootCompleted()) { + sOtherServicesStartIndex = mServices.size(); + } + } + + /** * Called at the beginning of {@code ActivityManagerService.systemReady()}. */ public void preSystemReady() { @@ -373,6 +408,13 @@ public final class SystemServiceManager implements Dumpable { Slog.i(TAG, "Calling on" + onWhat + "User " + curUserId + (prevUser != null ? " (from " + prevUser + ")" : "")); final int serviceLen = mServices.size(); + // Limit the lifecycle parallelization to all users other than the system user + // and only for the user start lifecycle phase for now. + final boolean useThreadPool = sUseLifecycleThreadPool + && curUserId != UserHandle.USER_SYSTEM + && onWhat.equals(USER_STARTING); + final ExecutorService threadPool = + useThreadPool ? Executors.newFixedThreadPool(mNumUserPoolThreads) : null; for (int i = 0; i < serviceLen; i++) { final SystemService service = mServices.get(i); final String serviceName = service.getClass().getName(); @@ -395,7 +437,11 @@ public final class SystemServiceManager implements Dumpable { } continue; } - t.traceBegin("ssm.on" + onWhat + "User-" + curUserId + "_" + serviceName); + // Only submit this service to the thread pool if it's in the "other" category. + final boolean submitToThreadPool = useThreadPool && i >= sOtherServicesStartIndex; + if (!submitToThreadPool) { + t.traceBegin("ssm.on" + onWhat + "User-" + curUserId + "_" + serviceName); + } long time = SystemClock.elapsedRealtime(); try { switch (onWhat) { @@ -403,7 +449,11 @@ public final class SystemServiceManager implements Dumpable { service.onUserSwitching(prevUser, curUser); break; case USER_STARTING: - service.onUserStarting(curUser); + if (submitToThreadPool) { + threadPool.submit(getOnStartUserRunnable(t, service, curUser)); + } else { + service.onUserStarting(curUser); + } break; case USER_UNLOCKING: service.onUserUnlocking(curUser); @@ -424,13 +474,54 @@ public final class SystemServiceManager implements Dumpable { Slog.wtf(TAG, "Failure reporting " + onWhat + " of user " + curUser + " to service " + serviceName, ex); } - warnIfTooLong(SystemClock.elapsedRealtime() - time, service, - "on" + onWhat + "User-" + curUserId); - t.traceEnd(); // what on service + if (!submitToThreadPool) { + warnIfTooLong(SystemClock.elapsedRealtime() - time, service, + "on" + onWhat + "User-" + curUserId); + t.traceEnd(); // what on service + } + } + if (useThreadPool) { + boolean terminated = false; + threadPool.shutdown(); + try { + terminated = threadPool.awaitTermination( + USER_POOL_SHUTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Slog.wtf(TAG, "User lifecycle thread pool was interrupted while awaiting completion" + + " of " + onWhat + " of user " + curUser, e); + Slog.e(TAG, "Couldn't terminate, disabling thread pool. " + + "Please capture a bug report."); + sUseLifecycleThreadPool = false; + } + if (!terminated) { + Slog.wtf(TAG, "User lifecycle thread pool was not terminated."); + } } t.traceEnd(); // main entry } + private Runnable getOnStartUserRunnable(TimingsTraceAndSlog oldTrace, SystemService service, + TargetUser curUser) { + return () -> { + final TimingsTraceAndSlog t = new TimingsTraceAndSlog(oldTrace); + final String serviceName = service.getClass().getName(); + try { + final int curUserId = curUser.getUserIdentifier(); + t.traceBegin("ssm.on" + USER_STARTING + "User-" + curUserId + "_" + serviceName); + long time = SystemClock.elapsedRealtime(); + service.onUserStarting(curUser); + warnIfTooLong(SystemClock.elapsedRealtime() - time, service, + "on" + USER_STARTING + "User-" + curUserId); + t.traceEnd(); + } catch (Exception e) { + Slog.wtf(TAG, "Failure reporting " + USER_STARTING + " of user " + curUser + + " to service " + serviceName, e); + Slog.e(TAG, "Disabling thread pool - please capture a bug report."); + sUseLifecycleThreadPool = false; + } + }; + } + /** Sets the safe mode flag for services to query. */ void setSafeMode(boolean safeMode) { mSafeMode = safeMode; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 1ce9f9b6ccb2..0eed1909944f 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -32,7 +32,7 @@ import static android.app.ActivityManager.INTENT_SENDER_ACTIVITY; import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; import static android.app.ActivityManager.PROCESS_STATE_TOP; -import static android.app.ActivityManager.StopBgUsersOnSwitch; +import static android.app.ActivityManager.StopUserOnSwitch; import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; import static android.app.AppOpsManager.OP_NONE; @@ -444,6 +444,14 @@ public class ActivityManagerService extends IActivityManager.Stub private static final String SYSTEM_PROPERTY_DEVICE_PROVISIONED = "persist.sys.device_provisioned"; + /** + * Enabling this flag enforces the requirement for context registered receivers to use one of + * {@link Context#RECEIVER_EXPORTED} or {@link Context#RECEIVER_NOT_EXPORTED} for unprotected + * broadcasts + */ + private static final boolean ENFORCE_DYNAMIC_RECEIVER_EXPLICIT_EXPORT = + SystemProperties.getBoolean("fw.enforce_dynamic_receiver_explicit_export", false); + static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityManagerService" : TAG_AM; static final String TAG_BACKUP = TAG + POSTFIX_BACKUP; private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST; @@ -12646,28 +12654,43 @@ public class ActivityManagerService extends IActivityManager.Stub // an error so the consumer can know to explicitly set the value for their flag. // If the caller is registering for a sticky broadcast with a null receiver, we won't // require a flag - if (!onlyProtectedBroadcasts && receiver != null && ( - CompatChanges.isChangeEnabled( - DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, callingUid) - && (flags & (Context.RECEIVER_EXPORTED | Context.RECEIVER_NOT_EXPORTED)) - == 0)) { - Slog.e(TAG, - callerPackage + ": Targeting T+ (version " + Build.VERSION_CODES.TIRAMISU - + " and above) requires that one of RECEIVER_EXPORTED or " - + "RECEIVER_NOT_EXPORTED be specified when registering a receiver"); - } else if (((flags & Context.RECEIVER_EXPORTED) != 0) && ( + final boolean explicitExportStateDefined = + (flags & (Context.RECEIVER_EXPORTED | Context.RECEIVER_NOT_EXPORTED)) != 0; + if (((flags & Context.RECEIVER_EXPORTED) != 0) && ( (flags & Context.RECEIVER_NOT_EXPORTED) != 0)) { throw new IllegalArgumentException( "Receiver can't specify both RECEIVER_EXPORTED and RECEIVER_NOT_EXPORTED" + "flag"); } + if (CompatChanges.isChangeEnabled(DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, + callingUid) + && !explicitExportStateDefined) { + if (ENFORCE_DYNAMIC_RECEIVER_EXPLICIT_EXPORT) { + throw new SecurityException( + callerPackage + ": Targeting T+ (version " + + Build.VERSION_CODES.TIRAMISU + + " and above) requires that one of RECEIVER_EXPORTED or " + + "RECEIVER_NOT_EXPORTED be specified when registering a " + + "receiver"); + } else { + Slog.wtf(TAG, + callerPackage + ": Targeting T+ (version " + + Build.VERSION_CODES.TIRAMISU + + " and above) requires that one of RECEIVER_EXPORTED or " + + "RECEIVER_NOT_EXPORTED be specified when registering a " + + "receiver"); + // Assume default behavior-- flag check is not enforced + flags |= Context.RECEIVER_EXPORTED; + } + } else if (!CompatChanges.isChangeEnabled(DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, + callingUid)) { + // Change is not enabled, thus not targeting T+. Assume exported. + flags |= Context.RECEIVER_EXPORTED; + } } // Dynamic receivers are exported by default for versions prior to T - final boolean exported = - ((flags & Context.RECEIVER_EXPORTED) != 0 - || (!CompatChanges.isChangeEnabled( - DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, callingUid))); + final boolean exported = (flags & Context.RECEIVER_EXPORTED) != 0; ArrayList<Intent> allSticky = null; if (stickyIntents != null) { @@ -15356,8 +15379,8 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public void setStopBackgroundUsersOnSwitch(@StopBgUsersOnSwitch int value) { - mUserController.setStopBackgroundUsersOnSwitch(value); + public void setStopUserOnSwitch(@StopUserOnSwitch int value) { + mUserController.setStopUserOnSwitch(value); } @Override @@ -16685,8 +16708,8 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public void setStopBackgroundUsersOnSwitch(int value) { - ActivityManagerService.this.setStopBackgroundUsersOnSwitch(value); + public void setStopUserOnSwitch(int value) { + ActivityManagerService.this.setStopUserOnSwitch(value); } } diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 25adddd0286b..5b33a710029d 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -87,6 +87,7 @@ import android.os.ShellCommand; import android.os.StrictMode; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.text.TextUtils; @@ -333,7 +334,7 @@ final class ActivityManagerShellCommand extends ShellCommand { case "get-isolated-pids": return runGetIsolatedProcesses(pw); case "set-stop-user-on-switch": - return runSetStopBackgroundUsersOnSwitch(pw); + return runSetStopUserOnSwitch(pw); default: return handleDefaultCommands(cmd); } @@ -1889,16 +1890,21 @@ final class ActivityManagerShellCommand extends ShellCommand { int userId = Integer.parseInt(getNextArgRequired()); boolean switched; - if (wait) { - switched = switchUserAndWaitForComplete(userId); - } else { - switched = mInterface.switchUser(userId); - } - if (switched) { - return 0; - } else { - pw.printf("Error: Failed to switch to user %d\n", userId); - return 1; + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "shell_runSwitchUser"); + try { + if (wait) { + switched = switchUserAndWaitForComplete(userId); + } else { + switched = mInterface.switchUser(userId); + } + if (switched) { + return 0; + } else { + pw.printf("Error: Failed to switch to user %d\n", userId); + return 1; + } + } finally { + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } } @@ -3184,25 +3190,24 @@ final class ActivityManagerShellCommand extends ShellCommand { return 0; } - private int runSetStopBackgroundUsersOnSwitch(PrintWriter pw) throws RemoteException { + private int runSetStopUserOnSwitch(PrintWriter pw) throws RemoteException { mInternal.enforceCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, - "setStopBackgroundUsersOnSwitch()"); + "setStopUserOnSwitch()"); String arg = getNextArg(); if (arg == null) { - Slogf.i(TAG, "runSetStopBackgroundUsersOnSwitch(): resetting to default value"); - mInternal.setStopBackgroundUsersOnSwitch( - ActivityManager.STOP_BG_USERS_ON_SWITCH_DEFAULT); + Slogf.i(TAG, "setStopUserOnSwitch(): resetting to default value"); + mInternal.setStopUserOnSwitch(ActivityManager.STOP_USER_ON_SWITCH_DEFAULT); pw.println("Reset to default value"); return 0; } boolean stop = Boolean.parseBoolean(arg); int value = stop - ? ActivityManager.STOP_BG_USERS_ON_SWITCH_TRUE - : ActivityManager.STOP_BG_USERS_ON_SWITCH_FALSE; + ? ActivityManager.STOP_USER_ON_SWITCH_TRUE + : ActivityManager.STOP_USER_ON_SWITCH_FALSE; - Slogf.i(TAG, "runSetStopBackgroundUsersOnSwitch(): setting to %d (%b)", value, stop); - mInternal.setStopBackgroundUsersOnSwitch(value); + Slogf.i(TAG, "runSetStopUserOnSwitch(): setting to %d (%b)", value, stop); + mInternal.setStopUserOnSwitch(value); pw.println("Set to " + stop); return 0; diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 8638c7da4da2..fd6f0994ecfb 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -792,7 +792,7 @@ public final class BroadcastQueue { + " due to receiver " + filter.receiverList.app + " (uid " + filter.receiverList.uid + ")" + " not specifying RECEIVER_EXPORTED"); - // skip = true; + skip = true; } if (skip) { diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index b5a9ee9adf77..c48ff9f9f2cc 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -97,6 +97,7 @@ public class SettingsToPropertiesMapper { DeviceConfig.NAMESPACE_SURFACE_FLINGER_NATIVE_BOOT, DeviceConfig.NAMESPACE_SWCODEC_NATIVE, DeviceConfig.NAMESPACE_TETHERING, + DeviceConfig.NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE, DeviceConfig.NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT, }; diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 0c518a0e988e..16d7f1d0ebd1 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -19,9 +19,9 @@ package com.android.server.am; import static android.Manifest.permission.INTERACT_ACROSS_PROFILES; import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; -import static android.app.ActivityManager.STOP_BG_USERS_ON_SWITCH_DEFAULT; -import static android.app.ActivityManager.STOP_BG_USERS_ON_SWITCH_TRUE; -import static android.app.ActivityManager.StopBgUsersOnSwitch; +import static android.app.ActivityManager.STOP_USER_ON_SWITCH_DEFAULT; +import static android.app.ActivityManager.STOP_USER_ON_SWITCH_TRUE; +import static android.app.ActivityManager.StopUserOnSwitch; import static android.app.ActivityManager.USER_OP_ERROR_IS_SYSTEM; import static android.app.ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP; import static android.app.ActivityManager.USER_OP_IS_CURRENT; @@ -376,7 +376,7 @@ class UserController implements Handler.Callback { * user is switched. */ @GuardedBy("mLock") - private @StopBgUsersOnSwitch int mStopBgUsersOnSwitch = STOP_BG_USERS_ON_SWITCH_DEFAULT; + private @StopUserOnSwitch int mStopUserOnSwitch = STOP_USER_ON_SWITCH_DEFAULT; UserController(ActivityManagerService service) { this(new Injector(service)); @@ -418,29 +418,27 @@ class UserController implements Handler.Callback { } } - void setStopBackgroundUsersOnSwitch(@StopBgUsersOnSwitch int value) { + void setStopUserOnSwitch(@StopUserOnSwitch int value) { if (mInjector.checkCallingPermission(android.Manifest.permission.MANAGE_USERS) == PackageManager.PERMISSION_DENIED && mInjector.checkCallingPermission( android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) == PackageManager.PERMISSION_DENIED) { throw new SecurityException( "You either need MANAGE_USERS or INTERACT_ACROSS_USERS_FULL permission to " - + "call setStopBackgroundUsersOnSwitch()"); + + "call setStopUserOnSwitch()"); } synchronized (mLock) { - Slogf.i(TAG, "setStopBackgroundUsersOnSwitch(): %d -> %d", - mStopBgUsersOnSwitch, value); - mStopBgUsersOnSwitch = value; + Slogf.i(TAG, "setStopUserOnSwitch(): %d -> %d", mStopUserOnSwitch, value); + mStopUserOnSwitch = value; } } - private boolean shouldStopBackgroundUsersOnSwitch() { + private boolean shouldStopUserOnSwitch() { synchronized (mLock) { - if (mStopBgUsersOnSwitch != STOP_BG_USERS_ON_SWITCH_DEFAULT) { - final boolean value = mStopBgUsersOnSwitch == STOP_BG_USERS_ON_SWITCH_TRUE; - Slogf.i(TAG, "isStopBackgroundUsersOnSwitch(): returning overridden value (%b)", - value); + if (mStopUserOnSwitch != STOP_USER_ON_SWITCH_DEFAULT) { + final boolean value = mStopUserOnSwitch == STOP_USER_ON_SWITCH_TRUE; + Slogf.i(TAG, "shouldStopUserOnSwitch(): returning overridden value (%b)", value); return value; } } @@ -1408,7 +1406,7 @@ class UserController implements Handler.Callback { @Nullable IProgressListener unlockListener) { TimingsTraceAndSlog t = new TimingsTraceAndSlog(); - t.traceBegin("startUser-" + userId + "-" + (foreground ? "fg" : "bg")); + t.traceBegin("UserController.startUser-" + userId + "-" + (foreground ? "fg" : "bg")); try { return startUserInternal(userId, foreground, unlockListener, t); } finally { @@ -1846,7 +1844,7 @@ class UserController implements Handler.Callback { mUserSwitchObservers.finishBroadcast(); } - private void stopBackgroundUsersOnSwitchIfEnforced(@UserIdInt int oldUserId) { + private void stopUserOnSwitchIfEnforced(@UserIdInt int oldUserId) { // Never stop system user if (oldUserId == UserHandle.USER_SYSTEM) { return; @@ -1854,18 +1852,17 @@ class UserController implements Handler.Callback { boolean hasRestriction = hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, oldUserId); synchronized (mLock) { - // If running in background is disabled or mStopBackgroundUsersOnSwitch mode, - // stop the user. - boolean disallowRunInBg = hasRestriction || shouldStopBackgroundUsersOnSwitch(); + // If running in background is disabled or mStopUserOnSwitch mode, stop the user. + boolean disallowRunInBg = hasRestriction || shouldStopUserOnSwitch(); if (!disallowRunInBg) { if (DEBUG_MU) { - Slogf.i(TAG, "stopBackgroundUsersIfEnforced() NOT stopping %d and related " - + "users", oldUserId); + Slogf.i(TAG, "stopUserOnSwitchIfEnforced() NOT stopping %d and related users", + oldUserId); } return; } if (DEBUG_MU) { - Slogf.i(TAG, "stopBackgroundUsersIfEnforced() stopping %d and related users", + Slogf.i(TAG, "stopUserOnSwitchIfEnforced() stopping %d and related users", oldUserId); } stopUsersLU(oldUserId, /* force= */ false, /* allowDelayedLocking= */ true, @@ -1979,7 +1976,7 @@ class UserController implements Handler.Callback { mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG); mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_COMPLETE_MSG, newUserId, 0)); stopGuestOrEphemeralUserIfBackground(oldUserId); - stopBackgroundUsersOnSwitchIfEnforced(oldUserId); + stopUserOnSwitchIfEnforced(oldUserId); t.traceEnd(); // end continueUserSwitch } @@ -2671,9 +2668,8 @@ class UserController implements Handler.Callback { pw.println(" mTargetUserId:" + mTargetUserId); pw.println(" mLastActiveUsers:" + mLastActiveUsers); pw.println(" mDelayUserDataLocking:" + mDelayUserDataLocking); - pw.println(" shouldStopBackgroundUsersOnSwitch():" - + shouldStopBackgroundUsersOnSwitch()); - pw.println(" mStopBgUsersOnSwitch:" + mStopBgUsersOnSwitch); + pw.println(" shouldStopUserOnSwitch():" + shouldStopUserOnSwitch()); + pw.println(" mStopUserOnSwitch:" + mStopUserOnSwitch); pw.println(" mMaxRunningUsers:" + mMaxRunningUsers); pw.println(" mUserSwitchUiEnabled:" + mUserSwitchUiEnabled); pw.println(" mInitialized:" + mInitialized); diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index ed494b569e87..47bd47ec20c8 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -2375,7 +2375,13 @@ public class AppOpsService extends IAppOpsService.Stub { return; } - if (!isCallerSystem && !isCallerInstrumented && !isCallerPermissionController) { + boolean doesCallerHavePermission = mContext.checkPermission( + android.Manifest.permission.GET_HISTORICAL_APP_OPS_STATS, + Binder.getCallingPid(), Binder.getCallingUid()) + == PackageManager.PERMISSION_GRANTED; + + if (!isCallerSystem && !isCallerInstrumented && !isCallerPermissionController + && !doesCallerHavePermission) { mHandler.post(() -> callback.sendResult(new Bundle())); return; } diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index e0775d48b42f..f42870b4b734 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -1381,7 +1381,8 @@ public class BiometricService extends SystemService { Slog.d(TAG, "handleAuthenticate: modality(" + preAuthStatus.first + "), status(" + preAuthStatus.second + "), preAuthInfo: " + preAuthInfo - + " requestId: " + requestId); + + " requestId: " + requestId + " promptInfo.isIgnoreEnrollmentState: " + + promptInfo.isIgnoreEnrollmentState()); if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS) { // If BIOMETRIC_WEAK or BIOMETRIC_STRONG are allowed, but not enrolled, but diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java index cd0ff10168bb..a5a3542f49c7 100644 --- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java +++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java @@ -83,6 +83,7 @@ class PreAuthInfo { final List<Pair<BiometricSensor, Integer>> ineligibleSensors; final boolean credentialAvailable; final boolean confirmationRequested; + final boolean ignoreEnrollmentState; static PreAuthInfo create(ITrustManager trustManager, DevicePolicyManager devicePolicyManager, @@ -114,7 +115,8 @@ class PreAuthInfo { @AuthenticatorStatus int status = getStatusForBiometricAuthenticator( devicePolicyManager, settingObserver, sensor, userId, opPackageName, checkDevicePolicyManager, requestedStrength, - promptInfo.getAllowedSensorIds()); + promptInfo.getAllowedSensorIds(), + promptInfo.isIgnoreEnrollmentState()); Slog.d(TAG, "Package: " + opPackageName + " Sensor ID: " + sensor.id @@ -130,7 +132,8 @@ class PreAuthInfo { } return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested, - eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested); + eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested, + promptInfo.isIgnoreEnrollmentState()); } /** @@ -145,7 +148,8 @@ class PreAuthInfo { BiometricService.SettingObserver settingObserver, BiometricSensor sensor, int userId, String opPackageName, boolean checkDevicePolicyManager, int requestedStrength, - @NonNull List<Integer> requestedSensorIds) { + @NonNull List<Integer> requestedSensorIds, + boolean ignoreEnrollmentState) { if (!requestedSensorIds.isEmpty() && !requestedSensorIds.contains(sensor.id)) { return BIOMETRIC_NO_HARDWARE; @@ -167,7 +171,8 @@ class PreAuthInfo { return BIOMETRIC_HARDWARE_NOT_DETECTED; } - if (!sensor.impl.hasEnrolledTemplates(userId, opPackageName)) { + if (!sensor.impl.hasEnrolledTemplates(userId, opPackageName) + && !ignoreEnrollmentState) { return BIOMETRIC_NOT_ENROLLED; } @@ -238,7 +243,7 @@ class PreAuthInfo { private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested, boolean credentialRequested, List<BiometricSensor> eligibleSensors, List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable, - boolean confirmationRequested) { + boolean confirmationRequested, boolean ignoreEnrollmentState) { mBiometricRequested = biometricRequested; mBiometricStrengthRequested = biometricStrengthRequested; this.credentialRequested = credentialRequested; @@ -247,6 +252,7 @@ class PreAuthInfo { this.ineligibleSensors = ineligibleSensors; this.credentialAvailable = credentialAvailable; this.confirmationRequested = confirmationRequested; + this.ignoreEnrollmentState = ignoreEnrollmentState; } private Pair<BiometricSensor, Integer> calculateErrorByPriority() { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java index af8e8c2f2c79..3e70ee52ff1b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java @@ -281,7 +281,7 @@ public class FingerprintService extends SystemService { @Override // Binder call public long authenticate(final IBinder token, final long operationId, final int sensorId, final int userId, final IFingerprintServiceReceiver receiver, - final String opPackageName) { + final String opPackageName, boolean ignoreEnrollmentState) { final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); final int callingUserId = UserHandle.getCallingUserId(); @@ -333,7 +333,8 @@ public class FingerprintService extends SystemService { && sensorProps != null && sensorProps.isAnyUdfpsType()) { final long identity2 = Binder.clearCallingIdentity(); try { - return authenticateWithPrompt(operationId, sensorProps, userId, receiver); + return authenticateWithPrompt(operationId, sensorProps, userId, receiver, + ignoreEnrollmentState); } finally { Binder.restoreCallingIdentity(identity2); } @@ -347,7 +348,8 @@ public class FingerprintService extends SystemService { final long operationId, @NonNull final FingerprintSensorPropertiesInternal props, final int userId, - final IFingerprintServiceReceiver receiver) { + final IFingerprintServiceReceiver receiver, + boolean ignoreEnrollmentState) { final Context context = getUiContext(); final Executor executor = context.getMainExecutor(); @@ -368,6 +370,7 @@ public class FingerprintService extends SystemService { }) .setAllowedSensorIds(new ArrayList<>( Collections.singletonList(props.sensorId))) + .setIgnoreEnrollmentState(ignoreEnrollmentState) .build(); final BiometricPrompt.AuthenticationCallback promptCallback = diff --git a/services/core/java/com/android/server/compat/OWNERS b/services/core/java/com/android/server/compat/OWNERS index cfd0a4b079ad..ee3086ab2fdb 100644 --- a/services/core/java/com/android/server/compat/OWNERS +++ b/services/core/java/com/android/server/compat/OWNERS @@ -1,6 +1 @@ -# Use this reviewer by default. -platform-compat-eng+reviews@google.com - -andreionea@google.com -mathewi@google.com -satayev@google.com +include tools/platform-compat:/OWNERS diff --git a/services/core/java/com/android/server/health/HealthHalCallbackHidl.java b/services/core/java/com/android/server/health/HealthHalCallbackHidl.java new file mode 100644 index 000000000000..7a6698085c0d --- /dev/null +++ b/services/core/java/com/android/server/health/HealthHalCallbackHidl.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.health; + +import static android.hardware.health.Translate.h2aTranslate; + +import android.annotation.NonNull; +import android.hardware.health.V2_0.IHealth; +import android.hardware.health.V2_0.Result; +import android.hardware.health.V2_1.BatteryCapacityLevel; +import android.hardware.health.V2_1.Constants; +import android.hardware.health.V2_1.IHealthInfoCallback; +import android.os.RemoteException; +import android.os.Trace; +import android.util.Slog; + +/** + * On service registration, {@link HealthServiceWrapperHidl.Callback#onRegistration} is called, + * which registers {@code this}, a {@link IHealthInfoCallback}, to the health service. + * + * <p>When the health service has updates to health info, {@link HealthInfoCallback#update} is + * called. + * + * @hide + */ +class HealthHalCallbackHidl extends IHealthInfoCallback.Stub + implements HealthServiceWrapperHidl.Callback { + + private static final String TAG = HealthHalCallbackHidl.class.getSimpleName(); + + private static void traceBegin(String name) { + Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, name); + } + + private static void traceEnd() { + Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); + } + + private HealthInfoCallback mCallback; + + HealthHalCallbackHidl(@NonNull HealthInfoCallback callback) { + mCallback = callback; + } + + @Override + public void healthInfoChanged(android.hardware.health.V2_0.HealthInfo props) { + android.hardware.health.V2_1.HealthInfo propsLatest = + new android.hardware.health.V2_1.HealthInfo(); + propsLatest.legacy = props; + + propsLatest.batteryCapacityLevel = BatteryCapacityLevel.UNSUPPORTED; + propsLatest.batteryChargeTimeToFullNowSeconds = + Constants.BATTERY_CHARGE_TIME_TO_FULL_NOW_SECONDS_UNSUPPORTED; + + mCallback.update(h2aTranslate(propsLatest)); + } + + @Override + public void healthInfoChanged_2_1(android.hardware.health.V2_1.HealthInfo props) { + mCallback.update(h2aTranslate(props)); + } + + // on new service registered + @Override + public void onRegistration(IHealth oldService, IHealth newService, String instance) { + if (newService == null) return; + + traceBegin("HealthUnregisterCallback"); + try { + if (oldService != null) { + int r = oldService.unregisterCallback(this); + if (r != Result.SUCCESS) { + Slog.w( + TAG, + "health: cannot unregister previous callback: " + Result.toString(r)); + } + } + } catch (RemoteException ex) { + Slog.w( + TAG, + "health: cannot unregister previous callback (transaction error): " + + ex.getMessage()); + } finally { + traceEnd(); + } + + traceBegin("HealthRegisterCallback"); + try { + int r = newService.registerCallback(this); + if (r != Result.SUCCESS) { + Slog.w(TAG, "health: cannot register callback: " + Result.toString(r)); + return; + } + // registerCallback does NOT guarantee that update is called + // immediately, so request a manual update here. + newService.update(); + } catch (RemoteException ex) { + Slog.e(TAG, "health: cannot register callback (transaction error): " + ex.getMessage()); + } finally { + traceEnd(); + } + } +} diff --git a/services/core/java/com/android/server/health/HealthInfoCallback.java b/services/core/java/com/android/server/health/HealthInfoCallback.java new file mode 100644 index 000000000000..c2a77fc862fa --- /dev/null +++ b/services/core/java/com/android/server/health/HealthInfoCallback.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.health; + +/** + * A wrapper over HIDL / AIDL IHealthInfoCallback. + * + * @hide + */ +public interface HealthInfoCallback { + /** + * Signals to the client that health info is changed. + * + * @param props the new health info. + */ + void update(android.hardware.health.HealthInfo props); +} diff --git a/services/core/java/com/android/server/health/HealthServiceWrapper.java b/services/core/java/com/android/server/health/HealthServiceWrapper.java new file mode 100644 index 000000000000..9b97554ee259 --- /dev/null +++ b/services/core/java/com/android/server/health/HealthServiceWrapper.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.health; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.BatteryProperty; +import android.os.HandlerThread; +import android.os.IBatteryPropertiesRegistrar; +import android.os.RemoteException; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.NoSuchElementException; + +/** + * HealthServiceWrapper wraps the internal IHealth service and refreshes the service when necessary. + * This is essentially a wrapper over IHealth that is useful for BatteryService. + * + * <p>The implementation may be backed by a HIDL or AIDL HAL. + * + * <p>On new registration of IHealth service, the internal service is refreshed. On death of an + * existing IHealth service, the internal service is NOT cleared to avoid race condition between + * death notification and new service notification. Hence, a caller must check for transaction + * errors when calling into the service. + * + * @hide Should only be used internally. + */ +public abstract class HealthServiceWrapper { + /** @return the handler thread. Exposed for testing. */ + @VisibleForTesting + abstract HandlerThread getHandlerThread(); + + /** + * Calls into get*() functions in the health HAL. This reads into the kernel interfaces + * directly. + * + * @see IBatteryPropertiesRegistrar#getProperty + */ + public abstract int getProperty(int id, BatteryProperty prop) throws RemoteException; + + /** + * Calls update() in the health HAL. + * + * @see IBatteryPropertiesRegistrar#scheduleUpdate + */ + public abstract void scheduleUpdate() throws RemoteException; + + /** + * Calls into getHealthInfo() in the health HAL. This returns a cached value in the health HAL + * implementation. + * + * @return health info. {@code null} if no health HAL service. {@code null} if any + * service-specific error when calling {@code getHealthInfo}, e.g. it is unsupported. + * @throws RemoteException for any transaction-level errors + */ + public abstract android.hardware.health.HealthInfo getHealthInfo() throws RemoteException; + + /** + * Create a new HealthServiceWrapper instance. + * + * @param healthInfoCallback the callback to call when health info changes + * @return the new HealthServiceWrapper instance, which may be backed by HIDL or AIDL service. + * @throws RemoteException transaction errors + * @throws NoSuchElementException no HIDL or AIDL service is available + */ + public static HealthServiceWrapper create(@Nullable HealthInfoCallback healthInfoCallback) + throws RemoteException, NoSuchElementException { + return create( + healthInfoCallback == null ? null : new HealthHalCallbackHidl(healthInfoCallback), + new HealthServiceWrapperHidl.IServiceManagerSupplier() {}, + new HealthServiceWrapperHidl.IHealthSupplier() {}); + } + + /** + * Create a new HealthServiceWrapper instance for testing. + * + * @param hidlRegCallback callback for HIDL service registration, or {@code null} if the client + * does not care about HIDL service registration notifications + * @param hidlServiceManagerSupplier supplier of HIDL service manager + * @param hidlHealthSupplier supplier of HIDL health HAL + * @return the new HealthServiceWrapper instance, which may be backed by HIDL or AIDL service. + */ + @VisibleForTesting + static @NonNull HealthServiceWrapper create( + @Nullable HealthServiceWrapperHidl.Callback hidlRegCallback, + @NonNull HealthServiceWrapperHidl.IServiceManagerSupplier hidlServiceManagerSupplier, + @NonNull HealthServiceWrapperHidl.IHealthSupplier hidlHealthSupplier) + throws RemoteException, NoSuchElementException { + return new HealthServiceWrapperHidl( + hidlRegCallback, hidlServiceManagerSupplier, hidlHealthSupplier); + } +} diff --git a/services/core/java/com/android/server/health/HealthServiceWrapperHidl.java b/services/core/java/com/android/server/health/HealthServiceWrapperHidl.java new file mode 100644 index 000000000000..0301174a45c6 --- /dev/null +++ b/services/core/java/com/android/server/health/HealthServiceWrapperHidl.java @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.health; + +import static android.hardware.health.Translate.h2aTranslate; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.hardware.health.HealthInfo; +import android.hardware.health.V2_0.IHealth; +import android.hardware.health.V2_0.Result; +import android.hidl.manager.V1_0.IServiceManager; +import android.hidl.manager.V1_0.IServiceNotification; +import android.os.BatteryManager; +import android.os.BatteryProperty; +import android.os.HandlerThread; +import android.os.RemoteException; +import android.os.Trace; +import android.util.MutableInt; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Implement {@link HealthServiceWrapper} backed by the HIDL HAL. + * + * @hide + */ +final class HealthServiceWrapperHidl extends HealthServiceWrapper { + private static final String TAG = "HealthServiceWrapperHidl"; + public static final String INSTANCE_VENDOR = "default"; + + private final IServiceNotification mNotification = new Notification(); + private final HandlerThread mHandlerThread = new HandlerThread("HealthServiceHwbinder"); + // These variables are fixed after init. + private Callback mCallback; + private IHealthSupplier mHealthSupplier; + private String mInstanceName; + + // Last IHealth service received. + private final AtomicReference<IHealth> mLastService = new AtomicReference<>(); + + private static void traceBegin(String name) { + Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, name); + } + + private static void traceEnd() { + Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); + } + + @Override + public int getProperty(int id, final BatteryProperty prop) throws RemoteException { + traceBegin("HealthGetProperty"); + try { + IHealth service = mLastService.get(); + if (service == null) throw new RemoteException("no health service"); + final MutableInt outResult = new MutableInt(Result.NOT_SUPPORTED); + switch (id) { + case BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER: + service.getChargeCounter( + (int result, int value) -> { + outResult.value = result; + if (result == Result.SUCCESS) prop.setLong(value); + }); + break; + case BatteryManager.BATTERY_PROPERTY_CURRENT_NOW: + service.getCurrentNow( + (int result, int value) -> { + outResult.value = result; + if (result == Result.SUCCESS) prop.setLong(value); + }); + break; + case BatteryManager.BATTERY_PROPERTY_CURRENT_AVERAGE: + service.getCurrentAverage( + (int result, int value) -> { + outResult.value = result; + if (result == Result.SUCCESS) prop.setLong(value); + }); + break; + case BatteryManager.BATTERY_PROPERTY_CAPACITY: + service.getCapacity( + (int result, int value) -> { + outResult.value = result; + if (result == Result.SUCCESS) prop.setLong(value); + }); + break; + case BatteryManager.BATTERY_PROPERTY_STATUS: + service.getChargeStatus( + (int result, int value) -> { + outResult.value = result; + if (result == Result.SUCCESS) prop.setLong(value); + }); + break; + case BatteryManager.BATTERY_PROPERTY_ENERGY_COUNTER: + service.getEnergyCounter( + (int result, long value) -> { + outResult.value = result; + if (result == Result.SUCCESS) prop.setLong(value); + }); + break; + } + return outResult.value; + } finally { + traceEnd(); + } + } + + @Override + public void scheduleUpdate() throws RemoteException { + getHandlerThread() + .getThreadHandler() + .post( + () -> { + traceBegin("HealthScheduleUpdate"); + try { + IHealth service = mLastService.get(); + if (service == null) { + Slog.e(TAG, "no health service"); + return; + } + service.update(); + } catch (RemoteException ex) { + Slog.e(TAG, "Cannot call update on health HAL", ex); + } finally { + traceEnd(); + } + }); + } + + private static class Mutable<T> { + public T value; + } + + @Override + public HealthInfo getHealthInfo() throws RemoteException { + IHealth service = mLastService.get(); + if (service == null) return null; + final Mutable<HealthInfo> ret = new Mutable<>(); + service.getHealthInfo( + (result, value) -> { + if (result == Result.SUCCESS) { + ret.value = h2aTranslate(value.legacy); + } + }); + return ret.value; + } + + /** + * Start monitoring registration of new IHealth services. Only instance {@link #INSTANCE_VENDOR} + * and in device / framework manifest are used. This function should only be called once. + * + * <p>mCallback.onRegistration() is called synchronously (aka in init thread) before this method + * returns if callback is not null. + * + * @throws RemoteException transaction error when talking to IServiceManager + * @throws NoSuchElementException if one of the following cases: - No service manager; - {@link + * #INSTANCE_VENDOR} is not in manifests (i.e. not available on this device), or none of + * these instances are available to current process. + * @throws NullPointerException when supplier is null + */ + @VisibleForTesting + HealthServiceWrapperHidl( + @Nullable Callback callback, + @NonNull IServiceManagerSupplier managerSupplier, + @NonNull IHealthSupplier healthSupplier) + throws RemoteException, NoSuchElementException, NullPointerException { + if (managerSupplier == null || healthSupplier == null) { + throw new NullPointerException(); + } + mHealthSupplier = healthSupplier; + + // Initialize mLastService and call callback for the first time (in init thread) + IHealth newService = null; + traceBegin("HealthInitGetService_" + INSTANCE_VENDOR); + try { + newService = healthSupplier.get(INSTANCE_VENDOR); + } catch (NoSuchElementException ex) { + /* ignored, handled below */ + } finally { + traceEnd(); + } + if (newService != null) { + mInstanceName = INSTANCE_VENDOR; + mLastService.set(newService); + } + + if (mInstanceName == null || newService == null) { + throw new NoSuchElementException( + String.format( + "IHealth service instance %s isn't available. Perhaps no permission?", + INSTANCE_VENDOR)); + } + + if (callback != null) { + mCallback = callback; + mCallback.onRegistration(null, newService, mInstanceName); + } + + // Register for future service registrations + traceBegin("HealthInitRegisterNotification"); + mHandlerThread.start(); + try { + managerSupplier + .get() + .registerForNotifications(IHealth.kInterfaceName, mInstanceName, mNotification); + } finally { + traceEnd(); + } + Slog.i(TAG, "health: HealthServiceWrapper listening to instance " + mInstanceName); + } + + @VisibleForTesting + public HandlerThread getHandlerThread() { + return mHandlerThread; + } + + /** Service registration callback. */ + interface Callback { + /** + * This function is invoked asynchronously when a new and related IServiceNotification is + * received. + * + * @param service the recently retrieved service from IServiceManager. Can be a dead service + * before service notification of a new service is delivered. Implementation must handle + * cases for {@link RemoteException}s when calling into service. + * @param instance instance name. + */ + void onRegistration(IHealth oldService, IHealth newService, String instance); + } + + /** + * Supplier of services. Must not return null; throw {@link NoSuchElementException} if a service + * is not available. + */ + interface IServiceManagerSupplier { + default IServiceManager get() throws NoSuchElementException, RemoteException { + return IServiceManager.getService(); + } + } + + /** + * Supplier of services. Must not return null; throw {@link NoSuchElementException} if a service + * is not available. + */ + interface IHealthSupplier { + default IHealth get(String name) throws NoSuchElementException, RemoteException { + return IHealth.getService(name, true /* retry */); + } + } + + private class Notification extends IServiceNotification.Stub { + @Override + public final void onRegistration( + String interfaceName, String instanceName, boolean preexisting) { + if (!IHealth.kInterfaceName.equals(interfaceName)) return; + if (!mInstanceName.equals(instanceName)) return; + + // This runnable only runs on mHandlerThread and ordering is ensured, hence + // no locking is needed inside the runnable. + mHandlerThread + .getThreadHandler() + .post( + new Runnable() { + @Override + public void run() { + try { + IHealth newService = mHealthSupplier.get(mInstanceName); + IHealth oldService = mLastService.getAndSet(newService); + + // preexisting may be inaccurate (race). Check for equality + // here. + if (Objects.equals(newService, oldService)) return; + + Slog.i( + TAG, + "health: new instance registered " + mInstanceName); + // #init() may be called with null callback. Skip null + // callbacks. + if (mCallback == null) return; + mCallback.onRegistration( + oldService, newService, mInstanceName); + } catch (NoSuchElementException | RemoteException ex) { + Slog.e( + TAG, + "health: Cannot get instance '" + + mInstanceName + + "': " + + ex.getMessage() + + ". Perhaps no permission?"); + } + } + }); + } + } +} diff --git a/services/core/java/com/android/server/health/Utils.java b/services/core/java/com/android/server/health/Utils.java new file mode 100644 index 000000000000..a8c978c50e42 --- /dev/null +++ b/services/core/java/com/android/server/health/Utils.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.health; + +/** + * Utils for {@link om.android.server.BatteryService} to deal with health info structs. + * + * @hide + */ +public class Utils { + private Utils() {} + + /** + * Copy health info struct. + * + * @param dst destination + * @param src source + */ + public static void copy( + android.hardware.health.V1_0.HealthInfo dst, + android.hardware.health.V1_0.HealthInfo src) { + dst.chargerAcOnline = src.chargerAcOnline; + dst.chargerUsbOnline = src.chargerUsbOnline; + dst.chargerWirelessOnline = src.chargerWirelessOnline; + dst.maxChargingCurrent = src.maxChargingCurrent; + dst.maxChargingVoltage = src.maxChargingVoltage; + dst.batteryStatus = src.batteryStatus; + dst.batteryHealth = src.batteryHealth; + dst.batteryPresent = src.batteryPresent; + dst.batteryLevel = src.batteryLevel; + dst.batteryVoltage = src.batteryVoltage; + dst.batteryTemperature = src.batteryTemperature; + dst.batteryCurrent = src.batteryCurrent; + dst.batteryCycleCount = src.batteryCycleCount; + dst.batteryFullCharge = src.batteryFullCharge; + dst.batteryChargeCounter = src.batteryChargeCounter; + dst.batteryTechnology = src.batteryTechnology; + } + + /** + * Copy battery fields of {@link android.hardware.health.HealthInfo} V1. This excludes + * non-battery fields like {@link android.hardware.health.HealthInfo#diskStats diskStats} and + * {@link android.hardware.health.HealthInfo#storageInfos storageInfos} + * + * @param dst destination + * @param src source + */ + public static void copyV1Battery( + android.hardware.health.HealthInfo dst, android.hardware.health.HealthInfo src) { + dst.chargerAcOnline = src.chargerAcOnline; + dst.chargerUsbOnline = src.chargerUsbOnline; + dst.chargerWirelessOnline = src.chargerWirelessOnline; + dst.maxChargingCurrentMicroamps = src.maxChargingCurrentMicroamps; + dst.maxChargingVoltageMicrovolts = src.maxChargingVoltageMicrovolts; + dst.batteryStatus = src.batteryStatus; + dst.batteryHealth = src.batteryHealth; + dst.batteryPresent = src.batteryPresent; + dst.batteryLevel = src.batteryLevel; + dst.batteryVoltageMillivolts = src.batteryVoltageMillivolts; + dst.batteryTemperatureTenthsCelsius = src.batteryTemperatureTenthsCelsius; + dst.batteryCurrentMicroamps = src.batteryCurrentMicroamps; + dst.batteryCycleCount = src.batteryCycleCount; + dst.batteryFullChargeUah = src.batteryFullChargeUah; + dst.batteryChargeCounterUah = src.batteryChargeCounterUah; + dst.batteryTechnology = src.batteryTechnology; + dst.batteryCurrentAverageMicroamps = src.batteryCurrentAverageMicroamps; + dst.batteryCapacityLevel = src.batteryCapacityLevel; + dst.batteryChargeTimeToFullNowSeconds = src.batteryChargeTimeToFullNowSeconds; + dst.batteryFullChargeDesignCapacityUah = src.batteryFullChargeDesignCapacityUah; + } +} diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index d14ef162de3c..619071da43c5 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -1827,7 +1827,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub broadcastFilterForAllUsers.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); mContext.registerReceiverAsUser(new ImmsBroadcastReceiverForAllUsers(), UserHandle.ALL, broadcastFilterForAllUsers, null, null, - Context.RECEIVER_NOT_EXPORTED); + Context.RECEIVER_EXPORTED); final String defaultImiId = mSettings.getSelectedInputMethod(); final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId); diff --git a/services/core/java/com/android/server/locales/TEST_MAPPING b/services/core/java/com/android/server/locales/TEST_MAPPING index 72b9c484cd8b..097c2bc262e1 100644 --- a/services/core/java/com/android/server/locales/TEST_MAPPING +++ b/services/core/java/com/android/server/locales/TEST_MAPPING @@ -7,6 +7,9 @@ "include-filter": "com.android.server.locales." } ] + }, + { + "name": "CtsLocaleManagerTestCases" } ] }
\ No newline at end of file diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java index 607218e20ea8..b424c2083bd4 100644 --- a/services/core/java/com/android/server/media/MediaSession2Record.java +++ b/services/core/java/com/android/server/media/MediaSession2Record.java @@ -146,6 +146,12 @@ public class MediaSession2Record implements MediaSessionRecordImpl { } @Override + public boolean canHandleVolumeKey() { + // TODO: Implement when MediaSession2 starts to get key events. + return false; + } + + @Override public int getSessionPolicies() { synchronized (mLock) { return mPolicies; diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 1525cd4da669..e4ed0e5d4186 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -26,7 +26,9 @@ import android.media.AudioAttributes; import android.media.AudioManager; import android.media.AudioSystem; import android.media.MediaMetadata; +import android.media.MediaRouter2Manager; import android.media.Rating; +import android.media.RoutingSessionInfo; import android.media.VolumeProvider; import android.media.session.ISession; import android.media.session.ISessionCallback; @@ -50,6 +52,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.SystemClock; +import android.text.TextUtils; import android.util.Log; import android.view.KeyEvent; @@ -121,6 +124,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR private final SessionCb mSessionCb; private final MediaSessionService mService; private final Context mContext; + private final boolean mVolumeAdjustmentForRemoteGroupSessions; private final Object mLock = new Object(); private final CopyOnWriteArrayList<ISessionControllerCallbackHolder> @@ -180,6 +184,8 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); mAudioAttrs = DEFAULT_ATTRIBUTES; mPolicies = policies; + mVolumeAdjustmentForRemoteGroupSessions = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions); // May throw RemoteException if the session app is killed. mSessionCb.mCb.asBinder().linkToDeath(this, 0); @@ -449,6 +455,33 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR } @Override + public boolean canHandleVolumeKey() { + if (isPlaybackTypeLocal() || mVolumeAdjustmentForRemoteGroupSessions) { + return true; + } + MediaRouter2Manager mRouter2Manager = MediaRouter2Manager.getInstance(mContext); + List<RoutingSessionInfo> sessions = + mRouter2Manager.getRoutingSessions(mPackageName); + boolean foundNonSystemSession = false; + boolean isGroup = false; + for (RoutingSessionInfo session : sessions) { + if (!session.isSystemSession()) { + foundNonSystemSession = true; + int selectedRouteCount = session.getSelectedRoutes().size(); + if (selectedRouteCount > 1) { + isGroup = true; + break; + } + } + } + if (!foundNonSystemSession) { + Log.d(TAG, "No routing session for " + mPackageName); + return false; + } + return !isGroup; + } + + @Override public int getSessionPolicies() { synchronized (mLock) { return mPolicies; diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java index 3c50597b8cfc..8f01f02f2ab1 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java +++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java @@ -131,6 +131,13 @@ public interface MediaSessionRecordImpl extends AutoCloseable { KeyEvent ke, int sequenceId, ResultReceiver cb); /** + * Returns whether the media session can handle volume key events. + * + * @return True if this media session can handle volume key events, false otherwise. + */ + boolean canHandleVolumeKey(); + + /** * Get session policies from custom policy provider set when MediaSessionRecord is instantiated. * If custom policy does not exist, will return null. */ diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java index c4c21df746b3..b75ba75e028b 100644 --- a/services/core/java/com/android/server/media/MediaSessionStack.java +++ b/services/core/java/com/android/server/media/MediaSessionStack.java @@ -325,8 +325,7 @@ class MediaSessionStack { int size = records.size(); for (int i = 0; i < size; i++) { MediaSessionRecord record = records.get(i); - // Do not send the volume key events to remote sessions. - if (record.checkPlaybackActiveState(true) && record.isPlaybackTypeLocal()) { + if (record.checkPlaybackActiveState(true) && record.canHandleVolumeKey()) { mCachedVolumeDefault = record; return record; } diff --git a/services/core/java/com/android/server/net/OWNERS b/services/core/java/com/android/server/net/OWNERS index 28ae6a417bd3..a15fc3eef539 100644 --- a/services/core/java/com/android/server/net/OWNERS +++ b/services/core/java/com/android/server/net/OWNERS @@ -1,11 +1,7 @@ set noparent -codewiz@google.com -jchalard@google.com +include platform/packages/modules/Connectivity:/OWNERS + jsharkey@android.com -junyulai@google.com -lorenzo@google.com -reminv@google.com -satk@google.com sudheersai@google.com yamasani@google.com diff --git a/services/core/java/com/android/server/notification/CountdownConditionProvider.java b/services/core/java/com/android/server/notification/CountdownConditionProvider.java index d04aac2f5717..471c9b97200f 100644 --- a/services/core/java/com/android/server/notification/CountdownConditionProvider.java +++ b/services/core/java/com/android/server/notification/CountdownConditionProvider.java @@ -32,6 +32,7 @@ import android.util.Log; import android.util.Slog; import com.android.server.notification.NotificationManagerService.DumpFilter; +import com.android.server.pm.PackageManagerService; import java.io.PrintWriter; @@ -114,11 +115,7 @@ public class CountdownConditionProvider extends SystemConditionProviderService { mIsAlarm = ZenModeConfig.isValidCountdownToAlarmConditionId(conditionId); final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); - final Intent intent = new Intent(ACTION) - .putExtra(EXTRA_CONDITION_ID, conditionId) - .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); - final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, REQUEST_CODE, - intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED); + final PendingIntent pendingIntent = getPendingIntent(conditionId); alarms.cancel(pendingIntent); if (mTime > 0) { final long now = System.currentTimeMillis(); @@ -138,6 +135,16 @@ public class CountdownConditionProvider extends SystemConditionProviderService { } } + PendingIntent getPendingIntent(Uri conditionId) { + final Intent intent = new Intent(ACTION) + .setPackage(PackageManagerService.PLATFORM_PACKAGE_NAME) + .putExtra(EXTRA_CONDITION_ID, conditionId) + .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, REQUEST_CODE, + intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + return pendingIntent; + } + @Override public void onUnsubscribe(Uri conditionId) { // noop diff --git a/services/core/java/com/android/server/notification/EventConditionProvider.java b/services/core/java/com/android/server/notification/EventConditionProvider.java index 25ad9280fa99..4be4f0a1e7f0 100644 --- a/services/core/java/com/android/server/notification/EventConditionProvider.java +++ b/services/core/java/com/android/server/notification/EventConditionProvider.java @@ -41,6 +41,7 @@ import android.util.SparseArray; import com.android.server.notification.CalendarTracker.CheckEventResult; import com.android.server.notification.NotificationManagerService.DumpFilter; +import com.android.server.pm.PackageManagerService; import java.io.PrintWriter; import java.util.ArrayList; @@ -266,12 +267,7 @@ public class EventConditionProvider extends SystemConditionProviderService { private void rescheduleAlarm(long now, long time) { mNextAlarmTime = time; final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); - final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, - REQUEST_CODE_EVALUATE, - new Intent(ACTION_EVALUATE) - .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) - .putExtra(EXTRA_TIME, time), - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + final PendingIntent pendingIntent = getPendingIntent(time); alarms.cancel(pendingIntent); if (time == 0 || time < now) { if (DEBUG) Slog.d(TAG, "Not scheduling evaluate: " + (time == 0 ? "no time specified" @@ -283,6 +279,17 @@ public class EventConditionProvider extends SystemConditionProviderService { alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent); } + PendingIntent getPendingIntent(long time) { + final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, + REQUEST_CODE_EVALUATE, + new Intent(ACTION_EVALUATE) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) + .setPackage(PackageManagerService.PLATFORM_PACKAGE_NAME) + .putExtra(EXTRA_TIME, time), + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + return pendingIntent; + } + private Condition createCondition(Uri id, int state) { final String summary = NOT_SHOWN; final String line1 = NOT_SHOWN; diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 5516109253df..6b7500ae4b2f 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -6784,11 +6784,13 @@ public class NotificationManagerService extends SystemService { // blocked apps + boolean isMediaNotification = n.isMediaNotification() + && n.extras.getParcelable(Notification.EXTRA_MEDIA_SESSION) != null; boolean isBlocked = !areNotificationsEnabledForPackageInt(pkg, uid); synchronized (mNotificationLock) { isBlocked |= isRecordBlockedLocked(r); } - if (isBlocked) { + if (isBlocked && !isMediaNotification) { if (DBG) { Slog.e(TAG, "Suppressing notification from package " + r.getSbn().getPackageName() + " by user request."); @@ -7174,7 +7176,13 @@ public class NotificationManagerService extends SystemService { return; } - if (appBanned || isRecordBlockedLocked(r)) { + final StatusBarNotification n = r.getSbn(); + final Notification notification = n.getNotification(); + + boolean isMediaNotification = notification.isMediaNotification() + && notification.extras.getParcelable( + Notification.EXTRA_MEDIA_SESSION) != null; + if (!isMediaNotification && (appBanned || isRecordBlockedLocked(r))) { mUsageStats.registerBlocked(r); if (DBG) { Slog.e(TAG, "Suppressing notification from package " + pkg); @@ -7189,8 +7197,6 @@ public class NotificationManagerService extends SystemService { mUsageStats.registerSuspendedByAdmin(r); } NotificationRecord old = mNotificationsByKey.get(key); - final StatusBarNotification n = r.getSbn(); - final Notification notification = n.getNotification(); // Make sure the SBN has an instance ID for statsd logging. if (old == null || old.getSbn().getInstanceId() == null) { diff --git a/services/core/java/com/android/server/pm/InstallParams.java b/services/core/java/com/android/server/pm/InstallParams.java index 0dff7d12ce74..d996fe46a4f6 100644 --- a/services/core/java/com/android/server/pm/InstallParams.java +++ b/services/core/java/com/android/server/pm/InstallParams.java @@ -169,13 +169,15 @@ final class InstallParams extends HandlerParams { final long sizeBytes = PackageManagerServiceUtils.calculateInstalledSize( mOriginInfo.mResolvedPath, mPackageAbiOverride); if (sizeBytes >= 0) { - try { - mPm.mInstaller.freeCache(null, sizeBytes + lowThreshold, 0, 0); - pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mPm.mContext, - mPackageLite, mOriginInfo.mResolvedPath, mInstallFlags, - mPackageAbiOverride); - } catch (Installer.InstallerException e) { - Slog.w(TAG, "Failed to free cache", e); + synchronized (mPm.mInstallLock) { + try { + mPm.mInstaller.freeCache(null, sizeBytes + lowThreshold, 0); + pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mPm.mContext, + mPackageLite, mOriginInfo.mResolvedPath, mInstallFlags, + mPackageAbiOverride); + } catch (Installer.InstallerException e) { + Slog.w(TAG, "Failed to free cache", e); + } } } diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 55355d81ffe2..c8bd2c0bcecf 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -632,11 +632,15 @@ public class Installer extends SystemService { } } - public void freeCache(String uuid, long targetFreeBytes, long cacheReservedBytes, int flags) - throws InstallerException { + /** + * Deletes cache from specified uuid until targetFreeBytes amount of space is free. + * flag denotes aggressive or non-aggresive mode where cache under quota is eligible or not + * respectively for clearing. + */ + public void freeCache(String uuid, long targetFreeBytes, int flags) throws InstallerException { if (!checkBeforeRemote()) return; try { - mInstalld.freeCache(uuid, targetFreeBytes, cacheReservedBytes, flags); + mInstalld.freeCache(uuid, targetFreeBytes, flags); } catch (Exception e) { throw InstallerException.from(e); } diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index e845aec9c6e0..a5b42f03b6df 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -46,6 +46,7 @@ import static dalvik.system.DexFile.isProfileGuidedCompilerFilter; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.ContentResolver; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.SharedLibraryInfo; @@ -65,6 +66,7 @@ import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.content.F2fsUtils; import com.android.internal.util.IndentingPrintWriter; import com.android.server.LocalServices; import com.android.server.apphibernation.AppHibernationManagerInternal; @@ -141,6 +143,7 @@ public class PackageDexOptimizer { private final Injector mInjector; + private final Context mContext; private static final Random sRandom = new Random(); PackageDexOptimizer(Installer installer, Object installLock, Context context, @@ -159,6 +162,7 @@ public class PackageDexOptimizer { } protected PackageDexOptimizer(PackageDexOptimizer from) { + this.mContext = from.mContext; this.mInstaller = from.mInstaller; this.mInstallLock = from.mInstallLock; this.mDexoptWakeLock = from.mDexoptWakeLock; @@ -169,6 +173,7 @@ public class PackageDexOptimizer { @VisibleForTesting PackageDexOptimizer(@NonNull Injector injector, Installer installer, Object installLock, Context context, String wakeLockTag) { + this.mContext = context; this.mInstaller = installer; this.mInstallLock = installLock; @@ -434,6 +439,13 @@ public class PackageDexOptimizer { long endTime = System.currentTimeMillis(); packageStats.setCompileTime(path, (int)(endTime - startTime)); } + if (oatDir != null) { + // Release odex/vdex compressed blocks to save user space. + // Compression support will be checked in F2fsUtils. + // The system app may be dexed, oatDir may be null, skip this situation. + final ContentResolver resolver = mContext.getContentResolver(); + F2fsUtils.releaseCompressedBlocks(resolver, new File(oatDir)); + } return DEX_OPT_PERFORMED; } catch (InstallerException e) { Slog.w(TAG, "Failed to dexopt", e); diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index fc266c86ccef..de1c2ad44d67 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -107,9 +107,13 @@ import java.io.IOException; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Random; +import java.util.TreeMap; +import java.util.TreeSet; import java.util.function.IntPredicate; import java.util.function.Supplier; @@ -1436,13 +1440,74 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } } + static class ParentChildSessionMap { + private TreeMap<PackageInstallerSession, TreeSet<PackageInstallerSession>> mSessionMap; + + private final Comparator<PackageInstallerSession> mSessionCreationComparator = + Comparator.comparingLong((PackageInstallerSession sess) -> sess.createdMillis) + .thenComparingInt(sess -> sess.sessionId); + + ParentChildSessionMap() { + mSessionMap = new TreeMap<>(mSessionCreationComparator); + } + + boolean containsSession() { + return !(mSessionMap.isEmpty()); + } + + private void addParentSession(PackageInstallerSession session) { + if (!mSessionMap.containsKey(session)) { + mSessionMap.put(session, new TreeSet<>(mSessionCreationComparator)); + } + } + + private void addChildSession(PackageInstallerSession session, + PackageInstallerSession parentSession) { + addParentSession(parentSession); + mSessionMap.get(parentSession).add(session); + } + + void addSession(PackageInstallerSession session, + PackageInstallerSession parentSession) { + if (session.hasParentSessionId()) { + addChildSession(session, parentSession); + } else { + addParentSession(session); + } + } + + void dump(String tag, IndentingPrintWriter pw) { + pw.println(tag + " install sessions:"); + pw.increaseIndent(); + + for (Map.Entry<PackageInstallerSession, TreeSet<PackageInstallerSession>> entry + : mSessionMap.entrySet()) { + PackageInstallerSession parentSession = entry.getKey(); + pw.print(tag + " "); + parentSession.dump(pw); + pw.println(); + pw.increaseIndent(); + + for (PackageInstallerSession childSession : entry.getValue()) { + pw.print(tag + " Child "); + childSession.dump(pw); + pw.println(); + } + + pw.decreaseIndent(); + } + + pw.println(); + pw.decreaseIndent(); + } + } + void dump(IndentingPrintWriter pw) { synchronized (mSessions) { - pw.println("Active install sessions:"); - pw.increaseIndent(); + ParentChildSessionMap activeSessionMap = new ParentChildSessionMap(); + ParentChildSessionMap orphanedChildSessionMap = new ParentChildSessionMap(); + ParentChildSessionMap finalizedSessionMap = new ParentChildSessionMap(); - List<PackageInstallerSession> finalizedSessions = new ArrayList<>(); - List<PackageInstallerSession> orphanedChildSessions = new ArrayList<>(); int N = mSessions.size(); for (int i = 0; i < N; i++) { final PackageInstallerSession session = mSessions.valueAt(i); @@ -1452,47 +1517,28 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements : session; // Do not print orphaned child sessions as active install sessions if (rootSession == null) { - orphanedChildSessions.add(session); + orphanedChildSessionMap.addSession(session, rootSession); continue; } // Do not print finalized staged session as active install sessions if (rootSession.isStagedAndInTerminalState()) { - finalizedSessions.add(session); + finalizedSessionMap.addSession(session, rootSession); continue; } - session.dump(pw); - pw.println(); + activeSessionMap.addSession(session, rootSession); } - pw.println(); - pw.decreaseIndent(); - if (!orphanedChildSessions.isEmpty()) { + activeSessionMap.dump("Active", pw); + + if (orphanedChildSessionMap.containsSession()) { // Presence of orphaned sessions indicate leak in cleanup for multi-package and // should be cleaned up. - pw.println("Orphaned install sessions:"); - pw.increaseIndent(); - N = orphanedChildSessions.size(); - for (int i = 0; i < N; i++) { - final PackageInstallerSession session = orphanedChildSessions.get(i); - session.dump(pw); - pw.println(); - } - pw.println(); - pw.decreaseIndent(); + orphanedChildSessionMap.dump("Orphaned", pw); } - pw.println("Finalized install sessions:"); - pw.increaseIndent(); - N = finalizedSessions.size(); - for (int i = 0; i < N; i++) { - final PackageInstallerSession session = finalizedSessions.get(i); - session.dump(pw); - pw.println(); - } - pw.println(); - pw.decreaseIndent(); + finalizedSessionMap.dump("Finalized", pw); pw.println("Historical install sessions:"); pw.increaseIndent(); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index e71ac1aab711..eba2f55feaf7 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -2909,7 +2909,6 @@ public class PackageManagerService extends IPackageManager.Stub volumeUuid); final boolean aggressive = (storageFlags & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0; - final long reservedBytes = storage.getStorageCacheBytes(file, storageFlags); // 1. Pre-flight to determine if we have any chance to succeed // 2. Consider preloaded data (after 1w honeymoon, unless aggressive) @@ -2926,10 +2925,11 @@ public class PackageManagerService extends IPackageManager.Stub } // 4. Consider cached app data (above quotas) - try { - mInstaller.freeCache(volumeUuid, bytes, reservedBytes, - Installer.FLAG_FREE_CACHE_V2); - } catch (InstallerException ignored) { + synchronized (mInstallLock) { + try { + mInstaller.freeCache(volumeUuid, bytes, Installer.FLAG_FREE_CACHE_V2); + } catch (InstallerException ignored) { + } } if (file.getUsableSpace() >= bytes) return; @@ -2953,10 +2953,12 @@ public class PackageManagerService extends IPackageManager.Stub } // 8. Consider cached app data (below quotas) - try { - mInstaller.freeCache(volumeUuid, bytes, reservedBytes, - Installer.FLAG_FREE_CACHE_V2 | Installer.FLAG_FREE_CACHE_V2_DEFY_QUOTA); - } catch (InstallerException ignored) { + synchronized (mInstallLock) { + try { + mInstaller.freeCache(volumeUuid, bytes, + Installer.FLAG_FREE_CACHE_V2 | Installer.FLAG_FREE_CACHE_V2_DEFY_QUOTA); + } catch (InstallerException ignored) { + } } if (file.getUsableSpace() >= bytes) return; @@ -2982,9 +2984,11 @@ public class PackageManagerService extends IPackageManager.Stub // 12. Clear temp install session files mInstallerService.freeStageDirs(volumeUuid); } else { - try { - mInstaller.freeCache(volumeUuid, bytes, 0, 0); - } catch (InstallerException ignored) { + synchronized (mInstallLock) { + try { + mInstaller.freeCache(volumeUuid, bytes, 0); + } catch (InstallerException ignored) { + } } } if (file.getUsableSpace() >= bytes) return; diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 3554cc02b9fc..fc59541af1ea 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -81,6 +81,7 @@ import android.os.ServiceSpecificException; import android.os.ShellCommand; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.os.incremental.V4Signature; @@ -2629,6 +2630,7 @@ class PackageManagerShellCommand extends ShellCommand { if (userType == null) { userType = UserInfo.getDefaultUserType(flags); } + Trace.traceBegin(Trace.TRACE_TAG_PACKAGE_MANAGER, "shell_runCreateUser"); try { if (UserManager.isUserTypeRestricted(userType)) { // In non-split user mode, userId can only be SYSTEM @@ -2645,6 +2647,8 @@ class PackageManagerShellCommand extends ShellCommand { } } catch (ServiceSpecificException e) { getErrPrintWriter().println("Error: " + e); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_PACKAGE_MANAGER); } if (info != null) { diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index b4bd086af272..42c88b3eee8a 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -22,8 +22,6 @@ import android.app.Person; import android.app.appsearch.AppSearchManager; import android.app.appsearch.AppSearchResult; import android.app.appsearch.AppSearchSession; -import android.app.appsearch.GenericDocument; -import android.app.appsearch.GetByDocumentIdRequest; import android.app.appsearch.PackageIdentifier; import android.app.appsearch.PutDocumentsRequest; import android.app.appsearch.RemoveByDocumentIdRequest; @@ -56,11 +54,12 @@ import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; import android.util.Xml; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.infra.AndroidFuture; +import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; -import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; import com.android.server.pm.ShortcutService.DumpFilter; @@ -88,7 +87,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; @@ -160,6 +159,8 @@ class ShortcutPackage extends ShortcutPackageItem { private final Object mLock = new Object(); + private final Executor mExecutor; + /** * An temp in-memory copy of shortcuts for this package that was loaded from xml, keyed on IDs. */ @@ -189,11 +190,8 @@ class ShortcutPackage extends ShortcutPackageItem { private long mLastKnownForegroundElapsedTime; - private boolean mIsInitilized; - - private boolean mRescanRequired; - private boolean mIsNewApp; - private List<ShortcutInfo> mManifestShortcuts; + @GuardedBy("mLock") + private boolean mIsAppSearchSchemaUpToDate; private ShortcutPackage(ShortcutUser shortcutUser, int packageUserId, String packageName, ShortcutPackageInfo spi) { @@ -201,6 +199,7 @@ class ShortcutPackage extends ShortcutPackageItem { spi != null ? spi : ShortcutPackageInfo.newEmpty()); mPackageUid = shortcutUser.mService.injectGetPackageUid(packageName, packageUserId); + mExecutor = BackgroundThread.getExecutor(); } public ShortcutPackage(ShortcutUser shortcutUser, int packageUserId, String packageName) { @@ -245,11 +244,11 @@ class ShortcutPackage extends ShortcutPackageItem { final String query = String.format("%s:-%s AND %s:%s", AppSearchShortcutInfo.KEY_FLAGS, ShortcutInfo.FLAG_SHADOW, AppSearchShortcutInfo.KEY_DISABLED_REASON, restoreBlockReason); - forEachShortcutMutateIf(query, si -> { + forEachShortcutMutate(si -> { if (restoreBlockReason == ShortcutInfo.DISABLED_REASON_NOT_DISABLED && !si.hasFlags(ShortcutInfo.FLAG_SHADOW) && si.getDisabledReason() == restoreBlockReason) { - return false; + return; } si.clearFlags(ShortcutInfo.FLAG_SHADOW); @@ -257,7 +256,6 @@ class ShortcutPackage extends ShortcutPackageItem { if (restoreBlockReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED) { si.addFlags(ShortcutInfo.FLAG_DISABLED); } - return true; }); // Because some launchers may not have been restored (e.g. allowBackup=false), // we need to re-calculate the pinned shortcuts. @@ -270,8 +268,7 @@ class ShortcutPackage extends ShortcutPackageItem { @Nullable public ShortcutInfo findShortcutById(@Nullable final String id) { if (id == null) return null; - final List<ShortcutInfo> ret = getShortcutById(Collections.singleton(id)); - return (ret == null || ret.isEmpty()) ? null : ret.get(0); + return mShortcuts.get(id); } public boolean isShortcutExistsAndInvisibleToPublisher(String id) { @@ -337,9 +334,8 @@ class ShortcutPackage extends ShortcutPackageItem { * Delete a shortcut by ID. This will *always* remove it even if it's immutable or invisible. */ private ShortcutInfo forceDeleteShortcutInner(@NonNull String id) { - final ShortcutInfo shortcut = findShortcutById(id); + final ShortcutInfo shortcut = mShortcuts.remove(id); if (shortcut != null) { - removeShortcut(id); mShortcutUser.mService.removeIconLocked(shortcut); shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_MANIFEST | ShortcutInfo.FLAG_CACHED_ALL); @@ -435,7 +431,8 @@ class ShortcutPackage extends ShortcutPackageItem { } changedShortcuts.add(shortcut); - deleted = deleteDynamicWithId(shortcut.getId(), /*ignoreInvisible=*/ true) != null; + deleted = deleteDynamicWithId(shortcut.getId(), /* ignoreInvisible =*/ true, + /*ignorePersistedShortcuts=*/ true) != null; } } else { // It's an update case. @@ -449,16 +446,14 @@ class ShortcutPackage extends ShortcutPackageItem { forceReplaceShortcutInner(newShortcut); if (isAppSearchEnabled()) { - mShortcutUser.mService.injectPostToHandler(() -> awaitInAppSearch("reportUsage", - session -> { - final AndroidFuture<Boolean> future = new AndroidFuture<>(); - session.reportUsage( - new ReportUsageRequest.Builder( - getPackageName(), newShortcut.getId()).build(), - mShortcutUser.mExecutor, - result -> future.complete(result.isSuccess())); - return future; - })); + runAsSystem(() -> fromAppSearch().thenAccept(session -> + session.reportUsage(new ReportUsageRequest.Builder( + getPackageName(), newShortcut.getId()).build(), mExecutor, result -> { + if (!result.isSuccess()) { + Slog.e(TAG, "Failed to report usage via AppSearch. " + + result.getErrorMessage()); + } + }))); } return deleted; } @@ -470,12 +465,7 @@ class ShortcutPackage extends ShortcutPackageItem { */ private List<ShortcutInfo> removeOrphans() { final List<ShortcutInfo> removeList = new ArrayList<>(1); - final String query = String.format("%s OR %s OR %s OR %s", - AppSearchShortcutInfo.QUERY_IS_PINNED, - AppSearchShortcutInfo.QUERY_IS_DYNAMIC, - AppSearchShortcutInfo.QUERY_IS_MANIFEST, - AppSearchShortcutInfo.QUERY_IS_CACHED); - forEachShortcut(query, si -> { + forEachShortcut(si -> { if (si.isAlive()) return; removeList.add(si); }); @@ -484,7 +474,6 @@ class ShortcutPackage extends ShortcutPackageItem { forceDeleteShortcutInner(removeList.get(i).getId()); } } - return removeList; } @@ -493,29 +482,21 @@ class ShortcutPackage extends ShortcutPackageItem { * * @return List of shortcuts that actually got removed. */ - public List<ShortcutInfo> deleteAllDynamicShortcuts(boolean ignoreInvisible) { + public List<ShortcutInfo> deleteAllDynamicShortcuts() { final long now = mShortcutUser.mService.injectCurrentTimeMillis(); - final String query; - if (!ignoreInvisible) { - query = AppSearchShortcutInfo.QUERY_IS_DYNAMIC; - } else { - query = String.format("%s %s", - AppSearchShortcutInfo.QUERY_IS_DYNAMIC, - AppSearchShortcutInfo.QUERY_IS_VISIBLE_TO_PUBLISHER); - } - final boolean[] changed = new boolean[1]; - forEachShortcutMutateIf(query, si -> { - if (si.isDynamic() && (!ignoreInvisible || si.isVisibleToPublisher())) { - changed[0] = true; + boolean changed = false; + for (int i = mShortcuts.size() - 1; i >= 0; i--) { + ShortcutInfo si = mShortcuts.valueAt(i); + if (si.isDynamic() && si.isVisibleToPublisher()) { + changed = true; si.setTimestamp(now); si.clearFlags(ShortcutInfo.FLAG_DYNAMIC); si.setRank(0); // It may still be pinned, so clear the rank. - return true; } - return false; - }); - if (changed[0]) { + } + removeAllShortcutsAsync(); + if (changed) { return removeOrphans(); } return null; @@ -528,10 +509,11 @@ class ShortcutPackage extends ShortcutPackageItem { * @return The deleted shortcut, or null if it was not actually removed because it is either * pinned or cached. */ - public ShortcutInfo deleteDynamicWithId(@NonNull String shortcutId, boolean ignoreInvisible) { + public ShortcutInfo deleteDynamicWithId(@NonNull String shortcutId, boolean ignoreInvisible, + boolean ignorePersistedShortcuts) { return deleteOrDisableWithId( shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false, ignoreInvisible, - ShortcutInfo.DISABLED_REASON_NOT_DISABLED); + ShortcutInfo.DISABLED_REASON_NOT_DISABLED, ignorePersistedShortcuts); } /** @@ -542,9 +524,9 @@ class ShortcutPackage extends ShortcutPackageItem { * it's still pinned. */ private ShortcutInfo disableDynamicWithId(@NonNull String shortcutId, boolean ignoreInvisible, - int disabledReason) { + int disabledReason, boolean ignorePersistedShortcuts) { return deleteOrDisableWithId(shortcutId, /* disable =*/ true, /* overrideImmutable=*/ false, - ignoreInvisible, disabledReason); + ignoreInvisible, disabledReason, ignorePersistedShortcuts); } /** @@ -560,7 +542,7 @@ class ShortcutPackage extends ShortcutPackageItem { } return deleteOrDisableWithId( shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false, ignoreInvisible, - ShortcutInfo.DISABLED_REASON_NOT_DISABLED); + ShortcutInfo.DISABLED_REASON_NOT_DISABLED, /*ignorePersistedShortcuts=*/ false); } /** @@ -574,7 +556,8 @@ class ShortcutPackage extends ShortcutPackageItem { int disabledMessageResId, boolean overrideImmutable, boolean ignoreInvisible, int disabledReason) { final ShortcutInfo deleted = deleteOrDisableWithId(shortcutId, /* disable =*/ true, - overrideImmutable, ignoreInvisible, disabledReason); + overrideImmutable, ignoreInvisible, disabledReason, + /*ignorePersistedShortcuts=*/ false); // If disabled id still exists, it is pinned and we need to update the disabled message. mutateShortcut(shortcutId, null, disabled -> { @@ -593,7 +576,8 @@ class ShortcutPackage extends ShortcutPackageItem { @Nullable private ShortcutInfo deleteOrDisableWithId(@NonNull String shortcutId, boolean disable, - boolean overrideImmutable, boolean ignoreInvisible, int disabledReason) { + boolean overrideImmutable, boolean ignoreInvisible, int disabledReason, + boolean ignorePersistedShortcuts) { Preconditions.checkState( (disable == (disabledReason != ShortcutInfo.DISABLED_REASON_NOT_DISABLED)), "disable and disabledReason disagree: " + disable + " vs " + disabledReason); @@ -606,8 +590,10 @@ class ShortcutPackage extends ShortcutPackageItem { if (!overrideImmutable) { ensureNotImmutable(oldShortcut, /*ignoreInvisible=*/ true); } + if (!ignorePersistedShortcuts) { + removeShortcutAsync(shortcutId); + } if (oldShortcut.isPinned() || oldShortcut.isCached()) { - mutateShortcut(oldShortcut.getId(), oldShortcut, si -> { si.setRank(0); si.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_MANIFEST); @@ -672,21 +658,19 @@ class ShortcutPackage extends ShortcutPackageItem { pinnedShortcuts.addAll(pinned); }); // Then, update the pinned state if necessary. - final List<ShortcutInfo> pinned = getShortcutById(pinnedShortcuts); + final List<ShortcutInfo> pinned = findAll(pinnedShortcuts); if (pinned != null) { pinned.forEach(si -> { if (!si.isPinned()) { si.addFlags(ShortcutInfo.FLAG_PINNED); } }); - saveShortcut(pinned); } - forEachShortcutMutateIf(AppSearchShortcutInfo.QUERY_IS_PINNED, si -> { + forEachShortcutMutate(si -> { if (!pinnedShortcuts.contains(si.getId()) && si.isPinned()) { si.clearFlags(ShortcutInfo.FLAG_PINNED); - return true; + return; } - return false; }); // Lastly, remove the ones that are no longer pinned, cached nor dynamic. @@ -777,9 +761,9 @@ class ShortcutPackage extends ShortcutPackageItem { /** * Find all shortcuts that match {@code query}. */ - public void findAll(@NonNull List<ShortcutInfo> result, @Nullable String query, + public void findAll(@NonNull List<ShortcutInfo> result, @Nullable Predicate<ShortcutInfo> filter, int cloneFlag) { - findAll(result, query, filter, cloneFlag, null, 0, /*getPinnedByAnyLauncher=*/ false); + findAll(result, filter, cloneFlag, null, 0, /*getPinnedByAnyLauncher=*/ false); } /** @@ -790,7 +774,7 @@ class ShortcutPackage extends ShortcutPackageItem { * adjusted for the caller too. */ public void findAll(@NonNull List<ShortcutInfo> result, - @Nullable String query, @Nullable Predicate<ShortcutInfo> filter, int cloneFlag, + @Nullable Predicate<ShortcutInfo> filter, int cloneFlag, @Nullable String callingLauncher, int launcherUserId, boolean getPinnedByAnyLauncher) { if (getPackageInfo().isShadow()) { // Restored and the app not installed yet, so don't return any. @@ -802,9 +786,8 @@ class ShortcutPackage extends ShortcutPackageItem { final ArraySet<String> pinnedByCallerSet = (callingLauncher == null) ? null : s.getLauncherShortcutsLocked(callingLauncher, getPackageUserId(), launcherUserId) .getPinnedShortcutIds(getPackageName(), getPackageUserId()); - forEachShortcut(query == null ? "" : query, si -> - filter(result, filter, cloneFlag, callingLauncher, pinnedByCallerSet, - getPinnedByAnyLauncher, si)); + forEachShortcut(si -> filter(result, filter, cloneFlag, callingLauncher, pinnedByCallerSet, + getPinnedByAnyLauncher, si)); } /** @@ -837,35 +820,12 @@ class ShortcutPackage extends ShortcutPackageItem { final ArraySet<String> pinnedByCallerSet = (callingLauncher == null) ? null : s.getLauncherShortcutsLocked(callingLauncher, getPackageUserId(), launcherUserId) .getPinnedShortcutIds(getPackageName(), getPackageUserId()); - final List<ShortcutInfo> shortcuts = getShortcutById(ids); - if (shortcuts != null) { - for (ShortcutInfo si : shortcuts) { - filter(result, query, cloneFlag, callingLauncher, pinnedByCallerSet, - getPinnedByAnyLauncher, si); - } + for (ShortcutInfo si : mShortcuts.values()) { + filter(result, query, cloneFlag, callingLauncher, pinnedByCallerSet, + getPinnedByAnyLauncher, si); } } - /** - * Find all pinned shortcuts that match {@code query}. - */ - public void findAllPinned(@NonNull List<ShortcutInfo> result, - @Nullable Predicate<ShortcutInfo> query, int cloneFlag, - @Nullable String callingLauncher, int launcherUserId, boolean getPinnedByAnyLauncher) { - if (getPackageInfo().isShadow()) { - // Restored and the app not installed yet, so don't return any. - return; - } - final ShortcutService s = mShortcutUser.mService; - - // Set of pinned shortcuts by the calling launcher. - final ArraySet<String> pinnedByCallerSet = (callingLauncher == null) ? null - : s.getLauncherShortcutsLocked(callingLauncher, getPackageUserId(), launcherUserId) - .getPinnedShortcutIds(getPackageName(), getPackageUserId()); - mShortcuts.values().forEach(si -> filter(result, query, cloneFlag, callingLauncher, - pinnedByCallerSet, getPinnedByAnyLauncher, si)); - } - private void filter(@NonNull final List<ShortcutInfo> result, @Nullable final Predicate<ShortcutInfo> query, final int cloneFlag, @Nullable final String callingLauncher, @@ -930,8 +890,8 @@ class ShortcutPackage extends ShortcutPackageItem { // Get the list of all dynamic shortcuts in this package. final ArrayList<ShortcutInfo> shortcuts = new ArrayList<>(); - findAll(shortcuts, AppSearchShortcutInfo.QUERY_IS_NON_MANIFEST_VISIBLE, - ShortcutInfo::isNonManifestVisible, ShortcutInfo.CLONE_REMOVE_FOR_APP_PREDICTION); + findAll(shortcuts, ShortcutInfo::isNonManifestVisible, + ShortcutInfo.CLONE_REMOVE_FOR_APP_PREDICTION); final List<ShortcutManager.ShareShortcutInfo> result = new ArrayList<>(); for (int i = 0; i < shortcuts.size(); i++) { @@ -975,8 +935,8 @@ class ShortcutPackage extends ShortcutPackageItem { // Get the list of all dynamic shortcuts in this package final ArrayList<ShortcutInfo> shortcuts = new ArrayList<>(); - findAll(shortcuts, AppSearchShortcutInfo.QUERY_IS_NON_MANIFEST_VISIBLE, - ShortcutInfo::isNonManifestVisible, ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER); + findAll(shortcuts, ShortcutInfo::isNonManifestVisible, + ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER); int sharingShortcutCount = 0; for (int i = 0; i < shortcuts.size(); i++) { @@ -1129,53 +1089,37 @@ class ShortcutPackage extends ShortcutPackageItem { getPackageInfo().getVersionCode(), pi.getLongVersionCode())); } getPackageInfo().updateFromPackageInfo(pi); - if (isAppSearchEnabled()) { - // Save the states in memory and resume package rescan when needed - mRescanRequired = true; - mIsNewApp = isNewApp; - mManifestShortcuts = newManifestShortcutList; - } else { - rescanPackage(isNewApp, newManifestShortcutList); - } - return true; // true means changed. - } - - private void rescanPackage( - final boolean isNewApp, @NonNull final List<ShortcutInfo> newManifestShortcutList) { - final ShortcutService s = mShortcutUser.mService; final long newVersionCode = getPackageInfo().getVersionCode(); // See if there are any shortcuts that were prevented restoring because the app was of a // lower version, and re-enable them. { - forEachShortcutMutateIf( - AppSearchShortcutInfo.QUERY_DISABLED_REASON_VERSION_LOWER, si -> { - if (si.getDisabledReason() != ShortcutInfo.DISABLED_REASON_VERSION_LOWER) { - return false; - } - if (getPackageInfo().getBackupSourceVersionCode() > newVersionCode) { - if (ShortcutService.DEBUG) { - Slog.d(TAG, - String.format( - "Shortcut %s require version %s, still not restored.", - si.getId(), - getPackageInfo().getBackupSourceVersionCode())); - } - return false; + forEachShortcutMutate(si -> { + if (si.getDisabledReason() != ShortcutInfo.DISABLED_REASON_VERSION_LOWER) { + return; + } + if (getPackageInfo().getBackupSourceVersionCode() > newVersionCode) { + if (ShortcutService.DEBUG) { + Slog.d(TAG, + String.format( + "Shortcut %s require version %s, still not restored.", + si.getId(), + getPackageInfo().getBackupSourceVersionCode())); } - Slog.i(TAG, String.format("Restoring shortcut: %s", si.getId())); - si.clearFlags(ShortcutInfo.FLAG_DISABLED); - si.setDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED); - return true; - }); + return; + } + Slog.i(TAG, String.format("Restoring shortcut: %s", si.getId())); + si.clearFlags(ShortcutInfo.FLAG_DISABLED); + si.setDisabledReason(ShortcutInfo.DISABLED_REASON_NOT_DISABLED); + }); } // For existing shortcuts, update timestamps if they have any resources. // Also check if shortcuts' activities are still main activities. Otherwise, disable them. if (!isNewApp) { final Resources publisherRes = getPackageResources(); - forEachShortcutMutateIf(si -> { + forEachShortcutMutate(si -> { // Disable dynamic shortcuts whose target activity is gone. if (si.isDynamic()) { if (si.getActivity() == null) { @@ -1187,15 +1131,16 @@ class ShortcutPackage extends ShortcutPackageItem { "%s is no longer main activity. Disabling shorcut %s.", getPackageName(), si.getId())); if (disableDynamicWithId(si.getId(), /*ignoreInvisible*/ false, - ShortcutInfo.DISABLED_REASON_APP_CHANGED) != null) { - return false; // Actually removed. + ShortcutInfo.DISABLED_REASON_APP_CHANGED, + /*ignorePersistedShortcuts*/ false) != null) { + return; } // Still pinned, so fall-through and possibly update the resources. } } if (!si.hasAnyResources() || publisherRes == null) { - return false; + return; } if (!si.isOriginallyFromManifest()) { @@ -1206,7 +1151,6 @@ class ShortcutPackage extends ShortcutPackageItem { // from resource names. (We don't allow resource strings for // non-manifest at the moment, but icons can still be resources.) si.setTimestamp(s.injectCurrentTimeMillis()); - return true; }); } @@ -1222,7 +1166,8 @@ class ShortcutPackage extends ShortcutPackageItem { // This will send a notification to the launcher, and also save . // TODO: List changed and removed manifest shortcuts and pass to packageShortcutsChanged() s.packageShortcutsChanged(getPackageName(), getPackageUserId(), null, null); - mManifestShortcuts = null; + + return true; } private boolean publishManifestShortcuts(List<ShortcutInfo> newManifestShortcutList) { @@ -1297,7 +1242,8 @@ class ShortcutPackage extends ShortcutPackageItem { final String id = toDisableList.valueAt(i); - disableWithId(id, /* disable message =*/ null, /* disable message resid */ 0, + disableWithId(id, /* disable message =*/ null, + /* disable message resid */ 0, /* overrideImmutable=*/ true, /*ignoreInvisible=*/ false, ShortcutInfo.DISABLED_REASON_APP_CHANGED); } @@ -1338,7 +1284,8 @@ class ShortcutPackage extends ShortcutPackageItem { service.wtf("Found manifest shortcuts in excess list."); continue; } - deleteDynamicWithId(shortcut.getId(), /*ignoreInvisible=*/ true); + deleteDynamicWithId(shortcut.getId(), /*ignoreInvisible=*/ true, + /*ignorePersistedShortcuts=*/ true); } } @@ -1494,12 +1441,11 @@ class ShortcutPackage extends ShortcutPackageItem { final List<ShortcutInfo> changedShortcuts = new ArrayList<>(1); if (publisherRes != null) { - forEachShortcutMutateIf(AppSearchShortcutInfo.QUERY_HAS_STRING_RESOURCE, si -> { - if (!si.hasStringResources()) return false; + forEachShortcutMutate(si -> { + if (!si.hasStringResources()) return; si.resolveResourceStrings(publisherRes); si.setTimestamp(s.injectCurrentTimeMillis()); changedShortcuts.add(si); - return true; }); } if (!CollectionUtils.isEmpty(changedShortcuts)) { @@ -1549,13 +1495,11 @@ class ShortcutPackage extends ShortcutPackageItem { final long now = s.injectCurrentTimeMillis(); // First, clear ranks for floating shortcuts. - forEachShortcutMutateIf(AppSearchShortcutInfo.QUERY_IS_FLOATING_AND_HAS_RANK, si -> { + forEachShortcutMutate(si -> { if (si.isFloating() && si.getRank() != 0) { si.setTimestamp(now); si.setRank(0); - return true; } - return false; }); // Then adjust ranks. Ranks are unique for each activity, so we first need to sort @@ -1581,7 +1525,7 @@ class ShortcutPackage extends ShortcutPackageItem { } // At this point, it must be dynamic. if (!si.isDynamic()) { - s.wtf("Non-dynamic shortcut found."); + s.wtf("Non-dynamic shortcut found. " + si.toInsecureString()); continue; } final int thisRank = rank++; @@ -1745,13 +1689,10 @@ class ShortcutPackage extends ShortcutPackageItem { ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount); ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime); if (!forBackup) { - /** - * Schema version should not be included in the backup because: - * 1. Schemas in AppSearch are created from scratch on new device - * 2. Shortcuts are restored from xml file (as opposed to from AppSearch) on new device - */ - ShortcutService.writeAttr(out, ATTR_SCHEMA_VERSON, (mIsInitilized) - ? AppSearchShortcutInfo.SCHEMA_VERSION : 0); + synchronized (mLock) { + ShortcutService.writeAttr(out, ATTR_SCHEMA_VERSON, (mIsAppSearchSchemaUpToDate) + ? AppSearchShortcutInfo.SCHEMA_VERSION : 0); + } } getPackageInfo().saveToXml(mShortcutUser.mService, out, forBackup); @@ -1763,6 +1704,8 @@ class ShortcutPackage extends ShortcutPackageItem { for (int j = 0; j < shareTargetSize; j++) { mShareTargets.get(j).saveToXml(out); } + saveShortcutsAsync(mShortcuts.values().stream().filter(ShortcutInfo::usesQuota) + .collect(Collectors.toList())); } out.endTag(null, TAG_ROOT); @@ -1943,8 +1886,10 @@ class ShortcutPackage extends ShortcutPackageItem { final ShortcutPackage ret = new ShortcutPackage(shortcutUser, shortcutUser.getUserId(), packageName); - ret.mIsInitilized = ShortcutService.parseIntAttribute(parser, ATTR_SCHEMA_VERSON, 0) - == AppSearchShortcutInfo.SCHEMA_VERSION; + synchronized (ret.mLock) { + ret.mIsAppSearchSchemaUpToDate = ShortcutService.parseIntAttribute( + parser, ATTR_SCHEMA_VERSON, 0) == AppSearchShortcutInfo.SCHEMA_VERSION; + } ret.mApiCallCount = ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT); ret.mLastResetTime = @@ -2297,8 +2242,15 @@ class ShortcutPackage extends ShortcutPackageItem { } else { mPackageIdentifiers.remove(packageName); } - awaitInAppSearch(true, "Update visibility", - session -> AndroidFuture.completedFuture(true)); + synchronized (mLock) { + mIsAppSearchSchemaUpToDate = false; + } + final long callingIdentity = Binder.clearCallingIdentity(); + try { + fromAppSearch(); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } } void mutateShortcut(@NonNull final String id, @Nullable final ShortcutInfo shortcut, @@ -2325,148 +2277,15 @@ class ShortcutPackage extends ShortcutPackageItem { private void saveShortcut(@NonNull final Collection<ShortcutInfo> shortcuts) { Objects.requireNonNull(shortcuts); - if (!isAppSearchEnabled()) { - // If AppSearch isn't enabled, save it in memory and we are done. - for (ShortcutInfo si : shortcuts) { - mShortcuts.put(si.getId(), si); - } - return; - } - // Otherwise, save pinned shortcuts in memory. - shortcuts.forEach(si -> { - if (si.isPinned()) { - mShortcuts.put(si.getId(), si); - } else { - mShortcuts.remove(si.getId()); - } - }); - // Then proceed to app search. - saveToAppSearch(shortcuts); - } - - private void saveToAppSearch(@NonNull final Collection<ShortcutInfo> shortcuts) { - Objects.requireNonNull(shortcuts); - if (!isAppSearchEnabled() || shortcuts.isEmpty()) { - // No need to invoke AppSearch when there's nothing to save. - return; - } - if (ShortcutService.DEBUG_REBOOT) { - Slog.d(TAG, "Saving shortcuts for user=" + mShortcutUser.getUserId() - + " pkg=" + getPackageName() + " ids=[" - + shortcuts.stream().map(ShortcutInfo::getId) - .collect(Collectors.joining(",")) + "]"); - } - awaitInAppSearch("Saving shortcuts", session -> { - final AndroidFuture<Boolean> future = new AndroidFuture<>(); - session.put(new PutDocumentsRequest.Builder() - .addGenericDocuments( - AppSearchShortcutInfo.toGenericDocuments(shortcuts)) - .build(), - mShortcutUser.mExecutor, - result -> { - if (!result.isSuccess()) { - for (AppSearchResult<Void> k : result.getFailures().values()) { - Slog.e(TAG, k.getErrorMessage()); - } - future.completeExceptionally(new RuntimeException( - "Failed to save shortcuts")); - return; - } - future.complete(true); - }); - return future; - }); - } - - /** - * Removes shortcuts from AppSearch. - */ - void removeShortcuts() { - if (!isAppSearchEnabled()) { - return; + for (ShortcutInfo si : shortcuts) { + mShortcuts.put(si.getId(), si); } - awaitInAppSearch("Removing all shortcuts from " + getPackageName(), session -> { - final AndroidFuture<Boolean> future = new AndroidFuture<>(); - session.remove("", getSearchSpec(), mShortcutUser.mExecutor, result -> { - if (!result.isSuccess()) { - future.completeExceptionally( - new RuntimeException(result.getErrorMessage())); - return; - } - future.complete(true); - }); - return future; - }); - } - - private void removeShortcut(@NonNull final String id) { - Objects.requireNonNull(id); - mShortcuts.remove(id); - if (!isAppSearchEnabled()) { - return; - } - awaitInAppSearch("Removing shortcut with id=" + id, session -> { - final AndroidFuture<Boolean> future = new AndroidFuture<>(); - session.remove( - new RemoveByDocumentIdRequest.Builder(getPackageName()).addIds(id).build(), - mShortcutUser.mExecutor, result -> { - if (!result.isSuccess()) { - final Map<String, AppSearchResult<Void>> failures = - result.getFailures(); - for (String key : failures.keySet()) { - Slog.e(TAG, "Failed deleting " + key + ", error message:" - + failures.get(key).getErrorMessage()); - } - future.completeExceptionally(new RuntimeException( - "Failed to delete shortcut: " + id)); - return; - } - future.complete(true); - }); - return future; - }); } @Nullable - private List<ShortcutInfo> getShortcutById(@NonNull final Collection<String> ids) { - final List<String> shortcutIds = new ArrayList<>(1); - for (String id : ids) { - if (id != null) { - shortcutIds.add(id); - } - } - if (!isAppSearchEnabled()) { - final List<ShortcutInfo> ret = new ArrayList<>(1); - for (int i = mShortcuts.size() - 1; i >= 0; i--) { - ShortcutInfo si = mShortcuts.valueAt(i); - if (shortcutIds.contains(si.getId())) { - ret.add(si); - } - } - return ret; - } - if (ShortcutService.DEBUG_REBOOT) { - Slog.d(TAG, "Getting shortcuts for user=" + mShortcutUser.getUserId() - + " pkg=" + getPackageName() + " ids: [" + String.join(",", ids) + "]"); - } - return awaitInAppSearch("Getting shortcut by id", session -> { - final AndroidFuture<List<ShortcutInfo>> future = new AndroidFuture<>(); - session.getByDocumentId( - new GetByDocumentIdRequest.Builder(getPackageName()) - .addIds(shortcutIds).build(), - mShortcutUser.mExecutor, - results -> { - final List<ShortcutInfo> ret = new ArrayList<>(1); - Map<String, GenericDocument> documents = results.getSuccesses(); - for (GenericDocument doc : documents.values()) { - final ShortcutInfo info = new AppSearchShortcutInfo(doc) - .toShortcutInfo(mShortcutUser.getUserId()); - ret.add(info); - } - future.complete(ret); - }); - return future; - }); + List<ShortcutInfo> findAll(@NonNull final Collection<String> ids) { + return ids.stream().map(mShortcuts::get) + .filter(Objects::nonNull).collect(Collectors.toList()); } private void forEachShortcut(@NonNull final Consumer<ShortcutInfo> cb) { @@ -2482,40 +2301,9 @@ class ShortcutPackage extends ShortcutPackageItem { } private void forEachShortcutMutate(@NonNull final Consumer<ShortcutInfo> cb) { - forEachShortcutMutateIf(si -> { + for (int i = mShortcuts.size() - 1; i >= 0; i--) { + ShortcutInfo si = mShortcuts.valueAt(i); cb.accept(si); - return true; - }); - } - - private void forEachShortcutMutateIf(@NonNull final Function<ShortcutInfo, Boolean> cb) { - forEachShortcutMutateIf("", cb); - } - - private void forEachShortcutMutateIf(@NonNull final String query, - @NonNull final Function<ShortcutInfo, Boolean> cb) { - if (!isAppSearchEnabled()) { - for (int i = mShortcuts.size() - 1; i >= 0; i--) { - ShortcutInfo si = mShortcuts.valueAt(i); - cb.apply(si); - } - return; - } - if (ShortcutService.DEBUG_REBOOT) { - Slog.d(TAG, "Changing shortcuts for user=" + mShortcutUser.getUserId() - + " pkg=" + getPackageName()); - } - final SearchResults res = awaitInAppSearch("Mutating shortcuts", session -> - AndroidFuture.completedFuture(session.search(query, getSearchSpec()))); - if (res == null) return; - List<ShortcutInfo> shortcuts = getNextPage(res); - while (!shortcuts.isEmpty()) { - final List<ShortcutInfo> changed = new ArrayList<>(1); - for (ShortcutInfo si : shortcuts) { - if (cb.apply(si)) changed.add(si); - } - saveShortcut(changed); - shortcuts = getNextPage(res); } } @@ -2526,114 +2314,10 @@ class ShortcutPackage extends ShortcutPackageItem { private void forEachShortcutStopWhen( @NonNull final String query, @NonNull final Function<ShortcutInfo, Boolean> cb) { - if (!isAppSearchEnabled()) { - for (int i = mShortcuts.size() - 1; i >= 0; i--) { - final ShortcutInfo si = mShortcuts.valueAt(i); - if (cb.apply(si)) { - return; - } - } - return; - } - if (ShortcutService.DEBUG_REBOOT) { - Slog.d(TAG, "Iterating shortcuts for user=" + mShortcutUser.getUserId() - + " pkg=" + getPackageName()); - } - final SearchResults res = awaitInAppSearch("Iterating shortcuts", session -> - AndroidFuture.completedFuture(session.search(query, getSearchSpec()))); - if (res == null) return; - List<ShortcutInfo> shortcuts = getNextPage(res); - while (!shortcuts.isEmpty()) { - for (ShortcutInfo si : shortcuts) { - if (cb.apply(si)) return; - } - shortcuts = getNextPage(res); - } - } - - private List<ShortcutInfo> getNextPage(@NonNull final SearchResults res) { - if (ShortcutService.DEBUG_REBOOT) { - Slog.d(TAG, "Get next page for search result for user=" + mShortcutUser.getUserId() - + " pkg=" + getPackageName()); - } - final AndroidFuture<List<ShortcutInfo>> future = new AndroidFuture<>(); - final List<ShortcutInfo> ret = new ArrayList<>(); - final long callingIdentity = Binder.clearCallingIdentity(); - try { - res.getNextPage(mShortcutUser.mExecutor, nextPage -> { - if (!nextPage.isSuccess()) { - future.complete(ret); - return; - } - final List<SearchResult> results = nextPage.getResultValue(); - if (results.isEmpty()) { - future.complete(ret); - return; - } - final List<ShortcutInfo> page = new ArrayList<>(results.size()); - for (SearchResult result : results) { - final ShortcutInfo si = new AppSearchShortcutInfo(result.getGenericDocument()) - .toShortcutInfo(mShortcutUser.getUserId()); - page.add(si); - } - ret.addAll(page); - future.complete(ret); - }); - return ConcurrentUtils.waitForFutureNoInterrupt(future, - "Getting next batch of shortcuts"); - } finally { - Binder.restoreCallingIdentity(callingIdentity); - } - } - - @Nullable - private <T> T awaitInAppSearch( - @NonNull final String description, - @NonNull final Function<AppSearchSession, CompletableFuture<T>> cb) { - return awaitInAppSearch(false, description, cb); - } - - @Nullable - private <T> T awaitInAppSearch( - final boolean forceReset, - @NonNull final String description, - @NonNull final Function<AppSearchSession, CompletableFuture<T>> cb) { - if (!isAppSearchEnabled()) { - throw new IllegalStateException( - "awaitInAppSearch called when app search integration is disabled"); - } - synchronized (mLock) { - final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); - final long callingIdentity = Binder.clearCallingIdentity(); - final AppSearchManager.SearchContext searchContext = - new AppSearchManager.SearchContext.Builder(getPackageName()).build(); - try (AppSearchSession session = ConcurrentUtils.waitForFutureNoInterrupt( - mShortcutUser.getAppSearch(searchContext), "Resetting app search")) { - StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() - .detectAll() - .penaltyLog() // TODO: change this to penaltyDeath to fix the call-site - .build()); - final boolean wasInitialized = mIsInitilized; - if (!wasInitialized || forceReset) { - ConcurrentUtils.waitForFutureNoInterrupt( - setupSchema(session), "Setting up schema"); - } - mIsInitilized = true; - if (!wasInitialized) { - restoreParsedShortcuts(false); - } - if (mRescanRequired) { - mRescanRequired = false; - rescanPackage(mIsNewApp, mManifestShortcuts); - } - return ConcurrentUtils.waitForFutureNoInterrupt(cb.apply(session), description); - } catch (Exception e) { - Slog.e(TAG, "Failed to initiate app search for shortcut package " - + getPackageName() + " user " + mShortcutUser.getUserId(), e); - return null; - } finally { - Binder.restoreCallingIdentity(callingIdentity); - StrictMode.setThreadPolicy(oldPolicy); + for (int i = mShortcuts.size() - 1; i >= 0; i--) { + final ShortcutInfo si = mShortcuts.valueAt(i); + if (cb.apply(si)) { + return; } } } @@ -2657,7 +2341,7 @@ class ShortcutPackage extends ShortcutPackageItem { } final AndroidFuture<AppSearchSession> future = new AndroidFuture<>(); session.setSchema( - schemaBuilder.build(), mShortcutUser.mExecutor, mShortcutUser.mExecutor, result -> { + schemaBuilder.build(), mExecutor, mShortcutUser.mExecutor, result -> { if (!result.isSuccess()) { future.completeExceptionally( new IllegalArgumentException(result.getErrorMessage())); @@ -2677,29 +2361,6 @@ class ShortcutPackage extends ShortcutPackageItem { .build(); } - /** - * Replace shortcuts parsed from xml file. - */ - void restoreParsedShortcuts() { - restoreParsedShortcuts(true); - } - - private void restoreParsedShortcuts(final boolean replace) { - if (ShortcutService.DEBUG_REBOOT) { - if (replace) { - Slog.d(TAG, "Replacing all shortcuts with the ones parsed from xml for user=" - + mShortcutUser.getUserId() + " pkg=" + getPackageName()); - } else { - Slog.d(TAG, "Restoring pinned shortcuts from xml for user=" - + mShortcutUser.getUserId() + " pkg=" + getPackageName()); - } - } - if (replace) { - removeShortcuts(); - } - saveToAppSearch(mShortcuts.values()); - } - private boolean verifyRanksSequential(List<ShortcutInfo> list) { boolean failed = false; @@ -2713,4 +2374,133 @@ class ShortcutPackage extends ShortcutPackageItem { } return failed; } + + // Async Operations + + /** + * Removes all shortcuts from AppSearch. + */ + void removeAllShortcutsAsync() { + if (!isAppSearchEnabled()) { + return; + } + runAsSystem(() -> fromAppSearch().thenAccept(session -> + session.remove("", getSearchSpec(), mShortcutUser.mExecutor, result -> { + if (!result.isSuccess()) { + Slog.e(TAG, "Failed to remove shortcuts from AppSearch. " + + result.getErrorMessage()); + } + }))); + } + + private void removeShortcutAsync(@NonNull final String... id) { + Objects.requireNonNull(id); + removeShortcutAsync(Arrays.asList(id)); + } + + private void removeShortcutAsync(@NonNull final Collection<String> ids) { + if (!isAppSearchEnabled()) { + return; + } + runAsSystem(() -> fromAppSearch().thenAccept(session -> + session.remove( + new RemoveByDocumentIdRequest.Builder(getPackageName()).addIds(ids).build(), + mShortcutUser.mExecutor, result -> { + if (!result.isSuccess()) { + final Map<String, AppSearchResult<Void>> failures = + result.getFailures(); + for (String key : failures.keySet()) { + Slog.e(TAG, "Failed deleting " + key + ", error message:" + + failures.get(key).getErrorMessage()); + } + } + }))); + } + + private void saveShortcutsAsync( + @NonNull final Collection<ShortcutInfo> shortcuts) { + Objects.requireNonNull(shortcuts); + if (!isAppSearchEnabled() || shortcuts.isEmpty()) { + // No need to invoke AppSearch when there's nothing to save. + return; + } + if (ShortcutService.DEBUG_REBOOT) { + Slog.d(TAG, "Saving shortcuts async for user=" + mShortcutUser.getUserId() + + " pkg=" + getPackageName() + " ids=[" + + shortcuts.stream().map(ShortcutInfo::getId) + .collect(Collectors.joining(",")) + "]"); + } + runAsSystem(() -> fromAppSearch().thenAccept(session -> { + if (shortcuts.isEmpty()) { + return; + } + session.put(new PutDocumentsRequest.Builder() + .addGenericDocuments( + AppSearchShortcutInfo.toGenericDocuments(shortcuts)) + .build(), + mShortcutUser.mExecutor, + result -> { + if (!result.isSuccess()) { + for (AppSearchResult<Void> k : result.getFailures().values()) { + Slog.e(TAG, k.getErrorMessage()); + } + } + }); + })); + } + + @VisibleForTesting + void getTopShortcutsFromPersistence(AndroidFuture<List<ShortcutInfo>> cb) { + runAsSystem(() -> fromAppSearch().thenAccept(session -> { + SearchResults res = session.search("", getSearchSpec()); + res.getNextPage(mShortcutUser.mExecutor, results -> { + if (!results.isSuccess()) { + cb.completeExceptionally(new IllegalStateException(results.getErrorMessage())); + return; + } + cb.complete(results.getResultValue().stream() + .map(SearchResult::getGenericDocument) + .map(AppSearchShortcutInfo::new) + .map(si -> si.toShortcutInfo(mShortcutUser.getUserId())) + .collect(Collectors.toList())); + }); + })); + } + + @NonNull + private AndroidFuture<AppSearchSession> fromAppSearch() { + final StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); + final AppSearchManager.SearchContext searchContext = + new AppSearchManager.SearchContext.Builder(getPackageName()).build(); + AndroidFuture<AppSearchSession> future = null; + try { + StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() + .detectAll() + .penaltyLog() // TODO: change this to penaltyDeath to fix the call-site + .build()); + future = mShortcutUser.getAppSearch(searchContext); + synchronized (mLock) { + if (!mIsAppSearchSchemaUpToDate) { + future = future.thenCompose(this::setupSchema); + } + mIsAppSearchSchemaUpToDate = true; + } + } catch (Exception e) { + Slog.e(TAG, "Failed to invoke app search pkg=" + + getPackageName() + " user=" + mShortcutUser.getUserId(), e); + Objects.requireNonNull(future).completeExceptionally(e); + } finally { + StrictMode.setThreadPolicy(oldPolicy); + } + return Objects.requireNonNull(future); + } + + private void runAsSystem(@NonNull final Runnable fn) { + final long callingIdentity = Binder.clearCallingIdentity(); + try { + fn.run(); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + } } diff --git a/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java b/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java index e21c9c2d0086..c1f57f970127 100644 --- a/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java +++ b/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java @@ -467,7 +467,7 @@ class ShortcutRequestPinProcessor { launcher.attemptToRestoreIfNeededAndSave(); if (launcher.hasPinned(original)) { if (DEBUG) { - Slog.d(TAG, "Shortcut " + original + " already pinned."); // This too. + Slog.d(TAG, "Shortcut " + original + " already pinned."); // This too. } return true; } @@ -517,7 +517,8 @@ class ShortcutRequestPinProcessor { if (DEBUG) { Slog.d(TAG, "Removing " + shortcutId + " as dynamic"); } - ps.deleteDynamicWithId(shortcutId, /*ignoreInvisible=*/ false); + ps.deleteDynamicWithId(shortcutId, /*ignoreInvisible=*/ false, + /*wasPushedOut=*/ false); } ps.adjustRanks(); // Shouldn't be needed, but just in case. diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 687a165e4032..85b743594b75 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -1965,13 +1965,12 @@ public class ShortcutService extends IShortcutService.Stub { ArrayList<ShortcutInfo> cachedOrPinned = new ArrayList<>(); ps.findAll(cachedOrPinned, - AppSearchShortcutInfo.QUERY_IS_VISIBLE_CACHED_OR_PINNED, (ShortcutInfo si) -> si.isVisibleToPublisher() && si.isDynamic() && (si.isCached() || si.isPinned()), ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO); // First, remove all un-pinned and non-cached; dynamic shortcuts - removedShortcuts = ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true); + removedShortcuts = ps.deleteAllDynamicShortcuts(); // Then, add/update all. We need to make sure to take over "pinned" flag. for (int i = 0; i < size; i++) { @@ -2381,7 +2380,8 @@ public class ShortcutService extends IShortcutService.Stub { if (!ps.isShortcutExistsAndVisibleToPublisher(id)) { continue; } - ShortcutInfo removed = ps.deleteDynamicWithId(id, /*ignoreInvisible=*/ true); + ShortcutInfo removed = ps.deleteDynamicWithId(id, /*ignoreInvisible=*/ true, + /*wasPushedOut*/ false); if (removed == null) { if (changedShortcuts == null) { changedShortcuts = new ArrayList<>(1); @@ -2412,11 +2412,10 @@ public class ShortcutService extends IShortcutService.Stub { userId); // Dynamic shortcuts that are either cached or pinned will not get deleted. ps.findAll(changedShortcuts, - AppSearchShortcutInfo.QUERY_IS_VISIBLE_CACHED_OR_PINNED, (ShortcutInfo si) -> si.isVisibleToPublisher() && si.isDynamic() && (si.isCached() || si.isPinned()), ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO); - removedShortcuts = ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true); + removedShortcuts = ps.deleteAllDynamicShortcuts(); changedShortcuts = prepareChangedShortcuts( changedShortcuts, null, removedShortcuts, ps); } @@ -2475,10 +2474,8 @@ public class ShortcutService extends IShortcutService.Stub { | (matchPinned ? ShortcutInfo.FLAG_PINNED : 0) | (matchManifest ? ShortcutInfo.FLAG_MANIFEST : 0) | (matchCached ? ShortcutInfo.FLAG_CACHED_ALL : 0); - final String query = AppSearchShortcutInfo.QUERY_IS_VISIBLE_TO_PUBLISHER + " " - + createQuery(matchDynamic, matchPinned, matchManifest, matchCached); return getShortcutsWithQueryLocked( - packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, query, + packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR, (ShortcutInfo si) -> si.isVisibleToPublisher() && (si.getFlags() & shortcutFlags) != 0); @@ -2542,13 +2539,12 @@ public class ShortcutService extends IShortcutService.Stub { @GuardedBy("mLock") private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName, - @UserIdInt int userId, int cloneFlags, @NonNull final String query, - @NonNull Predicate<ShortcutInfo> filter) { + @UserIdInt int userId, int cloneFlags, @NonNull Predicate<ShortcutInfo> filter) { final ArrayList<ShortcutInfo> ret = new ArrayList<>(); final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId); - ps.findAll(ret, query, filter, cloneFlags); + ps.findAll(ret, filter, cloneFlags); return new ParceledListSlice<>(setReturnedByServer(ret)); } @@ -2955,27 +2951,14 @@ public class ShortcutService extends IShortcutService.Stub { ((queryFlags & ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER) != 0); queryFlags |= (getPinnedByAnyLauncher ? ShortcutQuery.FLAG_MATCH_PINNED : 0); - final boolean matchPinnedOnly = - ((queryFlags & ShortcutQuery.FLAG_MATCH_PINNED) != 0) - && ((queryFlags & ShortcutQuery.FLAG_MATCH_CACHED) == 0) - && ((queryFlags & ShortcutQuery.FLAG_MATCH_DYNAMIC) == 0) - && ((queryFlags & ShortcutQuery.FLAG_MATCH_MANIFEST) == 0); - final Predicate<ShortcutInfo> filter = getFilterFromQuery(ids, locusIds, changedSince, componentName, queryFlags, getPinnedByAnyLauncher); - if (matchPinnedOnly) { - p.findAllPinned(ret, filter, cloneFlag, callingPackage, launcherUserId, - getPinnedByAnyLauncher); - } else if (ids != null && !ids.isEmpty()) { + if (ids != null && !ids.isEmpty()) { p.findAllByIds(ret, ids, filter, cloneFlag, callingPackage, launcherUserId, getPinnedByAnyLauncher); } else { - final boolean matchDynamic = (queryFlags & ShortcutQuery.FLAG_MATCH_DYNAMIC) != 0; - final boolean matchPinned = (queryFlags & ShortcutQuery.FLAG_MATCH_PINNED) != 0; - final boolean matchManifest = (queryFlags & ShortcutQuery.FLAG_MATCH_MANIFEST) != 0; - final boolean matchCached = (queryFlags & ShortcutQuery.FLAG_MATCH_CACHED) != 0; - p.findAll(ret, createQuery(matchDynamic, matchPinned, matchManifest, matchCached), - filter, cloneFlag, callingPackage, launcherUserId, getPinnedByAnyLauncher); + p.findAll(ret, filter, cloneFlag, callingPackage, launcherUserId, + getPinnedByAnyLauncher); } } @@ -3090,8 +3073,7 @@ public class ShortcutService extends IShortcutService.Stub { if (sp != null) { // List the shortcuts that are pinned only, these will get removed. removedShortcuts = new ArrayList<>(); - sp.findAll(removedShortcuts, AppSearchShortcutInfo.QUERY_IS_VISIBLE_PINNED_ONLY, - (ShortcutInfo si) -> si.isVisibleToPublisher() + sp.findAll(removedShortcuts, (ShortcutInfo si) -> si.isVisibleToPublisher() && si.isPinned() && !si.isCached() && !si.isDynamic() && !si.isDeclaredInManifest(), ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO, @@ -3183,8 +3165,7 @@ public class ShortcutService extends IShortcutService.Stub { if (doCache) { if (si.isLongLived()) { - sp.mutateShortcut(si.getId(), si, - shortcut -> shortcut.addFlags(cacheFlags)); + si.addFlags(cacheFlags); if (changedShortcuts == null) { changedShortcuts = new ArrayList<>(1); } @@ -3195,21 +3176,20 @@ public class ShortcutService extends IShortcutService.Stub { } } else { ShortcutInfo removed = null; - sp.mutateShortcut(si.getId(), si, shortcut -> - shortcut.clearFlags(cacheFlags)); + si.clearFlags(cacheFlags); if (!si.isDynamic() && !si.isCached()) { removed = sp.deleteLongLivedWithId(id, /*ignoreInvisible=*/ true); } - if (removed != null) { - if (removedShortcuts == null) { - removedShortcuts = new ArrayList<>(1); - } - removedShortcuts.add(removed); - } else { + if (removed == null) { if (changedShortcuts == null) { changedShortcuts = new ArrayList<>(1); } changedShortcuts.add(si); + } else { + if (removedShortcuts == null) { + removedShortcuts = new ArrayList<>(1); + } + removedShortcuts.add(removed); } } } @@ -5084,8 +5064,7 @@ public class ShortcutService extends IShortcutService.Stub { synchronized (mLock) { final ShortcutPackage pkg = getPackageShortcutForTest(packageName, userId); if (pkg == null) return; - - pkg.mutateShortcut(shortcutId, null, cb); + cb.accept(pkg.findShortcutById(shortcutId)); } } diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java index e66cb03950cc..408f045f47b8 100644 --- a/services/core/java/com/android/server/pm/ShortcutUser.java +++ b/services/core/java/com/android/server/pm/ShortcutUser.java @@ -186,7 +186,7 @@ class ShortcutUser { final ShortcutPackage removed = mPackages.remove(packageName); if (removed != null) { - removed.removeShortcuts(); + removed.removeAllShortcutsAsync(); } mService.cleanupBitmapsForPackage(mUserId, packageName); @@ -577,7 +577,7 @@ class ShortcutUser { Log.w(TAG, "Shortcuts for package " + sp.getPackageName() + " are being restored." + " Existing non-manifeset shortcuts will be overwritten."); } - sp.restoreParsedShortcuts(); + sp.removeAllShortcutsAsync(); addPackage(sp); restoredPackages[0]++; restoredShortcuts[0] += sp.getShortcutCount(); @@ -714,6 +714,7 @@ class ShortcutUser { .setSubtype(totalSharingShortcutCount)); } + @NonNull AndroidFuture<AppSearchSession> getAppSearch( @NonNull final AppSearchManager.SearchContext searchContext) { final AndroidFuture<AppSearchSession> future = new AndroidFuture<>(); diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java index 962816c6dd92..0fb8475bc3af 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java @@ -899,7 +899,7 @@ public class DomainVerificationService extends SystemService oldPkgState.getUserStates(); int oldUserStatesSize = oldUserStates.size(); if (oldUserStatesSize > 0) { - ArraySet<String> newWebDomains = mCollector.collectValidAutoVerifyDomains(newPkg); + ArraySet<String> newWebDomains = mCollector.collectAllWebDomains(newPkg); for (int oldUserStatesIndex = 0; oldUserStatesIndex < oldUserStatesSize; oldUserStatesIndex++) { int userId = oldUserStates.keyAt(oldUserStatesIndex); diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index 7826ddfc6401..0394d4cca110 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -95,7 +95,6 @@ import android.content.pm.UserInfo; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; -import android.hardware.health.V2_0.IHealth; import android.net.ConnectivityManager; import android.net.INetworkStatsService; import android.net.INetworkStatsSession; @@ -194,13 +193,13 @@ import com.android.internal.os.SystemServerCpuThreadReader.SystemServiceCpuThrea import com.android.internal.util.CollectionUtils; import com.android.internal.util.FrameworkStatsLog; import com.android.role.RoleManagerLocal; -import com.android.server.BatteryService; import com.android.server.BinderCallsStatsService; import com.android.server.LocalManagerRegistry; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.SystemServiceManager; import com.android.server.am.MemoryStatUtil.MemoryStat; +import com.android.server.health.HealthServiceWrapper; import com.android.server.notification.NotificationManagerService; import com.android.server.stats.pull.IonMemoryUtil.IonAllocations; import com.android.server.stats.pull.ProcfsMemoryUtil.MemorySnapshot; @@ -230,6 +229,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.MissingResourceException; +import java.util.NoSuchElementException; import java.util.Random; import java.util.Set; import java.util.UUID; @@ -358,7 +358,7 @@ public class StatsPullAtomService extends SystemService { private File mBaseDir; @GuardedBy("mHealthHalLock") - private BatteryService.HealthServiceWrapper mHealthService; + private HealthServiceWrapper mHealthService; @Nullable @GuardedBy("mCpuTimePerThreadFreqLock") @@ -807,10 +807,9 @@ public class StatsPullAtomService extends SystemService { KernelCpuThreadReaderSettingsObserver.getSettingsModifiedReader(mContext); // Initialize HealthService - mHealthService = new BatteryService.HealthServiceWrapper(); try { - mHealthService.init(); - } catch (RemoteException e) { + mHealthService = HealthServiceWrapper.create(null); + } catch (RemoteException | NoSuchElementException e) { Slog.e(TAG, "failed to initialize healthHalWrapper"); } @@ -3990,38 +3989,40 @@ public class StatsPullAtomService extends SystemService { } int pullHealthHalLocked(int atomTag, List<StatsEvent> pulledData) { - IHealth healthService = mHealthService.getLastService(); - if (healthService == null) { + if (mHealthService == null) { return StatsManager.PULL_SKIP; } + android.hardware.health.HealthInfo healthInfo; try { - healthService.getHealthInfo((result, value) -> { - int pulledValue; - switch(atomTag) { - case FrameworkStatsLog.BATTERY_LEVEL: - pulledValue = value.legacy.batteryLevel; - break; - case FrameworkStatsLog.REMAINING_BATTERY_CAPACITY: - pulledValue = value.legacy.batteryChargeCounter; - break; - case FrameworkStatsLog.FULL_BATTERY_CAPACITY: - pulledValue = value.legacy.batteryFullCharge; - break; - case FrameworkStatsLog.BATTERY_VOLTAGE: - pulledValue = value.legacy.batteryVoltage; - break; - case FrameworkStatsLog.BATTERY_CYCLE_COUNT: - pulledValue = value.legacy.batteryCycleCount; - break; - default: - throw new IllegalStateException("Invalid atomTag in healthHal puller: " - + atomTag); - } - pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag, pulledValue)); - }); + healthInfo = mHealthService.getHealthInfo(); } catch (RemoteException | IllegalStateException e) { return StatsManager.PULL_SKIP; } + if (healthInfo == null) { + return StatsManager.PULL_SKIP; + } + + int pulledValue; + switch (atomTag) { + case FrameworkStatsLog.BATTERY_LEVEL: + pulledValue = healthInfo.batteryLevel; + break; + case FrameworkStatsLog.REMAINING_BATTERY_CAPACITY: + pulledValue = healthInfo.batteryChargeCounterUah; + break; + case FrameworkStatsLog.FULL_BATTERY_CAPACITY: + pulledValue = healthInfo.batteryFullChargeUah; + break; + case FrameworkStatsLog.BATTERY_VOLTAGE: + pulledValue = healthInfo.batteryVoltageMillivolts; + break; + case FrameworkStatsLog.BATTERY_CYCLE_COUNT: + pulledValue = healthInfo.batteryCycleCount; + break; + default: + return StatsManager.PULL_SKIP; + } + pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag, pulledValue)); return StatsManager.PULL_SUCCESS; } diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java index 3177fac413fc..043646041158 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java @@ -457,17 +457,18 @@ public class TunerResourceManagerService extends SystemService implements IBinde if (!validateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB, lnbHandle)) { throw new RemoteException("lnbHandle can't be invalid"); } - if (!checkClientExists(clientId)) { - throw new RemoteException("Release lnb from unregistered client:" + clientId); - } - LnbResource lnb = getLnbResource(lnbHandle); - if (lnb == null) { - throw new RemoteException("Releasing lnb does not exist."); - } - if (lnb.getOwnerClientId() != clientId) { - throw new RemoteException("Client is not the current owner of the releasing lnb."); - } synchronized (mLock) { + if (!checkClientExists(clientId)) { + throw new RemoteException("Release lnb from unregistered client:" + clientId); + } + LnbResource lnb = getLnbResource(lnbHandle); + if (lnb == null) { + throw new RemoteException("Releasing lnb does not exist."); + } + if (lnb.getOwnerClientId() != clientId) { + throw new RemoteException("Client is not the current owner " + + "of the releasing lnb."); + } releaseLnbInternal(lnb); } } @@ -869,6 +870,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde frontendHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE; ClientProfile requestClient = getClientProfile(request.clientId); + // TODO: check if this is really needed if (requestClient == null) { return false; } @@ -1205,7 +1207,9 @@ public class TunerResourceManagerService extends SystemService implements IBinde @Override public void binderDied() { synchronized (mLock) { - removeClientProfile(mClientId); + if (checkClientExists(mClientId)) { + removeClientProfile(mClientId); + } } } @@ -1246,6 +1250,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde // Reclaim all the resources of the share owners of the frontend that is used by the current // resource reclaimed client. ClientProfile profile = getClientProfile(reclaimingClientId); + // TODO: check if this check is really needed. if (profile == null) { return true; } @@ -1553,6 +1558,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde } private void clearFrontendAndClientMapping(ClientProfile profile) { + // TODO: check if this check is really needed if (profile == null) { return; } @@ -1573,6 +1579,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde } private void clearAllResourcesAndClientMapping(ClientProfile profile) { + // TODO: check if this check is really needed. Maybe needed for reclaimResource path. if (profile == null) { return; } diff --git a/services/core/java/com/android/server/utils/TimingsTraceAndSlog.java b/services/core/java/com/android/server/utils/TimingsTraceAndSlog.java index 6bdb5cea7f9c..397acfac2812 100644 --- a/services/core/java/com/android/server/utils/TimingsTraceAndSlog.java +++ b/services/core/java/com/android/server/utils/TimingsTraceAndSlog.java @@ -78,6 +78,14 @@ public final class TimingsTraceAndSlog extends TimingsTraceLog { mTag = tag; } + /** + * @see TimingsTraceLog#TimingsTraceLog(TimingsTraceLog) + */ + public TimingsTraceAndSlog(@NonNull TimingsTraceAndSlog other) { + super(other); + this.mTag = other.mTag; + } + @Override public void traceBegin(@NonNull String name) { Slog.i(mTag, name); diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index 3d83995390f0..ec0b5f083a38 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -20,11 +20,11 @@ import static android.accessibilityservice.AccessibilityTrace.FLAGS_MAGNIFICATIO import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK; import static android.os.Build.IS_USER; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY; -import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL; import static com.android.server.accessibility.AccessibilityTraceFileProto.ENTRY; import static com.android.server.accessibility.AccessibilityTraceFileProto.MAGIC_NUMBER; @@ -926,6 +926,8 @@ final class AccessibilityController { final int windowType = windowState.mAttrs.type; if (isExcludedWindowType(windowType) || ((windowState.mAttrs.privateFlags + & PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION) != 0) + || ((windowState.mAttrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0)) { continue; } @@ -990,7 +992,6 @@ final class AccessibilityController { } } } - visibleWindows.clear(); mMagnificationRegion.op(mDrawBorderInset, mDrawBorderInset, @@ -1027,9 +1028,6 @@ final class AccessibilityController { private boolean isExcludedWindowType(int windowType) { return windowType == TYPE_MAGNIFICATION_OVERLAY - // Omit the touch region to avoid the cut out of the magnification - // bounds because nav bar panel is unmagnifiable. - || windowType == TYPE_NAVIGATION_BAR_PANEL // Omit the touch region of window magnification to avoid the cut out of the // magnification and the magnified center of window magnification could be // in the bounds diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java index 15db7909bfe8..2f9c1389f188 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java @@ -767,10 +767,6 @@ class ActivityMetricsLogger { if (compatStateInfo.mLastLoggedActivity == r) { compatStateInfo.mLastLoggedActivity = null; } - if (compatStateInfo.mVisibleActivities.isEmpty()) { - // No need to keep the entry if there are no visible activities. - mPackageUidToCompatStateInfo.remove(packageUid); - } } /** @@ -1266,13 +1262,14 @@ class ActivityMetricsLogger { * activity. * <li>If the current state is NOT_VISIBLE, there is a previously logged state for the * package UID and there are no other visible activities with the same package UID. - * <li>The last logged activity with the same package UID is either {@code activity} or the - * last logged state is NOT_VISIBLE or NOT_LETTERBOXED. + * <li>The last logged activity with the same package UID is either {@code activity} (or an + * activity that has been removed) or the last logged state is NOT_VISIBLE or NOT_LETTERBOXED. * </ul> * * <p>If the current state is NOT_VISIBLE and the previous state which was logged by {@code - * activity} wasn't, looks for the first visible activity with the same package UID that has - * a letterboxed state, or a non-letterboxed state if there isn't one, and logs that state. + * activity} (or an activity that has been removed) wasn't, looks for the first visible activity + * with the same package UID that has a letterboxed state, or a non-letterboxed state if + * there isn't one, and logs that state. * * <p>This method assumes that the caller is wrapping the call with a synchronized block so * that there won't be a race condition between two activities with the same package. @@ -1308,14 +1305,14 @@ class ActivityMetricsLogger { if (!isVisible && !visibleActivities.isEmpty()) { // There is another visible activity for this package UID. - if (activity == lastLoggedActivity) { + if (lastLoggedActivity == null || activity == lastLoggedActivity) { // Make sure a new visible state is logged if needed. findAppCompatStateToLog(compatStateInfo, packageUid); } return; } - if (activity != lastLoggedActivity + if (lastLoggedActivity != null && activity != lastLoggedActivity && lastLoggedState != APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE && lastLoggedState != APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED) { // Another visible activity for this package UID has logged a letterboxed state. @@ -1329,15 +1326,25 @@ class ActivityMetricsLogger { * Looks for the first visible activity in {@code compatStateInfo} that has a letterboxed * state, or a non-letterboxed state if there isn't one, and logs that state for the given * {@code packageUid}. + * + * <p>If there is a visible activity in {@code compatStateInfo} with the same state as the + * last logged state for the given {@code packageUid}, changes the last logged activity to + * reference the first such activity without actually logging the same state twice. */ private void findAppCompatStateToLog(PackageCompatStateInfo compatStateInfo, int packageUid) { final ArrayList<ActivityRecord> visibleActivities = compatStateInfo.mVisibleActivities; + final int lastLoggedState = compatStateInfo.mLastLoggedState; ActivityRecord activityToLog = null; int stateToLog = APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE; for (int i = 0; i < visibleActivities.size(); i++) { ActivityRecord activity = visibleActivities.get(i); int state = activity.getAppCompatState(); + if (state == lastLoggedState) { + // Change last logged activity without logging the same state twice. + compatStateInfo.mLastLoggedActivity = activity; + return; + } if (state == APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE) { // This shouldn't happen. Slog.w(TAG, "Visible activity with NOT_VISIBLE App Compat state for package UID: " diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index 535a061ee4ab..f94777339fae 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -934,6 +934,10 @@ public class AppTransitionController { voiceInteraction); applyAnimations(closingWcs, closingApps, transit, false /* visible */, animLp, voiceInteraction); + final RecentsAnimationController rac = mService.getRecentsAnimationController(); + if (rac != null) { + rac.sendTasksAppeared(); + } for (int i = 0; i < openingApps.size(); ++i) { openingApps.valueAtUnchecked(i).mOverrideTaskTransition = false; diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index b6552cb1b962..427bbeb78fb0 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -27,6 +27,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN; import static com.android.server.wm.DisplayRotationProto.FIXED_TO_USER_ROTATION_MODE; import static com.android.server.wm.DisplayRotationProto.FROZEN_TO_USER_ROTATION; +import static com.android.server.wm.DisplayRotationProto.IS_FIXED_TO_USER_ROTATION; import static com.android.server.wm.DisplayRotationProto.LAST_ORIENTATION; import static com.android.server.wm.DisplayRotationProto.ROTATION; import static com.android.server.wm.DisplayRotationProto.USER_ROTATION; @@ -1527,6 +1528,7 @@ public class DisplayRotation { proto.write(USER_ROTATION, getUserRotation()); proto.write(FIXED_TO_USER_ROTATION_MODE, mFixedToUserRotation); proto.write(LAST_ORIENTATION, mLastOrientation); + proto.write(IS_FIXED_TO_USER_ROTATION, isFixedToUserRotation()); proto.end(token); } diff --git a/services/core/java/com/android/server/wm/PackageConfigPersister.java b/services/core/java/com/android/server/wm/PackageConfigPersister.java index 5145e8eaaadd..3de98f18dae8 100644 --- a/services/core/java/com/android/server/wm/PackageConfigPersister.java +++ b/services/core/java/com/android/server/wm/PackageConfigPersister.java @@ -16,10 +16,8 @@ package com.android.server.wm; -import static android.app.UiModeManager.MODE_NIGHT_AUTO; -import static android.app.UiModeManager.MODE_NIGHT_CUSTOM; - import android.annotation.NonNull; +import android.content.res.Configuration; import android.os.Environment; import android.os.LocaleList; import android.util.AtomicFile; @@ -308,7 +306,7 @@ public class PackageConfigPersister { } boolean isResetNightMode() { - return mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM; + return mNightMode == Configuration.UI_MODE_NIGHT_UNDEFINED; } @Override diff --git a/services/core/java/com/android/server/wm/PinnedTaskController.java b/services/core/java/com/android/server/wm/PinnedTaskController.java index b4963c5b9f1c..b54208d11974 100644 --- a/services/core/java/com/android/server/wm/PinnedTaskController.java +++ b/services/core/java/com/android/server/wm/PinnedTaskController.java @@ -17,7 +17,6 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -173,10 +172,8 @@ class PinnedTaskController { * to avoid flickering when running PiP animation across different orientations. */ void deferOrientationChangeForEnteringPipFromFullScreenIfNeeded() { - final Task topFullscreenTask = mDisplayContent.getDefaultTaskDisplayArea() - .getTopRootTaskInWindowingMode(WINDOWING_MODE_FULLSCREEN); - final ActivityRecord topFullscreen = topFullscreenTask != null - ? topFullscreenTask.topRunningActivity() : null; + final ActivityRecord topFullscreen = mDisplayContent.getActivity( + a -> a.fillsParent() && !a.getTask().inMultiWindowMode()); if (topFullscreen == null || topFullscreen.hasFixedRotationTransform()) { return; } diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index 634f4894f3ee..38e3e3a82cb6 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; @@ -162,6 +163,8 @@ public class RecentsAnimationController implements DeathRecipient { private boolean mNavigationBarAttachedToApp; private ActivityRecord mNavBarAttachedApp; + private final ArrayList<RemoteAnimationTarget> mPendingTaskAppears = new ArrayList<>(); + /** * An app transition listener to cancel the recents animation only after the app transition * starts or is canceled. @@ -731,11 +734,19 @@ public class RecentsAnimationController implements DeathRecipient { return; } ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "addTaskToTargets, target: %s", target); - try { - mRunner.onTaskAppeared(target); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to report task appeared", e); - } + mPendingTaskAppears.add(target); + } + } + + void sendTasksAppeared() { + if (mPendingTaskAppears.isEmpty() || mRunner == null) return; + try { + final RemoteAnimationTarget[] targets = mPendingTaskAppears.toArray( + new RemoteAnimationTarget[0]); + mRunner.onTasksAppeared(targets); + mPendingTaskAppears.clear(); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to report task appeared", e); } } @@ -743,10 +754,15 @@ public class RecentsAnimationController implements DeathRecipient { OnAnimationFinishedCallback finishedCallback) { final SparseBooleanArray recentTaskIds = mService.mAtmService.getRecentTasks().getRecentTaskIds(); + // The target must be built off the root task (the leaf task surface would be cropped + // within the root surface). However, recents only tracks leaf task ids, so we'll replace + // the task-id with the leaf id. + final Task leafTask = task.getTopLeafTask(); + int taskId = leafTask.mTaskId; TaskAnimationAdapter adapter = (TaskAnimationAdapter) addAnimation(task, - !recentTaskIds.get(task.mTaskId), true /* hidden */, finishedCallback); - mPendingNewTaskTargets.add(task.mTaskId); - return adapter.createRemoteAnimationTarget(); + !recentTaskIds.get(taskId), true /* hidden */, finishedCallback); + mPendingNewTaskTargets.add(taskId); + return adapter.createRemoteAnimationTarget(taskId); } void logRecentsAnimationStartTime(int durationMs) { @@ -781,7 +797,8 @@ public class RecentsAnimationController implements DeathRecipient { final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>(); for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { final TaskAnimationAdapter taskAdapter = mPendingAnimations.get(i); - final RemoteAnimationTarget target = taskAdapter.createRemoteAnimationTarget(); + final RemoteAnimationTarget target = + taskAdapter.createRemoteAnimationTarget(INVALID_TASK_ID); if (target != null) { targets.add(target); } else { @@ -994,6 +1011,8 @@ public class RecentsAnimationController implements DeathRecipient { removeAnimation(taskAdapter); taskAdapter.onCleanup(); } + // Should already be empty, but clean-up pending task-appears in-case they weren't sent. + mPendingTaskAppears.clear(); for (int i = mPendingWallpaperAnimations.size() - 1; i >= 0; i--) { final WallpaperAnimationAdapter wallpaperAdapter = mPendingWallpaperAnimations.get(i); @@ -1217,7 +1236,14 @@ public class RecentsAnimationController implements DeathRecipient { mLocalBounds.offsetTo(tmpPos.x, tmpPos.y); } - RemoteAnimationTarget createRemoteAnimationTarget() { + /** + * @param overrideTaskId overrides the target's taskId. It may differ from mTaskId and thus + * can differ from taskInfo. This mismatch is needed, however, in + * some cases where we are animating root tasks but need need leaf + * ids for identification. If this is INVALID (-1), then mTaskId + * will be used. + */ + RemoteAnimationTarget createRemoteAnimationTarget(int overrideTaskId) { final ActivityRecord topApp = mTask.getTopVisibleActivity(); final WindowState mainWindow = topApp != null ? topApp.findMainWindow() @@ -1231,7 +1257,10 @@ public class RecentsAnimationController implements DeathRecipient { final int mode = topApp.getActivityType() == mTargetActivityType ? MODE_OPENING : MODE_CLOSING; - mTarget = new RemoteAnimationTarget(mTask.mTaskId, mode, mCapturedLeash, + if (overrideTaskId < 0) { + overrideTaskId = mTask.mTaskId; + } + mTarget = new RemoteAnimationTarget(overrideTaskId, mode, mCapturedLeash, !topApp.fillsParent(), new Rect(), insets, mTask.getPrefixOrderIndex(), new Point(mBounds.left, mBounds.top), mLocalBounds, mBounds, mTask.getWindowConfiguration(), diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 367feac18975..4a8f36273a33 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -2366,6 +2366,16 @@ class Task extends TaskFragment { return true; } + /** Return the top-most leaf-task under this one, or this task if it is a leaf. */ + public Task getTopLeafTask() { + for (int i = mChildren.size() - 1; i >= 0; --i) { + final Task child = mChildren.get(i).asTask(); + if (child == null) continue; + return child.getTopLeafTask(); + } + return this; + } + int getDescendantTaskCount() { final int[] currentCount = {0}; final PooledConsumer c = PooledLambda.obtainConsumer((t, count) -> { count[0]++; }, diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 49bbd8a60a3f..0eaa25be3094 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -31,6 +31,7 @@ import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.content.res.Configuration.ORIENTATION_UNDEFINED; +import static android.os.Process.INVALID_UID; import static android.os.UserHandle.USER_NULL; import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.TRANSIT_CLOSE; @@ -220,6 +221,8 @@ class TaskFragment extends WindowContainer<WindowContainer> { /** Organizer that organizing this TaskFragment. */ @Nullable private ITaskFragmentOrganizer mTaskFragmentOrganizer; + private int mTaskFragmentOrganizerUid = INVALID_UID; + private @Nullable String mTaskFragmentOrganizerProcessName; /** Client assigned unique token for this TaskFragment if this is created by an organizer. */ @Nullable @@ -232,13 +235,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { */ private boolean mDelayLastActivityRemoval; - /** - * The PID of the organizer that created this TaskFragment. It should be the same as the PID - * of {@link android.window.TaskFragmentCreationParams#getOwnerToken()}. - * {@link ActivityRecord#INVALID_PID} if this is not an organizer-created TaskFragment. - */ - private int mTaskFragmentOrganizerPid = ActivityRecord.INVALID_PID; - final Point mLastSurfaceSize = new Point(); private final Rect mTmpInsets = new Rect(); @@ -334,9 +330,11 @@ class TaskFragment extends WindowContainer<WindowContainer> { mDelayLastActivityRemoval = false; } - void setTaskFragmentOrganizer(TaskFragmentOrganizerToken organizer, int pid) { + void setTaskFragmentOrganizer(@NonNull TaskFragmentOrganizerToken organizer, int uid, + @NonNull String processName) { mTaskFragmentOrganizer = ITaskFragmentOrganizer.Stub.asInterface(organizer.asBinder()); - mTaskFragmentOrganizerPid = pid; + mTaskFragmentOrganizerUid = uid; + mTaskFragmentOrganizerProcessName = processName; } /** Whether this TaskFragment is organized by the given {@code organizer}. */ @@ -2176,9 +2174,11 @@ class TaskFragment extends WindowContainer<WindowContainer> { List<IBinder> childActivities = new ArrayList<>(); for (int i = 0; i < getChildCount(); i++) { WindowContainer wc = getChildAt(i); - if (mTaskFragmentOrganizerPid != ActivityRecord.INVALID_PID + if (mTaskFragmentOrganizerUid != INVALID_UID && wc.asActivityRecord() != null - && wc.asActivityRecord().getPid() == mTaskFragmentOrganizerPid) { + && wc.asActivityRecord().info.processName.equals( + mTaskFragmentOrganizerProcessName) + && wc.asActivityRecord().getUid() == mTaskFragmentOrganizerUid) { // Only includes Activities that belong to the organizer process for security. childActivities.add(wc.asActivityRecord().token); } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index f6c835640a2a..3d479d1e0d68 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -580,27 +580,130 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub task.getDisplayArea().setLaunchAdjacentFlagRootTask(clearRoot ? null : task); break; } - case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS: + case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS: { effects |= setAdjacentRootsHierarchyOp(hop); break; - } - // The following operations may change task order so they are skipped while in lock task - // mode. The above operations are still allowed because they don't move tasks. And it may - // be necessary such as clearing launch root after entering lock task mode. - if (isInLockTaskMode) { - Slog.w(TAG, "Skip applying hierarchy operation " + hop + " while in lock task mode"); - return effects; + } + case HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT: { + final TaskFragmentCreationParams taskFragmentCreationOptions = + hop.getTaskFragmentCreationOptions(); + createTaskFragment(taskFragmentCreationOptions, errorCallbackToken); + break; + } + case HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT: { + final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer()); + if (wc == null || !wc.isAttached()) { + Slog.e(TAG, "Attempt to operate on unknown or detached container: " + wc); + break; + } + final TaskFragment taskFragment = wc.asTaskFragment(); + if (taskFragment == null || taskFragment.asTask() != null) { + throw new IllegalArgumentException( + "Can only delete organized TaskFragment, but not Task."); + } + if (isInLockTaskMode) { + final ActivityRecord bottomActivity = taskFragment.getActivity( + a -> !a.finishing, false /* traverseTopToBottom */); + if (bottomActivity != null + && mService.getLockTaskController().activityBlockedFromFinish( + bottomActivity)) { + Slog.w(TAG, "Skip removing TaskFragment due in lock task mode."); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, + new IllegalStateException( + "Not allow to delete task fragment in lock task mode.")); + break; + } + } + effects |= deleteTaskFragment(taskFragment, errorCallbackToken); + break; + } + case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT: { + final IBinder fragmentToken = hop.getContainer(); + if (!mLaunchTaskFragments.containsKey(fragmentToken)) { + final Throwable exception = new IllegalArgumentException( + "Not allowed to operate with invalid fragment token"); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); + break; + } + final Intent activityIntent = hop.getActivityIntent(); + final Bundle activityOptions = hop.getLaunchOptions(); + final TaskFragment tf = mLaunchTaskFragments.get(fragmentToken); + final int result = mService.getActivityStartController() + .startActivityInTaskFragment(tf, activityIntent, activityOptions, + hop.getCallingActivity()); + if (!isStartResultSuccessful(result)) { + sendTaskFragmentOperationFailure(tf.getTaskFragmentOrganizer(), + errorCallbackToken, + convertStartFailureToThrowable(result, activityIntent)); + } + break; + } + case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: { + final IBinder fragmentToken = hop.getNewParent(); + final ActivityRecord activity = ActivityRecord.forTokenLocked(hop.getContainer()); + if (!mLaunchTaskFragments.containsKey(fragmentToken) || activity == null) { + final Throwable exception = new IllegalArgumentException( + "Not allowed to operate with invalid fragment token or activity."); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); + break; + } + activity.reparent(mLaunchTaskFragments.get(fragmentToken), POSITION_TOP); + effects |= TRANSACT_EFFECTS_LIFECYCLE; + break; + } + case HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS: { + final IBinder fragmentToken = hop.getContainer(); + final IBinder adjacentFragmentToken = hop.getAdjacentRoot(); + final TaskFragment tf1 = mLaunchTaskFragments.get(fragmentToken); + final TaskFragment tf2 = adjacentFragmentToken != null + ? mLaunchTaskFragments.get(adjacentFragmentToken) + : null; + if (tf1 == null || (adjacentFragmentToken != null && tf2 == null)) { + final Throwable exception = new IllegalArgumentException( + "Not allowed to set adjacent on invalid fragment tokens"); + sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); + break; + } + tf1.setAdjacentTaskFragment(tf2); + effects |= TRANSACT_EFFECTS_LIFECYCLE; + + final Bundle bundle = hop.getLaunchOptions(); + final WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams = + bundle != null ? new WindowContainerTransaction.TaskFragmentAdjacentParams( + bundle) : null; + if (adjacentParams == null) { + break; + } + + tf1.setDelayLastActivityRemoval( + adjacentParams.shouldDelayPrimaryLastActivityRemoval()); + if (tf2 != null) { + tf2.setDelayLastActivityRemoval( + adjacentParams.shouldDelaySecondaryLastActivityRemoval()); + } + break; + } + default: { + // The other operations may change task order so they are skipped while in lock + // task mode. The above operations are still allowed because they don't move + // tasks. And it may be necessary such as clearing launch root after entering + // lock task mode. + if (isInLockTaskMode) { + Slog.w(TAG, "Skip applying hierarchy operation " + hop + + " while in lock task mode"); + return effects; + } + } } - final WindowContainer wc; - final IBinder fragmentToken; switch (type) { - case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT: + case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT: { effects |= reparentChildrenTasksHierarchyOp(hop, transition, syncId); break; + } case HIERARCHY_OP_TYPE_REORDER: - case HIERARCHY_OP_TYPE_REPARENT: - wc = WindowContainer.fromBinder(hop.getContainer()); + case HIERARCHY_OP_TYPE_REPARENT: { + final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer()); if (wc == null || !wc.isAttached()) { Slog.e(TAG, "Attempt to operate on detached container: " + wc); break; @@ -629,7 +732,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } effects |= sanitizeAndApplyHierarchyOp(wc, hop); break; - case HIERARCHY_OP_TYPE_LAUNCH_TASK: + } + case HIERARCHY_OP_TYPE_LAUNCH_TASK: { mService.mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS, "launchTask HierarchyOp"); final Bundle launchOpts = hop.getLaunchOptions(); @@ -638,7 +742,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub launchOpts.remove(WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID); final SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle(launchOpts, caller.mPid, caller.mUid); - final Integer[] starterResult = { null }; + final Integer[] starterResult = {null}; // startActivityFromRecents should not be called in lock. mService.mH.post(() -> { try { @@ -659,10 +763,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } } break; - case HIERARCHY_OP_TYPE_PENDING_INTENT: + } + case HIERARCHY_OP_TYPE_PENDING_INTENT: { String resolvedType = hop.getActivityIntent() != null ? hop.getActivityIntent().resolveTypeIfNeeded( - mService.mContext.getContentResolver()) + mService.mContext.getContentResolver()) : null; Bundle options = null; @@ -682,57 +787,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub hop.getActivityIntent(), resolvedType, null /* finishReceiver */, null /* requiredPermission */, options); break; - case HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT: - final TaskFragmentCreationParams taskFragmentCreationOptions = - hop.getTaskFragmentCreationOptions(); - createTaskFragment(taskFragmentCreationOptions, errorCallbackToken); - break; - case HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT: - wc = WindowContainer.fromBinder(hop.getContainer()); - if (wc == null || !wc.isAttached()) { - Slog.e(TAG, "Attempt to operate on unknown or detached container: " + wc); - break; - } - final TaskFragment taskFragment = wc.asTaskFragment(); - if (taskFragment == null || taskFragment.asTask() != null) { - throw new IllegalArgumentException( - "Can only delete organized TaskFragment, but not Task."); - } - effects |= deleteTaskFragment(taskFragment, errorCallbackToken); - break; - case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT: - fragmentToken = hop.getContainer(); - if (!mLaunchTaskFragments.containsKey(fragmentToken)) { - final Throwable exception = new IllegalArgumentException( - "Not allowed to operate with invalid fragment token"); - sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); - break; - } - final Intent activityIntent = hop.getActivityIntent(); - final Bundle activityOptions = hop.getLaunchOptions(); - final TaskFragment tf = mLaunchTaskFragments.get(fragmentToken); - final int result = mService.getActivityStartController() - .startActivityInTaskFragment(tf, activityIntent, activityOptions, - hop.getCallingActivity()); - if (!isStartResultSuccessful(result)) { - sendTaskFragmentOperationFailure(tf.getTaskFragmentOrganizer(), - errorCallbackToken, - convertStartFailureToThrowable(result, activityIntent)); - } - break; - case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: - fragmentToken = hop.getNewParent(); - final ActivityRecord activity = ActivityRecord.forTokenLocked(hop.getContainer()); - if (!mLaunchTaskFragments.containsKey(fragmentToken) || activity == null) { - final Throwable exception = new IllegalArgumentException( - "Not allowed to operate with invalid fragment token or activity."); - sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); - break; - } - activity.reparent(mLaunchTaskFragments.get(fragmentToken), POSITION_TOP); - effects |= TRANSACT_EFFECTS_LIFECYCLE; - break; - case HIERARCHY_OP_TYPE_REPARENT_CHILDREN: + } + case HIERARCHY_OP_TYPE_REPARENT_CHILDREN: { final WindowContainer oldParent = WindowContainer.fromBinder(hop.getContainer()); final WindowContainer newParent = hop.getNewParent() != null ? WindowContainer.fromBinder(hop.getNewParent()) @@ -745,37 +801,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub reparentTaskFragment(oldParent, newParent, errorCallbackToken); effects |= TRANSACT_EFFECTS_LIFECYCLE; break; - case HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS: - fragmentToken = hop.getContainer(); - final IBinder adjacentFragmentToken = hop.getAdjacentRoot(); - final TaskFragment tf1 = mLaunchTaskFragments.get(fragmentToken); - final TaskFragment tf2 = adjacentFragmentToken != null - ? mLaunchTaskFragments.get(adjacentFragmentToken) - : null; - if (tf1 == null || (adjacentFragmentToken != null && tf2 == null)) { - final Throwable exception = new IllegalArgumentException( - "Not allowed to set adjacent on invalid fragment tokens"); - sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); - break; - } - tf1.setAdjacentTaskFragment(tf2); - effects |= TRANSACT_EFFECTS_LIFECYCLE; - - final Bundle bundle = hop.getLaunchOptions(); - final WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams = - bundle != null ? new WindowContainerTransaction.TaskFragmentAdjacentParams( - bundle) : null; - if (adjacentParams == null) { - break; - } - - tf1.setDelayLastActivityRemoval( - adjacentParams.shouldDelayPrimaryLastActivityRemoval()); - if (tf2 != null) { - tf2.setDelayLastActivityRemoval( - adjacentParams.shouldDelaySecondaryLastActivityRemoval()); - } - break; + } } return effects; } @@ -1204,8 +1230,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub creationParams.getFragmentToken(), true /* createdByOrganizer */); // Set task fragment organizer immediately, since it might have to be notified about further // actions. - taskFragment.setTaskFragmentOrganizer( - creationParams.getOrganizer(), ownerActivity.getPid()); + taskFragment.setTaskFragmentOrganizer(creationParams.getOrganizer(), + ownerActivity.getUid(), ownerActivity.info.processName); ownerActivity.getTask().addChild(taskFragment, POSITION_TOP); taskFragment.setWindowingMode(creationParams.getWindowingMode()); taskFragment.setBounds(creationParams.getInitialBounds()); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 92c0a34dc382..ebfdfa3eb5da 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -12747,6 +12747,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override + public @Nullable ComponentName getProfileOwnerOrDeviceOwnerSupervisionComponent( + @NonNull UserHandle userHandle) { + return DevicePolicyManagerService.this.getProfileOwnerOrDeviceOwnerSupervisionComponent( + userHandle); + } + + @Override public boolean isActiveDeviceOwner(int uid) { return isDeviceOwner(new CallerIdentity(uid, null, null)); } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index b44b692bce12..752ad0b9c48a 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1357,6 +1357,7 @@ public final class SystemServer implements Dumpable { */ private void startOtherServices(@NonNull TimingsTraceAndSlog t) { t.traceBegin("startOtherServices"); + mSystemServiceManager.updateOtherServicesStartIndex(); final Context context = mSystemContext; DynamicSystemService dynamicSystem = null; diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt index 3ffce8c426a4..ad79c65691c3 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt @@ -624,6 +624,60 @@ class DomainVerificationPackageTest { } @Test + fun migratePackageSelected() { + val pkgName = PKG_ONE + val pkgBefore = mockPkgState(pkgName, UUID_ONE, SIGNATURE_ONE, + listOf(DOMAIN_1), listOf(DOMAIN_2)) + val pkgAfter = mockPkgState(pkgName, UUID_TWO, SIGNATURE_TWO, + listOf(DOMAIN_1), listOf(DOMAIN_2)) + + val map = mutableMapOf<String, PackageStateInternal>() + val service = makeService { map[it] } + service.addPackage(pkgBefore) + + // Only insert the package after addPackage call to ensure the service doesn't access + // a live package inside the addPackage logic. It should only use the provided input. + map[pkgName] = pkgBefore + + assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_1), STATE_SUCCESS)) + .isEqualTo(DomainVerificationManager.STATUS_OK) + + assertThat(service.setUserSelection(UUID_ONE, setOf(DOMAIN_2), true, USER_ID)) + .isEqualTo(DomainVerificationManager.STATUS_OK) + + service.getInfo(pkgName).run { + assertThat(identifier).isEqualTo(UUID_ONE) + assertThat(hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to STATE_SUCCESS, + )) + } + assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to DOMAIN_STATE_VERIFIED, + DOMAIN_2 to DOMAIN_STATE_SELECTED, + )) + assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName) + + // Now remove the package because migrateState shouldn't use it either + map.remove(pkgName) + + service.migrateState(pkgBefore, pkgAfter) + + map[pkgName] = pkgAfter + + service.getInfo(pkgName).run { + assertThat(identifier).isEqualTo(UUID_TWO) + assertThat(hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to STATE_SUCCESS, + )) + } + assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf( + DOMAIN_1 to DOMAIN_STATE_VERIFIED, + DOMAIN_2 to DOMAIN_STATE_SELECTED, + )) + assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName) + } + + @Test fun backupAndRestore() { // This test acts as a proxy for true user restore through PackageManager, // as that's much harder to test for real. @@ -804,7 +858,8 @@ class DomainVerificationPackageTest { pkgName: String, domainSetId: UUID, signature: String, - domains: List<String> = listOf(DOMAIN_1, DOMAIN_2), + autoVerifyDomains: List<String> = listOf(DOMAIN_1, DOMAIN_2), + otherDomains: List<String> = listOf(), isSystemApp: Boolean = false ) = mockThrowOnUnmocked<PackageStateInternal> { val pkg = mockThrowOnUnmocked<AndroidPackage> { @@ -812,23 +867,25 @@ class DomainVerificationPackageTest { whenever(targetSdkVersion) { Build.VERSION_CODES.S } whenever(isEnabled) { true } + fun baseIntent(domain: String) = ParsedIntentInfoImpl().apply { + intentFilter.apply { + addAction(Intent.ACTION_VIEW) + addCategory(Intent.CATEGORY_BROWSABLE) + addCategory(Intent.CATEGORY_DEFAULT) + addDataScheme("http") + addDataScheme("https") + addDataPath("/sub", PatternMatcher.PATTERN_LITERAL) + addDataAuthority(domain, null) + } + } + val activityList = listOf( ParsedActivityImpl().apply { - domains.forEach { - addIntent( - ParsedIntentInfoImpl().apply { - intentFilter.apply { - autoVerify = true - addAction(Intent.ACTION_VIEW) - addCategory(Intent.CATEGORY_BROWSABLE) - addCategory(Intent.CATEGORY_DEFAULT) - addDataScheme("http") - addDataScheme("https") - addDataPath("/sub", PatternMatcher.PATTERN_LITERAL) - addDataAuthority(it, null) - } - } - ) + autoVerifyDomains.forEach { + addIntent(baseIntent(it).apply { intentFilter.autoVerify = true }) + } + otherDomains.forEach { + addIntent(baseIntent(it).apply { intentFilter.autoVerify = false }) } }, ) diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp index 6bb127a1b3c5..48a8b1bca99b 100644 --- a/services/tests/mockingservicestests/Android.bp +++ b/services/tests/mockingservicestests/Android.bp @@ -29,7 +29,10 @@ package { android_test { name: "FrameworksMockingServicesTests", - defaults: ["FrameworkMockingServicesTests-jni-defaults"], + defaults: [ + "FrameworkMockingServicesTests-jni-defaults", + "modules-utils-testable-device-config-defaults", + ], srcs: [ "src/**/*.java", @@ -67,9 +70,7 @@ android_test { ], jni_libs: [ - "libdexmakerjvmtiagent", "libpsi", - "libstaticjvmtiagent", ], certificate: "platform", diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index 61cd4448cc35..a9099ae35b75 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -621,12 +621,8 @@ public class AlarmManagerServiceTest { } private void setTareEnabled(boolean enabled) { - doReturn(enabled ? 1 : 0).when( - () -> Settings.Global.getInt(mContentResolver, Settings.Global.ENABLE_TARE)); - doReturn(enabled ? 1 : 0).when( - () -> Settings.Global.getInt(mContentResolver, - Settings.Global.ENABLE_TARE, Settings.Global.DEFAULT_ENABLE_TARE)); - mService.mConstants.onChange(true); + when(mEconomyManagerInternal.isEnabled()).thenReturn(enabled); + mService.mConstants.onTareEnabledStateChanged(enabled); } /** diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java index 609768c0e62a..766ba72d7cb7 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java @@ -38,10 +38,10 @@ import android.provider.DeviceConfig; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.modules.utils.testing.TestableDeviceConfig; import com.android.server.LocalServices; import com.android.server.ServiceThread; import com.android.server.appop.AppOpsService; -import com.android.server.testables.TestableDeviceConfig; import com.android.server.wm.ActivityTaskManagerService; import org.junit.Before; diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java index a883293b13b9..303f95565c76 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java @@ -40,10 +40,10 @@ import android.text.TextUtils; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.modules.utils.testing.TestableDeviceConfig; import com.android.server.LocalServices; import com.android.server.ServiceThread; import com.android.server.appop.AppOpsService; -import com.android.server.testables.TestableDeviceConfig; import com.android.server.wm.ActivityTaskManagerService; import org.junit.After; diff --git a/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreConfigTest.java b/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreConfigTest.java index ad19a4893153..d747914caa9a 100644 --- a/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreConfigTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreConfigTest.java @@ -29,7 +29,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.server.testables.TestableDeviceConfig; +import com.android.modules.utils.testing.TestableDeviceConfig; import org.junit.Before; import org.junit.Rule; diff --git a/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesServiceTest.java index 007191f02631..349da03e3f4e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/compat/overrides/AppCompatOverridesServiceTest.java @@ -59,7 +59,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.compat.CompatibilityOverrideConfig; import com.android.internal.compat.CompatibilityOverridesToRemoveConfig; import com.android.internal.compat.IPlatformCompat; -import com.android.server.testables.TestableDeviceConfig.TestableDeviceConfigRule; +import com.android.modules.utils.testing.TestableDeviceConfig.TestableDeviceConfigRule; import org.junit.Before; import org.junit.Rule; diff --git a/services/tests/mockingservicestests/src/com/android/server/power/FaceDownDetectorTest.java b/services/tests/mockingservicestests/src/com/android/server/power/FaceDownDetectorTest.java index e093f1396385..9fac0c029815 100644 --- a/services/tests/mockingservicestests/src/com/android/server/power/FaceDownDetectorTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/power/FaceDownDetectorTest.java @@ -34,7 +34,7 @@ import android.testing.TestableContext; import androidx.test.platform.app.InstrumentationRegistry; -import com.android.server.testables.TestableDeviceConfig; +import com.android.modules.utils.testing.TestableDeviceConfig; import org.junit.Before; import org.junit.ClassRule; diff --git a/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java b/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java index f94377fe51c2..6a3548178cba 100644 --- a/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java @@ -36,7 +36,7 @@ import android.testing.TestableContext; import androidx.test.platform.app.InstrumentationRegistry; -import com.android.server.testables.TestableDeviceConfig; +import com.android.modules.utils.testing.TestableDeviceConfig; import org.junit.Before; import org.junit.ClassRule; diff --git a/services/tests/mockingservicestests/src/com/android/server/testables/MultipleStaticMocksTest.java b/services/tests/mockingservicestests/src/com/android/server/testables/MultipleStaticMocksTest.java deleted file mode 100644 index c0ab70a57327..000000000000 --- a/services/tests/mockingservicestests/src/com/android/server/testables/MultipleStaticMocksTest.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (C) 2019 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.server.testables; - -import static com.google.common.truth.Truth.assertThat; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.dx.mockito.inline.extended.ExtendedMockito; -import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.ArrayList; -import java.util.List; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class MultipleStaticMocksTest { - @Rule - public StaticMockFixtureRule mStaticMockFixtureRule = - new StaticMockFixtureRule(AB::new, CD::new); - - private List<String> mCollected; - - @Test - public void testMultipleStaticMocks() throws Exception { - mCollected = new ArrayList<>(); - int n = 0; - - A.a(); - n = verifyCollected(n, "A.a"); - - D.b(); - n = verifyCollected(n, "D.b"); - - C.b(); - n = verifyCollected(n, "C.b"); - - B.a(); - n = verifyCollected(n, "B.a"); - } - - private int verifyCollected(int n, String... last) { - assertThat(mCollected).hasSize(n + last.length); - assertThat(mCollected.subList(n, mCollected.size())) - .containsExactlyElementsIn(last).inOrder(); - return n + last.length; - } - - private static class A { - /* package */ static void a() {} - /* package */ static void b() {} - } - - private static class B { - /* package */ static void a() {} - /* package */ static void b() {} - } - - private static class C { - /* package */ static void a() {} - /* package */ static void b() {} - } - - private static class D { - /* package */ static void a() {} - /* package */ static void b() {} - } - - /** - * AB StaticMockFixture class that handles two mocked classes, {@link A} and {@link B}. - */ - private class AB implements StaticMockFixture { - @Override - public StaticMockitoSessionBuilder setUpMockedClasses( - StaticMockitoSessionBuilder sessionBuilder) { - sessionBuilder.spyStatic(A.class); - sessionBuilder.spyStatic(B.class); - return sessionBuilder; - } - - @Override - public void setUpMockBehaviors() { - ExtendedMockito.doAnswer(invocation -> { - mCollected.add("A.a"); - return null; - }).when(A::a); - ExtendedMockito.doAnswer(invocation -> { - mCollected.add("A.b"); - return null; - }).when(A::b); - ExtendedMockito.doAnswer(invocation -> { - mCollected.add("B.a"); - return null; - }).when(B::a); - ExtendedMockito.doAnswer(invocation -> { - mCollected.add("B.b"); - return null; - }).when(B::b); - } - - @Override - public void tearDown() { - - } - } - - /** - * AB StaticMockFixture class that handles two mocked classes, {@link C} and {@link D}. - */ - private class CD implements StaticMockFixture { - @Override - public StaticMockitoSessionBuilder setUpMockedClasses( - StaticMockitoSessionBuilder sessionBuilder) { - sessionBuilder.spyStatic(C.class); - sessionBuilder.spyStatic(D.class); - return sessionBuilder; - } - - @Override - public void setUpMockBehaviors() { - ExtendedMockito.doAnswer(invocation -> { - mCollected.add("C.a"); - return null; - }).when(C::a); - ExtendedMockito.doAnswer(invocation -> { - mCollected.add("C.b"); - return null; - }).when(C::b); - ExtendedMockito.doAnswer(invocation -> { - mCollected.add("D.a"); - return null; - }).when(D::a); - ExtendedMockito.doAnswer(invocation -> { - mCollected.add("D.b"); - return null; - }).when(D::b); - } - - @Override - public void tearDown() { - - } - } -} diff --git a/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixture.java b/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixture.java deleted file mode 100644 index 0303fe1e1eb0..000000000000 --- a/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixture.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2019 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.server.testables; - - -import com.android.dx.mockito.inline.extended.StaticMockitoSession; -import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder; - -/** - * Provides support for a set of static mocks for use within a single shared - * {@link StaticMockitoSession}. - */ -public interface StaticMockFixture { - /** - * Adds any required mock or spy classes managed by this {@link StaticMockFixture} to the - * {@link StaticMockitoSessionBuilder} provided. - * - * Call this to set up the classes that this expects to be mocked, by adding them to the - * {@link StaticMockitoSessionBuilder} using - * {@link StaticMockitoSessionBuilder#mockStatic(Class)}, - * {@link StaticMockitoSessionBuilder#spyStatic(Class)} or similar as appropriate. - * - * @param sessionBuilder the {@link StaticMockitoSessionBuilder} to which the classes should be - * added to mock, spy, or otherwise as required - * @return sessionBuilder, to allow for fluent programming - */ - StaticMockitoSessionBuilder setUpMockedClasses(StaticMockitoSessionBuilder sessionBuilder); - - /** - * Configures the behaviours of any mock or spy classes managed by this - * {@link StaticMockFixture}. - * - * Call this after {@link StaticMockitoSessionBuilder#startMocking()} has been called. - * This sets up any default behaviors for the mocks, spys, etc. - */ - void setUpMockBehaviors(); - - /** - * Tear everything down. - */ - void tearDown(); -} diff --git a/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRule.java b/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRule.java deleted file mode 100644 index 3566aee2eba3..000000000000 --- a/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRule.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) 2019 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.server.testables; - -import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; - -import com.android.dx.mockito.inline.extended.StaticMockitoSession; -import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder; - -import org.junit.AssumptionViolatedException; -import org.junit.rules.TestRule; -import org.junit.rules.TestWatcher; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; -import org.mockito.quality.Strictness; - -import java.util.function.Supplier; - -/** - * <p>StaticMockFixtureRule is a {@link TestRule} that wraps one or more {@link StaticMockFixture}s - * to set them up and tear it down automatically. This works well when you have no other static - * mocks than the ones supported by their respective {@link StaticMockFixture}s.</p> - * - * <p>StaticMockFixtureRule should be defined as a rule on your test so it can clean up after - * itself. Like the following:</p> - * <pre class="prettyprint"> -* public final StaticMockFixture mStaticMockFixtures = ...; - * @Rule - * public final StaticMockFixtureRule mStaticMockFixtureRule = - * new StaticMockFixtureRule(mStaticMockFixtures); - * </pre> - */ -public class StaticMockFixtureRule implements TestRule { - private StaticMockitoSession mMockitoSession; - private StaticMockFixture[] mStaticMockFixtures; - private Supplier<? extends StaticMockFixture>[] mSupplier; - - /** - * Constructs a StaticMockFixtureRule that always uses the same {@link StaticMockFixture} - * instance(s). - * - * @param staticMockFixtures the {@link StaticMockFixture}(s) to use. - */ - public StaticMockFixtureRule(StaticMockFixture... staticMockFixtures) { - mStaticMockFixtures = staticMockFixtures; - mSupplier = null; - } - - /** - * Constructs a StaticMockFixtureRule that retrieves a new {@link StaticMockFixture} instance - * from one or more {@link Supplier<? extends StaticMockFixture >}s for each test invocation. - * - * @param supplier the {@link Supplier<? extends StaticMockFixture >}(s) that will supply the - * {@link StaticMockFixture}(s). - */ - @SafeVarargs - public StaticMockFixtureRule(Supplier<? extends StaticMockFixture>... supplier) { - mStaticMockFixtures = null; - mSupplier = supplier; - } - - @Override - public Statement apply(Statement base, Description description) { - StaticMockitoSessionBuilder sessionBuilder = getSessionBuilder(); - - if (mSupplier != null) { - mStaticMockFixtures = new StaticMockFixture[mSupplier.length]; - for (int i = 0; i < mSupplier.length; i++) { - mStaticMockFixtures[i] = mSupplier[i].get(); - } - } - - for (int i = 0; i < mStaticMockFixtures.length; i++) { - sessionBuilder = mStaticMockFixtures[i].setUpMockedClasses(sessionBuilder); - } - - mMockitoSession = sessionBuilder.startMocking(); - - for (int i = 0; i < mStaticMockFixtures.length; i++) { - mStaticMockFixtures[i].setUpMockBehaviors(); - } - - return new TestWatcher() { - @Override - protected void succeeded(Description description) { - tearDown(null); - } - - @Override - protected void skipped(AssumptionViolatedException e, Description description) { - tearDown(e); - } - - @Override - protected void failed(Throwable e, Description description) { - tearDown(e); - } - }.apply(base, description); - } - - /** - * This allows overriding the creation of the builder for a new {@link StaticMockitoSession}. - * Mainly for testing, but also useful if you have other requirements for the session. - * - * @return a new {@link StaticMockitoSessionBuilder}. - */ - public StaticMockitoSessionBuilder getSessionBuilder() { - return mockitoSession().strictness(Strictness.LENIENT); - } - - private void tearDown(Throwable e) { - mMockitoSession.finishMocking(e); - - for (int i = mStaticMockFixtures.length - 1; i >= 0; i--) { - mStaticMockFixtures[i].tearDown(); - if (mSupplier != null) { - mStaticMockFixtures[i] = null; - } - } - - if (mSupplier != null) { - mStaticMockFixtures = null; - } - - mMockitoSession = null; - } -} diff --git a/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRuleTest.java b/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRuleTest.java deleted file mode 100644 index 8e0ccf01d7a7..000000000000 --- a/services/tests/mockingservicestests/src/com/android/server/testables/StaticMockFixtureRuleTest.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright (C) 2019 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.server.testables; - -import static org.junit.Assert.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; -import static org.mockito.quality.Strictness.LENIENT; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.dx.mockito.inline.extended.StaticMockitoSession; -import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder; - -import org.junit.After; -import org.junit.AssumptionViolatedException; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.Description; -import org.junit.runner.RunWith; -import org.junit.runners.model.Statement; -import org.mockito.InOrder; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoSession; - -import java.util.function.Supplier; - -/** Tests that StaticMockFixture manages fixtures and suppliers correctly. */ -@RunWith(AndroidJUnit4.class) -@SmallTest -public class StaticMockFixtureRuleTest { - private MockitoSession mMockitoSession; - - @Mock private StaticMockitoSessionBuilder mSessionBuilder; - @Mock private StaticMockitoSession mSession; - @Mock private StaticMockFixture mA1; - @Mock private StaticMockFixture mB1; - @Mock private StaticMockFixture mA2; - @Mock private StaticMockFixture mB2; - @Mock private Supplier<StaticMockFixture> mSupplyA; - @Mock private Supplier<StaticMockFixture> mSupplyB; - @Mock private Statement mStatement; - @Mock private Statement mSkipStatement; - @Mock private Statement mThrowStatement; - @Mock private Description mDescription; - - @Before - public void setUp() throws Throwable { - mMockitoSession = Mockito.mockitoSession() - .strictness(LENIENT) - .initMocks(this) - .startMocking(); - prepareMockBehaviours(); - } - - @After - public void tearDown() { - mMockitoSession.finishMocking(); - } - - private void prepareFixtureMocks(StaticMockFixture... mocks) { - for (StaticMockFixture mock : mocks) { - when(mock.setUpMockedClasses(any())).thenAnswer( - invocation -> invocation.getArgument(0)); - doNothing().when(mock).setUpMockBehaviors(); - } - } - - private void prepareMockBehaviours() throws Throwable { - when(mSessionBuilder.startMocking()).thenReturn(mSession); - when(mSupplyA.get()).thenReturn(mA1, mA2); - when(mSupplyB.get()).thenReturn(mB1, mB2); - prepareFixtureMocks(mA1, mA2, mB1, mB2); - when(mA1.setUpMockedClasses(any())).thenAnswer(invocation -> invocation.getArgument(0)); - doNothing().when(mA1).setUpMockBehaviors(); - when(mB1.setUpMockedClasses(any())).thenAnswer(invocation -> invocation.getArgument(0)); - doNothing().when(mB1).setUpMockBehaviors(); - doNothing().when(mStatement).evaluate(); - doThrow(new AssumptionViolatedException("bad assumption, test should be skipped")) - .when(mSkipStatement).evaluate(); - doThrow(new IllegalArgumentException("bad argument, test should be failed")) - .when(mThrowStatement).evaluate(); - doNothing().when(mA1).tearDown(); - doNothing().when(mB1).tearDown(); - } - - private InOrder mocksInOrder() { - return inOrder(mSessionBuilder, mSession, mSupplyA, mSupplyB, mA1, mA2, mB1, mB2, - mStatement, mSkipStatement, mThrowStatement, mDescription); - } - - private void verifyNoMoreImportantMockInteractions() { - verifyNoMoreInteractions(mSupplyA, mSupplyB, mA1, mA2, mB1, mB2, mStatement, - mSkipStatement, mThrowStatement); - } - - @Test - public void testRuleWorksWithExplicitFixtures() throws Throwable { - InOrder inOrder = mocksInOrder(); - - StaticMockFixtureRule rule = new StaticMockFixtureRule(mA1, mB1) { - @Override public StaticMockitoSessionBuilder getSessionBuilder() { - return mSessionBuilder; - } - }; - Statement runMe = rule.apply(mStatement, mDescription); - - inOrder.verify(mA1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class)); - inOrder.verify(mB1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class)); - inOrder.verify(mA1).setUpMockBehaviors(); - inOrder.verify(mB1).setUpMockBehaviors(); - - runMe.evaluate(); - - inOrder.verify(mStatement).evaluate(); - // note: tearDown in reverse order - inOrder.verify(mB1).tearDown(); - inOrder.verify(mA1).tearDown(); - - // Round two: use the same fixtures again. - rule.apply(mStatement, mDescription).evaluate(); - - inOrder.verify(mA1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class)); - inOrder.verify(mB1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class)); - inOrder.verify(mA1).setUpMockBehaviors(); - inOrder.verify(mB1).setUpMockBehaviors(); - inOrder.verify(mStatement).evaluate(); - // note: tearDown in reverse order - inOrder.verify(mB1).tearDown(); - inOrder.verify(mA1).tearDown(); - - verifyNoMoreImportantMockInteractions(); - } - - @Test - public void testRuleWorksWithFixtureSuppliers() throws Throwable { - InOrder inOrder = mocksInOrder(); - - StaticMockFixtureRule rule = new StaticMockFixtureRule(mSupplyA, mSupplyB) { - @Override public StaticMockitoSessionBuilder getSessionBuilder() { - return mSessionBuilder; - } - }; - Statement runMe = rule.apply(mStatement, mDescription); - - inOrder.verify(mSupplyA).get(); - inOrder.verify(mSupplyB).get(); - inOrder.verify(mA1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class)); - inOrder.verify(mB1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class)); - inOrder.verify(mA1).setUpMockBehaviors(); - inOrder.verify(mB1).setUpMockBehaviors(); - - runMe.evaluate(); - - inOrder.verify(mStatement).evaluate(); - // note: tearDown in reverse order - inOrder.verify(mB1).tearDown(); - inOrder.verify(mA1).tearDown(); - - // Round two: use the same suppliers again to retrieve different fixtures: mA2 and mB2 - rule.apply(mStatement, mDescription).evaluate(); - - inOrder.verify(mSupplyA).get(); - inOrder.verify(mSupplyB).get(); - inOrder.verify(mA2).setUpMockedClasses(any(StaticMockitoSessionBuilder.class)); - inOrder.verify(mB2).setUpMockedClasses(any(StaticMockitoSessionBuilder.class)); - inOrder.verify(mA2).setUpMockBehaviors(); - inOrder.verify(mB2).setUpMockBehaviors(); - inOrder.verify(mStatement).evaluate(); - // note: tearDown in reverse order - inOrder.verify(mB2).tearDown(); - inOrder.verify(mA2).tearDown(); - - verifyNoMoreImportantMockInteractions(); - } - - @Test - public void testTearDownOnSkippedTests() throws Throwable { - InOrder inOrder = mocksInOrder(); - - StaticMockFixtureRule rule = new StaticMockFixtureRule(mA1, mB1) { - @Override public StaticMockitoSessionBuilder getSessionBuilder() { - return mSessionBuilder; - } - }; - Statement skipStatement = rule.apply(mSkipStatement, mDescription); - - inOrder.verify(mA1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class)); - inOrder.verify(mB1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class)); - inOrder.verify(mA1).setUpMockBehaviors(); - inOrder.verify(mB1).setUpMockBehaviors(); - - try { - skipStatement.evaluate(); - fail("AssumptionViolatedException should have been thrown"); - } catch (AssumptionViolatedException e) { - // expected - } - - inOrder.verify(mSkipStatement).evaluate(); - // note: tearDown in reverse order - inOrder.verify(mB1).tearDown(); - inOrder.verify(mA1).tearDown(); - - verifyNoMoreImportantMockInteractions(); - } - - @Test - public void testTearDownOnFailedTests() throws Throwable { - InOrder inOrder = mocksInOrder(); - - StaticMockFixtureRule rule = new StaticMockFixtureRule(mA1, mB1) { - @Override public StaticMockitoSessionBuilder getSessionBuilder() { - return mSessionBuilder; - } - }; - Statement failStatement = rule.apply(mThrowStatement, mDescription); - - inOrder.verify(mA1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class)); - inOrder.verify(mB1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class)); - inOrder.verify(mA1).setUpMockBehaviors(); - inOrder.verify(mB1).setUpMockBehaviors(); - - try { - failStatement.evaluate(); - fail("IllegalArgumentException should have been thrown"); - } catch (IllegalArgumentException e) { - // expected - } - - inOrder.verify(mThrowStatement).evaluate(); - // note: tearDown in reverse order - inOrder.verify(mB1).tearDown(); - inOrder.verify(mA1).tearDown(); - - verifyNoMoreImportantMockInteractions(); - } -} diff --git a/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfig.java b/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfig.java deleted file mode 100644 index 60a7f78c6949..000000000000 --- a/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfig.java +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright (C) 2019 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.server.testables; - -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyFloat; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.nullable; -import static org.mockito.Mockito.when; - -import android.provider.DeviceConfig; -import android.provider.DeviceConfig.Properties; -import android.util.ArrayMap; -import android.util.Pair; - -import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder; - -import org.junit.rules.TestRule; -import org.mockito.ArgumentMatchers; -import org.mockito.Mockito; -import org.mockito.stubbing.Answer; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executor; - -/** - * TestableDeviceConfig is a {@link StaticMockFixture} that uses ExtendedMockito to replace the real - * implementation of DeviceConfig with essentially a local HashMap in the callers process. This - * allows for unit testing that do not modify the real DeviceConfig on the device at all. - */ -public final class TestableDeviceConfig implements StaticMockFixture { - - private Map<DeviceConfig.OnPropertiesChangedListener, Pair<String, Executor>> - mOnPropertiesChangedListenerMap = new HashMap<>(); - private Map<String, String> mKeyValueMap = new ConcurrentHashMap<>(); - - /** - * Clears out all local overrides. - */ - public void clearDeviceConfig() { - mKeyValueMap.clear(); - } - - /** - * {@inheritDoc} - */ - @Override - public StaticMockitoSessionBuilder setUpMockedClasses( - StaticMockitoSessionBuilder sessionBuilder) { - sessionBuilder.spyStatic(DeviceConfig.class); - return sessionBuilder; - } - - /** - * {@inheritDoc} - */ - @Override - public void setUpMockBehaviors() { - doAnswer((Answer<Void>) invocationOnMock -> { - String namespace = invocationOnMock.getArgument(0); - Executor executor = invocationOnMock.getArgument(1); - DeviceConfig.OnPropertiesChangedListener onPropertiesChangedListener = - invocationOnMock.getArgument(2); - mOnPropertiesChangedListenerMap.put( - onPropertiesChangedListener, new Pair<>(namespace, executor)); - return null; - }).when(() -> DeviceConfig.addOnPropertiesChangedListener( - anyString(), any(Executor.class), - any(DeviceConfig.OnPropertiesChangedListener.class))); - - doAnswer((Answer<Boolean>) invocationOnMock -> { - String namespace = invocationOnMock.getArgument(0); - String name = invocationOnMock.getArgument(1); - String value = invocationOnMock.getArgument(2); - mKeyValueMap.put(getKey(namespace, name), value); - invokeListeners(namespace, getProperties(namespace, name, value)); - return true; - } - ).when(() -> DeviceConfig.setProperty(anyString(), anyString(), anyString(), anyBoolean())); - - doAnswer((Answer<Boolean>) invocationOnMock -> { - String namespace = invocationOnMock.getArgument(0); - String name = invocationOnMock.getArgument(1); - mKeyValueMap.remove(getKey(namespace, name)); - invokeListeners(namespace, getProperties(namespace, name, null)); - return true; - } - ).when(() -> DeviceConfig.deleteProperty(anyString(), anyString())); - - doAnswer((Answer<Boolean>) invocationOnMock -> { - Properties properties = invocationOnMock.getArgument(0); - String namespace = properties.getNamespace(); - Map<String, String> keyValues = new ArrayMap<>(); - for (String name : properties.getKeyset()) { - String value = properties.getString(name, /* defaultValue= */ ""); - mKeyValueMap.put(getKey(namespace, name), value); - keyValues.put(name.toLowerCase(), value); - } - invokeListeners(namespace, getProperties(namespace, keyValues)); - return true; - } - ).when(() -> DeviceConfig.setProperties(any(Properties.class))); - - doAnswer((Answer<String>) invocationOnMock -> { - String namespace = invocationOnMock.getArgument(0); - String name = invocationOnMock.getArgument(1); - return mKeyValueMap.get(getKey(namespace, name)); - }).when(() -> DeviceConfig.getProperty(anyString(), anyString())); - - doAnswer((Answer<Properties>) invocationOnMock -> { - String namespace = invocationOnMock.getArgument(0); - final int varargStartIdx = 1; - Map<String, String> keyValues = new ArrayMap<>(); - if (invocationOnMock.getArguments().length == varargStartIdx) { - mKeyValueMap.entrySet().forEach(entry -> { - Pair<String, String> nameSpaceAndName = getNameSpaceAndName(entry.getKey()); - if (!nameSpaceAndName.first.equals(namespace)) { - return; - } - keyValues.put(nameSpaceAndName.second.toLowerCase(), entry.getValue()); - }); - } else { - for (int i = varargStartIdx; i < invocationOnMock.getArguments().length; ++i) { - String name = invocationOnMock.getArgument(i); - keyValues.put(name.toLowerCase(), mKeyValueMap.get(getKey(namespace, name))); - } - } - return getProperties(namespace, keyValues); - }).when(() -> DeviceConfig.getProperties(anyString(), ArgumentMatchers.<String>any())); - } - - /** - * {@inheritDoc} - */ - @Override - public void tearDown() { - clearDeviceConfig(); - mOnPropertiesChangedListenerMap.clear(); - } - - private static String getKey(String namespace, String name) { - return namespace + "/" + name; - } - - private Pair<String, String> getNameSpaceAndName(String key) { - final String[] values = key.split("/"); - return Pair.create(values[0], values[1]); - } - - private void invokeListeners(String namespace, Properties properties) { - for (DeviceConfig.OnPropertiesChangedListener listener : - mOnPropertiesChangedListenerMap.keySet()) { - if (namespace.equals(mOnPropertiesChangedListenerMap.get(listener).first)) { - mOnPropertiesChangedListenerMap.get(listener).second.execute( - () -> listener.onPropertiesChanged(properties)); - } - } - } - - private Properties getProperties(String namespace, String name, String value) { - return getProperties(namespace, Collections.singletonMap(name.toLowerCase(), value)); - } - - private Properties getProperties(String namespace, Map<String, String> keyValues) { - Properties properties = Mockito.mock(Properties.class); - when(properties.getNamespace()).thenReturn(namespace); - when(properties.getKeyset()).thenReturn(keyValues.keySet()); - when(properties.getBoolean(anyString(), anyBoolean())).thenAnswer( - invocation -> { - String key = invocation.getArgument(0); - boolean defaultValue = invocation.getArgument(1); - final String value = keyValues.get(key.toLowerCase()); - if (value != null) { - return Boolean.parseBoolean(value); - } else { - return defaultValue; - } - } - ); - when(properties.getFloat(anyString(), anyFloat())).thenAnswer( - invocation -> { - String key = invocation.getArgument(0); - float defaultValue = invocation.getArgument(1); - final String value = keyValues.get(key.toLowerCase()); - if (value != null) { - try { - return Float.parseFloat(value); - } catch (NumberFormatException e) { - return defaultValue; - } - } else { - return defaultValue; - } - } - ); - when(properties.getInt(anyString(), anyInt())).thenAnswer( - invocation -> { - String key = invocation.getArgument(0); - int defaultValue = invocation.getArgument(1); - final String value = keyValues.get(key.toLowerCase()); - if (value != null) { - try { - return Integer.parseInt(value); - } catch (NumberFormatException e) { - return defaultValue; - } - } else { - return defaultValue; - } - } - ); - when(properties.getLong(anyString(), anyLong())).thenAnswer( - invocation -> { - String key = invocation.getArgument(0); - long defaultValue = invocation.getArgument(1); - final String value = keyValues.get(key.toLowerCase()); - if (value != null) { - try { - return Long.parseLong(value); - } catch (NumberFormatException e) { - return defaultValue; - } - } else { - return defaultValue; - } - } - ); - when(properties.getString(anyString(), nullable(String.class))).thenAnswer( - invocation -> { - String key = invocation.getArgument(0); - String defaultValue = invocation.getArgument(1); - final String value = keyValues.get(key.toLowerCase()); - if (value != null) { - return value; - } else { - return defaultValue; - } - } - ); - - return properties; - } - - /** - * <p>TestableDeviceConfigRule is a {@link TestRule} that wraps a {@link TestableDeviceConfig} - * to set it up and tear it down automatically. This works well when you have no other static - * mocks.</p> - * - * <p>TestableDeviceConfigRule should be defined as a rule on your test so it can clean up after - * itself. Like the following:</p> - * <pre class="prettyprint"> - * @Rule - * public final TestableDeviceConfigRule mTestableDeviceConfigRule = - * new TestableDeviceConfigRule(); - * </pre> - */ - public static class TestableDeviceConfigRule extends StaticMockFixtureRule { - public TestableDeviceConfigRule() { - super(TestableDeviceConfig::new); - } - } -} diff --git a/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfigAndOtherStaticMocksTest.java b/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfigAndOtherStaticMocksTest.java deleted file mode 100644 index 9616d13e42fe..000000000000 --- a/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfigAndOtherStaticMocksTest.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (C) 2019 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.server.testables; - -import android.provider.DeviceConfig; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.dx.mockito.inline.extended.ExtendedMockito; -import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder; - -import org.junit.Assert; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.ArrayList; -import java.util.List; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class TestableDeviceConfigAndOtherStaticMocksTest { - @Rule - public StaticMockFixtureRule mStaticMockFixtureRule = - new StaticMockFixtureRule(TestableDeviceConfig::new, AB::new, CD::new); - - private List<String> mCollected; - - @Test - public void testDeviceConfigAndOtherStaticMocks() throws Exception { - mCollected = new ArrayList<>(); - int n = 0; - - String namespace = "foo"; - String flag = "bar"; - String flagValue = "new value"; - - Assert.assertNull(DeviceConfig.getProperty(namespace, flag)); - - A.a(); - verifyCollected(++n, "A.a"); - - DeviceConfig.setProperty(namespace, flag, flagValue, false); - - D.b(); - verifyCollected(++n, "D.b"); - - Assert.assertEquals(flagValue, DeviceConfig.getProperty(namespace, flag)); - - C.b(); - verifyCollected(++n, "C.b"); - - B.a(); - verifyCollected(++n, "B.a"); - } - - private void verifyCollected(int n, String last) { - Assert.assertEquals(n, mCollected.size()); - Assert.assertEquals(last, mCollected.get(n - 1)); - } - - private static class A { - /* package */ static void a() {} - /* package */ static void b() {} - } - - private static class B { - /* package */ static void a() {} - /* package */ static void b() {} - } - - private static class C { - /* package */ static void a() {} - /* package */ static void b() {} - } - - private static class D { - /* package */ static void a() {} - /* package */ static void b() {} - } - - /** - * AB StaticMockFixture class that handles two mocked classes, {@link A} and {@link B}. - */ - private class AB implements StaticMockFixture { - @Override - public StaticMockitoSessionBuilder setUpMockedClasses( - StaticMockitoSessionBuilder sessionBuilder) { - sessionBuilder.spyStatic(A.class); - sessionBuilder.spyStatic(B.class); - return sessionBuilder; - } - - @Override - public void setUpMockBehaviors() { - ExtendedMockito.doAnswer(invocation -> { - mCollected.add("A.a"); - return null; - }).when(A::a); - ExtendedMockito.doAnswer(invocation -> { - mCollected.add("A.b"); - return null; - }).when(A::b); - ExtendedMockito.doAnswer(invocation -> { - mCollected.add("B.a"); - return null; - }).when(B::a); - ExtendedMockito.doAnswer(invocation -> { - mCollected.add("B.b"); - return null; - }).when(B::b); - } - - @Override - public void tearDown() { - - } - } - - /** - * AB StaticMockFixture class that handles two mocked classes, {@link C} and {@link D}. - */ - private class CD implements StaticMockFixture { - @Override - public StaticMockitoSessionBuilder setUpMockedClasses( - StaticMockitoSessionBuilder sessionBuilder) { - sessionBuilder.spyStatic(C.class); - sessionBuilder.spyStatic(D.class); - return sessionBuilder; - } - - @Override - public void setUpMockBehaviors() { - ExtendedMockito.doAnswer(invocation -> { - mCollected.add("C.a"); - return null; - }).when(C::a); - ExtendedMockito.doAnswer(invocation -> { - mCollected.add("C.b"); - return null; - }).when(C::b); - ExtendedMockito.doAnswer(invocation -> { - mCollected.add("D.a"); - return null; - }).when(D::a); - ExtendedMockito.doAnswer(invocation -> { - mCollected.add("D.b"); - return null; - }).when(D::b); - } - - @Override - public void tearDown() { - - } - } -} diff --git a/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfigTest.java b/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfigTest.java deleted file mode 100644 index f9f43876f39a..000000000000 --- a/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfigTest.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright (C) 2019 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.server.testables; - -import static android.provider.DeviceConfig.OnPropertiesChangedListener; - -import static com.google.common.truth.Truth.assertThat; - -import android.app.ActivityThread; -import android.platform.test.annotations.Presubmit; -import android.provider.DeviceConfig; -import android.provider.DeviceConfig.BadConfigException; -import android.provider.DeviceConfig.Properties; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -/** Tests that ensure appropriate settings are backed up. */ -@Presubmit -@RunWith(AndroidJUnit4.class) -@SmallTest -public class TestableDeviceConfigTest { - private static final String sNamespace = "namespace1"; - private static final String sKey = "key1"; - private static final String sValue = "value1"; - private static final long WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS = 2000; // 2 sec - - @Rule - public TestableDeviceConfig.TestableDeviceConfigRule - mTestableDeviceConfig = new TestableDeviceConfig.TestableDeviceConfigRule(); - - @Test - public void getProperty_empty() { - String result = DeviceConfig.getProperty(sNamespace, sKey); - assertThat(result).isNull(); - } - - @Test - public void setAndGetProperty_sameNamespace() { - DeviceConfig.setProperty(sNamespace, sKey, sValue, false); - String result = DeviceConfig.getProperty(sNamespace, sKey); - assertThat(result).isEqualTo(sValue); - } - - @Test - public void setAndGetProperty_differentNamespace() { - String newNamespace = "namespace2"; - DeviceConfig.setProperty(sNamespace, sKey, sValue, false); - String result = DeviceConfig.getProperty(newNamespace, sKey); - assertThat(result).isNull(); - } - - @Test - public void setAndGetProperty_multipleNamespaces() { - String newNamespace = "namespace2"; - String newValue = "value2"; - DeviceConfig.setProperty(sNamespace, sKey, sValue, false); - DeviceConfig.setProperty(newNamespace, sKey, newValue, false); - String result = DeviceConfig.getProperty(sNamespace, sKey); - assertThat(result).isEqualTo(sValue); - result = DeviceConfig.getProperty(newNamespace, sKey); - assertThat(result).isEqualTo(newValue); - } - - @Test - public void setAndGetProperty_overrideValue() { - String newValue = "value2"; - DeviceConfig.setProperty(sNamespace, sKey, sValue, false); - DeviceConfig.setProperty(sNamespace, sKey, newValue, false); - String result = DeviceConfig.getProperty(sNamespace, sKey); - assertThat(result).isEqualTo(newValue); - } - - @Test - public void setProperties() throws BadConfigException { - String newKey = "key2"; - String newValue = "value2"; - DeviceConfig.setProperties(new Properties.Builder(sNamespace).setString(sKey, - sValue).setString(newKey, newValue).build()); - assertThat(DeviceConfig.getProperty(sNamespace, sKey)).isEqualTo(sValue); - assertThat(DeviceConfig.getProperty(sNamespace, newKey)).isEqualTo(newValue); - } - - @Test - public void deleteProperty() { - DeviceConfig.setProperty(sNamespace, sKey, sValue, false); - assertThat(DeviceConfig.getProperty(sNamespace, sKey)).isEqualTo(sValue); - DeviceConfig.deleteProperty(sNamespace, sKey); - assertThat(DeviceConfig.getProperty(sNamespace, sKey)).isNull(); - String newNamespace = "namespace2"; - String newValue = "value2"; - DeviceConfig.setProperty(newNamespace, sKey, newValue, false); - assertThat(DeviceConfig.getProperty(newNamespace, sKey)).isEqualTo(newValue); - DeviceConfig.deleteProperty(newNamespace, sKey); - assertThat(DeviceConfig.getProperty(newNamespace, sKey)).isNull(); - } - - @Test - public void getProperties_empty() { - String newKey = "key2"; - String newValue = "value2"; - DeviceConfig.setProperty(sNamespace, sKey, sValue, false); - Properties properties = DeviceConfig.getProperties(sNamespace); - assertThat(properties.getString(sKey, null)).isEqualTo(sValue); - assertThat(properties.getString(newKey, null)).isNull(); - - DeviceConfig.setProperty(sNamespace, newKey, newValue, false); - properties = DeviceConfig.getProperties(sNamespace); - assertThat(properties.getString(sKey, null)).isEqualTo(sValue); - assertThat(properties.getString(newKey, null)).isEqualTo(newValue); - - } - - @Test - public void getProperties() { - Properties properties = DeviceConfig.getProperties(sNamespace, sKey); - assertThat(properties.getString(sKey, null)).isNull(); - - DeviceConfig.setProperty(sNamespace, sKey, sValue, false); - properties = DeviceConfig.getProperties(sNamespace, sKey); - assertThat(properties.getString(sKey, null)).isEqualTo(sValue); - - String newKey = "key2"; - String newValue = "value2"; - DeviceConfig.setProperty(sNamespace, newKey, newValue, false); - properties = DeviceConfig.getProperties(sNamespace, sKey, newKey); - assertThat(properties.getString(sKey, null)).isEqualTo(sValue); - assertThat(properties.getString(newKey, null)).isEqualTo(newValue); - - String unsetKey = "key3"; - properties = DeviceConfig.getProperties(sNamespace, newKey, unsetKey); - assertThat(properties.getKeyset()).containsExactly(newKey, unsetKey); - assertThat(properties.getString(newKey, null)).isEqualTo(newValue); - assertThat(properties.getString(unsetKey, null)).isNull(); - } - - @Test - public void testListener_setProperty() throws InterruptedException { - CountDownLatch countDownLatch = new CountDownLatch(1); - - OnPropertiesChangedListener changeListener = (properties) -> { - assertThat(properties.getNamespace()).isEqualTo(sNamespace); - assertThat(properties.getKeyset()).containsExactly(sKey); - assertThat(properties.getString(sKey, "bogus_value")).isEqualTo(sValue); - assertThat(properties.getString("bogus_key", "bogus_value")).isEqualTo("bogus_value"); - countDownLatch.countDown(); - }; - try { - DeviceConfig.addOnPropertiesChangedListener(sNamespace, - ActivityThread.currentApplication().getMainExecutor(), changeListener); - DeviceConfig.setProperty(sNamespace, sKey, sValue, false); - assertThat(countDownLatch.await( - WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue(); - } finally { - DeviceConfig.removeOnPropertiesChangedListener(changeListener); - } - } - - @Test - public void testListener_setProperties() throws BadConfigException, InterruptedException { - CountDownLatch countDownLatch = new CountDownLatch(1); - String newKey = "key2"; - String newValue = "value2"; - - OnPropertiesChangedListener changeListener = (properties) -> { - assertThat(properties.getNamespace()).isEqualTo(sNamespace); - assertThat(properties.getKeyset()).containsExactly(sKey, newKey); - assertThat(properties.getString(sKey, "bogus_value")).isEqualTo(sValue); - assertThat(properties.getString(newKey, "bogus_value")).isEqualTo(newValue); - assertThat(properties.getString("bogus_key", "bogus_value")).isEqualTo("bogus_value"); - countDownLatch.countDown(); - }; - try { - DeviceConfig.addOnPropertiesChangedListener(sNamespace, - ActivityThread.currentApplication().getMainExecutor(), changeListener); - DeviceConfig.setProperties(new Properties.Builder(sNamespace).setString(sKey, - sValue).setString(newKey, newValue).build()); - assertThat(countDownLatch.await( - WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue(); - } finally { - DeviceConfig.removeOnPropertiesChangedListener(changeListener); - } - } - - @Test - public void testListener_deleteProperty() throws InterruptedException { - CountDownLatch countDownLatch = new CountDownLatch(1); - - OnPropertiesChangedListener changeListener = (properties) -> { - assertThat(properties.getNamespace()).isEqualTo(sNamespace); - assertThat(properties.getKeyset()).containsExactly(sKey); - assertThat(properties.getString(sKey, "bogus_value")).isEqualTo("bogus_value"); - assertThat(properties.getString("bogus_key", "bogus_value")).isEqualTo("bogus_value"); - countDownLatch.countDown(); - }; - try { - DeviceConfig.addOnPropertiesChangedListener(sNamespace, - ActivityThread.currentApplication().getMainExecutor(), changeListener); - DeviceConfig.deleteProperty(sNamespace, sKey); - assertThat(countDownLatch.await( - WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue(); - } finally { - DeviceConfig.removeOnPropertiesChangedListener(changeListener); - } - } -} - - diff --git a/services/tests/mockingservicestests/src/com/android/server/usage/UserUsageStatsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/usage/UserUsageStatsServiceTest.java index 1542b01be8e1..d74ff8f0b209 100644 --- a/services/tests/mockingservicestests/src/com/android/server/usage/UserUsageStatsServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/usage/UserUsageStatsServiceTest.java @@ -83,7 +83,7 @@ public class UserUsageStatsServiceTest { HashMap<String, Long> installedPkgs = new HashMap<>(); installedPkgs.put(TEST_PACKAGE_NAME, System.currentTimeMillis()); - mService.init(System.currentTimeMillis(), installedPkgs); + mService.init(System.currentTimeMillis(), installedPkgs, true); } @After diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java index b1da890a8751..3ade9ff61735 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -16,13 +16,22 @@ package com.android.server.accessibility; +import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL; +import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; +import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE; +import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -38,18 +47,23 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; -import android.content.res.Resources; import android.graphics.drawable.Icon; +import android.hardware.display.DisplayManagerGlobal; import android.os.IBinder; import android.provider.Settings; -import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; +import android.testing.TestableContext; +import android.view.Display; +import android.view.DisplayAdjustments; +import android.view.DisplayInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import com.android.compatibility.common.util.TestUtils; import com.android.server.LocalServices; import com.android.server.accessibility.AccessibilityManagerService.AccessibilityDisplayListener; +import com.android.server.accessibility.magnification.FullScreenMagnificationController; import com.android.server.accessibility.magnification.MagnificationController; import com.android.server.accessibility.magnification.WindowMagnificationManager; import com.android.server.accessibility.test.MessageCapturingHandler; @@ -57,20 +71,30 @@ import com.android.server.pm.UserManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; + /** * APCT tests for {@link AccessibilityManagerService}. */ -public class AccessibilityManagerServiceTest extends AndroidTestCase { +public class AccessibilityManagerServiceTest { private static final String TAG = "A11Y_MANAGER_SERVICE_TEST"; private static final int ACTION_ID = 20; private static final String LABEL = "label"; private static final String INTENT_ACTION = "TESTACTION"; private static final String DESCRIPTION = "description"; private static final PendingIntent TEST_PENDING_INTENT = PendingIntent.getBroadcast( - InstrumentationRegistry.getTargetContext(), 0, new Intent(INTENT_ACTION), PendingIntent.FLAG_MUTABLE_UNAUDITED); + InstrumentationRegistry.getTargetContext(), 0, new Intent(INTENT_ACTION), + PendingIntent.FLAG_MUTABLE_UNAUDITED); private static final RemoteAction TEST_ACTION = new RemoteAction( Icon.createWithContentUri("content://test"), LABEL, @@ -79,11 +103,12 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { private static final AccessibilityAction NEW_ACCESSIBILITY_ACTION = new AccessibilityAction(ACTION_ID, LABEL); + private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY + 1; + static final ComponentName COMPONENT_NAME = new ComponentName( "com.android.server.accessibility", "AccessibilityManagerServiceTest"); static final int SERVICE_ID = 42; - @Mock private Context mMockContext; @Mock private AccessibilityServiceInfo mMockServiceInfo; @Mock private ResolveInfo mMockResolveInfo; @Mock private AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport; @@ -100,16 +125,19 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { @Mock private IAccessibilityServiceClient mMockServiceClient; @Mock private WindowMagnificationManager mMockWindowMagnificationMgr; @Mock private MagnificationController mMockMagnificationController; - @Mock private Resources mMockResources; + @Mock private FullScreenMagnificationController mMockFullScreenMagnificationController; - private AccessibilityUserState mUserState; + @Rule + public final TestableContext mTestableContext = new TestableContext( + getInstrumentation().getTargetContext(), null); private MessageCapturingHandler mHandler = new MessageCapturingHandler(null); private AccessibilityServiceConnection mAccessibilityServiceConnection; + private AccessibilityInputFilter mInputFilter; private AccessibilityManagerService mA11yms; - @Override - protected void setUp() throws Exception { + @Before + public void setUp() throws Exception { MockitoAnnotations.initMocks(this); LocalServices.removeServiceForTest(WindowManagerInternal.class); LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class); @@ -120,43 +148,54 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { ActivityTaskManagerInternal.class, mMockActivityTaskManagerInternal); LocalServices.addService( UserManagerInternal.class, mMockUserManagerInternal); + mInputFilter = Mockito.mock(FakeInputFilter.class); when(mMockMagnificationController.getWindowMagnificationMgr()).thenReturn( mMockWindowMagnificationMgr); + when(mMockMagnificationController.getFullScreenMagnificationController()).thenReturn( + mMockFullScreenMagnificationController); when(mMockWindowManagerService.getAccessibilityController()).thenReturn( mMockA11yController); when(mMockA11yController.isAccessibilityTracingEnabled()).thenReturn(false); when(mMockUserManagerInternal.isUserUnlockingOrUnlocked(anyInt())).thenReturn(true); + + final ArrayList<Display> displays = new ArrayList<>(); + final Display defaultDisplay = new Display(DisplayManagerGlobal.getInstance(), + Display.DEFAULT_DISPLAY, new DisplayInfo(), + DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); + final Display testDisplay = new Display(DisplayManagerGlobal.getInstance(), TEST_DISPLAY, + new DisplayInfo(), DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); + displays.add(defaultDisplay); + displays.add(testDisplay); + when(mMockA11yDisplayListener.getValidDisplayList()).thenReturn(displays); + mA11yms = new AccessibilityManagerService( - InstrumentationRegistry.getContext(), - mMockPackageManager, - mMockSecurityPolicy, - mMockSystemActionPerformer, - mMockA11yWindowManager, - mMockA11yDisplayListener, - mMockMagnificationController); - - mMockResources = mock(Resources.class); - when(mMockContext.getResources()).thenReturn(mMockResources); - - mUserState = new AccessibilityUserState( - mA11yms.getCurrentUserIdLocked(), mMockContext, mA11yms); - mA11yms.mUserStates.put(mA11yms.getCurrentUserIdLocked(), mUserState); + mTestableContext, + mMockPackageManager, + mMockSecurityPolicy, + mMockSystemActionPerformer, + mMockA11yWindowManager, + mMockA11yDisplayListener, + mMockMagnificationController, + mInputFilter); + + final AccessibilityUserState userState = new AccessibilityUserState( + mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms); + mA11yms.mUserStates.put(mA11yms.getCurrentUserIdLocked(), userState); } private void setupAccessibilityServiceConnection() { - when(mMockContext.getSystemService(Context.DISPLAY_SERVICE)).thenReturn( - InstrumentationRegistry.getContext().getSystemService( - Context.DISPLAY_SERVICE)); - + final AccessibilityUserState userState = mA11yms.mUserStates.get( + mA11yms.getCurrentUserIdLocked()); when(mMockServiceInfo.getResolveInfo()).thenReturn(mMockResolveInfo); mMockResolveInfo.serviceInfo = mock(ServiceInfo.class); mMockResolveInfo.serviceInfo.applicationInfo = mock(ApplicationInfo.class); when(mMockBinder.queryLocalInterface(any())).thenReturn(mMockServiceClient); + mTestableContext.addMockService(COMPONENT_NAME, mMockBinder); mAccessibilityServiceConnection = new AccessibilityServiceConnection( - mUserState, - mMockContext, + userState, + mTestableContext, COMPONENT_NAME, mMockServiceInfo, SERVICE_ID, @@ -170,74 +209,147 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { mMockA11yWindowManager, mMockActivityTaskManagerInternal); mAccessibilityServiceConnection.bindLocked(); - mAccessibilityServiceConnection.onServiceConnected(COMPONENT_NAME, mMockBinder); } @SmallTest + @Test public void testRegisterSystemActionWithoutPermission() throws Exception { doThrow(SecurityException.class).when(mMockSecurityPolicy) .enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY); try { mA11yms.registerSystemAction(TEST_ACTION, ACTION_ID); - fail(); + Assert.fail(); } catch (SecurityException expected) { } verify(mMockSystemActionPerformer, never()).registerSystemAction(ACTION_ID, TEST_ACTION); } @SmallTest + @Test public void testRegisterSystemAction() throws Exception { mA11yms.registerSystemAction(TEST_ACTION, ACTION_ID); verify(mMockSystemActionPerformer).registerSystemAction(ACTION_ID, TEST_ACTION); } - @SmallTest + @Test public void testUnregisterSystemActionWithoutPermission() throws Exception { doThrow(SecurityException.class).when(mMockSecurityPolicy) .enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY); try { mA11yms.unregisterSystemAction(ACTION_ID); - fail(); + Assert.fail(); } catch (SecurityException expected) { } verify(mMockSystemActionPerformer, never()).unregisterSystemAction(ACTION_ID); } @SmallTest + @Test public void testUnregisterSystemAction() throws Exception { mA11yms.unregisterSystemAction(ACTION_ID); verify(mMockSystemActionPerformer).unregisterSystemAction(ACTION_ID); } @SmallTest + @Test public void testOnSystemActionsChanged() throws Exception { setupAccessibilityServiceConnection(); - mA11yms.notifySystemActionsChangedLocked(mUserState); + final AccessibilityUserState userState = mA11yms.mUserStates.get( + mA11yms.getCurrentUserIdLocked()); + + mA11yms.notifySystemActionsChangedLocked(userState); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); verify(mMockServiceClient).onSystemActionsChanged(); } @SmallTest + @Test public void testOnMagnificationTransitionFailed_capabilitiesIsAll_fallBackToPreviousMode() { final AccessibilityUserState userState = mA11yms.mUserStates.get( mA11yms.getCurrentUserIdLocked()); + userState.setMagnificationCapabilitiesLocked(ACCESSIBILITY_MAGNIFICATION_MODE_ALL); + userState.setMagnificationModeLocked(Display.DEFAULT_DISPLAY, + ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN + ); + + mA11yms.onMagnificationTransitionEndedLocked(Display.DEFAULT_DISPLAY, false); + + Assert.assertEquals(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW, + userState.getMagnificationModeLocked(Display.DEFAULT_DISPLAY)); + } + + @SmallTest + @Test + public void testOnMagnificationTransitionSuccess_capabilitiesIsAll_inputFilterRefreshMode() { + final AccessibilityUserState userState = mA11yms.mUserStates.get( + mA11yms.getCurrentUserIdLocked()); userState.setMagnificationCapabilitiesLocked( - Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL); - userState.setMagnificationModeLocked( - Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); + ACCESSIBILITY_MAGNIFICATION_MODE_ALL); + userState.setMagnificationModeLocked(Display.DEFAULT_DISPLAY, + ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN + ); + + mA11yms.onMagnificationTransitionEndedLocked(Display.DEFAULT_DISPLAY, true); + + ArgumentCaptor<Display> displayCaptor = ArgumentCaptor.forClass(Display.class); + verify(mInputFilter, timeout(100)).refreshMagnificationMode(displayCaptor.capture()); + Assert.assertEquals(Display.DEFAULT_DISPLAY, displayCaptor.getValue().getDisplayId()); + } + + @SmallTest + @Test + public void testChangeMagnificationModeOnDefaultDisplay_capabilitiesIsAll_persistChangedMode() + throws Exception { + final AccessibilityUserState userState = mA11yms.mUserStates.get( + mA11yms.getCurrentUserIdLocked()); + userState.setMagnificationCapabilitiesLocked( + ACCESSIBILITY_MAGNIFICATION_MODE_ALL); + userState.setMagnificationModeLocked(Display.DEFAULT_DISPLAY, + ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN + ); + + mA11yms.changeMagnificationMode(Display.DEFAULT_DISPLAY, + ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); + + TestUtils.waitUntil("magnification mode " + ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW + + " is not persisted in setting", 1, + () -> { + final int userMode = Settings.Secure.getIntForUser( + mTestableContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, + ACCESSIBILITY_MAGNIFICATION_MODE_NONE, + mA11yms.getCurrentUserIdLocked()); + return userMode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW; + }); + } - mA11yms.onMagnificationTransitionEndedLocked(false); + @SmallTest + @Test + public void testChangeMagnificationModeOnTestDisplay_capabilitiesIsAll_transitMode() { + final AccessibilityUserState userState = mA11yms.mUserStates.get( + mA11yms.getCurrentUserIdLocked()); + userState.setMagnificationCapabilitiesLocked( + ACCESSIBILITY_MAGNIFICATION_MODE_ALL); + userState.setMagnificationModeLocked(TEST_DISPLAY, + ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN + ); + + mA11yms.changeMagnificationMode(TEST_DISPLAY, + ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); - assertEquals(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW, - userState.getMagnificationModeLocked()); + verify(mMockMagnificationController).transitionMagnificationModeLocked(eq(TEST_DISPLAY), + eq(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW), ArgumentMatchers.isNotNull()); } @SmallTest + @Test public void testOnClientChange_magnificationEnabledAndCapabilityAll_requestConnection() { - mUserState.mAccessibilityShortcutKeyTargets.add(MAGNIFICATION_CONTROLLER_NAME); - mUserState.setMagnificationCapabilitiesLocked( + final AccessibilityUserState userState = mA11yms.mUserStates.get( + mA11yms.getCurrentUserIdLocked()); + userState.mAccessibilityShortcutKeyTargets.add(MAGNIFICATION_CONTROLLER_NAME); + userState.setMagnificationCapabilitiesLocked( Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL); // Invokes client change to trigger onUserStateChanged. @@ -247,6 +359,7 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { } @SmallTest + @Test public void testOnClientChange_boundServiceCanControlMagnification_requestConnection() { setupAccessibilityServiceConnection(); when(mMockSecurityPolicy.canControlMagnification(any())).thenReturn(true); @@ -256,4 +369,11 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { verify(mMockWindowMagnificationMgr).requestConnection(true); } + + public static class FakeInputFilter extends AccessibilityInputFilter { + FakeInputFilter(Context context, + AccessibilityManagerService service) { + super(context, service); + } + } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java index 81ca92cc3c8c..b9d94edc5981 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java @@ -48,6 +48,7 @@ import android.provider.Settings; import android.test.mock.MockContentResolver; import android.testing.DexmakerShareClassLoaderRule; import android.util.ArraySet; +import android.view.Display; import androidx.test.InstrumentationRegistry; @@ -79,6 +80,8 @@ public class AccessibilityUserStateTest { private static final int USER_ID = 42; + private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY; + // Mock package-private class AccessibilityServiceConnection @Rule public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule = new DexmakerShareClassLoaderRule(); @@ -143,7 +146,8 @@ public class AccessibilityUserStateTest { mUserState.setAutoclickEnabledLocked(true); mUserState.setUserNonInteractiveUiTimeoutLocked(30); mUserState.setUserInteractiveUiTimeoutLocked(30); - mUserState.setMagnificationModeLocked(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); + mUserState.setMagnificationModeLocked(TEST_DISPLAY, + ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); mUserState.setFocusAppearanceLocked(20, Color.BLUE); mUserState.onSwitchToAnotherUserLocked(); @@ -165,7 +169,7 @@ public class AccessibilityUserStateTest { assertEquals(0, mUserState.getUserNonInteractiveUiTimeoutLocked()); assertEquals(0, mUserState.getUserInteractiveUiTimeoutLocked()); assertEquals(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN, - mUserState.getMagnificationModeLocked()); + mUserState.getMagnificationModeLocked(TEST_DISPLAY)); assertEquals(mFocusStrokeWidthDefaultValue, mUserState.getFocusStrokeWidthLocked()); assertEquals(mFocusColorDefaultValue, mUserState.getFocusColorLocked()); } @@ -360,12 +364,13 @@ public class AccessibilityUserStateTest { @Test public void setWindowMagnificationMode_returnExpectedMagnificationMode() { assertEquals(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN, - mUserState.getMagnificationModeLocked()); + mUserState.getMagnificationModeLocked(TEST_DISPLAY)); - mUserState.setMagnificationModeLocked(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); + mUserState.setMagnificationModeLocked(TEST_DISPLAY, + ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); assertEquals(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW, - mUserState.getMagnificationModeLocked()); + mUserState.getMagnificationModeLocked(TEST_DISPLAY)); } @Test diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java index 69061c14c70e..8a521d8a7490 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java @@ -150,7 +150,7 @@ public class MagnificationControllerTest { MODE_WINDOW, mTransitionCallBack); - verify(mTransitionCallBack).onResult(true); + verify(mTransitionCallBack).onResult(TEST_DISPLAY, true); verify(mScreenMagnificationController, never()).reset(anyInt(), any(MagnificationAnimationCallback.class)); verify(mMockConnection.getConnection(), never()).enableWindowMagnification(anyInt(), @@ -171,7 +171,7 @@ public class MagnificationControllerTest { mCallbackArgumentCaptor.capture()); mCallbackArgumentCaptor.getValue().onResult(true); mMockConnection.invokeCallbacks(); - verify(mTransitionCallBack).onResult(true); + verify(mTransitionCallBack).onResult(TEST_DISPLAY, true); assertEquals(MAGNIFIED_CENTER_X, mWindowMagnificationManager.getCenterX(TEST_DISPLAY), 0); assertEquals(MAGNIFIED_CENTER_Y, mWindowMagnificationManager.getCenterY(TEST_DISPLAY), 0); } @@ -189,7 +189,7 @@ public class MagnificationControllerTest { mTransitionCallBack); mMockConnection.invokeCallbacks(); - verify(mTransitionCallBack).onResult(true); + verify(mTransitionCallBack).onResult(TEST_DISPLAY, true); assertEquals(MAGNIFIED_CENTER_X, mWindowMagnificationManager.getCenterX(TEST_DISPLAY), 0); assertEquals(MAGNIFIED_CENTER_Y, mWindowMagnificationManager.getCenterY(TEST_DISPLAY), 0); } @@ -225,7 +225,7 @@ public class MagnificationControllerTest { verify(mScreenMagnificationController).setScaleAndCenter(TEST_DISPLAY, DEFAULT_SCALE, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y, true, MAGNIFICATION_GESTURE_HANDLER_ID); - verify(mTransitionCallBack).onResult(true); + verify(mTransitionCallBack).onResult(TEST_DISPLAY, true); } @Test @@ -245,7 +245,7 @@ public class MagnificationControllerTest { verify(mScreenMagnificationController).setScaleAndCenter(TEST_DISPLAY, DEFAULT_SCALE, magnificationBounds.exactCenterX(), magnificationBounds.exactCenterY(), true, MAGNIFICATION_GESTURE_HANDLER_ID); - verify(mTransitionCallBack).onResult(true); + verify(mTransitionCallBack).onResult(TEST_DISPLAY, true); } @Test @@ -265,7 +265,7 @@ public class MagnificationControllerTest { 0); assertEquals(MAGNIFIED_CENTER_Y, mScreenMagnificationController.getCenterY(TEST_DISPLAY), 0); - verify(mTransitionCallBack).onResult(true); + verify(mTransitionCallBack).onResult(TEST_DISPLAY, true); } @Test @@ -285,7 +285,7 @@ public class MagnificationControllerTest { verify(mScreenMagnificationController, never()).setScaleAndCenter(TEST_DISPLAY, DEFAULT_SCALE, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y, true, MAGNIFICATION_GESTURE_HANDLER_ID); - verify(mTransitionCallBack).onResult(false); + verify(mTransitionCallBack).onResult(TEST_DISPLAY, false); } @Test @@ -595,6 +595,13 @@ public class MagnificationControllerTest { verify(mScaleProvider).onUserRemoved(SECOND_USER_ID); } + @Test + public void onChangeMagnificationMode_delegateToService() { + mMagnificationController.onChangeMagnificationMode(TEST_DISPLAY, MODE_WINDOW); + + verify(mService).changeMagnificationMode(TEST_DISPLAY, MODE_WINDOW); + } + private void setMagnificationEnabled(int mode) throws RemoteException { setMagnificationEnabled(mode, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y); } diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java index f4a83c3c5a23..36c37c4dbf2a 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java @@ -72,6 +72,7 @@ import android.os.Message; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; +import android.platform.test.annotations.Presubmit; import android.util.IntArray; import androidx.test.filters.MediumTest; @@ -109,6 +110,7 @@ import java.util.function.Function; * Build/Install/Run: * atest FrameworksServicesTests:ActivityManagerServiceTest */ +@Presubmit @SmallTest public class ActivityManagerServiceTest { private static final String TAG = ActivityManagerServiceTest.class.getSimpleName(); diff --git a/services/tests/servicestests/src/com/android/server/am/UidObserverControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UidObserverControllerTest.java index 0db118d3db47..8a3f246fd656 100644 --- a/services/tests/servicestests/src/com/android/server/am/UidObserverControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UidObserverControllerTest.java @@ -42,6 +42,7 @@ import android.app.IUidObserver; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; import android.util.DebugUtils; import android.util.Pair; import android.util.SparseArray; @@ -57,6 +58,7 @@ import org.mockito.MockitoAnnotations; import java.util.ArrayList; +@Presubmit @SmallTest public class UidObserverControllerTest { private static final int TEST_UID1 = 1111; diff --git a/services/tests/servicestests/src/com/android/server/BatteryServiceTest.java b/services/tests/servicestests/src/com/android/server/health/HealthServiceWrapperTest.java index a2ecbc30ec64..c97a67bad590 100644 --- a/services/tests/servicestests/src/com/android/server/BatteryServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/health/HealthServiceWrapperTest.java @@ -14,19 +14,25 @@ * limitations under the License. */ -package com.android.server; +package com.android.server.health; -import static junit.framework.Assert.*; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.fail; import static org.mockito.Mockito.*; import android.hardware.health.V2_0.IHealth; import android.hidl.manager.V1_0.IServiceManager; import android.hidl.manager.V1_0.IServiceNotification; -import android.test.AndroidTestCase; +import android.os.RemoteException; import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -36,36 +42,39 @@ import java.util.Arrays; import java.util.Collection; import java.util.NoSuchElementException; -public class BatteryServiceTest extends AndroidTestCase { +@RunWith(AndroidJUnit4.class) +public class HealthServiceWrapperTest { @Mock IServiceManager mMockedManager; @Mock IHealth mMockedHal; @Mock IHealth mMockedHal2; - @Mock BatteryService.HealthServiceWrapper.Callback mCallback; - @Mock BatteryService.HealthServiceWrapper.IServiceManagerSupplier mManagerSupplier; - @Mock BatteryService.HealthServiceWrapper.IHealthSupplier mHealthServiceSupplier; - BatteryService.HealthServiceWrapper mWrapper; + @Mock HealthServiceWrapperHidl.Callback mCallback; + @Mock HealthServiceWrapperHidl.IServiceManagerSupplier mManagerSupplier; + @Mock HealthServiceWrapperHidl.IHealthSupplier mHealthServiceSupplier; + HealthServiceWrapper mWrapper; - private static final String VENDOR = BatteryService.HealthServiceWrapper.INSTANCE_VENDOR; + private static final String VENDOR = HealthServiceWrapperHidl.INSTANCE_VENDOR; - @Override + @Before public void setUp() { MockitoAnnotations.initMocks(this); } - @Override + @After public void tearDown() { - if (mWrapper != null) - mWrapper.getHandlerThread().quitSafely(); + if (mWrapper != null) mWrapper.getHandlerThread().quitSafely(); } public static <T> ArgumentMatcher<T> isOneOf(Collection<T> collection) { return new ArgumentMatcher<T>() { - @Override public boolean matches(T e) { + @Override + public boolean matches(T e) { return collection.contains(e); } - @Override public String toString() { + + @Override + public String toString() { return collection.toString(); } }; @@ -73,27 +82,29 @@ public class BatteryServiceTest extends AndroidTestCase { private void initForInstances(String... instanceNamesArr) throws Exception { final Collection<String> instanceNames = Arrays.asList(instanceNamesArr); - doAnswer((invocation) -> { - // technically, preexisting is ignored by - // BatteryService.HealthServiceWrapper.Notification, but still call it correctly. - sendNotification(invocation, true); - sendNotification(invocation, true); - sendNotification(invocation, false); - return null; - }).when(mMockedManager).registerForNotifications( - eq(IHealth.kInterfaceName), - argThat(isOneOf(instanceNames)), - any(IServiceNotification.class)); + doAnswer( + (invocation) -> { + // technically, preexisting is ignored by + // HealthServiceWrapperHidl.Notification, but still call it correctly. + sendNotification(invocation, true); + sendNotification(invocation, true); + sendNotification(invocation, false); + return null; + }) + .when(mMockedManager) + .registerForNotifications( + eq(IHealth.kInterfaceName), + argThat(isOneOf(instanceNames)), + any(IServiceNotification.class)); doReturn(mMockedManager).when(mManagerSupplier).get(); - doReturn(mMockedHal) // init calls this - .doReturn(mMockedHal) // notification 1 - .doReturn(mMockedHal) // notification 2 - .doReturn(mMockedHal2) // notification 3 - .doThrow(new RuntimeException("Should not call getService for more than 4 times")) - .when(mHealthServiceSupplier).get(argThat(isOneOf(instanceNames))); - - mWrapper = new BatteryService.HealthServiceWrapper(); + doReturn(mMockedHal) // init calls this + .doReturn(mMockedHal) // notification 1 + .doReturn(mMockedHal) // notification 2 + .doReturn(mMockedHal2) // notification 3 + .doThrow(new RuntimeException("Should not call getService for more than 4 times")) + .when(mHealthServiceSupplier) + .get(argThat(isOneOf(instanceNames))); } private void waitHandlerThreadFinish() throws Exception { @@ -108,16 +119,20 @@ public class BatteryServiceTest extends AndroidTestCase { private static void sendNotification(InvocationOnMock invocation, boolean preexisting) throws Exception { - ((IServiceNotification)invocation.getArguments()[2]).onRegistration( - IHealth.kInterfaceName, - (String)invocation.getArguments()[1], - preexisting); + ((IServiceNotification) invocation.getArguments()[2]) + .onRegistration( + IHealth.kInterfaceName, (String) invocation.getArguments()[1], preexisting); + } + + private void createWrapper() throws RemoteException { + mWrapper = HealthServiceWrapper.create(mCallback, mManagerSupplier, mHealthServiceSupplier); } @SmallTest + @Test public void testWrapPreferVendor() throws Exception { initForInstances(VENDOR); - mWrapper.init(mCallback, mManagerSupplier, mHealthServiceSupplier); + createWrapper(); waitHandlerThreadFinish(); verify(mCallback, times(1)).onRegistration(same(null), same(mMockedHal), eq(VENDOR)); verify(mCallback, never()).onRegistration(same(mMockedHal), same(mMockedHal), anyString()); @@ -125,10 +140,11 @@ public class BatteryServiceTest extends AndroidTestCase { } @SmallTest + @Test public void testNoService() throws Exception { initForInstances("unrelated"); try { - mWrapper.init(mCallback, mManagerSupplier, mHealthServiceSupplier); + createWrapper(); fail("Expect NoSuchElementException"); } catch (NoSuchElementException ex) { // expected diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java index 4f77afb4969f..f45c869949b5 100644 --- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java @@ -46,18 +46,6 @@ import android.app.IUidObserver; import android.app.PendingIntent; import android.app.Person; import android.app.admin.DevicePolicyManager; -import android.app.appsearch.AppSearchBatchResult; -import android.app.appsearch.AppSearchManager; -import android.app.appsearch.AppSearchResult; -import android.app.appsearch.GenericDocument; -import android.app.appsearch.PackageIdentifier; -import android.app.appsearch.SearchResultPage; -import android.app.appsearch.SetSchemaResponse; -import android.app.appsearch.aidl.AppSearchBatchResultParcel; -import android.app.appsearch.aidl.AppSearchResultParcel; -import android.app.appsearch.aidl.IAppSearchBatchResultCallback; -import android.app.appsearch.aidl.IAppSearchManager; -import android.app.appsearch.aidl.IAppSearchResultCallback; import android.app.role.OnRoleHoldersChangedListener; import android.app.usage.UsageStatsManagerInternal; import android.content.ActivityNotFoundException; @@ -92,9 +80,7 @@ import android.net.Uri; import android.os.Bundle; import android.os.FileUtils; import android.os.Handler; -import android.os.IBinder; import android.os.Looper; -import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteException; @@ -106,6 +92,7 @@ import android.util.ArrayMap; import android.util.Log; import android.util.Pair; +import com.android.internal.infra.AndroidFuture; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.pm.LauncherAppsService.LauncherAppsImpl; @@ -168,7 +155,6 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { case Context.DEVICE_POLICY_SERVICE: return mMockDevicePolicyManager; case Context.APP_SEARCH_SERVICE: - return new AppSearchManager(this, mMockAppSearchManager); case Context.ROLE_SERVICE: // RoleManager is final and cannot be mocked, so we only override the inject // accessor methods in ShortcutService. @@ -647,260 +633,6 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { } } - protected class MockAppSearchManager implements IAppSearchManager { - - protected Map<String, List<PackageIdentifier>> mSchemasVisibleToPackages = - new ArrayMap<>(1); - private Map<String, Map<String, GenericDocument>> mDocumentMap = new ArrayMap<>(1); - - private String getKey(UserHandle userHandle, String databaseName) { - return userHandle.getIdentifier() + "@" + databaseName; - } - - @Override - public void setSchema(String packageName, String databaseName, List<Bundle> schemaBundles, - List<String> schemasNotDisplayedBySystem, - Map<String, List<Bundle>> schemasVisibleToPackagesBundles, boolean forceOverride, - int version, UserHandle userHandle, long binderCallStartTimeMillis, - IAppSearchResultCallback callback) throws RemoteException { - for (Map.Entry<String, List<Bundle>> entry : - schemasVisibleToPackagesBundles.entrySet()) { - final String key = entry.getKey(); - final List<PackageIdentifier> packageIdentifiers; - if (!mSchemasVisibleToPackages.containsKey(key)) { - packageIdentifiers = new ArrayList<>(entry.getValue().size()); - mSchemasVisibleToPackages.put(key, packageIdentifiers); - } else { - packageIdentifiers = mSchemasVisibleToPackages.get(key); - } - for (int i = 0; i < entry.getValue().size(); i++) { - packageIdentifiers.add(new PackageIdentifier(entry.getValue().get(i))); - } - } - final SetSchemaResponse response = new SetSchemaResponse.Builder().build(); - callback.onResult( - new AppSearchResultParcel( - AppSearchResult.newSuccessfulResult(response.getBundle()))); - } - - @Override - public void getSchema(String packageName, String databaseName, UserHandle userHandle, - IAppSearchResultCallback callback) throws RemoteException { - ignore(callback); - } - - @Override - public void getNamespaces(String packageName, String databaseName, UserHandle userHandle, - IAppSearchResultCallback callback) throws RemoteException { - ignore(callback); - } - - @Override - public void putDocuments(String packageName, String databaseName, - List<Bundle> documentBundles, UserHandle userHandle, long binderCallStartTimeMillis, - IAppSearchBatchResultCallback callback) - throws RemoteException { - final List<GenericDocument> docs = new ArrayList<>(documentBundles.size()); - for (Bundle bundle : documentBundles) { - docs.add(new GenericDocument(bundle)); - } - final AppSearchBatchResult.Builder<String, Void> builder = - new AppSearchBatchResult.Builder<>(); - final String key = getKey(userHandle, databaseName); - Map<String, GenericDocument> docMap = mDocumentMap.get(key); - for (GenericDocument doc : docs) { - builder.setSuccess(doc.getId(), null); - if (docMap == null) { - docMap = new ArrayMap<>(1); - mDocumentMap.put(key, docMap); - } - docMap.put(doc.getId(), doc); - } - callback.onResult(new AppSearchBatchResultParcel<>(builder.build())); - } - - @Override - public void getDocuments(String packageName, String databaseName, String namespace, - List<String> ids, Map<String, List<String>> typePropertyPaths, - UserHandle userHandle, long binderCallStartTimeMillis, - IAppSearchBatchResultCallback callback) throws RemoteException { - final AppSearchBatchResult.Builder<String, Bundle> builder = - new AppSearchBatchResult.Builder<>(); - final String key = getKey(userHandle, databaseName); - if (!mDocumentMap.containsKey(key)) { - for (String id : ids) { - builder.setFailure(id, AppSearchResult.RESULT_NOT_FOUND, - key + " not found when getting: " + id); - } - } else { - final Map<String, GenericDocument> docs = mDocumentMap.get(key); - for (String id : ids) { - if (docs.containsKey(id)) { - builder.setSuccess(id, docs.get(id).getBundle()); - } else { - builder.setFailure(id, AppSearchResult.RESULT_NOT_FOUND, - "shortcut not found: " + id); - } - } - } - callback.onResult(new AppSearchBatchResultParcel<>(builder.build())); - } - - @Override - public void query(String packageName, String databaseName, String queryExpression, - Bundle searchSpecBundle, UserHandle userHandle, long binderCallStartTimeMillis, - IAppSearchResultCallback callback) - throws RemoteException { - final String key = getKey(userHandle, databaseName); - if (!mDocumentMap.containsKey(key)) { - final Bundle page = new Bundle(); - page.putLong(SearchResultPage.NEXT_PAGE_TOKEN_FIELD, 1); - page.putParcelableArrayList(SearchResultPage.RESULTS_FIELD, new ArrayList<>()); - callback.onResult( - new AppSearchResultParcel<>(AppSearchResult.newSuccessfulResult(page))); - return; - } - final List<GenericDocument> documents = new ArrayList<>(mDocumentMap.get(key).values()); - final Bundle page = new Bundle(); - page.putLong(SearchResultPage.NEXT_PAGE_TOKEN_FIELD, 0); - final ArrayList<Bundle> resultBundles = new ArrayList<>(); - for (GenericDocument document : documents) { - final Bundle resultBundle = new Bundle(); - resultBundle.putBundle("document", document.getBundle()); - resultBundle.putString("packageName", packageName); - resultBundle.putString("databaseName", databaseName); - resultBundle.putParcelableArrayList("matches", new ArrayList<>()); - resultBundles.add(resultBundle); - } - page.putParcelableArrayList(SearchResultPage.RESULTS_FIELD, resultBundles); - callback.onResult( - new AppSearchResultParcel<>(AppSearchResult.newSuccessfulResult(page))); - } - - @Override - public void globalQuery(String packageName, String queryExpression, Bundle searchSpecBundle, - UserHandle userHandle, long binderCallStartTimeMillis, - IAppSearchResultCallback callback) throws RemoteException { - ignore(callback); - } - - @Override - public void getNextPage(String packageName, long nextPageToken, UserHandle userHandle, - IAppSearchResultCallback callback) throws RemoteException { - final Bundle page = new Bundle(); - page.putLong(SearchResultPage.NEXT_PAGE_TOKEN_FIELD, 1); - page.putParcelableArrayList(SearchResultPage.RESULTS_FIELD, new ArrayList<>()); - callback.onResult( - new AppSearchResultParcel<>(AppSearchResult.newSuccessfulResult(page))); - } - - @Override - public void invalidateNextPageToken(String packageName, long nextPageToken, - UserHandle userHandle) throws RemoteException { - } - - @Override - public void writeQueryResultsToFile(String packageName, String databaseName, - ParcelFileDescriptor fileDescriptor, String queryExpression, - Bundle searchSpecBundle, UserHandle userHandle, IAppSearchResultCallback callback) - throws RemoteException { - ignore(callback); - } - - @Override - public void putDocumentsFromFile(String packageName, String databaseName, - ParcelFileDescriptor fileDescriptor, UserHandle userHandle, - IAppSearchResultCallback callback) - throws RemoteException { - ignore(callback); - } - - @Override - public void reportUsage(String packageName, String databaseName, String namespace, - String documentId, long usageTimestampMillis, boolean systemUsage, - UserHandle userHandle, - IAppSearchResultCallback callback) - throws RemoteException { - ignore(callback); - } - - @Override - public void removeByDocumentId(String packageName, String databaseName, String namespace, - List<String> ids, UserHandle userHandle, long binderCallStartTimeMillis, - IAppSearchBatchResultCallback callback) - throws RemoteException { - final AppSearchBatchResult.Builder<String, Void> builder = - new AppSearchBatchResult.Builder<>(); - final String key = getKey(userHandle, databaseName); - if (!mDocumentMap.containsKey(key)) { - for (String id : ids) { - builder.setFailure(id, AppSearchResult.RESULT_NOT_FOUND, - "package " + key + " not found when removing " + id); - } - } else { - final Map<String, GenericDocument> docs = mDocumentMap.get(key); - for (String id : ids) { - if (docs.containsKey(id)) { - docs.remove(id); - builder.setSuccess(id, null); - } else { - builder.setFailure(id, AppSearchResult.RESULT_NOT_FOUND, - "shortcut not found when removing " + id); - } - } - } - callback.onResult(new AppSearchBatchResultParcel<>(builder.build())); - } - - @Override - public void removeByQuery(String packageName, String databaseName, String queryExpression, - Bundle searchSpecBundle, UserHandle userHandle, long binderCallStartTimeMillis, - IAppSearchResultCallback callback) - throws RemoteException { - final String key = getKey(userHandle, databaseName); - if (!mDocumentMap.containsKey(key)) { - callback.onResult( - new AppSearchResultParcel<>(AppSearchResult.newSuccessfulResult(null))); - return; - } - mDocumentMap.get(key).clear(); - callback.onResult( - new AppSearchResultParcel<>(AppSearchResult.newSuccessfulResult(null))); - } - - @Override - public void getStorageInfo(String packageName, String databaseName, UserHandle userHandle, - IAppSearchResultCallback callback) throws RemoteException { - ignore(callback); - } - - @Override - public void persistToDisk(String packageName, UserHandle userHandle, - long binderCallStartTimeMillis) throws RemoteException { - } - - @Override - public void initialize(String packageName, UserHandle userHandle, - long binderCallStartTimeMillis, IAppSearchResultCallback callback) - throws RemoteException { - ignore(callback); - } - - @Override - public IBinder asBinder() { - return null; - } - - private void removeShortcuts() { - mDocumentMap.clear(); - } - - private void ignore(IAppSearchResultCallback callback) throws RemoteException { - callback.onResult( - new AppSearchResultParcel<>(AppSearchResult.newSuccessfulResult(null))); - } - } - public static class ShortcutActivity extends Activity { } @@ -952,7 +684,6 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { protected PackageManagerInternal mMockPackageManagerInternal; protected UserManager mMockUserManager; protected DevicePolicyManager mMockDevicePolicyManager; - protected MockAppSearchManager mMockAppSearchManager; protected UserManagerInternal mMockUserManagerInternal; protected UsageStatsManagerInternal mMockUsageStatsManagerInternal; protected ActivityManagerInternal mMockActivityManagerInternal; @@ -1102,7 +833,6 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { mMockPackageManagerInternal = mock(PackageManagerInternal.class); mMockUserManager = mock(UserManager.class); mMockDevicePolicyManager = mock(DevicePolicyManager.class); - mMockAppSearchManager = new MockAppSearchManager(); mMockUserManagerInternal = mock(UserManagerInternal.class); mMockUsageStatsManagerInternal = mock(UsageStatsManagerInternal.class); mMockActivityManagerInternal = mock(ActivityManagerInternal.class); @@ -1314,9 +1044,6 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { shutdownServices(); - mMockAppSearchManager.removeShortcuts(); - mMockAppSearchManager = null; - super.tearDown(); } @@ -2235,6 +1962,18 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { return p == null ? null : p.getAllShareTargetsForTest(); } + protected void resetPersistedShortcuts() { + final ShortcutPackage p = mService.getPackageShortcutForTest( + getCallingPackage(), getCallingUserId()); + p.removeAllShortcutsAsync(); + } + + protected void getPersistedShortcut(AndroidFuture<List<ShortcutInfo>> cb) { + final ShortcutPackage p = mService.getPackageShortcutForTest( + getCallingPackage(), getCallingUserId()); + p.getTopShortcutsFromPersistence(cb); + } + /** * @return the number of shortcuts stored internally for the caller that can be used as a share * target in the ShareSheet. Such shortcuts have a matching category with at least one of the @@ -2425,8 +2164,6 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { deleteAllSavedFiles(); - mMockAppSearchManager.removeShortcuts(); - initService(); mService.applyRestore(payload, USER_0); diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest12.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest12.java index 9598a00df33d..bc2d2563b12d 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest12.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest12.java @@ -18,9 +18,20 @@ package com.android.server.pm; import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list; import android.app.PendingIntent; +import android.content.pm.ShortcutInfo; import android.os.RemoteException; +import android.os.SystemClock; import android.os.UserHandle; +import com.android.internal.infra.AndroidFuture; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.stream.Collectors; + /** * Tests for {@link android.app.appsearch.AppSearchManager} and relevant APIs in ShortcutManager. * @@ -28,6 +39,21 @@ import android.os.UserHandle; */ public class ShortcutManagerTest12 extends BaseShortcutManagerTest { + @Override + protected void setUp() throws Exception { + super.setUp(); + mService.updateConfigurationLocked( + ShortcutService.ConfigConstants.KEY_MAX_SHORTCUTS + "=5," + + ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=1"); + } + + @Override + protected void tearDown() throws Exception { + setCaller(CALLING_PACKAGE_1, USER_0); + mService.getPackageShortcutForTest(CALLING_PACKAGE_1, USER_0).removeAllShortcutsAsync(); + super.tearDown(); + } + public void testGetShortcutIntents_ReturnsMutablePendingIntents() throws RemoteException { setDefaultLauncher(USER_0, LAUNCHER_1); @@ -41,4 +67,201 @@ public class ShortcutManagerTest12 extends BaseShortcutManagerTest { assertNotNull(intent); }); } + + public void testSetDynamicShortcuts_PersistsShortcutsToDisk() throws RemoteException { + if (!mService.isAppSearchEnabled()) { + return; + } + setCaller(CALLING_PACKAGE_1, USER_0); + // Verifies setDynamicShortcuts persists shortcuts into AppSearch + mManager.setDynamicShortcuts(list( + makeShortcut("s1"), + makeShortcut("s2"), + makeShortcut("s3") + )); + List<ShortcutInfo> shortcuts = getAllPersistedShortcuts(); + assertNotNull(shortcuts); + assertEquals(3, shortcuts.size()); + Set<String> shortcutIds = + shortcuts.stream().map(ShortcutInfo::getId).collect(Collectors.toSet()); + assertTrue(shortcutIds.contains("s1")); + assertTrue(shortcutIds.contains("s2")); + assertTrue(shortcutIds.contains("s3")); + + // Verifies removeAllDynamicShortcuts removes shortcuts from persistence layer + mManager.removeAllDynamicShortcuts(); + shortcuts = getAllPersistedShortcuts(); + assertNotNull(shortcuts); + assertTrue(shortcuts.isEmpty()); + } + + public void testAddDynamicShortcuts_PersistsShortcutsToDisk() { + if (!mService.isAppSearchEnabled()) { + return; + } + setCaller(CALLING_PACKAGE_1, USER_0); + mManager.setDynamicShortcuts(list( + makeShortcut("s1"), + makeShortcut("s2"), + makeShortcut("s3") + )); + // Verifies addDynamicShortcuts persists shortcuts into AppSearch + mManager.addDynamicShortcuts(list(makeShortcut("s4"), makeShortcut("s5"))); + final List<ShortcutInfo> shortcuts = getAllPersistedShortcuts(); + assertNotNull(shortcuts); + assertEquals(5, shortcuts.size()); + final Set<String> shortcutIds = + shortcuts.stream().map(ShortcutInfo::getId).collect(Collectors.toSet()); + assertTrue(shortcutIds.contains("s1")); + assertTrue(shortcutIds.contains("s2")); + assertTrue(shortcutIds.contains("s3")); + assertTrue(shortcutIds.contains("s4")); + assertTrue(shortcutIds.contains("s5")); + } + + public void testPushDynamicShortcuts_PersistsShortcutsToDisk() { + if (!mService.isAppSearchEnabled()) { + return; + } + setCaller(CALLING_PACKAGE_1, USER_0); + mManager.setDynamicShortcuts(list( + makeShortcut("s1"), + makeShortcut("s2"), + makeShortcut("s3"), + makeShortcut("s4"), + makeShortcut("s5") + )); + List<ShortcutInfo> shortcuts = getAllPersistedShortcuts(); + assertNotNull(shortcuts); + assertEquals(5, shortcuts.size()); + Set<String> shortcutIds = + shortcuts.stream().map(ShortcutInfo::getId).collect(Collectors.toSet()); + assertTrue(shortcutIds.contains("s1")); + assertTrue(shortcutIds.contains("s2")); + assertTrue(shortcutIds.contains("s3")); + assertTrue(shortcutIds.contains("s4")); + assertTrue(shortcutIds.contains("s5")); + // Verifies pushDynamicShortcuts further persists shortcuts into AppSearch without + // removing previous shortcuts when max number of shortcuts is reached. + mManager.pushDynamicShortcut(makeShortcut("s6")); + shortcuts = getAllPersistedShortcuts(); + assertNotNull(shortcuts); + assertEquals(6, shortcuts.size()); + shortcutIds = shortcuts.stream().map(ShortcutInfo::getId).collect(Collectors.toSet()); + assertTrue(shortcutIds.contains("s1")); + assertTrue(shortcutIds.contains("s2")); + assertTrue(shortcutIds.contains("s3")); + assertTrue(shortcutIds.contains("s4")); + assertTrue(shortcutIds.contains("s5")); + assertTrue(shortcutIds.contains("s6")); + } + + public void testRemoveDynamicShortcuts_RemovesShortcutsFromDisk() { + if (!mService.isAppSearchEnabled()) { + return; + } + setCaller(CALLING_PACKAGE_1, USER_0); + mManager.setDynamicShortcuts(list( + makeShortcut("s1"), + makeShortcut("s2"), + makeShortcut("s3"), + makeShortcut("s4"), + makeShortcut("s5") + )); + + // Verifies removeDynamicShortcuts removes shortcuts from persistence layer + mManager.removeDynamicShortcuts(list("s1")); + final List<ShortcutInfo> shortcuts = getAllPersistedShortcuts(); + assertNotNull(shortcuts); + assertEquals(4, shortcuts.size()); + final Set<String> shortcutIds = + shortcuts.stream().map(ShortcutInfo::getId).collect(Collectors.toSet()); + assertTrue(shortcutIds.contains("s2")); + assertTrue(shortcutIds.contains("s3")); + assertTrue(shortcutIds.contains("s4")); + assertTrue(shortcutIds.contains("s5")); + } + + public void testRemoveLongLivedShortcuts_RemovesShortcutsFromDisk() { + if (!mService.isAppSearchEnabled()) { + return; + } + setCaller(CALLING_PACKAGE_1, USER_0); + mManager.setDynamicShortcuts(list( + makeShortcut("s1"), + makeShortcut("s2"), + makeShortcut("s3"), + makeShortcut("s4"), + makeShortcut("s5") + )); + mManager.removeDynamicShortcuts(list("s2")); + final List<ShortcutInfo> shortcuts = getAllPersistedShortcuts(); + assertNotNull(shortcuts); + assertEquals(4, shortcuts.size()); + final Set<String> shortcutIds = + shortcuts.stream().map(ShortcutInfo::getId).collect(Collectors.toSet()); + assertTrue(shortcutIds.contains("s1")); + assertTrue(shortcutIds.contains("s3")); + assertTrue(shortcutIds.contains("s4")); + assertTrue(shortcutIds.contains("s5")); + } + + public void testDisableShortcuts_RemovesShortcutsFromDisk() { + if (!mService.isAppSearchEnabled()) { + return; + } + setCaller(CALLING_PACKAGE_1, USER_0); + mManager.setDynamicShortcuts(list( + makeShortcut("s1"), + makeShortcut("s2"), + makeShortcut("s3"), + makeShortcut("s4"), + makeShortcut("s5") + )); + // Verifies disableShortcuts removes shortcuts from persistence layer + mManager.disableShortcuts(list("s3")); + final List<ShortcutInfo> shortcuts = getAllPersistedShortcuts(); + assertNotNull(shortcuts); + assertEquals(4, shortcuts.size()); + final Set<String> shortcutIds = + shortcuts.stream().map(ShortcutInfo::getId).collect(Collectors.toSet()); + assertTrue(shortcutIds.contains("s1")); + assertTrue(shortcutIds.contains("s2")); + assertTrue(shortcutIds.contains("s4")); + assertTrue(shortcutIds.contains("s5")); + } + + public void testUpdateShortcuts_UpdateShortcutsOnDisk() { + if (!mService.isAppSearchEnabled()) { + return; + } + setCaller(CALLING_PACKAGE_1, USER_0); + mManager.setDynamicShortcuts(list( + makeShortcut("s1"), + makeShortcut("s2"), + makeShortcut("s3"), + makeShortcut("s4"), + makeShortcut("s5") + )); + // Verifies disableShortcuts removes shortcuts from persistence layer + mManager.updateShortcuts(list(makeShortcutWithShortLabel("s3", "custom"))); + final List<ShortcutInfo> shortcuts = getAllPersistedShortcuts(); + assertNotNull(shortcuts); + assertEquals(5, shortcuts.size()); + final Map<String, ShortcutInfo> map = shortcuts.stream() + .collect(Collectors.toMap(ShortcutInfo::getId, Function.identity())); + assertTrue(map.containsKey("s3")); + assertEquals("custom", map.get("s3").getShortLabel()); + } + + private List<ShortcutInfo> getAllPersistedShortcuts() { + try { + SystemClock.sleep(500); + final AndroidFuture<List<ShortcutInfo>> future = new AndroidFuture<>(); + getPersistedShortcut(future); + return future.get(10, TimeUnit.SECONDS); + } catch (Exception e) { + throw new RuntimeException(e); + } + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/CountdownConditionProviderTest.java b/services/tests/uiservicestests/src/com/android/server/notification/CountdownConditionProviderTest.java new file mode 100644 index 000000000000..ddb9b43be76f --- /dev/null +++ b/services/tests/uiservicestests/src/com/android/server/notification/CountdownConditionProviderTest.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.notification; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; + +import android.app.Application; +import android.app.PendingIntent; +import android.content.Intent; +import android.net.Uri; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper.RunWithLooper; + +import androidx.test.filters.SmallTest; + +import com.android.server.UiServiceTestCase; +import com.android.server.pm.PackageManagerService; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidTestingRunner.class) +@SmallTest +@RunWithLooper +public class CountdownConditionProviderTest extends UiServiceTestCase { + + CountdownConditionProvider mService; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + Intent startIntent = + new Intent("com.android.server.notification.CountdownConditionProvider"); + startIntent.setPackage("android"); + CountdownConditionProvider service = new CountdownConditionProvider(); + service.attach( + getContext(), + null, // ActivityThread not actually used in Service + CountdownConditionProvider.class.getName(), + null, // token not needed when not talking with the activity manager + mock(Application.class), + null // mocked services don't talk with the activity manager + ); + service.onCreate(); + service.onBind(startIntent); + mService = spy(service); + } + + @Test + public void testGetPendingIntent() { + PendingIntent pi = mService.getPendingIntent(Uri.EMPTY); + assertEquals(PackageManagerService.PLATFORM_PACKAGE_NAME, pi.getIntent().getPackage()); + } +} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/EventConditionProviderTest.java b/services/tests/uiservicestests/src/com/android/server/notification/EventConditionProviderTest.java new file mode 100644 index 000000000000..4c440cabf9c4 --- /dev/null +++ b/services/tests/uiservicestests/src/com/android/server/notification/EventConditionProviderTest.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.notification; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; + +import android.app.Application; +import android.app.PendingIntent; +import android.content.Intent; +import android.net.Uri; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper.RunWithLooper; + +import androidx.test.filters.SmallTest; + +import com.android.server.UiServiceTestCase; +import com.android.server.pm.PackageManagerService; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidTestingRunner.class) +@SmallTest +@RunWithLooper +public class EventConditionProviderTest extends UiServiceTestCase { + + EventConditionProvider mService; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + Intent startIntent = + new Intent("com.android.server.notification.EventConditionProvider"); + startIntent.setPackage("android"); + EventConditionProvider service = new EventConditionProvider(); + service.attach( + getContext(), + null, // ActivityThread not actually used in Service + CountdownConditionProvider.class.getName(), + null, // token not needed when not talking with the activity manager + mock(Application.class), + null // mocked services don't talk with the activity manager + ); + service.onCreate(); + service.onBind(startIntent); + mService = spy(service); + } + + @Test + public void testGetPendingIntent() { + PendingIntent pi = mService.getPendingIntent(1000); + assertEquals(PackageManagerService.PLATFORM_PACKAGE_NAME, pi.getIntent().getPackage()); + } +} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index def38a6427a7..5a5a4e754390 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -142,6 +142,7 @@ import android.content.res.Resources; import android.graphics.Color; import android.graphics.drawable.Icon; import android.media.AudioManager; +import android.media.session.MediaSession; import android.net.Uri; import android.os.Binder; import android.os.Build; @@ -214,6 +215,7 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.stubbing.Answer; @@ -478,6 +480,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { ArgumentCaptor<IntentFilter> intentFilterCaptor = ArgumentCaptor.forClass(IntentFilter.class); + Mockito.doReturn(new Intent()).when(mContext).registerReceiverAsUser( + any(), any(), any(), any(), any()); + Mockito.doReturn(new Intent()).when(mContext).registerReceiver(any(), any()); verify(mContext, atLeastOnce()).registerReceiverAsUser(broadcastReceiverCaptor.capture(), any(), intentFilterCaptor.capture(), any(), any()); verify(mContext, atLeastOnce()).registerReceiver(broadcastReceiverCaptor.capture(), @@ -8461,4 +8466,45 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { fail("call to matchesCallFilter with listener permissions should work"); } } + + @Test + public void testMediaNotificationsBypassBlock() throws Exception { + when(mAmi.getPendingIntentFlags(any(IIntentSender.class))) + .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT); + when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); + + Notification.Builder nb = new Notification.Builder( + mContext, mTestNotificationChannel.getId()) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .addAction(new Notification.Action.Builder(null, "test", null).build()); + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, + nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + mBinderService.setNotificationsEnabledForPackage( + r.getSbn().getPackageName(), r.getUid(), false); + + // normal blocked notifications - blocked + assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), + r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse(); + + // just using the style - blocked + nb.setStyle(new Notification.MediaStyle()); + sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, + nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); + r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), + r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse(); + + // style + media session - bypasses block + nb.setStyle(new Notification.MediaStyle().setMediaSession(mock(MediaSession.Token.class))); + sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, + nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); + r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), + r.getSbn().getId(), r.getSbn().getTag(), r, false)).isTrue(); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java index 423ba94729ff..011d190ea83a 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java @@ -23,6 +23,8 @@ import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; +import static android.app.PendingIntent.FLAG_MUTABLE; +import static android.app.PendingIntent.FLAG_ONE_SHOT; import static android.content.pm.PackageManager.FEATURE_WATCH; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.UserHandle.USER_SYSTEM; @@ -84,6 +86,7 @@ import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutServiceInternal; import android.content.res.Resources; import android.media.AudioManager; +import android.media.session.MediaSession; import android.os.Binder; import android.os.Build; import android.os.IBinder; @@ -711,4 +714,102 @@ public class NotificationPermissionMigrationTest extends UiServiceTestCase { assertThat(r.isImportanceFixed()).isTrue(); } + + @Test + public void testMediaNotificationsBypassBlock() throws Exception { + when(mAmi.getPendingIntentFlags(any(IIntentSender.class))) + .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT); + when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); + + Notification.Builder nb = new Notification.Builder( + mContext, mTestNotificationChannel.getId()) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .addAction(new Notification.Action.Builder(null, "test", null).build()); + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, + nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + when(mPermissionHelper.hasPermission(mUid)).thenReturn(false); + + // normal blocked notifications - blocked + assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), + r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse(); + + // just using the style - blocked + nb.setStyle(new Notification.MediaStyle()); + sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, + nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); + r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), + r.getSbn().getId(), r.getSbn().getTag(), r, false)).isFalse(); + + // style + media session - bypasses block + nb.setStyle(new Notification.MediaStyle().setMediaSession(mock(MediaSession.Token.class))); + sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, + nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); + r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + assertThat(mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), + r.getSbn().getId(), r.getSbn().getTag(), r, false)).isTrue(); + } + + @Test + public void testMediaNotificationsBypassBlock_atPost() throws Exception { + when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false); + when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); + + Notification.Builder nb = new Notification.Builder( + mContext, mTestNotificationChannel.getId()) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .addAction(new Notification.Action.Builder(null, "test", null).build()); + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, + nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + when(mPermissionHelper.hasPermission(anyInt())).thenReturn(false); + + mService.addEnqueuedNotification(r); + NotificationManagerService.PostNotificationRunnable runnable = + mService.new PostNotificationRunnable(r.getKey()); + runnable.run(); + waitForIdle(); + + verify(mUsageStats).registerBlocked(any()); + verify(mUsageStats, never()).registerPostedByApp(any()); + + // just using the style - blocked + mService.clearNotifications(); + reset(mUsageStats); + nb.setStyle(new Notification.MediaStyle()); + sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, + nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); + r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + mService.addEnqueuedNotification(r); + runnable = mService.new PostNotificationRunnable(r.getKey()); + runnable.run(); + waitForIdle(); + + verify(mUsageStats).registerBlocked(any()); + verify(mUsageStats, never()).registerPostedByApp(any()); + + // style + media session - bypasses block + mService.clearNotifications(); + reset(mUsageStats); + nb.setStyle(new Notification.MediaStyle().setMediaSession(mock(MediaSession.Token.class))); + sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0, + nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); + r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + mService.addEnqueuedNotification(r); + runnable = mService.new PostNotificationRunnable(r.getKey()); + runnable.run(); + waitForIdle(); + + verify(mUsageStats, never()).registerBlocked(any()); + verify(mUsageStats).registerPostedByApp(any()); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index d4991fce8047..2954d783c149 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -1084,6 +1084,7 @@ public class SizeCompatTests extends WindowTestsBase { @Test @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, + ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY, ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM}) public void testOverrideMinAspectRatioScreenOrientationNotSetThenChangedToPortrait() { // In this test, the activity's orientation isn't fixed to portrait, therefore the override @@ -1115,6 +1116,7 @@ public class SizeCompatTests extends WindowTestsBase { @Test @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, + ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY, ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM}) public void testOverrideMinAspectRatioScreenOrientationLandscapeThenChangedToPortrait() { // In this test, the activity's orientation isn't fixed to portrait, therefore the override @@ -1147,6 +1149,7 @@ public class SizeCompatTests extends WindowTestsBase { @Test @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, + ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY, ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM}) public void testOverrideMinAspectRatioScreenOrientationPortraitThenChangedToUnspecified() { setUpDisplaySizeWithApp(1000, 1200); @@ -1175,6 +1178,52 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, + ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM}) + @DisableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY}) + public void testOverrideMinAspectRatioPortraitOnlyDisabledScreenOrientationNotSet() { + setUpDisplaySizeWithApp(1000, 1200); + + // Create a size compat activity on the same task. + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setTask(mTask) + .setComponent(ComponentName.createRelative(mContext, + SizeCompatTests.class.getName())) + .setUid(android.os.Process.myUid()) + .build(); + + // The per-package override forces the activity into a 3:2 aspect ratio + assertEquals(1200, activity.getBounds().height()); + assertEquals(1200 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE, + activity.getBounds().width(), 0.5); + } + + @Test + @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, + ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM}) + @DisableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY}) + public void testOverrideMinAspectRatioPortraitOnlyDisabledScreenOrientationLandscape() { + // In this test, the activity's orientation isn't fixed to portrait, therefore the override + // isn't applied. + + setUpDisplaySizeWithApp(1000, 1200); + + // Create a size compat activity on the same task. + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setTask(mTask) + .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) + .setComponent(ComponentName.createRelative(mContext, + SizeCompatTests.class.getName())) + .setUid(android.os.Process.myUid()) + .build(); + + // The per-package override forces the activity into a 3:2 aspect ratio + assertEquals(1000 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE, + activity.getBounds().height(), 0.5); + assertEquals(1000, activity.getBounds().width()); + } + + @Test @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM}) public void testOverrideMinAspectRatioWithoutGlobalOverride() { // In this test, only OVERRIDE_MIN_ASPECT_RATIO_1_5 is set, which has no effect without diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index cbdf4fe4551f..d89d64ad0037 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -254,7 +254,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { }); // Allow transaction to change a TaskFragment created by the organizer. - mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* pid */); + mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, + "Test:TaskFragmentOrganizer" /* processName */); mAtm.getWindowOrganizerController().applyTransaction(mTransaction); } @@ -276,7 +277,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { }); // Allow transaction to change a TaskFragment created by the organizer. - mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* pid */); + mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, + "Test:TaskFragmentOrganizer" /* processName */); mAtm.getWindowOrganizerController().applyTransaction(mTransaction); } @@ -301,7 +303,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { }); // Allow transaction to change a TaskFragment created by the organizer. - mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* pid */); + mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, + "Test:TaskFragmentOrganizer" /* processName */); clearInvocations(mAtm.mRootWindowContainer); mAtm.getWindowOrganizerController().applyTransaction(mTransaction); @@ -337,8 +340,10 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { }); // Allow transaction to change a TaskFragment created by the organizer. - mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* pid */); - taskFragment2.setTaskFragmentOrganizer(mOrganizerToken, 10 /* pid */); + mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, + "Test:TaskFragmentOrganizer" /* processName */); + taskFragment2.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, + "Test:TaskFragmentOrganizer" /* processName */); clearInvocations(mAtm.mRootWindowContainer); mAtm.getWindowOrganizerController().applyTransaction(mTransaction); @@ -391,7 +396,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { }); // Allow transaction to change a TaskFragment created by the organizer. - mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* pid */); + mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* uid */, + "Test:TaskFragmentOrganizer" /* processName */); clearInvocations(mAtm.mRootWindowContainer); mAtm.getWindowOrganizerController().applyTransaction(mTransaction); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java index 42f4d583f5ff..6737b1ade785 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java @@ -20,12 +20,15 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.clearInvocations; import android.graphics.Rect; +import android.os.Binder; import android.platform.test.annotations.Presubmit; import android.view.SurfaceControl; import android.window.ITaskFragmentOrganizer; +import android.window.TaskFragmentInfo; import android.window.TaskFragmentOrganizer; import androidx.test.filters.MediumTest; @@ -64,6 +67,7 @@ public class TaskFragmentTest extends WindowTestsBase { mTaskFragment = new TaskFragmentBuilder(mAtm) .setCreateParentTask() .setOrganizer(mOrganizer) + .setFragmentToken(new Binder()) .build(); mLeash = mTaskFragment.getSurfaceControl(); spyOn(mTaskFragment); @@ -103,4 +107,23 @@ public class TaskFragmentTest extends WindowTestsBase { verify(mTransaction).setPosition(mLeash, 500, 500); verify(mTransaction).setWindowCrop(mLeash, 500, 500); } + + /** + * Tests that when a {@link TaskFragmentInfo} is generated from a {@link TaskFragment}, an + * activity that has not yet been attached to a process because it is being initialized but + * belongs to the TaskFragmentOrganizer process is still reported in the TaskFragmentInfo. + */ + @Test + public void testActivityStillReported_NotYetAssignedToProcess() { + mTaskFragment.addChild(new ActivityBuilder(mAtm).setUid(DEFAULT_TASK_FRAGMENT_ORGANIZER_UID) + .setProcessName(DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME).build()); + final ActivityRecord activity = mTaskFragment.getTopMostActivity(); + // Remove the process to simulate an activity that has not yet been attached to a process + activity.app = null; + final TaskFragmentInfo info = activity.getTaskFragment().getTaskFragmentInfo(); + assertEquals(1, info.getRunningActivityCount()); + assertEquals(1, info.getActivities().size()); + assertEquals(false, info.isEmpty()); + assertEquals(activity.token, info.getActivities().get(0)); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 386ff4b80933..996b4b22dde7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -126,6 +126,9 @@ class WindowTestsBase extends SystemServiceTestsBase { // Default package name static final String DEFAULT_COMPONENT_PACKAGE_NAME = "com.foo"; + static final int DEFAULT_TASK_FRAGMENT_ORGANIZER_UID = 10000; + static final String DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME = "Test:TaskFragmentOrganizer"; + // Default base activity name private static final String DEFAULT_COMPONENT_CLASS_NAME = ".BarActivity"; @@ -1234,7 +1237,8 @@ class WindowTestsBase extends SystemServiceTestsBase { } if (mOrganizer != null) { taskFragment.setTaskFragmentOrganizer( - mOrganizer.getOrganizerToken(), 10000 /* pid */); + mOrganizer.getOrganizerToken(), DEFAULT_TASK_FRAGMENT_ORGANIZER_UID, + DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME); } return taskFragment; } diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java index 0bd7b20c1364..2d3b9286d69a 100644 --- a/services/usage/java/com/android/server/usage/StorageStatsService.java +++ b/services/usage/java/com/android/server/usage/StorageStatsService.java @@ -31,8 +31,11 @@ import android.app.usage.ExternalStorageStats; import android.app.usage.IStorageStatsManager; import android.app.usage.StorageStats; import android.app.usage.UsageStatsManagerInternal; +import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; @@ -155,6 +158,21 @@ public class StorageStatsService extends IStorageStatsManager.Stub { }); LocalManagerRegistry.addManager(StorageStatsManagerLocal.class, new LocalService()); + + IntentFilter prFilter = new IntentFilter(); + prFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + prFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED); + prFilter.addDataScheme("package"); + mContext.registerReceiver(new BroadcastReceiver() { + @Override public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Intent.ACTION_PACKAGE_REMOVED.equals(action) + || Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) { + mHandler.removeMessages(H.MSG_PACKAGE_REMOVED); + mHandler.sendEmptyMessage(H.MSG_PACKAGE_REMOVED); + } + } + }, prFilter); } private void invalidateMounts() { @@ -531,6 +549,7 @@ public class StorageStatsService extends IStorageStatsManager.Stub { private static final int MSG_CHECK_STORAGE_DELTA = 100; private static final int MSG_LOAD_CACHED_QUOTAS_FROM_FILE = 101; private static final int MSG_RECALCULATE_QUOTAS = 102; + private static final int MSG_PACKAGE_REMOVED = 103; /** * By only triggering a re-calculation after the storage has changed sizes, we can avoid * recalculating quotas too often. Minimum change delta defines the percentage of change @@ -599,6 +618,11 @@ public class StorageStatsService extends IStorageStatsManager.Stub { sendEmptyMessageDelayed(MSG_RECALCULATE_QUOTAS, DELAY_RECALCULATE_QUOTAS); break; } + case MSG_PACKAGE_REMOVED: { + // recalculate quotas when package is removed + recalculateQuotas(getInitializedStrategy()); + break; + } default: if (DEBUG) { Slog.v(TAG, ">>> default message case "); diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 6dbf4c56da3b..a32bd2dadc18 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -403,6 +403,7 @@ public class UsageStatsService extends SystemService implements if (userId == UserHandle.USER_SYSTEM) { UsageStatsIdleService.scheduleUpdateMappingsJob(getContext()); } + final boolean deleteObsoleteData = shouldDeleteObsoleteData(UserHandle.of(userId)); synchronized (mLock) { // This should be safe to add this early. Other than reportEventOrAddToQueue and // getBackupPayload, every other user grabs the lock before accessing @@ -425,7 +426,7 @@ public class UsageStatsService extends SystemService implements boolean needToFlush = !pendingEvents.isEmpty(); initializeUserUsageStatsServiceLocked(userId, System.currentTimeMillis(), - installedPackages); + installedPackages, deleteObsoleteData); final UserUsageStatsService userService = getUserUsageStatsServiceLocked(userId); if (userService == null) { Slog.i(TAG, "Attempted to unlock stopped or removed user " + userId); @@ -651,13 +652,13 @@ public class UsageStatsService extends SystemService implements * when the user is initially unlocked. */ private void initializeUserUsageStatsServiceLocked(int userId, long currentTimeMillis, - HashMap<String, Long> installedPackages) { + HashMap<String, Long> installedPackages, boolean deleteObsoleteData) { final File usageStatsDir = new File(Environment.getDataSystemCeDirectory(userId), "usagestats"); final UserUsageStatsService service = new UserUsageStatsService(getContext(), userId, usageStatsDir, this); try { - service.init(currentTimeMillis, installedPackages); + service.init(currentTimeMillis, installedPackages, deleteObsoleteData); mUserState.put(userId, service); } catch (Exception e) { if (mUserManager.isUserUnlocked(userId)) { @@ -1246,6 +1247,10 @@ public class UsageStatsService extends SystemService implements * Called by the Binder stub. */ private boolean updatePackageMappingsData() { + // don't update the mappings if a profile user is defined + if (!shouldDeleteObsoleteData(UserHandle.SYSTEM)) { + return true; // return true so job scheduler doesn't reschedule the job + } // fetch the installed packages outside the lock so it doesn't block package manager. final HashMap<String, Long> installedPkgs = getInstalledPackages(UserHandle.USER_SYSTEM); synchronized (mLock) { @@ -1584,6 +1589,13 @@ public class UsageStatsService extends SystemService implements mEstimatedLaunchTimeChangedListeners.remove(listener); } + private boolean shouldDeleteObsoleteData(UserHandle userHandle) { + final DevicePolicyManagerInternal dpmInternal = getDpmInternal(); + // If a profile owner is not defined for the given user, obsolete data should be deleted + return dpmInternal == null + || dpmInternal.getProfileOwnerOrDeviceOwnerSupervisionComponent(userHandle) == null; + } + private String buildFullToken(String packageName, String token) { final StringBuilder sb = new StringBuilder(packageName.length() + token.length() + 1); sb.append(packageName); @@ -2862,8 +2874,12 @@ public class UsageStatsService extends SystemService implements private class MyPackageMonitor extends PackageMonitor { @Override public void onPackageRemoved(String packageName, int uid) { - mHandler.obtainMessage(MSG_PACKAGE_REMOVED, getChangingUserId(), 0, packageName) - .sendToTarget(); + final int changingUserId = getChangingUserId(); + // Only remove the package's data if a profile owner is not defined for the user + if (shouldDeleteObsoleteData(UserHandle.of(changingUserId))) { + mHandler.obtainMessage(MSG_PACKAGE_REMOVED, changingUserId, 0, packageName) + .sendToTarget(); + } super.onPackageRemoved(packageName, uid); } } diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index 23694fc5b418..79f5808739db 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -117,8 +117,9 @@ class UserUsageStatsService { mSystemTimeSnapshot = System.currentTimeMillis(); } - void init(final long currentTimeMillis, HashMap<String, Long> installedPackages) { - readPackageMappingsLocked(installedPackages); + void init(final long currentTimeMillis, HashMap<String, Long> installedPackages, + boolean deleteObsoleteData) { + readPackageMappingsLocked(installedPackages, deleteObsoleteData); mDatabase.init(currentTimeMillis); if (mDatabase.wasUpgradePerformed()) { mDatabase.prunePackagesDataOnUpgrade(installedPackages); @@ -182,12 +183,13 @@ class UserUsageStatsService { return mDatabase.onPackageRemoved(packageName, timeRemoved); } - private void readPackageMappingsLocked(HashMap<String, Long> installedPackages) { + private void readPackageMappingsLocked(HashMap<String, Long> installedPackages, + boolean deleteObsoleteData) { mDatabase.readMappingsLocked(); // Package mappings for the system user are updated after 24 hours via a job scheduled by // UsageStatsIdleService to ensure restored data is not lost on first boot. Additionally, // this makes user service initialization a little quicker on subsequent boots. - if (mUserId != UserHandle.USER_SYSTEM) { + if (mUserId != UserHandle.USER_SYSTEM && deleteObsoleteData) { updatePackageMappingsLocked(installedPackages); } } diff --git a/telephony/java/android/telephony/AvailableNetworkInfo.java b/telephony/java/android/telephony/AvailableNetworkInfo.java index ae597e02f33c..2b355ae216e3 100644 --- a/telephony/java/android/telephony/AvailableNetworkInfo.java +++ b/telephony/java/android/telephony/AvailableNetworkInfo.java @@ -16,11 +16,14 @@ package android.telephony; +import android.annotation.IntDef; import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; import android.telephony.RadioAccessSpecifier; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -32,7 +35,6 @@ import java.util.Objects; * Network Service when passed through {@link TelephonyManager#updateAvailableNetworks} */ public final class AvailableNetworkInfo implements Parcelable { - /* * Defines number of priority level high. */ @@ -48,6 +50,14 @@ public final class AvailableNetworkInfo implements Parcelable { */ public static final int PRIORITY_LOW = 3; + /** @hide */ + @IntDef({ + PRIORITY_HIGH, + PRIORITY_MED, + PRIORITY_LOW, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface AvailableNetworkInfoPriority {} /** * subscription Id of the available network. This value must be one of the entry retrieved from * {@link SubscriptionManager#getOpportunisticSubscriptions} @@ -62,7 +72,7 @@ public final class AvailableNetworkInfo implements Parcelable { * for network selection. If there are more than one subId with highest priority then the * network with highest RSRP is chosen. */ - private int mPriority; + private @AvailableNetworkInfoPriority int mPriority; /** * Describes the List of PLMN ids (MCC-MNC) associated with mSubId. @@ -77,8 +87,7 @@ public final class AvailableNetworkInfo implements Parcelable { * Opportunistic network service will use these bands to scan. * * When no specific bands are specified (empty array or null) CBRS band - * {@link AccessNetworkConstants.EutranBand.BAND_48 - * } will be used for network scan. + * {@link AccessNetworkConstants.EutranBand.BAND_48} will be used for network scan. * * See {@link AccessNetworkConstants} for details. * @@ -94,7 +103,7 @@ public final class AvailableNetworkInfo implements Parcelable { * If this entry is left empty, {@link RadioAcccessSpecifier}s with {@link AccessNetworkType}s * of {@link AccessNetworkConstants.AccessNetworkType.EUTRAN} and {@link * AccessNetworkConstants.AccessNetworkType.NGRAN} with bands 48 and 71 on each will be assumed - * by Opportunistic network service. + * by Opportunistic network service for a network scan. */ private ArrayList<RadioAccessSpecifier> mRadioAccessSpecifiers; @@ -117,6 +126,7 @@ public final class AvailableNetworkInfo implements Parcelable { * network with highest RSRP is chosen. * @return priority level */ + @AvailableNetworkInfoPriority public int getPriority() { return mPriority; } @@ -149,15 +159,9 @@ public final class AvailableNetworkInfo implements Parcelable { * Returns a list of {@link RadioAccessSpecifier} associated with the available network. * Opportunistic network service will use this to determine which bands to scan for. * - * the returned value is one of {@link AccessNetworkConstants.AccessNetworkType}. When no - * specific access network type is specified, {@link RadioAccessSpecifier}s with {@link - * AccessNetworkType}s of {@link AccessNetworkConstants.AccessNetworkType.EUTRAN} and {@link - * AccessNetworkConstants.AccessNetworkType.NGRAN} with bands 48 and 71 on each will be assumed - * by Opportunistic network service. * @return the access network type associated with the available network. - * @hide */ - public List<RadioAccessSpecifier> getRadioAccessSpecifiers() { + public @NonNull List<RadioAccessSpecifier> getRadioAccessSpecifiers() { return (List<RadioAccessSpecifier>) mRadioAccessSpecifiers.clone(); } @@ -193,9 +197,9 @@ public final class AvailableNetworkInfo implements Parcelable { } /** @hide */ - private AvailableNetworkInfo(int subId, int priority, @NonNull List<String> mccMncs, - @NonNull List<Integer> bands, @NonNull List<RadioAccessSpecifier> - radioAccessSpecifiers) { + private AvailableNetworkInfo(int subId, @AvailableNetworkInfoPriority int priority, + @NonNull List<String> mccMncs, @NonNull List<Integer> bands, + @NonNull List<RadioAccessSpecifier> radioAccessSpecifiers) { mSubId = subId; mPriority = priority; mMccMncs = new ArrayList<String>(mccMncs); @@ -261,27 +265,39 @@ public final class AvailableNetworkInfo implements Parcelable { * * <pre><code> * - * AvailableNetworkInfo aNI = new AvailableNetworkInfo.Builder() - * .setSubId(1) + * AvailableNetworkInfo aNI = new AvailableNetworkInfo.Builder(subId) * .setPriority(AvailableNetworkInfo.PRIORITY_MED) + * .setRadioAccessSpecifiers(radioAccessSpecifiers) + * .setMccMncs(mccMncs) * .build(); * </code></pre> - * - * @hide */ public static final class Builder { private int mSubId = Integer.MIN_VALUE; - private int mPriority = AvailableNetworkInfo.PRIORITY_LOW; + private @AvailableNetworkInfoPriority int mPriority = AvailableNetworkInfo.PRIORITY_LOW; private ArrayList<String> mMccMncs = new ArrayList<>(); - private ArrayList<Integer> mBands = new ArrayList<>(); private ArrayList<RadioAccessSpecifier> mRadioAccessSpecifiers = new ArrayList<>(); - public @NonNull Builder setSubId(int subId) { + /** + * + */ + /** + * Creates an AvailableNetworkInfo Builder with specified subscription id. + * + * @param subId of the availableNetwork. + */ + public Builder(int subId) { mSubId = subId; - return this; } - public @NonNull Builder setPriority(int priority) { + /** + * Sets the priority for the subscription id. + * + * @param priority of the subscription id. See {@link AvailableNetworkInfo#getPriority} for + * more details + * @return the original Builder object. + */ + public @NonNull Builder setPriority(@AvailableNetworkInfoPriority int priority) { if (priority > AvailableNetworkInfo.PRIORITY_LOW || priority < AvailableNetworkInfo.PRIORITY_HIGH) { throw new IllegalArgumentException("A valid priority must be set"); @@ -290,30 +306,48 @@ public final class AvailableNetworkInfo implements Parcelable { return this; } - public @NonNull Builder setMccMncs(@NonNull ArrayList<String> mccMncs) { - Objects.requireNonNull(mccMncs, "A non-null ArrayList of mccmncs must be set. An empty " - + "list is still accepted. Please read documentation in " - + "AvailableNetworkService to see consequences of an empty Arraylist."); - mMccMncs = mccMncs; + /** + * Sets the list of mccmncs associated with the subscription id. + * + * @param mccMncs nonull list of mccmncs. An empty List is still accepted. Please read + * documentation in {@link AvailableNetworkInfo} to see consequences of an empty List. + * @return the original Builder object. + */ + public @NonNull Builder setMccMncs(@NonNull List<String> mccMncs) { + Objects.requireNonNull(mccMncs, "A non-null List of mccmncs must be set. An empty " + + "List is still accepted. Please read documentation in " + + "AvailableNetworkInfo to see consequences of an empty List."); + mMccMncs = new ArrayList<>(mccMncs); return this; } + /** + * Sets the list of mccmncs associated with the subscription id. + * + * @param radioAccessSpecifiers nonull list of radioAccessSpecifiers. An empty List is still + * accepted. Please read documentation in {@link AvailableNetworkInfo} to see + * consequences of an empty List. + * @return the original Builder object. + */ public @NonNull Builder setRadioAccessSpecifiers( - @NonNull ArrayList<RadioAccessSpecifier> radioAccessSpecifiers) { - Objects.requireNonNull(radioAccessSpecifiers, "A non-null ArrayList of " - + "RadioAccessSpecifiers must be set. An empty list is still accepted. Please " - + "read documentation in AvailableNetworkService to see consequences of an " - + "empty Arraylist."); - mRadioAccessSpecifiers = radioAccessSpecifiers; + @NonNull List<RadioAccessSpecifier> radioAccessSpecifiers) { + Objects.requireNonNull(radioAccessSpecifiers, "A non-null List of " + + "RadioAccessSpecifiers must be set. An empty List is still accepted. Please " + + "read documentation in AvailableNetworkInfo to see consequences of an " + + "empty List."); + mRadioAccessSpecifiers = new ArrayList<>(radioAccessSpecifiers); return this; } + /** + * @return an AvailableNetworkInfo object with all the fields previously set by the Builder. + */ public @NonNull AvailableNetworkInfo build() { if (mSubId == Integer.MIN_VALUE) { throw new IllegalArgumentException("A valid subId must be set"); } - return new AvailableNetworkInfo(mSubId, mPriority, mMccMncs, mBands, + return new AvailableNetworkInfo(mSubId, mPriority, mMccMncs, new ArrayList<>(), mRadioAccessSpecifiers); } } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 2141e4288848..2dfa9a452aba 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -21,6 +21,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SuppressAutoDoc; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; @@ -3696,6 +3697,49 @@ public class CarrierConfigManager { "show_wifi_calling_icon_in_status_bar_bool"; /** + * Configuration to indicate that the carrier supports opportunistic data + * auto provisioning. Based on this flag, the device downloads and activates + * corresponding opportunistic profile. + */ + public static final String KEY_CARRIER_SUPPORTS_OPP_DATA_AUTO_PROVISIONING_BOOL = + "carrier_supports_opp_data_auto_provisioning_bool"; + + /** + * SMDP+ server address for downloading opportunistic eSIM profile. + * FQDN (Fully Qualified Domain Name) of the SM-DP+ (e.g., smdp.gsma.com) restricted to the + * Alphanumeric mode character set defined in table 5 of ISO/IEC 18004 [15] excluding '$'. + */ + public static final String KEY_SMDP_SERVER_ADDRESS_STRING = + "smdp_server_address_string"; + + /** + * This timer value is used in the eSIM Exponential Backoff download retry algorithm. + * Value should be in seconds. + * <OL> + * <LI>When the first download failure occurs, retry download after BACKOFF_TIMER_VALUE + * seconds.</LI> + * + * <LI>If download fails again then, retry after either BACKOFF_TIMER_VALUE, + * 2xBACKOFF_TIMER_VALUE, or 3xBACKOFF_TIMER_VALUE seconds.</LI> + * + * <LI>In general after the cth failed attempt, retry after k * BACKOFF_TIMER_VALUE + * seconds, where k is a random integer between 1 and 2^c − 1. Max c value is + * {@link #KEY_ESIM_MAX_DOWNLOAD_RETRY_ATTEMPTS_INT}</LI> + * </OL> + */ + public static final String KEY_ESIM_DOWNLOAD_RETRY_BACKOFF_TIMER_SEC_INT = + "esim_download_retry_backoff_timer_sec_int"; + + /** + * If eSIM profile download fails then, the number of retry attempts by UE + * will be based on this configuration. If download still fails even after the + * MAX attempts configured by this item then the retry is postponed until next + * device bootup. + */ + public static final String KEY_ESIM_MAX_DOWNLOAD_RETRY_ATTEMPTS_INT = + "esim_max_download_retry_attempts_int"; + + /** * Controls RSRP threshold at which OpportunisticNetworkService will decide whether * the opportunistic network is good enough for internet data. */ @@ -3905,6 +3949,30 @@ public class CarrierConfigManager { public static final String KEY_ENABLE_4G_OPPORTUNISTIC_NETWORK_SCAN_BOOL = "enabled_4g_opportunistic_network_scan_bool"; + /** + * Only relevant when the device supports opportunistic networks but does not support + * simultaneuous 5G+5G. Controls how long, in milliseconds, to wait before opportunistic network + * goes out of service before switching the 5G capability back to primary stack. The idea of + * waiting a few seconds is to minimize the calling of the expensive capability switching + * operation in the case where CBRS goes back into service shortly after going out of it. + * + * @hide + */ + public static final String KEY_TIME_TO_SWITCH_BACK_TO_PRIMARY_IF_OPPORTUNISTIC_OOS_LONG = + "time_to_switch_back_to_primary_if_opportunistic_oos_long"; + + /** + * Only relevant when the device supports opportunistic networks but does not support + * simultaneuous 5G+5G. Controls how long, in milliseconds, after 5G capability has switched back + * to primary stack due to opportunistic network being OOS. The idea is to minimizing the + * 'ping-ponging' effect where device is constantly witching capability back and forth between + * primary and opportunistic stack. + * + * @hide + */ + public static final String KEY_OPPORTUNISTIC_TIME_TO_SCAN_AFTER_CAPABILITY_SWITCH_TO_PRIMARY_LONG + = "opportunistic_time_to_scan_after_capability_switch_to_primary_long"; + /** * Indicates zero or more emergency number prefix(es), because some carrier requires * if users dial an emergency number address with a specific prefix, the combination of the @@ -5780,6 +5848,10 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_UNMETERED_NR_SA_SUB6_BOOL, false); sDefaults.putBoolean(KEY_ASCII_7_BIT_SUPPORT_FOR_LONG_MESSAGE_BOOL, false); sDefaults.putBoolean(KEY_SHOW_WIFI_CALLING_ICON_IN_STATUS_BAR_BOOL, false); + sDefaults.putBoolean(KEY_CARRIER_SUPPORTS_OPP_DATA_AUTO_PROVISIONING_BOOL, false); + sDefaults.putString(KEY_SMDP_SERVER_ADDRESS_STRING, ""); + sDefaults.putInt(KEY_ESIM_MAX_DOWNLOAD_RETRY_ATTEMPTS_INT, 5); + sDefaults.putInt(KEY_ESIM_DOWNLOAD_RETRY_BACKOFF_TIMER_SEC_INT, 60); /* Default value is minimum RSRP level needed for SIGNAL_STRENGTH_GOOD */ sDefaults.putInt(KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_RSRP_INT, -108); /* Default value is minimum RSRP level needed for SIGNAL_STRENGTH_MODERATE */ @@ -5823,6 +5895,10 @@ public class CarrierConfigManager { /* Default value is 2 seconds. */ sDefaults.putLong(KEY_OPPORTUNISTIC_NETWORK_5G_DATA_SWITCH_EXIT_HYSTERESIS_TIME_LONG, 2000); sDefaults.putBoolean(KEY_ENABLE_4G_OPPORTUNISTIC_NETWORK_SCAN_BOOL, true); + sDefaults.putInt(KEY_TIME_TO_SWITCH_BACK_TO_PRIMARY_IF_OPPORTUNISTIC_OOS_LONG, 60000); + sDefaults.putInt( + KEY_OPPORTUNISTIC_TIME_TO_SCAN_AFTER_CAPABILITY_SWITCH_TO_PRIMARY_LONG, + 120000); sDefaults.putAll(Gps.getDefaults()); sDefaults.putIntArray(KEY_CDMA_ENHANCED_ROAMING_INDICATOR_FOR_HOME_NETWORK_INT_ARRAY, new int[] { @@ -5978,12 +6054,15 @@ public class CarrierConfigManager { * any carrier specific configuration has been applied. * * <p>Requires Permission: - * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}, or the calling app + * has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges()}). * * @param subId the subscription ID, normally obtained from {@link SubscriptionManager}. * @return A {@link PersistableBundle} containing the config for the given subId, or default * values for an invalid subId. */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) @Nullable public PersistableBundle getConfigForSubId(int subId) { try { @@ -6072,10 +6151,13 @@ public class CarrierConfigManager { * called to confirm whether any carrier specific configuration has been applied. * * <p>Requires Permission: - * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}, or the calling app + * has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges()}). * * @see #getConfigForSubId */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) @Nullable public PersistableBundle getConfig() { return getConfigForSubId(SubscriptionManager.getDefaultSubscriptionId()); @@ -6084,8 +6166,8 @@ public class CarrierConfigManager { /** * Determines whether a configuration {@link PersistableBundle} obtained from * {@link #getConfig()} or {@link #getConfigForSubId(int)} corresponds to an identified carrier. - * <p> - * When an app receives the {@link CarrierConfigManager#ACTION_CARRIER_CONFIG_CHANGED} + * + * <p>When an app receives the {@link CarrierConfigManager#ACTION_CARRIER_CONFIG_CHANGED} * broadcast which informs it that the carrier configuration has changed, it is possible * that another reload of the carrier configuration has begun since the intent was sent. * In this case, the carrier configuration the app fetches (e.g. via {@link #getConfig()}) @@ -6094,14 +6176,12 @@ public class CarrierConfigManager { * return true because it may belong to another previous identified carrier. Users should * always call {@link #getConfig()} or {@link #getConfigForSubId(int)} after receiving the * broadcast {@link #ACTION_CARRIER_CONFIG_CHANGED}. - * </p> - * <p> - * After using {@link #getConfig()} or {@link #getConfigForSubId(int)} an app should always + * + * <p>After using {@link #getConfig()} or {@link #getConfigForSubId(int)} an app should always * use this method to confirm whether any carrier specific configuration has been applied. * Especially when an app misses the broadcast {@link #ACTION_CARRIER_CONFIG_CHANGED} but it * still needs to get the current configuration, it must use this method to verify whether the * configuration is default or carrier overridden. - * </p> * * @param bundle the configuration bundle to be checked. * @return boolean true if any carrier specific configuration bundle has been applied, false @@ -6113,19 +6193,20 @@ public class CarrierConfigManager { /** * Calling this method triggers telephony services to fetch the current carrier configuration. - * <p> - * Normally this does not need to be called because the platform reloads config on its own. + * + * <p>Normally this does not need to be called because the platform reloads config on its own. * This should be called by a carrier service app if it wants to update config at an arbitrary * moment. - * </p> - * <p>Requires that the calling app has carrier privileges. - * <p> - * This method returns before the reload has completed, and - * {@link android.service.carrier.CarrierService#onLoadConfig} will be called from an - * arbitrary thread. - * </p> - * @see TelephonyManager#hasCarrierPrivileges + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}, or the calling app + * has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges()}). + * + * <p>This method returns before the reload has completed, and {@link + * android.service.carrier.CarrierService#onLoadConfig} will be called from an arbitrary thread. */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyConfigChangedForSubId(int subId) { try { ICarrierConfigLoader loader = getICarrierConfigLoader(); @@ -6141,11 +6222,10 @@ public class CarrierConfigManager { } /** - * Request the carrier config loader to update the cofig for phoneId. - * <p> - * Depending on simState, the config may be cleared or loaded from config app. This is only used - * by SubscriptionInfoUpdater. - * </p> + * Request the carrier config loader to update the config for phoneId. + * + * <p>Depending on simState, the config may be cleared or loaded from config app. This is only + * used by SubscriptionInfoUpdater. * * @hide */ @@ -6216,13 +6296,16 @@ public class CarrierConfigManager { * Gets the configuration values for a component using its prefix. * * <p>Requires Permission: - * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}, or the calling app + * has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges()}). * * @param prefix prefix of the component. * @param subId the subscription ID, normally obtained from {@link SubscriptionManager}. * * @see #getConfigForSubId */ + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) @Nullable public PersistableBundle getConfigByComponentForSubId(@NonNull String prefix, int subId) { PersistableBundle configs = getConfigForSubId(subId); diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 02363069891d..50f2abd3c1ca 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -13626,15 +13626,18 @@ public class TelephonyManager { } /** - * It indicates whether modem is enabled or not per slot. - * It's the corresponding status of TelephonyManager.enableModemForSlot. + * Indicates whether or not there is a modem stack enabled for the given SIM slot. * * <p>Requires Permission: - * READ_PRIVILEGED_PHONE_STATE or - * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}, + * READ_PRIVILEGED_PHONE_STATE or that the calling app has carrier privileges (see + * {@link #hasCarrierPrivileges()}). + * * @param slotIndex which slot it's checking. */ - @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges + @RequiresPermission(anyOf = {android.Manifest.permission.READ_PHONE_STATE, + android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE}) public boolean isModemEnabledForSlot(int slotIndex) { try { ITelephony telephony = getITelephony(); diff --git a/tests/FlickerTests/OWNERS b/tests/FlickerTests/OWNERS index b5561010e7f9..c1221e3940d2 100644 --- a/tests/FlickerTests/OWNERS +++ b/tests/FlickerTests/OWNERS @@ -1,3 +1,4 @@ # Bug component: 909476 include /services/core/java/com/android/server/wm/OWNERS -natanieljr@google.com
\ No newline at end of file +natanieljr@google.com +pablogamito@google.com diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt index 8a2ddf1a243d..3c9ba20c340a 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt @@ -196,7 +196,7 @@ abstract class CloseAppTransition(protected val testSpec: FlickerTestParameter) testSpec.replacesLayer(testApp.component, LAUNCHER_COMPONENT) } - @Presubmit + @Postsubmit @Test fun runPresubmitAssertion() { flickerRule.checkPresubmitAssertions() diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt index eb7d29dfee74..4a904044cd74 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt @@ -95,7 +95,7 @@ class ChangeAppRotationTest( } } - @Presubmit + @Postsubmit @Test fun runPresubmitAssertion() { flickerRule.checkPresubmitAssertions() diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp index 5d2eda3293f0..a266b476bc0d 100644 --- a/tools/aapt2/util/Files.cpp +++ b/tools/aapt2/util/Files.cpp @@ -154,7 +154,7 @@ StringPiece GetFilename(const StringPiece& path) { const char* end = path.end(); const char* last_dir_sep = path.begin(); for (const char* c = path.begin(); c != end; ++c) { - if (*c == sDirSep) { + if (*c == sDirSep || *c == sInvariantDirSep) { last_dir_sep = c + 1; } } diff --git a/tools/aapt2/util/Files.h b/tools/aapt2/util/Files.h index 877cd56d6c69..a2b1b58e5d4f 100644 --- a/tools/aapt2/util/Files.h +++ b/tools/aapt2/util/Files.h @@ -41,6 +41,8 @@ constexpr const char sDirSep = '/'; constexpr const char sPathSep = ':'; #endif +constexpr const char sInvariantDirSep = '/'; + enum class FileType { kUnknown = 0, kNonExistant, |