diff options
204 files changed, 6742 insertions, 4446 deletions
diff --git a/Android.bp b/Android.bp index f8059475f6f4..933d1aff842b 100644 --- a/Android.bp +++ b/Android.bp @@ -476,21 +476,28 @@ java_library { } // TODO(b/145644363): move this to under StubLibraries.bp or ApiDocs.bp -metalava_framework_docs_args = "--manifest $(location core/res/AndroidManifest.xml) " + - "--hide-package com.android.server " + - "--hide-package android.audio.policy.configuration.V7_0 " + - "--error UnhiddenSystemApi " + - "--hide RequiresPermission " + - "--hide CallbackInterface " + - "--hide MissingPermission --hide BroadcastBehavior " + - "--hide HiddenSuperclass --hide DeprecationMismatch --hide UnavailableSymbol " + - "--hide SdkConstant --hide HiddenTypeParameter --hide Todo --hide Typo " + - "--error NoSettingsProvider " + - "--force-convert-to-warning-nullability-annotations +*:-android.*:+android.icu.*:-dalvik.* " + +metalava_framework_docs_args = "" + "--api-lint-ignore-prefix android.icu. " + "--api-lint-ignore-prefix java. " + "--api-lint-ignore-prefix junit. " + - "--api-lint-ignore-prefix org. " + "--api-lint-ignore-prefix org. " + + "--error NoSettingsProvider " + + "--error UnhiddenSystemApi " + + "--force-convert-to-warning-nullability-annotations +*:-android.*:+android.icu.*:-dalvik.* " + + "--hide BroadcastBehavior " + + "--hide CallbackInterface " + + "--hide DeprecationMismatch " + + "--hide HiddenSuperclass " + + "--hide HiddenTypeParameter " + + "--hide MissingPermission " + + "--hide-package android.audio.policy.configuration.V7_0 " + + "--hide-package com.android.server " + + "--hide RequiresPermission " + + "--hide SdkConstant " + + "--hide Todo " + + "--hide Typo " + + "--hide UnavailableSymbol " + + "--manifest $(location core/res/AndroidManifest.xml) " packages_to_document = [ "android", @@ -584,7 +591,7 @@ stubs_defaults { libs: [ "art.module.public.api", "sdk_module-lib_current_framework-tethering", - "sdk_module-lib_current_framework-connectivity-tiramisu", + "sdk_module-lib_current_framework-connectivity-t", "sdk_public_current_framework-bluetooth", // There are a few classes from modules used by the core that // need to be resolved by metalava. We use a prebuilt stub of the diff --git a/StubLibraries.bp b/StubLibraries.bp index 726ab2a70a5e..94f4374594ec 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -258,7 +258,7 @@ java_library { srcs: [":module-lib-api-stubs-docs-non-updatable"], libs: [ "sdk_module-lib_current_framework-tethering", - "sdk_module-lib_current_framework-connectivity-tiramisu", + "sdk_module-lib_current_framework-connectivity-t", "sdk_public_current_framework-bluetooth", // NOTE: The below can be removed once the prebuilt stub contains bluetooth. "sdk_system_current_android", diff --git a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java index 8c8d2bfe16e3..88082f7dfa4b 100644 --- a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java +++ b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java @@ -39,7 +39,9 @@ public class EconomyManager { /** @hide */ public static final String KEY_AM_MAX_SATIATED_BALANCE = "am_max_satiated_balance"; /** @hide */ - public static final String KEY_AM_MAX_CIRCULATION = "am_max_circulation"; + public static final String KEY_AM_INITIAL_CONSUMPTION_LIMIT = "am_initial_consumption_limit"; + /** @hide */ + public static final String KEY_AM_HARD_CONSUMPTION_LIMIT = "am_hard_consumption_limit"; // TODO: Add AlarmManager modifier keys /** @hide */ public static final String KEY_AM_REWARD_TOP_ACTIVITY_INSTANT = @@ -163,7 +165,9 @@ public class EconomyManager { public static final String KEY_JS_MAX_SATIATED_BALANCE = "js_max_satiated_balance"; /** @hide */ - public static final String KEY_JS_MAX_CIRCULATION = "js_max_circulation"; + public static final String KEY_JS_INITIAL_CONSUMPTION_LIMIT = "js_initial_consumption_limit"; + /** @hide */ + public static final String KEY_JS_HARD_CONSUMPTION_LIMIT = "js_hard_consumption_limit"; // TODO: Add JobScheduler modifier keys /** @hide */ public static final String KEY_JS_REWARD_TOP_ACTIVITY_INSTANT = @@ -280,7 +284,9 @@ public class EconomyManager { /** @hide */ public static final int DEFAULT_AM_MAX_SATIATED_BALANCE = 1440; /** @hide */ - public static final int DEFAULT_AM_MAX_CIRCULATION = 52000; + public static final int DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT = 28800; + /** @hide */ + public static final int DEFAULT_AM_HARD_CONSUMPTION_LIMIT = 52000; // TODO: add AlarmManager modifier default values /** @hide */ public static final int DEFAULT_AM_REWARD_TOP_ACTIVITY_INSTANT = 0; @@ -359,7 +365,7 @@ public class EconomyManager { // Default values JobScheduler factors // TODO: add time_since_usage variable to min satiated balance factors /** @hide */ - public static final int DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED = 50000; + public static final int DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED = 20000; /** @hide */ public static final int DEFAULT_JS_MIN_SATIATED_BALANCE_HEADLESS_SYSTEM_APP = 10000; /** @hide */ @@ -367,7 +373,9 @@ public class EconomyManager { /** @hide */ public static final int DEFAULT_JS_MAX_SATIATED_BALANCE = 60000; /** @hide */ - public static final int DEFAULT_JS_MAX_CIRCULATION = 691200; + public static final int DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT = 460_000; + /** @hide */ + public static final int DEFAULT_JS_HARD_CONSUMPTION_LIMIT = 900_000; // TODO: add JobScheduler modifier default values /** @hide */ public static final int DEFAULT_JS_REWARD_TOP_ACTIVITY_INSTANT = 0; 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 4e73b026abce..b9362789c6c6 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -3007,7 +3007,7 @@ public class JobSchedulerService extends com.android.server.SystemService } } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) { if (DEBUG) { - Slog.d(TAG, "Disconnected from power @ " + sElapsedRealtimeClock.millis()); + Slog.d(TAG, "Battery discharging @ " + sElapsedRealtimeClock.millis()); } if (mCharging) { mCharging = false; 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 a6a007f46b58..c0a81487fe62 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java @@ -55,9 +55,6 @@ import com.android.server.pm.UserManagerInternal; import com.android.server.usage.AppStandbyInternal; import com.android.server.utils.AlarmQueue; -import libcore.util.EmptyArray; - -import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.function.Consumer; @@ -105,54 +102,13 @@ class Agent { private final BalanceThresholdAlarmQueue mBalanceThresholdAlarmQueue; /** - * Comparator to use to sort apps before we distribute ARCs so that we try to give the most - * important apps ARCs first. + * Check the affordability notes of all apps. */ - @VisibleForTesting - final Comparator<PackageInfo> mPackageDistributionComparator = - new Comparator<PackageInfo>() { - @Override - public int compare(PackageInfo pi1, PackageInfo pi2) { - final ApplicationInfo appInfo1 = pi1.applicationInfo; - final ApplicationInfo appInfo2 = pi2.applicationInfo; - // Put any packages that don't declare an application at the end. A missing - // <application> tag likely means the app won't be doing any work anyway. - if (appInfo1 == null) { - if (appInfo2 == null) { - return 0; - } - return 1; - } else if (appInfo2 == null) { - return -1; - } - // Privileged apps eat first. They're likely required for the device to - // function properly. - // TODO: include headless system apps - if (appInfo1.isPrivilegedApp()) { - if (!appInfo2.isPrivilegedApp()) { - return -1; - } - } else if (appInfo2.isPrivilegedApp()) { - return 1; - } - - // Sort by most recently used. - final long timeSinceLastUsedMs1 = - mAppStandbyInternal.getTimeSinceLastUsedByUser( - pi1.packageName, UserHandle.getUserId(pi1.applicationInfo.uid)); - final long timeSinceLastUsedMs2 = - mAppStandbyInternal.getTimeSinceLastUsedByUser( - pi2.packageName, UserHandle.getUserId(pi2.applicationInfo.uid)); - if (timeSinceLastUsedMs1 < timeSinceLastUsedMs2) { - return -1; - } else if (timeSinceLastUsedMs1 > timeSinceLastUsedMs2) { - return 1; - } - return 0; - } - }; - - private static final int MSG_CHECK_BALANCE = 0; + private static final int MSG_CHECK_ALL_AFFORDABILITY = 0; + /** + * Check the affordability notes of a single app. + */ + private static final int MSG_CHECK_INDIVIDUAL_AFFORDABILITY = 1; Agent(@NonNull InternalResourceService irs, @NonNull Scribe scribe) { mLock = irs.getLock(); @@ -179,7 +135,7 @@ class Agent { @Override public void accept(OngoingEvent ongoingEvent) { - mTotal += getActualDeltaLocked(ongoingEvent, mLedger, mNowElapsed, mNow); + mTotal += getActualDeltaLocked(ongoingEvent, mLedger, mNowElapsed, mNow).price; } } @@ -204,6 +160,11 @@ class Agent { } @GuardedBy("mLock") + private boolean isAffordableLocked(long balance, long price, long ctp) { + return balance >= price && mScribe.getRemainingConsumableNarcsLocked() >= ctp; + } + + @GuardedBy("mLock") void noteInstantaneousEventLocked(final int userId, @NonNull final String pkgName, final int eventId, @Nullable String tag) { if (mIrs.isSystem(userId, pkgName)) { @@ -218,10 +179,13 @@ class Agent { final int eventType = getEventType(eventId); switch (eventType) { case TYPE_ACTION: - final long actionCost = economicPolicy.getCostOfAction(eventId, userId, pkgName); + final EconomicPolicy.Cost actionCost = + economicPolicy.getCostOfAction(eventId, userId, pkgName); recordTransactionLocked(userId, pkgName, ledger, - new Ledger.Transaction(now, now, eventId, tag, -actionCost), true); + new Ledger.Transaction(now, now, eventId, tag, + -actionCost.price, actionCost.costToProduce), + true); break; case TYPE_REWARD: @@ -231,7 +195,7 @@ class Agent { final long rewardVal = Math.max(0, Math.min(reward.maxDailyReward - rewardSum, reward.instantReward)); recordTransactionLocked(userId, pkgName, ledger, - new Ledger.Transaction(now, now, eventId, tag, rewardVal), true); + new Ledger.Transaction(now, now, eventId, tag, rewardVal, 0), true); } break; @@ -268,11 +232,12 @@ class Agent { final int eventType = getEventType(eventId); switch (eventType) { case TYPE_ACTION: - final long actionCost = economicPolicy.getCostOfAction(eventId, userId, pkgName); + final EconomicPolicy.Cost actionCost = + economicPolicy.getCostOfAction(eventId, userId, pkgName); if (ongoingEvent == null) { ongoingEvents.add(eventId, tag, - new OngoingEvent(eventId, tag, null, startElapsed, -actionCost)); + new OngoingEvent(eventId, tag, startElapsed, actionCost)); } else { ongoingEvent.refCount++; } @@ -283,7 +248,7 @@ class Agent { if (reward != null) { if (ongoingEvent == null) { ongoingEvents.add(eventId, tag, new OngoingEvent( - eventId, tag, reward, startElapsed, reward.ongoingRewardPerSecond)); + eventId, tag, startElapsed, reward)); } else { ongoingEvent.refCount++; } @@ -306,52 +271,7 @@ class Agent { @GuardedBy("mLock") void onPricingChangedLocked() { - final long now = getCurrentTimeMillis(); - final long nowElapsed = SystemClock.elapsedRealtime(); - final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked(); - - mCurrentOngoingEvents.forEach((userId, pkgName, ongoingEvents) -> { - final ArraySet<ActionAffordabilityNote> actionAffordabilityNotes = - mActionAffordabilityNotes.get(userId, pkgName); - final boolean[] wasAffordable; - if (actionAffordabilityNotes != null) { - final int size = actionAffordabilityNotes.size(); - wasAffordable = new boolean[size]; - for (int i = 0; i < size; ++i) { - final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i); - final long originalBalance = - mScribe.getLedgerLocked(userId, pkgName).getCurrentBalance(); - wasAffordable[i] = originalBalance >= note.getCachedModifiedPrice(); - } - } else { - wasAffordable = EmptyArray.BOOLEAN; - } - ongoingEvents.forEach((ongoingEvent) -> { - // Disable balance check & affordability notifications here because we're in the - // middle of updating ongoing action costs/prices and sending out notifications - // or rescheduling the balance check alarm would be a waste since we'll have to - // redo them again after all of our internal state is updated. - stopOngoingActionLocked(userId, pkgName, ongoingEvent.eventId, - ongoingEvent.tag, nowElapsed, now, false, false); - noteOngoingEventLocked(userId, pkgName, ongoingEvent.eventId, ongoingEvent.tag, - nowElapsed, false); - }); - if (actionAffordabilityNotes != null) { - final int size = actionAffordabilityNotes.size(); - for (int i = 0; i < size; ++i) { - final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i); - note.recalculateModifiedPrice(economicPolicy, userId, pkgName); - final long newBalance = - mScribe.getLedgerLocked(userId, pkgName).getCurrentBalance(); - final boolean isAffordable = newBalance >= note.getCachedModifiedPrice(); - if (wasAffordable[i] != isAffordable) { - note.setNewAffordability(isAffordable); - mIrs.postAffordabilityChanged(userId, pkgName, note); - } - } - } - scheduleBalanceCheckLocked(userId, pkgName); - }); + onAnythingChangedLocked(true); } @GuardedBy("mLock") @@ -365,46 +285,80 @@ class Agent { SparseArrayMap<String, OngoingEvent> ongoingEvents = mCurrentOngoingEvents.get(userId, pkgName); if (ongoingEvents != null) { + mOngoingEventUpdater.reset(userId, pkgName, now, nowElapsed); + ongoingEvents.forEach(mOngoingEventUpdater); final ArraySet<ActionAffordabilityNote> actionAffordabilityNotes = mActionAffordabilityNotes.get(userId, pkgName); - final boolean[] wasAffordable; if (actionAffordabilityNotes != null) { final int size = actionAffordabilityNotes.size(); - wasAffordable = new boolean[size]; + final long newBalance = + mScribe.getLedgerLocked(userId, pkgName).getCurrentBalance(); for (int n = 0; n < size; ++n) { final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n); - final long originalBalance = - mScribe.getLedgerLocked(userId, pkgName).getCurrentBalance(); - wasAffordable[n] = originalBalance >= note.getCachedModifiedPrice(); + note.recalculateCosts(economicPolicy, userId, pkgName); + final boolean isAffordable = + isAffordableLocked(newBalance, + note.getCachedModifiedPrice(), note.getCtp()); + if (note.isCurrentlyAffordable() != isAffordable) { + note.setNewAffordability(isAffordable); + mIrs.postAffordabilityChanged(userId, pkgName, note); + } } - } else { - wasAffordable = EmptyArray.BOOLEAN; } - ongoingEvents.forEach((ongoingEvent) -> { - // Disable balance check & affordability notifications here because we're in the - // middle of updating ongoing action costs/prices and sending out notifications - // or rescheduling the balance check alarm would be a waste since we'll have to - // redo them again after all of our internal state is updated. - stopOngoingActionLocked(userId, pkgName, ongoingEvent.eventId, - ongoingEvent.tag, nowElapsed, now, false, false); - noteOngoingEventLocked(userId, pkgName, ongoingEvent.eventId, ongoingEvent.tag, - nowElapsed, false); - }); + scheduleBalanceCheckLocked(userId, pkgName); + } + } + } + + @GuardedBy("mLock") + private void onAnythingChangedLocked(final boolean updateOngoingEvents) { + final long now = getCurrentTimeMillis(); + final long nowElapsed = SystemClock.elapsedRealtime(); + final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked(); + + for (int uIdx = mCurrentOngoingEvents.numMaps() - 1; uIdx >= 0; --uIdx) { + final int userId = mCurrentOngoingEvents.keyAt(uIdx); + + for (int pIdx = mCurrentOngoingEvents.numElementsForKey(userId) - 1; pIdx >= 0; + --pIdx) { + final String pkgName = mCurrentOngoingEvents.keyAt(uIdx, pIdx); + + SparseArrayMap<String, OngoingEvent> ongoingEvents = + mCurrentOngoingEvents.valueAt(uIdx, pIdx); + if (ongoingEvents != null) { + if (updateOngoingEvents) { + mOngoingEventUpdater.reset(userId, pkgName, now, nowElapsed); + ongoingEvents.forEach(mOngoingEventUpdater); + } + scheduleBalanceCheckLocked(userId, pkgName); + } + } + } + for (int uIdx = mActionAffordabilityNotes.numMaps() - 1; uIdx >= 0; --uIdx) { + final int userId = mActionAffordabilityNotes.keyAt(uIdx); + + for (int pIdx = mActionAffordabilityNotes.numElementsForKey(userId) - 1; pIdx >= 0; + --pIdx) { + final String pkgName = mActionAffordabilityNotes.keyAt(uIdx, pIdx); + + final ArraySet<ActionAffordabilityNote> actionAffordabilityNotes = + mActionAffordabilityNotes.valueAt(uIdx, pIdx); + if (actionAffordabilityNotes != null) { final int size = actionAffordabilityNotes.size(); + final long newBalance = getBalanceLocked(userId, pkgName); for (int n = 0; n < size; ++n) { final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n); - note.recalculateModifiedPrice(economicPolicy, userId, pkgName); - final long newBalance = - mScribe.getLedgerLocked(userId, pkgName).getCurrentBalance(); - final boolean isAffordable = newBalance >= note.getCachedModifiedPrice(); - if (wasAffordable[n] != isAffordable) { + note.recalculateCosts(economicPolicy, userId, pkgName); + final boolean isAffordable = + isAffordableLocked(newBalance, + note.getCachedModifiedPrice(), note.getCtp()); + if (note.isCurrentlyAffordable() != isAffordable) { note.setNewAffordability(isAffordable); mIrs.postAffordabilityChanged(userId, pkgName, note); } } } - scheduleBalanceCheckLocked(userId, pkgName); } } } @@ -416,9 +370,9 @@ class Agent { } /** - * @param updateBalanceCheck Whether or not to reschedule the affordability/balance + * @param updateBalanceCheck Whether to reschedule the affordability/balance * check alarm. - * @param notifyOnAffordabilityChange Whether or not to evaluate the app's ability to afford + * @param notifyOnAffordabilityChange Whether to evaluate the app's ability to afford * registered bills and notify listeners about any changes. */ @GuardedBy("mLock") @@ -453,9 +407,11 @@ class Agent { if (ongoingEvent.refCount <= 0) { final long startElapsed = ongoingEvent.startTimeElapsed; final long startTime = now - (nowElapsed - startElapsed); - final long actualDelta = getActualDeltaLocked(ongoingEvent, ledger, nowElapsed, now); + final EconomicPolicy.Cost actualDelta = + getActualDeltaLocked(ongoingEvent, ledger, nowElapsed, now); recordTransactionLocked(userId, pkgName, ledger, - new Ledger.Transaction(startTime, now, eventId, tag, actualDelta), + new Ledger.Transaction(startTime, now, eventId, tag, actualDelta.price, + actualDelta.costToProduce), notifyOnAffordabilityChange); ongoingEvents.delete(eventId, tag); @@ -466,17 +422,20 @@ class Agent { } @GuardedBy("mLock") - private long getActualDeltaLocked(@NonNull OngoingEvent ongoingEvent, @NonNull Ledger ledger, - long nowElapsed, long now) { + @NonNull + private EconomicPolicy.Cost getActualDeltaLocked(@NonNull OngoingEvent ongoingEvent, + @NonNull Ledger ledger, long nowElapsed, long now) { final long startElapsed = ongoingEvent.startTimeElapsed; final long durationSecs = (nowElapsed - startElapsed) / 1000; - final long computedDelta = durationSecs * ongoingEvent.deltaPerSec; + final long computedDelta = durationSecs * ongoingEvent.getDeltaPerSec(); if (ongoingEvent.reward == null) { - return computedDelta; + return new EconomicPolicy.Cost( + durationSecs * ongoingEvent.getCtpPerSec(), computedDelta); } final long rewardSum = ledger.get24HourSum(ongoingEvent.eventId, now); - return Math.max(0, - Math.min(ongoingEvent.reward.maxDailyReward - rewardSum, computedDelta)); + return new EconomicPolicy.Cost(0, + Math.max(0, + Math.min(ongoingEvent.reward.maxDailyReward - rewardSum, computedDelta))); } @VisibleForTesting @@ -494,22 +453,6 @@ class Agent { return; } final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked(); - final long maxCirculationAllowed = mIrs.getMaxCirculationLocked(); - final long curNarcsInCirculation = mScribe.getNarcsInCirculationLocked(); - final long newArcsInCirculation = curNarcsInCirculation + transaction.delta; - if (transaction.delta > 0 && newArcsInCirculation > maxCirculationAllowed) { - // Set lower bound at 0 so we don't accidentally take away credits when we were trying - // to _give_ the app credits. - final long newDelta = Math.max(0, maxCirculationAllowed - curNarcsInCirculation); - Slog.i(TAG, "Would result in too many credits in circulation. Decreasing transaction " - + eventToString(transaction.eventId) - + (transaction.tag == null ? "" : ":" + transaction.tag) - + " for " + appToString(userId, pkgName) - + " by " + narcToString(transaction.delta - newDelta)); - transaction = new Ledger.Transaction( - transaction.startTimeMs, transaction.endTimeMs, - transaction.eventId, transaction.tag, newDelta); - } final long originalBalance = ledger.getCurrentBalance(); if (transaction.delta > 0 && originalBalance + transaction.delta > economicPolicy.getMaxSatiatedBalance()) { @@ -524,10 +467,10 @@ class Agent { + " by " + narcToString(transaction.delta - newDelta)); transaction = new Ledger.Transaction( transaction.startTimeMs, transaction.endTimeMs, - transaction.eventId, transaction.tag, newDelta); + transaction.eventId, transaction.tag, newDelta, transaction.ctp); } ledger.recordTransaction(transaction); - mScribe.adjustNarcsInCirculationLocked(transaction.delta); + mScribe.adjustRemainingConsumableNarcsLocked(-transaction.ctp); if (transaction.delta != 0 && notifyOnAffordabilityChange) { final ArraySet<ActionAffordabilityNote> actionAffordabilityNotes = mActionAffordabilityNotes.get(userId, pkgName); @@ -535,7 +478,9 @@ class Agent { final long newBalance = ledger.getCurrentBalance(); for (int i = 0; i < actionAffordabilityNotes.size(); ++i) { final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i); - final boolean isAffordable = newBalance >= note.getCachedModifiedPrice(); + final boolean isAffordable = + isAffordableLocked(newBalance, + note.getCachedModifiedPrice(), note.getCtp()); if (note.isCurrentlyAffordable() != isAffordable) { note.setNewAffordability(isAffordable); mIrs.postAffordabilityChanged(userId, pkgName, note); @@ -543,6 +488,10 @@ class Agent { } } } + if (transaction.ctp != 0) { + mHandler.sendEmptyMessage(MSG_CHECK_ALL_AFFORDABILITY); + mIrs.maybePerformQuantitativeEasingLocked(); + } } /** @@ -599,8 +548,8 @@ class Agent { } recordTransactionLocked(userId, pkgName, ledger, - new Ledger.Transaction( - now, now, REGULATION_WEALTH_RECLAMATION, null, -toReclaim), + new Ledger.Transaction(now, now, REGULATION_WEALTH_RECLAMATION, + null, -toReclaim, 0), true); } } @@ -648,7 +597,7 @@ class Agent { final long now = getCurrentTimeMillis(); final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName); recordTransactionLocked(userId, pkgName, ledger, - new Ledger.Transaction(now, now, REGULATION_DEMOTION, null, -toReclaim), + new Ledger.Transaction(now, now, REGULATION_DEMOTION, null, -toReclaim, 0), true); } } @@ -665,10 +614,13 @@ class Agent { return !mIrs.isSystem(userId, packageInfo.packageName); } + void onCreditSupplyChanged() { + mHandler.sendEmptyMessage(MSG_CHECK_ALL_AFFORDABILITY); + } + @GuardedBy("mLock") void distributeBasicIncomeLocked(int batteryLevel) { List<PackageInfo> pkgs = mIrs.getInstalledPackages(); - pkgs.sort(mPackageDistributionComparator); final long now = getCurrentTimeMillis(); for (int i = 0; i < pkgs.size(); ++i) { @@ -686,7 +638,7 @@ class Agent { if (shortfall > 0) { recordTransactionLocked(userId, pkgName, ledger, new Ledger.Transaction(now, now, REGULATION_BASIC_INCOME, - null, (long) (perc * shortfall)), true); + null, (long) (perc * shortfall), 0), true); } } } @@ -705,12 +657,8 @@ class Agent { @GuardedBy("mLock") void grantBirthrightsLocked(final int userId) { final List<PackageInfo> pkgs = mIrs.getInstalledPackages(userId); - final long maxBirthright = - mIrs.getMaxCirculationLocked() / mIrs.getInstalledPackages().size(); final long now = getCurrentTimeMillis(); - pkgs.sort(mPackageDistributionComparator); - for (int i = 0; i < pkgs.size(); ++i) { final PackageInfo packageInfo = pkgs.get(i); if (!shouldGiveCredits(packageInfo)) { @@ -726,7 +674,7 @@ class Agent { recordTransactionLocked(userId, pkgName, ledger, new Ledger.Transaction(now, now, REGULATION_BIRTHRIGHT, null, - Math.min(maxBirthright, mIrs.getMinBalanceLocked(userId, pkgName))), + mIrs.getMinBalanceLocked(userId, pkgName), 0), true); } } @@ -740,14 +688,11 @@ class Agent { return; } - List<PackageInfo> pkgs = mIrs.getInstalledPackages(); - final int numPackages = pkgs.size(); - final long maxBirthright = mIrs.getMaxCirculationLocked() / numPackages; final long now = getCurrentTimeMillis(); recordTransactionLocked(userId, pkgName, ledger, new Ledger.Transaction(now, now, REGULATION_BIRTHRIGHT, null, - Math.min(maxBirthright, mIrs.getMinBalanceLocked(userId, pkgName))), true); + mIrs.getMinBalanceLocked(userId, pkgName), 0), true); } @GuardedBy("mLock") @@ -762,7 +707,7 @@ class Agent { final long now = getCurrentTimeMillis(); recordTransactionLocked(userId, pkgName, ledger, - new Ledger.Transaction(now, now, REGULATION_PROMOTION, null, missing), true); + new Ledger.Transaction(now, now, REGULATION_PROMOTION, null, missing, 0), true); } @GuardedBy("mLock") @@ -779,7 +724,7 @@ class Agent { private void reclaimAssetsLocked(final int userId, @NonNull final String pkgName) { final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName); if (ledger.getCurrentBalance() != 0) { - mScribe.adjustNarcsInCirculationLocked(-ledger.getCurrentBalance()); + mScribe.adjustRemainingConsumableNarcsLocked(-ledger.getCurrentBalance()); } mScribe.discardLedgerLocked(userId, pkgName); mCurrentOngoingEvents.delete(userId, pkgName); @@ -803,6 +748,7 @@ class Agent { static final long WILL_NOT_CROSS_THRESHOLD = -1; private long mCurBalance; + private long mRemainingConsumableCredits; /** * The maximum change in credits per second towards the upper threshold * {@link #mUpperThreshold}. A value of 0 means the current ongoing events will never @@ -815,15 +761,25 @@ class Agent { * result in the app crossing the lower threshold. */ private long mMaxDeltaPerSecToLowerThreshold; + /** + * The maximum change in credits per second towards the highest CTP threshold below the + * remaining consumable credits (cached in {@link #mCtpThreshold}). A value of 0 means + * the current ongoing events will never result in the app crossing the lower threshold. + */ + private long mMaxDeltaPerSecToCtpThreshold; private long mUpperThreshold; private long mLowerThreshold; + private long mCtpThreshold; - void reset(long curBalance, + void reset(long curBalance, long remainingConsumableCredits, @Nullable ArraySet<ActionAffordabilityNote> actionAffordabilityNotes) { mCurBalance = curBalance; + mRemainingConsumableCredits = remainingConsumableCredits; mMaxDeltaPerSecToUpperThreshold = mMaxDeltaPerSecToLowerThreshold = 0; + mMaxDeltaPerSecToCtpThreshold = 0; mUpperThreshold = Long.MIN_VALUE; mLowerThreshold = Long.MAX_VALUE; + mCtpThreshold = 0; if (actionAffordabilityNotes != null) { for (int i = 0; i < actionAffordabilityNotes.size(); ++i) { final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i); @@ -835,6 +791,10 @@ class Agent { mUpperThreshold = (mUpperThreshold == Long.MIN_VALUE) ? price : Math.min(mUpperThreshold, price); } + final long ctp = note.getCtp(); + if (ctp <= mRemainingConsumableCredits) { + mCtpThreshold = Math.max(mCtpThreshold, ctp); + } } } } @@ -847,13 +807,23 @@ class Agent { * threshold. */ long getTimeToCrossLowerThresholdMs() { - if (mMaxDeltaPerSecToLowerThreshold == 0) { - // Will never cross upper threshold based on current events. + if (mMaxDeltaPerSecToLowerThreshold == 0 && mMaxDeltaPerSecToCtpThreshold == 0) { + // Will never cross lower threshold based on current events. return WILL_NOT_CROSS_THRESHOLD; } - // deltaPerSec is a negative value, so do threshold-balance to cancel out the negative. - final long minSeconds = - (mLowerThreshold - mCurBalance) / mMaxDeltaPerSecToLowerThreshold; + long minSeconds = Long.MAX_VALUE; + if (mMaxDeltaPerSecToLowerThreshold != 0) { + // deltaPerSec is a negative value, so do threshold-balance to cancel out the + // negative. + minSeconds = (mLowerThreshold - mCurBalance) / mMaxDeltaPerSecToLowerThreshold; + } + if (mMaxDeltaPerSecToCtpThreshold != 0) { + minSeconds = Math.min(minSeconds, + // deltaPerSec is a negative value, so do threshold-balance to cancel + // out the negative. + (mCtpThreshold - mRemainingConsumableCredits) + / mMaxDeltaPerSecToCtpThreshold); + } return minSeconds * 1000; } @@ -876,10 +846,15 @@ class Agent { @Override public void accept(OngoingEvent ongoingEvent) { - if (mCurBalance >= mLowerThreshold && ongoingEvent.deltaPerSec < 0) { - mMaxDeltaPerSecToLowerThreshold += ongoingEvent.deltaPerSec; - } else if (mCurBalance < mUpperThreshold && ongoingEvent.deltaPerSec > 0) { - mMaxDeltaPerSecToUpperThreshold += ongoingEvent.deltaPerSec; + final long deltaPerSec = ongoingEvent.getDeltaPerSec(); + if (mCurBalance >= mLowerThreshold && deltaPerSec < 0) { + mMaxDeltaPerSecToLowerThreshold += deltaPerSec; + } else if (mCurBalance < mUpperThreshold && deltaPerSec > 0) { + mMaxDeltaPerSecToUpperThreshold += deltaPerSec; + } + final long ctpPerSec = ongoingEvent.getCtpPerSec(); + if (mRemainingConsumableCredits >= mCtpThreshold && deltaPerSec < 0) { + mMaxDeltaPerSecToCtpThreshold -= ctpPerSec; } } } @@ -896,8 +871,9 @@ class Agent { mBalanceThresholdAlarmQueue.removeAlarmForKey(new Package(userId, pkgName)); return; } - mTrendCalculator.reset( - getBalanceLocked(userId, pkgName), mActionAffordabilityNotes.get(userId, pkgName)); + mTrendCalculator.reset(getBalanceLocked(userId, pkgName), + mScribe.getRemainingConsumableNarcsLocked(), + mActionAffordabilityNotes.get(userId, pkgName)); ongoingEvents.forEach(mTrendCalculator); final long lowerTimeMs = mTrendCalculator.getTimeToCrossLowerThresholdMs(); final long upperTimeMs = mTrendCalculator.getTimeToCrossUpperThresholdMs(); @@ -931,20 +907,79 @@ class Agent { public final String tag; @Nullable public final EconomicPolicy.Reward reward; - public final long deltaPerSec; + @Nullable + public final EconomicPolicy.Cost actionCost; public int refCount; - OngoingEvent(int eventId, @Nullable String tag, - @Nullable EconomicPolicy.Reward reward, long startTimeElapsed, long deltaPerSec) { + OngoingEvent(int eventId, @Nullable String tag, long startTimeElapsed, + @NonNull EconomicPolicy.Reward reward) { this.startTimeElapsed = startTimeElapsed; this.eventId = eventId; this.tag = tag; this.reward = reward; - this.deltaPerSec = deltaPerSec; + this.actionCost = null; + refCount = 1; + } + + OngoingEvent(int eventId, @Nullable String tag, long startTimeElapsed, + @NonNull EconomicPolicy.Cost actionCost) { + this.startTimeElapsed = startTimeElapsed; + this.eventId = eventId; + this.tag = tag; + this.reward = null; + this.actionCost = actionCost; refCount = 1; } + + long getDeltaPerSec() { + if (actionCost != null) { + return -actionCost.price; + } + if (reward != null) { + return reward.ongoingRewardPerSecond; + } + Slog.wtfStack(TAG, "No action or reward in ongoing event?!??!"); + return 0; + } + + long getCtpPerSec() { + if (actionCost != null) { + return actionCost.costToProduce; + } + return 0; + } + } + + private class OngoingEventUpdater implements Consumer<OngoingEvent> { + private int mUserId; + private String mPkgName; + private long mNow; + private long mNowElapsed; + + private void reset(int userId, String pkgName, long now, long nowElapsed) { + mUserId = userId; + mPkgName = pkgName; + mNow = now; + mNowElapsed = nowElapsed; + } + + @Override + public void accept(OngoingEvent ongoingEvent) { + // Disable balance check & affordability notifications here because + // we're in the middle of updating ongoing action costs/prices and + // sending out notifications or rescheduling the balance check alarm + // would be a waste since we'll have to redo them again after all of + // our internal state is updated. + final boolean updateBalanceCheck = false; + stopOngoingActionLocked(mUserId, mPkgName, ongoingEvent.eventId, ongoingEvent.tag, + mNowElapsed, mNow, updateBalanceCheck, /* notifyOnAffordabilityChange */ false); + noteOngoingEventLocked(mUserId, mPkgName, ongoingEvent.eventId, ongoingEvent.tag, + mNowElapsed, updateBalanceCheck); + } } + private final OngoingEventUpdater mOngoingEventUpdater = new OngoingEventUpdater(); + private static final class Package { public final String packageName; public final int userId; @@ -996,7 +1031,8 @@ class Agent { protected void processExpiredAlarms(@NonNull ArraySet<Package> expired) { for (int i = 0; i < expired.size(); ++i) { Package p = expired.valueAt(i); - mHandler.obtainMessage(MSG_CHECK_BALANCE, p.userId, 0, p.packageName) + mHandler.obtainMessage( + MSG_CHECK_INDIVIDUAL_AFFORDABILITY, p.userId, 0, p.packageName) .sendToTarget(); } } @@ -1023,9 +1059,10 @@ class Agent { note.setNewAffordability(true); return; } - note.recalculateModifiedPrice(economicPolicy, userId, pkgName); + note.recalculateCosts(economicPolicy, userId, pkgName); note.setNewAffordability( - getBalanceLocked(userId, pkgName) >= note.getCachedModifiedPrice()); + isAffordableLocked(getBalanceLocked(userId, pkgName), + note.getCachedModifiedPrice(), note.getCtp())); mIrs.postAffordabilityChanged(userId, pkgName, note); // Update ongoing alarm scheduleBalanceCheckLocked(userId, pkgName); @@ -1052,6 +1089,7 @@ class Agent { static final class ActionAffordabilityNote { private final EconomyManagerInternal.ActionBill mActionBill; private final EconomyManagerInternal.AffordabilityChangeListener mListener; + private long mCtp; private long mModifiedPrice; private boolean mIsAffordable; @@ -1086,22 +1124,29 @@ class Agent { return mModifiedPrice; } + private long getCtp() { + return mCtp; + } + @VisibleForTesting - long recalculateModifiedPrice(@NonNull EconomicPolicy economicPolicy, + void recalculateCosts(@NonNull EconomicPolicy economicPolicy, int userId, @NonNull String pkgName) { long modifiedPrice = 0; + long ctp = 0; final List<EconomyManagerInternal.AnticipatedAction> anticipatedActions = mActionBill.getAnticipatedActions(); for (int i = 0; i < anticipatedActions.size(); ++i) { final EconomyManagerInternal.AnticipatedAction aa = anticipatedActions.get(i); - final long actionCost = + final EconomicPolicy.Cost actionCost = economicPolicy.getCostOfAction(aa.actionId, userId, pkgName); - modifiedPrice += actionCost * aa.numInstantaneousCalls - + actionCost * (aa.ongoingDurationMs / 1000); + modifiedPrice += actionCost.price * aa.numInstantaneousCalls + + actionCost.price * (aa.ongoingDurationMs / 1000); + ctp += actionCost.costToProduce * aa.numInstantaneousCalls + + actionCost.costToProduce * (aa.ongoingDurationMs / 1000); } mModifiedPrice = modifiedPrice; - return modifiedPrice; + mCtp = ctp; } boolean isCurrentlyAffordable() { @@ -1138,7 +1183,15 @@ class Agent { @Override public void handleMessage(Message msg) { switch (msg.what) { - case MSG_CHECK_BALANCE: { + case MSG_CHECK_ALL_AFFORDABILITY: { + synchronized (mLock) { + removeMessages(MSG_CHECK_ALL_AFFORDABILITY); + onAnythingChangedLocked(false); + } + } + break; + + case MSG_CHECK_INDIVIDUAL_AFFORDABILITY: { final int userId = msg.arg1; final String pkgName = (String) msg.obj; synchronized (mLock) { @@ -1151,8 +1204,8 @@ class Agent { for (int i = 0; i < actionAffordabilityNotes.size(); ++i) { final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i); - final boolean isAffordable = - newBalance >= note.getCachedModifiedPrice(); + final boolean isAffordable = isAffordableLocked( + newBalance, note.getCachedModifiedPrice(), note.getCtp()); if (note.isCurrentlyAffordable() != isAffordable) { note.setNewAffordability(isAffordable); mIrs.postAffordabilityChanged(userId, pkgName, note); @@ -1207,7 +1260,12 @@ class Agent { pw.print(" runtime="); TimeUtils.formatDuration(nowElapsed - ongoingEvent.startTimeElapsed, pw); pw.print(" delta/sec="); - pw.print(ongoingEvent.deltaPerSec); + pw.print(narcToString(ongoingEvent.getDeltaPerSec())); + final long ctp = ongoingEvent.getCtpPerSec(); + if (ctp != 0) { + pw.print(" ctp/sec="); + pw.print(narcToString(ongoingEvent.getCtpPerSec())); + } pw.print(" refCount="); pw.print(ongoingEvent.refCount); pw.println(); 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 e1e6e47155aa..71e00cf053e2 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java @@ -33,7 +33,8 @@ import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_NO import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP; import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE; 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_HARD_CONSUMPTION_LIMIT; +import static android.app.tare.EconomyManager.DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT; 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; @@ -70,7 +71,8 @@ import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_INEXACT_NONWAK import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP; import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE; 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_HARD_CONSUMPTION_LIMIT; +import static android.app.tare.EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT; 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; @@ -143,7 +145,8 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { private long mMinSatiatedBalanceExempted; private long mMinSatiatedBalanceOther; private long mMaxSatiatedBalance; - private long mMaxSatiatedCirculation; + private long mInitialSatiatedConsumptionLimit; + private long mHardSatiatedConsumptionLimit; private final KeyValueListParser mParser = new KeyValueListParser(','); private final InternalResourceService mInternalResourceService; @@ -179,8 +182,13 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { } @Override - long getMaxSatiatedCirculation() { - return mMaxSatiatedCirculation; + long getInitialSatiatedConsumptionLimit() { + return mInitialSatiatedConsumptionLimit; + } + + @Override + long getHardSatiatedConsumptionLimit() { + return mHardSatiatedConsumptionLimit; } @NonNull @@ -217,8 +225,11 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP)); mMaxSatiatedBalance = arcToNarc(mParser.getInt(KEY_AM_MAX_SATIATED_BALANCE, DEFAULT_AM_MAX_SATIATED_BALANCE)); - mMaxSatiatedCirculation = arcToNarc(mParser.getInt(KEY_AM_MAX_CIRCULATION, - DEFAULT_AM_MAX_CIRCULATION)); + mInitialSatiatedConsumptionLimit = arcToNarc(mParser.getInt( + KEY_AM_INITIAL_CONSUMPTION_LIMIT, DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT)); + mHardSatiatedConsumptionLimit = Math.max(mInitialSatiatedConsumptionLimit, + arcToNarc(mParser.getInt( + KEY_AM_HARD_CONSUMPTION_LIMIT, DEFAULT_AM_HARD_CONSUMPTION_LIMIT))); final long exactAllowWhileIdleWakeupBasePrice = arcToNarc( mParser.getInt(KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE, @@ -357,7 +368,11 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { pw.print("Other", narcToString(mMinSatiatedBalanceOther)).println(); pw.decreaseIndent(); pw.print("Max satiated balance", narcToString(mMaxSatiatedBalance)).println(); - pw.print("Max satiated circulation", narcToString(mMaxSatiatedCirculation)).println(); + pw.print("Consumption limits: ["); + pw.print(narcToString(mInitialSatiatedConsumptionLimit)); + pw.print(", "); + pw.print(narcToString(mHardSatiatedConsumptionLimit)); + pw.println("]"); pw.println(); pw.println("Actions:"); diff --git a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java index a4e7b808ca6c..2109a8575777 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java @@ -34,7 +34,7 @@ public class CompleteEconomicPolicy extends EconomicPolicy { private final SparseArray<Reward> mRewards = new SparseArray<>(); private final int[] mCostModifiers; private long mMaxSatiatedBalance; - private long mMaxSatiatedCirculation; + private long mConsumptionLimit; CompleteEconomicPolicy(@NonNull InternalResourceService irs) { super(irs); @@ -74,9 +74,9 @@ public class CompleteEconomicPolicy extends EconomicPolicy { max = 0; for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) { - max += mEnabledEconomicPolicies.valueAt(i).getMaxSatiatedCirculation(); + max += mEnabledEconomicPolicies.valueAt(i).getInitialSatiatedConsumptionLimit(); } - mMaxSatiatedCirculation = max; + mConsumptionLimit = max; } @Override @@ -94,8 +94,13 @@ public class CompleteEconomicPolicy extends EconomicPolicy { } @Override - long getMaxSatiatedCirculation() { - return mMaxSatiatedCirculation; + long getInitialSatiatedConsumptionLimit() { + return mConsumptionLimit; + } + + @Override + long getHardSatiatedConsumptionLimit() { + return mConsumptionLimit; } @NonNull 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 c1177b20bb0e..1e480155f18a 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java @@ -151,6 +151,16 @@ public abstract class EconomicPolicy { } } + static class Cost { + public final long costToProduce; + public final long price; + + Cost(long costToProduce, long price) { + this.costToProduce = costToProduce; + this.price = price; + } + } + private static final Modifier[] COST_MODIFIER_BY_INDEX = new Modifier[NUM_COST_MODIFIERS]; EconomicPolicy(@NonNull InternalResourceService irs) { @@ -193,10 +203,18 @@ public abstract class EconomicPolicy { abstract long getMaxSatiatedBalance(); /** - * Returns the maximum number of narcs that should be in circulation at once when the device is - * at 100% battery. + * Returns the maximum number of narcs that should be consumed during a full 100% discharge + * cycle. This is the initial limit. The system may choose to increase the limit over time, + * but the increased limit should never exceed the value returned from + * {@link #getHardSatiatedConsumptionLimit()}. */ - abstract long getMaxSatiatedCirculation(); + abstract long getInitialSatiatedConsumptionLimit(); + + /** + * Returns the maximum number of narcs that should be consumed during a full 100% discharge + * cycle. This is the hard limit that should never be exceeded. + */ + abstract long getHardSatiatedConsumptionLimit(); /** Return the set of modifiers that should apply to this policy's costs. */ @NonNull @@ -211,10 +229,11 @@ public abstract class EconomicPolicy { void dump(IndentingPrintWriter pw) { } - final long getCostOfAction(int actionId, int userId, @NonNull String pkgName) { + @NonNull + final Cost getCostOfAction(int actionId, int userId, @NonNull String pkgName) { final Action action = getAction(actionId); if (action == null) { - return 0; + return new Cost(0, 0); } long ctp = action.costToProduce; long price = action.basePrice; @@ -235,7 +254,7 @@ public abstract class EconomicPolicy { (ProcessStateModifier) getModifier(COST_MODIFIER_PROCESS_STATE); price = processStateModifier.getModifiedPrice(userId, pkgName, ctp, price); } - return price; + return new Cost(ctp, price); } private static void initModifier(@Modifier.CostModifier final int modifierId, 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 36895a5c3a25..c93480700ca7 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java @@ -69,6 +69,7 @@ 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.EconomicPolicy.Cost; import com.android.server.tare.EconomyManagerInternal.TareStateChangeListener; import java.io.FileDescriptor; @@ -100,6 +101,11 @@ public class InternalResourceService extends SystemService { private static final long MIN_UNUSED_TIME_MS = 3 * DAY_IN_MILLIS; /** The amount of time to delay reclamation by after boot. */ private static final long RECLAMATION_STARTUP_DELAY_MS = 30_000L; + /** + * The battery level above which we may consider quantitative easing (increasing the consumption + * limit). + */ + private static final int QUANTITATIVE_EASING_BATTERY_THRESHOLD = 50; private static final int PACKAGE_QUERY_FLAGS = PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE | PackageManager.MATCH_APEX; @@ -122,44 +128,6 @@ public class InternalResourceService extends SystemService { @GuardedBy("mLock") private CompleteEconomicPolicy mCompleteEconomicPolicy; - private static final class ReclamationConfig { - /** - * ARC circulation threshold (% circulating vs scaled maximum) above which this config - * should come into play. - */ - public final double circulationPercentageThreshold; - /** @see Agent#reclaimUnusedAssetsLocked(double, long, boolean) */ - public final double reclamationPercentage; - /** @see Agent#reclaimUnusedAssetsLocked(double, long, boolean) */ - public final long minUsedTimeMs; - /** @see Agent#reclaimUnusedAssetsLocked(double, long, boolean) */ - public final boolean scaleMinBalance; - - ReclamationConfig(double circulationPercentageThreshold, double reclamationPercentage, - long minUsedTimeMs, boolean scaleMinBalance) { - this.circulationPercentageThreshold = circulationPercentageThreshold; - this.reclamationPercentage = reclamationPercentage; - this.minUsedTimeMs = minUsedTimeMs; - this.scaleMinBalance = scaleMinBalance; - } - } - - /** - * Sorted list of reclamation configs used to determine how many credits to force reclaim when - * the circulation percentage is too high. The list should *always* be sorted in descending - * order of {@link ReclamationConfig#circulationPercentageThreshold}. - */ - @GuardedBy("mLock") - private final List<ReclamationConfig> mReclamationConfigs = List.of( - new ReclamationConfig(2, .75, 12 * HOUR_IN_MILLIS, true), - new ReclamationConfig(1.6, .5, DAY_IN_MILLIS, true), - new ReclamationConfig(1.4, .25, DAY_IN_MILLIS, true), - new ReclamationConfig(1.2, .25, 2 * DAY_IN_MILLIS, true), - new ReclamationConfig(1, .25, MIN_UNUSED_TIME_MS, false), - new ReclamationConfig( - .9, DEFAULT_UNUSED_RECLAMATION_PERCENTAGE, MIN_UNUSED_TIME_MS, false) - ); - @NonNull @GuardedBy("mLock") private final List<PackageInfo> mPkgCache = new ArrayList<>(); @@ -266,8 +234,7 @@ public class InternalResourceService extends SystemService { private static final int MSG_NOTIFY_AFFORDABILITY_CHANGE_LISTENER = 0; 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 int MSG_NOTIFY_STATE_CHANGE_LISTENERS = 3; private static final String ALARM_TAG_WEALTH_RECLAMATION = "*tare.reclamation*"; /** @@ -362,8 +329,8 @@ public class InternalResourceService extends SystemService { } @GuardedBy("mLock") - long getMaxCirculationLocked() { - return mCurrentBatteryLevel * mCompleteEconomicPolicy.getMaxSatiatedCirculation() / 100; + long getConsumptionLimitLocked() { + return mCurrentBatteryLevel * mScribe.getSatiatedConsumptionLimitLocked() / 100; } @GuardedBy("mLock") @@ -372,6 +339,11 @@ public class InternalResourceService extends SystemService { / 100; } + @GuardedBy("mLock") + long getInitialSatiatedConsumptionLimitLocked() { + return mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit(); + } + int getUid(final int userId, @NonNull final String pkgName) { synchronized (mPackageToUidCache) { Integer uid = mPackageToUidCache.get(userId, pkgName); @@ -403,12 +375,15 @@ public class InternalResourceService extends SystemService { void onBatteryLevelChanged() { synchronized (mLock) { final int newBatteryLevel = getCurrentBatteryLevel(); - if (newBatteryLevel > mCurrentBatteryLevel) { + final boolean increased = newBatteryLevel > mCurrentBatteryLevel; + if (increased) { mAgent.distributeBasicIncomeLocked(newBatteryLevel); - } else if (newBatteryLevel < mCurrentBatteryLevel) { - mHandler.obtainMessage(MSG_MAYBE_FORCE_RECLAIM).sendToTarget(); + } else if (newBatteryLevel == mCurrentBatteryLevel) { + Slog.wtf(TAG, "Battery level stayed the same"); + return; } mCurrentBatteryLevel = newBatteryLevel; + adjustCreditSupplyLocked(increased); } } @@ -546,6 +521,31 @@ public class InternalResourceService extends SystemService { } } + /** + * Try to increase the consumption limit if apps are reaching the current limit too quickly. + */ + @GuardedBy("mLock") + void maybePerformQuantitativeEasingLocked() { + // We don't need to increase the limit if the device runs out of consumable credits + // when the battery is low. + final long remainingConsumableNarcs = mScribe.getRemainingConsumableNarcsLocked(); + if (mCurrentBatteryLevel <= QUANTITATIVE_EASING_BATTERY_THRESHOLD + || remainingConsumableNarcs > 0) { + return; + } + final long currentConsumptionLimit = mScribe.getSatiatedConsumptionLimitLocked(); + final long shortfall = (mCurrentBatteryLevel - QUANTITATIVE_EASING_BATTERY_THRESHOLD) + * currentConsumptionLimit / 100; + final long newConsumptionLimit = Math.min(currentConsumptionLimit + shortfall, + mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit()); + if (newConsumptionLimit != currentConsumptionLimit) { + Slog.i(TAG, "Increasing consumption limit from " + narcToString(currentConsumptionLimit) + + " to " + narcToString(newConsumptionLimit)); + mScribe.setConsumptionLimitLocked(newConsumptionLimit); + adjustCreditSupplyLocked(/* allowIncrease */ true); + } + } + void postAffordabilityChanged(final int userId, @NonNull final String pkgName, @NonNull Agent.ActionAffordabilityNote affordabilityNote) { if (DEBUG) { @@ -560,6 +560,23 @@ public class InternalResourceService extends SystemService { } @GuardedBy("mLock") + private void adjustCreditSupplyLocked(boolean allowIncrease) { + final long newLimit = getConsumptionLimitLocked(); + final long remainingConsumableNarcs = mScribe.getRemainingConsumableNarcsLocked(); + if (remainingConsumableNarcs == newLimit) { + return; + } + if (remainingConsumableNarcs > newLimit) { + mScribe.adjustRemainingConsumableNarcsLocked(newLimit - remainingConsumableNarcs); + } else if (allowIncrease) { + final double perc = mCurrentBatteryLevel / 100d; + final long shortfall = newLimit - remainingConsumableNarcs; + mScribe.adjustRemainingConsumableNarcsLocked((long) (perc * shortfall)); + } + mAgent.onCreditSupplyChanged(); + } + + @GuardedBy("mLock") private void processUsageEventLocked(final int userId, @NonNull UsageEvents.Event event) { if (!mIsEnabled) { return; @@ -650,48 +667,6 @@ public class InternalResourceService extends SystemService { } } - /** - * Reclaim unused ARCs above apps' minimum balances if there are too many credits currently - * in circulation. - */ - @GuardedBy("mLock") - private void maybeForceReclaimLocked() { - final long maxCirculation = getMaxCirculationLocked(); - if (maxCirculation == 0) { - Slog.wtf(TAG, "Max scaled circulation is 0..."); - mAgent.reclaimUnusedAssetsLocked(1, HOUR_IN_MILLIS, true); - mScribe.setLastReclamationTimeLocked(getCurrentTimeMillis()); - scheduleUnusedWealthReclamationLocked(); - return; - } - final long curCirculation = mScribe.getNarcsInCirculationLocked(); - final double circulationPerc = 1.0 * curCirculation / maxCirculation; - if (DEBUG) { - Slog.d(TAG, "Circulation %: " + circulationPerc); - } - final int numConfigs = mReclamationConfigs.size(); - if (numConfigs == 0) { - return; - } - // The configs are sorted in descending order of circulationPercentageThreshold, so we can - // short-circuit if the current circulation is lower than the lowest threshold. - if (circulationPerc - < mReclamationConfigs.get(numConfigs - 1).circulationPercentageThreshold) { - return; - } - // TODO: maybe exclude apps we think will be launched in the next few hours - for (int i = 0; i < numConfigs; ++i) { - final ReclamationConfig config = mReclamationConfigs.get(i); - if (circulationPerc >= config.circulationPercentageThreshold) { - mAgent.reclaimUnusedAssetsLocked( - config.reclamationPercentage, config.minUsedTimeMs, config.scaleMinBalance); - mScribe.setLastReclamationTimeLocked(getCurrentTimeMillis()); - scheduleUnusedWealthReclamationLocked(); - break; - } - } - } - private void registerListeners() { final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_BATTERY_LEVEL_CHANGED); @@ -731,8 +706,18 @@ public class InternalResourceService extends SystemService { final boolean isFirstSetup = !mScribe.recordExists(); if (isFirstSetup) { mAgent.grantBirthrightsLocked(); + mScribe.setConsumptionLimitLocked( + mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit()); } else { mScribe.loadFromDiskLocked(); + if (mScribe.getSatiatedConsumptionLimitLocked() + < mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit() + || mScribe.getSatiatedConsumptionLimitLocked() + > mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit()) { + // Reset the consumption limit since several factors may have changed. + mScribe.setConsumptionLimitLocked( + mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit()); + } } scheduleUnusedWealthReclamationLocked(); } @@ -787,14 +772,6 @@ public class InternalResourceService extends SystemService { @Override public void handleMessage(Message msg) { switch (msg.what) { - case MSG_MAYBE_FORCE_RECLAIM: { - removeMessages(MSG_MAYBE_FORCE_RECLAIM); - synchronized (mLock) { - maybeForceReclaimLocked(); - } - } - break; - case MSG_NOTIFY_AFFORDABILITY_CHANGE_LISTENER: { final SomeArgs args = (SomeArgs) msg.obj; final int userId = args.argi1; @@ -936,12 +913,13 @@ public class InternalResourceService extends SystemService { synchronized (mLock) { for (int i = 0; i < projectedActions.size(); ++i) { AnticipatedAction action = projectedActions.get(i); - final long cost = mCompleteEconomicPolicy.getCostOfAction( + final Cost cost = mCompleteEconomicPolicy.getCostOfAction( action.actionId, userId, pkgName); - requiredBalance += cost * action.numInstantaneousCalls - + cost * (action.ongoingDurationMs / 1000); + requiredBalance += cost.price * action.numInstantaneousCalls + + cost.price * (action.ongoingDurationMs / 1000); } - return mAgent.getBalanceLocked(userId, pkgName) >= requiredBalance; + return mAgent.getBalanceLocked(userId, pkgName) >= requiredBalance + && mScribe.getRemainingConsumableNarcsLocked() >= requiredBalance; } } @@ -960,14 +938,17 @@ public class InternalResourceService extends SystemService { synchronized (mLock) { for (int i = 0; i < projectedActions.size(); ++i) { AnticipatedAction action = projectedActions.get(i); - final long cost = mCompleteEconomicPolicy.getCostOfAction( + final Cost cost = mCompleteEconomicPolicy.getCostOfAction( action.actionId, userId, pkgName); - totalCostPerSecond += cost; + totalCostPerSecond += cost.price; } if (totalCostPerSecond == 0) { return FOREVER_MS; } - return mAgent.getBalanceLocked(userId, pkgName) * 1000 / totalCostPerSecond; + final long minBalance = Math.min( + mAgent.getBalanceLocked(userId, pkgName), + mScribe.getRemainingConsumableNarcsLocked()); + return minBalance * 1000 / totalCostPerSecond; } } @@ -1085,10 +1066,20 @@ public class InternalResourceService extends SystemService { private void updateEconomicPolicy() { synchronized (mLock) { + final long initialLimit = + mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit(); + final long hardLimit = mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit(); mCompleteEconomicPolicy.tearDown(); mCompleteEconomicPolicy = new CompleteEconomicPolicy(InternalResourceService.this); if (mIsEnabled && mBootPhase >= PHASE_SYSTEM_SERVICES_READY) { mCompleteEconomicPolicy.setup(); + if (initialLimit != mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit() + || hardLimit + != mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit()) { + // Reset the consumption limit since several factors may have changed. + mScribe.setConsumptionLimitLocked( + mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit()); + } mAgent.onPricingChangedLocked(); } } @@ -1110,18 +1101,23 @@ public class InternalResourceService extends SystemService { pw.print("Current battery level: "); pw.println(mCurrentBatteryLevel); - final long maxCirculation = getMaxCirculationLocked(); - pw.print("Max circulation (current/satiated): "); - pw.print(narcToString(maxCirculation)); + final long consumptionLimit = getConsumptionLimitLocked(); + pw.print("Consumption limit (current/initial-satiated/current-satiated): "); + pw.print(narcToString(consumptionLimit)); + pw.print("/"); + pw.print(narcToString(mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit())); pw.print("/"); - pw.println(narcToString(mCompleteEconomicPolicy.getMaxSatiatedCirculation())); + pw.println(narcToString(mScribe.getSatiatedConsumptionLimitLocked())); - final long currentCirculation = mScribe.getNarcsInCirculationLocked(); - pw.print("Current GDP: "); - pw.print(narcToString(currentCirculation)); + final long remainingConsumable = mScribe.getRemainingConsumableNarcsLocked(); + pw.print("Goods remaining: "); + pw.print(narcToString(remainingConsumable)); pw.print(" ("); - pw.print(String.format("%.2f", 100f * currentCirculation / maxCirculation)); - pw.println("% of current max)"); + pw.print(String.format("%.2f", 100f * remainingConsumable / consumptionLimit)); + pw.println("% of current limit)"); + + pw.print("Device wealth: "); + pw.println(narcToString(mScribe.getNarcsInCirculationForLoggingLocked())); pw.println(); pw.print("Exempted apps", mExemptedApps); 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 1f8ce26a3fd9..0eddd2223d23 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java @@ -38,7 +38,8 @@ import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MIN_START_BA import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MIN_START_CTP; import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE; 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_HARD_CONSUMPTION_LIMIT; +import static android.app.tare.EconomyManager.DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT; 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; @@ -79,7 +80,8 @@ import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_MIN_START_BASE_P import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_MIN_START_CTP; import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE; 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_HARD_CONSUMPTION_LIMIT; +import static android.app.tare.EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT; 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; @@ -145,7 +147,8 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { private long mMinSatiatedBalanceExempted; private long mMinSatiatedBalanceOther; private long mMaxSatiatedBalance; - private long mMaxSatiatedCirculation; + private long mInitialSatiatedConsumptionLimit; + private long mHardSatiatedConsumptionLimit; private final KeyValueListParser mParser = new KeyValueListParser(','); private final InternalResourceService mInternalResourceService; @@ -181,8 +184,13 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { } @Override - long getMaxSatiatedCirculation() { - return mMaxSatiatedCirculation; + long getInitialSatiatedConsumptionLimit() { + return mInitialSatiatedConsumptionLimit; + } + + @Override + long getHardSatiatedConsumptionLimit() { + return mHardSatiatedConsumptionLimit; } @NonNull @@ -221,8 +229,11 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP)); mMaxSatiatedBalance = arcToNarc(mParser.getInt(KEY_JS_MAX_SATIATED_BALANCE, DEFAULT_JS_MAX_SATIATED_BALANCE)); - mMaxSatiatedCirculation = arcToNarc(mParser.getInt(KEY_JS_MAX_CIRCULATION, - DEFAULT_JS_MAX_CIRCULATION)); + mInitialSatiatedConsumptionLimit = arcToNarc(mParser.getInt( + KEY_JS_INITIAL_CONSUMPTION_LIMIT, DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT)); + mHardSatiatedConsumptionLimit = Math.max(mInitialSatiatedConsumptionLimit, + arcToNarc(mParser.getInt( + KEY_JS_HARD_CONSUMPTION_LIMIT, DEFAULT_JS_HARD_CONSUMPTION_LIMIT))); mActions.put(ACTION_JOB_MAX_START, new Action(ACTION_JOB_MAX_START, arcToNarc(mParser.getInt(KEY_JS_ACTION_JOB_MAX_START_CTP, @@ -332,7 +343,11 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { pw.print("Other", narcToString(mMinSatiatedBalanceOther)).println(); pw.decreaseIndent(); pw.print("Max satiated balance", narcToString(mMaxSatiatedBalance)).println(); - pw.print("Max satiated circulation", narcToString(mMaxSatiatedCirculation)).println(); + pw.print("Consumption limits: ["); + pw.print(narcToString(mInitialSatiatedConsumptionLimit)); + pw.print(", "); + pw.print(narcToString(mHardSatiatedConsumptionLimit)); + pw.println("]"); pw.println(); pw.println("Actions:"); diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java index f4917ad82761..dfdc20a32999 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java @@ -41,14 +41,16 @@ class Ledger { @Nullable public final String tag; public final long delta; + public final long ctp; Transaction(long startTimeMs, long endTimeMs, - int eventId, @Nullable String tag, long delta) { + int eventId, @Nullable String tag, long delta, long ctp) { this.startTimeMs = startTimeMs; this.endTimeMs = endTimeMs; this.eventId = eventId; this.tag = tag; this.delta = delta; + this.ctp = ctp; } } @@ -144,7 +146,10 @@ class Ledger { pw.print(")"); } pw.print(" --> "); - pw.println(narcToString(transaction.delta)); + pw.print(narcToString(transaction.delta)); + pw.print(" (ctp="); + pw.print(narcToString(transaction.ctp)); + pw.println(")"); } } } diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java index 86968ef23a49..866211024169 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java @@ -73,6 +73,7 @@ public class Scribe { private static final String XML_TAG_TRANSACTION = "transaction"; private static final String XML_TAG_USER = "user"; + private static final String XML_ATTR_CTP = "ctp"; private static final String XML_ATTR_DELTA = "delta"; private static final String XML_ATTR_EVENT_ID = "eventId"; private static final String XML_ATTR_TAG = "tag"; @@ -83,6 +84,8 @@ public class Scribe { private static final String XML_ATTR_USER_ID = "userId"; private static final String XML_ATTR_VERSION = "version"; private static final String XML_ATTR_LAST_RECLAMATION_TIME = "lastReclamationTime"; + private static final String XML_ATTR_REMAINING_CONSUMABLE_NARCS = "remainingConsumableNarcs"; + private static final String XML_ATTR_CONSUMPTION_LIMIT = "consumptionLimit"; /** Version of the file schema. */ private static final int STATE_FILE_VERSION = 0; @@ -95,7 +98,9 @@ public class Scribe { @GuardedBy("mIrs.getLock()") private long mLastReclamationTime; @GuardedBy("mIrs.getLock()") - private long mNarcsInCirculation; + private long mSatiatedConsumptionLimit; + @GuardedBy("mIrs.getLock()") + private long mRemainingConsumableNarcs; @GuardedBy("mIrs.getLock()") private final SparseArrayMap<String, Ledger> mLedgers = new SparseArrayMap<>(); @@ -117,10 +122,10 @@ public class Scribe { } @GuardedBy("mIrs.getLock()") - void adjustNarcsInCirculationLocked(long delta) { + void adjustRemainingConsumableNarcsLocked(long delta) { if (delta != 0) { // No point doing any work if the change is 0. - mNarcsInCirculation += delta; + mRemainingConsumableNarcs += delta; postWrite(); } } @@ -132,6 +137,11 @@ public class Scribe { } @GuardedBy("mIrs.getLock()") + long getSatiatedConsumptionLimitLocked() { + return mSatiatedConsumptionLimit; + } + + @GuardedBy("mIrs.getLock()") long getLastReclamationTimeLocked() { return mLastReclamationTime; } @@ -153,19 +163,37 @@ public class Scribe { return mLedgers; } - /** Returns the total amount of narcs currently allocated to apps. */ + /** + * Returns the sum of credits granted to all apps on the system. This is expensive so don't + * call it for normal operation. + */ + @GuardedBy("mIrs.getLock()") + long getNarcsInCirculationForLoggingLocked() { + long sum = 0; + for (int uIdx = mLedgers.numMaps() - 1; uIdx >= 0; --uIdx) { + for (int pIdx = mLedgers.numElementsForKeyAt(uIdx) - 1; pIdx >= 0; --pIdx) { + sum += mLedgers.valueAt(uIdx, pIdx).getCurrentBalance(); + } + } + return sum; + } + + /** Returns the total amount of narcs that remain to be consumed. */ @GuardedBy("mIrs.getLock()") - long getNarcsInCirculationLocked() { - return mNarcsInCirculation; + long getRemainingConsumableNarcsLocked() { + return mRemainingConsumableNarcs; } @GuardedBy("mIrs.getLock()") void loadFromDiskLocked() { mLedgers.clear(); - mNarcsInCirculation = 0; if (!recordExists()) { + mSatiatedConsumptionLimit = mIrs.getInitialSatiatedConsumptionLimitLocked(); + mRemainingConsumableNarcs = mIrs.getConsumptionLimitLocked(); return; } + mSatiatedConsumptionLimit = 0; + mRemainingConsumableNarcs = 0; final SparseArray<ArraySet<String>> installedPackagesPerUser = new SparseArray<>(); final List<PackageInfo> installedPackages = mIrs.getInstalledPackages(); @@ -222,6 +250,13 @@ public class Scribe { case XML_TAG_HIGH_LEVEL_STATE: mLastReclamationTime = parser.getAttributeLong(null, XML_ATTR_LAST_RECLAMATION_TIME); + mSatiatedConsumptionLimit = + parser.getAttributeLong(null, XML_ATTR_CONSUMPTION_LIMIT, + mIrs.getInitialSatiatedConsumptionLimitLocked()); + final long consumptionLimit = mIrs.getConsumptionLimitLocked(); + mRemainingConsumableNarcs = Math.min(consumptionLimit, + parser.getAttributeLong(null, XML_ATTR_REMAINING_CONSUMABLE_NARCS, + consumptionLimit)); break; case XML_TAG_USER: earliestEndTime = Math.min(earliestEndTime, @@ -249,6 +284,18 @@ public class Scribe { } @GuardedBy("mIrs.getLock()") + void setConsumptionLimitLocked(long limit) { + if (mRemainingConsumableNarcs > limit) { + mRemainingConsumableNarcs = limit; + } else if (limit > mSatiatedConsumptionLimit) { + final long diff = mSatiatedConsumptionLimit - mRemainingConsumableNarcs; + mRemainingConsumableNarcs = (limit - diff); + } + mSatiatedConsumptionLimit = limit; + postWrite(); + } + + @GuardedBy("mIrs.getLock()") void setLastReclamationTimeLocked(long time) { mLastReclamationTime = time; postWrite(); @@ -259,7 +306,8 @@ public class Scribe { TareHandlerThread.getHandler().removeCallbacks(mCleanRunnable); TareHandlerThread.getHandler().removeCallbacks(mWriteRunnable); mLedgers.clear(); - mNarcsInCirculation = 0; + mRemainingConsumableNarcs = 0; + mSatiatedConsumptionLimit = 0; mLastReclamationTime = 0; } @@ -339,13 +387,14 @@ public class Scribe { final long endTime = parser.getAttributeLong(null, XML_ATTR_END_TIME); final int eventId = parser.getAttributeInt(null, XML_ATTR_EVENT_ID); final long delta = parser.getAttributeLong(null, XML_ATTR_DELTA); + final long ctp = parser.getAttributeLong(null, XML_ATTR_CTP); if (endTime <= endTimeCutoff) { if (DEBUG) { Slog.d(TAG, "Skipping event because it's too old."); } continue; } - transactions.add(new Ledger.Transaction(startTime, endTime, eventId, tag, delta)); + transactions.add(new Ledger.Transaction(startTime, endTime, eventId, tag, delta, ctp)); } if (!isInstalled) { @@ -395,7 +444,6 @@ public class Scribe { final Ledger ledger = ledgerData.second; if (ledger != null) { mLedgers.add(curUser, ledgerData.first, ledger); - mNarcsInCirculation += Math.max(0, ledger.getCurrentBalance()); final Ledger.Transaction transaction = ledger.getEarliestTransaction(); if (transaction != null) { earliestEndTime = Math.min(earliestEndTime, transaction.endTimeMs); @@ -442,6 +490,9 @@ public class Scribe { out.startTag(null, XML_TAG_HIGH_LEVEL_STATE); out.attributeLong(null, XML_ATTR_LAST_RECLAMATION_TIME, mLastReclamationTime); + out.attributeLong(null, XML_ATTR_CONSUMPTION_LIMIT, mSatiatedConsumptionLimit); + out.attributeLong(null, XML_ATTR_REMAINING_CONSUMABLE_NARCS, + mRemainingConsumableNarcs); out.endTag(null, XML_TAG_HIGH_LEVEL_STATE); for (int uIdx = mLedgers.numMaps() - 1; uIdx >= 0; --uIdx) { @@ -505,6 +556,7 @@ public class Scribe { out.attribute(null, XML_ATTR_TAG, transaction.tag); } out.attributeLong(null, XML_ATTR_DELTA, transaction.delta); + out.attributeLong(null, XML_ATTR_CTP, transaction.ctp); out.endTag(null, XML_TAG_TRANSACTION); } diff --git a/api/Android.bp b/api/Android.bp index 66c7823ed416..bbe26b73c721 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -113,7 +113,7 @@ combined_apis { "framework-appsearch", "framework-bluetooth", "framework-connectivity", - "framework-connectivity-tiramisu", + "framework-connectivity-t", "framework-graphics", "framework-media", "framework-mediaprovider", diff --git a/core/api/current.txt b/core/api/current.txt index 5b1f59a9e88f..3a6fc590648e 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -7800,7 +7800,6 @@ package android.app.admin { } public final class DevicePolicyResources { - ctor public DevicePolicyResources(); } public static final class DevicePolicyResources.Drawables { @@ -14391,6 +14390,8 @@ package android.graphics { method @NonNull @Size(min=3) public abstract float[] fromXyz(@NonNull @Size(min=3) float[]); method @NonNull public static android.graphics.ColorSpace get(@NonNull android.graphics.ColorSpace.Named); method @IntRange(from=1, to=4) public int getComponentCount(); + method public int getDataSpace(); + method @Nullable public static android.graphics.ColorSpace getFromDataSpace(int); method @IntRange(from=android.graphics.ColorSpace.MIN_ID, to=android.graphics.ColorSpace.MAX_ID) public int getId(); method public abstract float getMaxValue(@IntRange(from=0, to=3) int); method public abstract float getMinValue(@IntRange(from=0, to=3) int); @@ -16870,6 +16871,7 @@ package android.hardware { field public static final long USAGE_SENSOR_DIRECT_DATA = 8388608L; // 0x800000L field public static final long USAGE_VIDEO_ENCODE = 65536L; // 0x10000L field public static final int YCBCR_420_888 = 35; // 0x23 + field public static final int YCBCR_P010 = 54; // 0x36 } public final class Sensor { @@ -27445,7 +27447,6 @@ package android.nfc.cardemulation { field public static final String CATEGORY_PAYMENT = "payment"; field public static final String EXTRA_CATEGORY = "category"; field public static final String EXTRA_SERVICE_COMPONENT = "component"; - field public static final String EXTRA_USERID = "android.nfc.cardemulation.extra.USERID"; field public static final int SELECTION_MODE_ALWAYS_ASK = 1; // 0x1 field public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2; // 0x2 field public static final int SELECTION_MODE_PREFER_DEFAULT = 0; // 0x0 @@ -30685,7 +30686,7 @@ package android.os { public class BaseBundle { method public void clear(); method public boolean containsKey(String); - method @Nullable public Object get(String); + method @Deprecated @Nullable public Object get(String); method public boolean getBoolean(String); method public boolean getBoolean(String, boolean); method @Nullable public boolean[] getBooleanArray(@Nullable String); @@ -30927,16 +30928,21 @@ package android.os { method public float getFloat(String, float); method @Nullable public float[] getFloatArray(@Nullable String); method @Nullable public java.util.ArrayList<java.lang.Integer> getIntegerArrayList(@Nullable String); - method @Nullable public <T extends android.os.Parcelable> T getParcelable(@Nullable String); - method @Nullable public android.os.Parcelable[] getParcelableArray(@Nullable String); - method @Nullable public <T extends android.os.Parcelable> java.util.ArrayList<T> getParcelableArrayList(@Nullable String); - method @Nullable public java.io.Serializable getSerializable(@Nullable String); + method @Deprecated @Nullable public <T extends android.os.Parcelable> T getParcelable(@Nullable String); + method @Nullable public <T> T getParcelable(@Nullable String, @NonNull Class<T>); + method @Deprecated @Nullable public android.os.Parcelable[] getParcelableArray(@Nullable String); + method @Nullable public <T> T[] getParcelableArray(@Nullable String, @NonNull Class<T>); + method @Deprecated @Nullable public <T extends android.os.Parcelable> java.util.ArrayList<T> getParcelableArrayList(@Nullable String); + method @Nullable public <T> java.util.ArrayList<T> getParcelableArrayList(@Nullable String, @NonNull Class<T>); + method @Deprecated @Nullable public java.io.Serializable getSerializable(@Nullable String); + method @Nullable public <T extends java.io.Serializable> T getSerializable(@Nullable String, @NonNull Class<T>); method public short getShort(String); method public short getShort(String, short); method @Nullable public short[] getShortArray(@Nullable String); method @Nullable public android.util.Size getSize(@Nullable String); method @Nullable public android.util.SizeF getSizeF(@Nullable String); - method @Nullable public <T extends android.os.Parcelable> android.util.SparseArray<T> getSparseParcelableArray(@Nullable String); + method @Deprecated @Nullable public <T extends android.os.Parcelable> android.util.SparseArray<T> getSparseParcelableArray(@Nullable String); + method @Nullable public <T> android.util.SparseArray<T> getSparseParcelableArray(@Nullable String, @NonNull Class<T>); method @Nullable public java.util.ArrayList<java.lang.String> getStringArrayList(@Nullable String); method public boolean hasFileDescriptors(); method public void putAll(android.os.Bundle); @@ -49286,7 +49292,7 @@ package android.view { method @NonNull public android.view.SurfaceControl.Transaction setDataSpace(@NonNull android.view.SurfaceControl, int); method @NonNull public android.view.SurfaceControl.Transaction setFrameRate(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0) float, int); method @NonNull public android.view.SurfaceControl.Transaction setFrameRate(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0) float, int, int); - method @NonNull public android.view.SurfaceControl.Transaction setGeometry(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, int); + method @Deprecated @NonNull public android.view.SurfaceControl.Transaction setGeometry(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, int); method @NonNull public android.view.SurfaceControl.Transaction setLayer(@NonNull android.view.SurfaceControl, @IntRange(from=java.lang.Integer.MIN_VALUE, to=java.lang.Integer.MAX_VALUE) int); method @NonNull public android.view.SurfaceControl.Transaction setOpaque(@NonNull android.view.SurfaceControl, boolean); method @NonNull public android.view.SurfaceControl.Transaction setPosition(@NonNull android.view.SurfaceControl, float, float); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index ed7fc66c20e7..2c0e6416b455 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -205,7 +205,6 @@ package android { field public static final String MODIFY_PARENTAL_CONTROLS = "android.permission.MODIFY_PARENTAL_CONTROLS"; field public static final String MODIFY_QUIET_MODE = "android.permission.MODIFY_QUIET_MODE"; field public static final String MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE = "android.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE"; - field public static final String MODIFY_TOUCH_MODE_STATE = "android.permission.MODIFY_TOUCH_MODE_STATE"; field public static final String MOVE_PACKAGE = "android.permission.MOVE_PACKAGE"; field public static final String NETWORK_AIRPLANE_MODE = "android.permission.NETWORK_AIRPLANE_MODE"; field public static final String NETWORK_CARRIER_PROVISIONING = "android.permission.NETWORK_CARRIER_PROVISIONING"; @@ -1076,7 +1075,7 @@ package android.app.admin { } public class DevicePolicyManager { - method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public int checkProvisioningPreCondition(@NonNull String, @NonNull String); + method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public int checkProvisioningPrecondition(@NonNull String, @NonNull String); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException; 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); @@ -1129,21 +1128,6 @@ package android.app.admin { field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER"; field @Deprecated public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE"; field @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP) public static final String ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER = "android.app.action.UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER"; - field public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; // 0x6 - field public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11; // 0xb - field public static final int CODE_DEVICE_ADMIN_NOT_SUPPORTED = 13; // 0xd - field public static final int CODE_HAS_DEVICE_OWNER = 1; // 0x1 - field public static final int CODE_HAS_PAIRED = 8; // 0x8 - field public static final int CODE_MANAGED_USERS_NOT_SUPPORTED = 9; // 0x9 - field public static final int CODE_NONSYSTEM_USER_EXISTS = 5; // 0x5 - field public static final int CODE_NOT_SYSTEM_USER = 7; // 0x7 - field public static final int CODE_OK = 0; // 0x0 - field public static final int CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS = 15; // 0xf - field public static final int CODE_SYSTEM_USER = 10; // 0xa - field public static final int CODE_UNKNOWN_ERROR = -1; // 0xffffffff - field public static final int CODE_USER_HAS_PROFILE_OWNER = 2; // 0x2 - field public static final int CODE_USER_NOT_RUNNING = 3; // 0x3 - field public static final int CODE_USER_SETUP_COMPLETED = 4; // 0x4 field public static final String EXTRA_FORCE_UPDATE_ROLE_HOLDER = "android.app.extra.FORCE_UPDATE_ROLE_HOLDER"; field public static final String EXTRA_LOST_MODE_LOCATION = "android.app.extra.LOST_MODE_LOCATION"; field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME"; @@ -1186,6 +1170,21 @@ package android.app.admin { field public static final int STATE_USER_SETUP_FINALIZED = 3; // 0x3 field public static final int STATE_USER_SETUP_INCOMPLETE = 1; // 0x1 field public static final int STATE_USER_UNMANAGED = 0; // 0x0 + field public static final int STATUS_ACCOUNTS_NOT_EMPTY = 6; // 0x6 + field public static final int STATUS_CANNOT_ADD_MANAGED_PROFILE = 11; // 0xb + field public static final int STATUS_DEVICE_ADMIN_NOT_SUPPORTED = 13; // 0xd + field public static final int STATUS_HAS_DEVICE_OWNER = 1; // 0x1 + field public static final int STATUS_HAS_PAIRED = 8; // 0x8 + field public static final int STATUS_MANAGED_USERS_NOT_SUPPORTED = 9; // 0x9 + field public static final int STATUS_NONSYSTEM_USER_EXISTS = 5; // 0x5 + field public static final int STATUS_NOT_SYSTEM_USER = 7; // 0x7 + field public static final int STATUS_OK = 0; // 0x0 + field public static final int STATUS_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS = 15; // 0xf + field public static final int STATUS_SYSTEM_USER = 10; // 0xa + field public static final int STATUS_UNKNOWN_ERROR = -1; // 0xffffffff + field public static final int STATUS_USER_HAS_PROFILE_OWNER = 2; // 0x2 + field public static final int STATUS_USER_NOT_RUNNING = 3; // 0x3 + field public static final int STATUS_USER_SETUP_COMPLETED = 4; // 0x4 } public static final class DevicePolicyResources.Strings { @@ -6789,7 +6788,8 @@ package android.media.tv { method @NonNull @RequiresPermission(android.Manifest.permission.TIS_EXTENSION_INTERFACE) public java.util.List<java.lang.String> getAvailableExtensionInterfaceNames(@NonNull String); method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public java.util.List<android.media.tv.TvStreamConfig> getAvailableTvStreamConfigList(String); method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public int getClientPid(@NonNull String); - method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public int getClientPriority(int, @Nullable String); + method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public int getClientPriority(int, @NonNull String); + method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public int getClientPriority(int); method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TUNED_INFO) public java.util.List<android.media.tv.TunedInfo> getCurrentTunedInfos(); method @NonNull @RequiresPermission("android.permission.DVB_DEVICE") public java.util.List<android.media.tv.DvbDeviceInfo> getDvbDeviceList(); method @Nullable @RequiresPermission(android.Manifest.permission.TIS_EXTENSION_INTERFACE) public android.os.IBinder getExtensionInterface(@NonNull String, @NonNull String); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 29b8248dc8f2..9757b2ff318f 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -525,7 +525,6 @@ package android.app.admin { 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 @Deprecated public static final int CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14; // 0xe field public static final int DEVICE_OWNER_TYPE_DEFAULT = 0; // 0x0 field public static final int DEVICE_OWNER_TYPE_FINANCED = 1; // 0x1 field public static final int OPERATION_CLEAR_APPLICATION_USER_DATA = 23; // 0x17 @@ -569,6 +568,7 @@ package android.app.admin { field public static final int OPERATION_SWITCH_USER = 2; // 0x2 field public static final int OPERATION_UNINSTALL_CA_CERT = 40; // 0x28 field public static final int OPERATION_WIPE_DATA = 8; // 0x8 + field @Deprecated public static final int STATUS_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14; // 0xe } public static final class SecurityLog.SecurityEvent implements android.os.Parcelable { diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 9a7093ec1806..753df3d66b3a 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2523,7 +2523,7 @@ public class DevicePolicyManager { public @interface UserProvisioningState {} /** - * Result code for {@link #checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPrecondition}. * * <p>Unknown error code returned for {@link #ACTION_PROVISION_MANAGED_DEVICE}, * {@link #ACTION_PROVISION_MANAGED_PROFILE} and {@link #ACTION_PROVISION_MANAGED_USER}. @@ -2531,10 +2531,10 @@ public class DevicePolicyManager { * @hide */ @SystemApi - public static final int CODE_UNKNOWN_ERROR = -1; + public static final int STATUS_UNKNOWN_ERROR = -1; /** - * Result code for {@link #checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPrecondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE}, * {@link #ACTION_PROVISION_MANAGED_PROFILE} and {@link #ACTION_PROVISION_MANAGED_USER} @@ -2543,10 +2543,10 @@ public class DevicePolicyManager { * @hide */ @SystemApi - public static final int CODE_OK = 0; + public static final int STATUS_OK = 0; /** - * Result code for {@link #checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPrecondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} when the device already has a * device owner. @@ -2554,10 +2554,10 @@ public class DevicePolicyManager { * @hide */ @SystemApi - public static final int CODE_HAS_DEVICE_OWNER = 1; + public static final int STATUS_HAS_DEVICE_OWNER = 1; /** - * Result code for {@link #checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPrecondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} when the user has a profile owner * and for {@link #ACTION_PROVISION_MANAGED_PROFILE} when the profile owner is already set. @@ -2565,20 +2565,20 @@ public class DevicePolicyManager { * @hide */ @SystemApi - public static final int CODE_USER_HAS_PROFILE_OWNER = 2; + public static final int STATUS_USER_HAS_PROFILE_OWNER = 2; /** - * Result code for {@link #checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPrecondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} when the user isn't running. * * @hide */ @SystemApi - public static final int CODE_USER_NOT_RUNNING = 3; + public static final int STATUS_USER_NOT_RUNNING = 3; /** - * Result code for {@link #checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPrecondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} if the device has already been * setup and for {@link #ACTION_PROVISION_MANAGED_USER} if the user has already been setup. @@ -2586,7 +2586,7 @@ public class DevicePolicyManager { * @hide */ @SystemApi - public static final int CODE_USER_SETUP_COMPLETED = 4; + public static final int STATUS_USER_SETUP_COMPLETED = 4; /** * Code used to indicate that the device also has a user other than the system user. @@ -2594,7 +2594,7 @@ public class DevicePolicyManager { * @hide */ @SystemApi - public static final int CODE_NONSYSTEM_USER_EXISTS = 5; + public static final int STATUS_NONSYSTEM_USER_EXISTS = 5; /** * Code used to indicate that device has an account that prevents provisioning. @@ -2602,20 +2602,20 @@ public class DevicePolicyManager { * @hide */ @SystemApi - public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; + public static final int STATUS_ACCOUNTS_NOT_EMPTY = 6; /** - * Result code for {@link #checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPrecondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} if the user is not a system user. * * @hide */ @SystemApi - public static final int CODE_NOT_SYSTEM_USER = 7; + public static final int STATUS_NOT_SYSTEM_USER = 7; /** - * Result code for {@link #checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPrecondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} and * {@link #ACTION_PROVISION_MANAGED_USER} when the device is a watch and is already paired. @@ -2623,10 +2623,10 @@ public class DevicePolicyManager { * @hide */ @SystemApi - public static final int CODE_HAS_PAIRED = 8; + public static final int STATUS_HAS_PAIRED = 8; /** - * Result code for {@link #checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPrecondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_PROFILE} and * {@link #ACTION_PROVISION_MANAGED_USER} on devices which do not support managed users. @@ -2635,10 +2635,10 @@ public class DevicePolicyManager { * @hide */ @SystemApi - public static final int CODE_MANAGED_USERS_NOT_SUPPORTED = 9; + public static final int STATUS_MANAGED_USERS_NOT_SUPPORTED = 9; /** - * Result code for {@link #checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPrecondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_USER} if the user is a system user and * for {@link #ACTION_PROVISION_MANAGED_DEVICE} on devices running headless system user mode @@ -2647,10 +2647,10 @@ public class DevicePolicyManager { * @hide */ @SystemApi - public static final int CODE_SYSTEM_USER = 10; + public static final int STATUS_SYSTEM_USER = 10; /** - * Result code for {@link #checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPrecondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_PROFILE} when the user cannot have more * managed profiles. @@ -2658,19 +2658,10 @@ public class DevicePolicyManager { * @hide */ @SystemApi - public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11; + public static final int STATUS_CANNOT_ADD_MANAGED_PROFILE = 11; /** - * TODO (b/137101239): clean up split system user codes - * - * @hide - * @deprecated not used anymore but can't be removed since it's a @TestApi. - **/ - @Deprecated - public static final int CODE_NOT_SYSTEM_USER_SPLIT = 12; - - /** - * Result code for {@link #checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPrecondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE}, * {@link #ACTION_PROVISION_MANAGED_PROFILE} on devices which do not support device @@ -2679,21 +2670,21 @@ public class DevicePolicyManager { * @hide */ @SystemApi - public static final int CODE_DEVICE_ADMIN_NOT_SUPPORTED = 13; + public static final int STATUS_DEVICE_ADMIN_NOT_SUPPORTED = 13; /** * TODO (b/137101239): clean up split system user codes - * Result code for {@link #checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPrecondition}. * * @hide * @deprecated not used anymore but can't be removed since it's a @TestApi. */ @Deprecated @TestApi - public static final int CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14; + public static final int STATUS_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14; /** - * Result code for {@link #checkProvisioningPreCondition}. + * Result code for {@link #checkProvisioningPrecondition}. * * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} and * {@link #ACTION_PROVISION_MANAGED_PROFILE} on devices which do not support provisioning. @@ -2701,24 +2692,24 @@ public class DevicePolicyManager { * @hide */ @SystemApi - public static final int CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS = 15; + public static final int STATUS_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS = 15; /** - * Result codes for {@link #checkProvisioningPreCondition} indicating all the provisioning pre + * Result codes for {@link #checkProvisioningPrecondition} indicating all the provisioning pre * conditions. * * @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = { "CODE_" }, value = { - CODE_UNKNOWN_ERROR, CODE_OK, CODE_HAS_DEVICE_OWNER, CODE_USER_HAS_PROFILE_OWNER, - CODE_USER_NOT_RUNNING, CODE_USER_SETUP_COMPLETED, CODE_NOT_SYSTEM_USER, CODE_HAS_PAIRED, - CODE_MANAGED_USERS_NOT_SUPPORTED, CODE_SYSTEM_USER, CODE_CANNOT_ADD_MANAGED_PROFILE, - CODE_NOT_SYSTEM_USER_SPLIT, CODE_DEVICE_ADMIN_NOT_SUPPORTED, - CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER, - CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS + @IntDef(prefix = { "STATUS_" }, value = { + STATUS_UNKNOWN_ERROR, STATUS_OK, STATUS_HAS_DEVICE_OWNER, STATUS_USER_HAS_PROFILE_OWNER, + STATUS_USER_NOT_RUNNING, STATUS_USER_SETUP_COMPLETED, STATUS_NOT_SYSTEM_USER, + STATUS_HAS_PAIRED, STATUS_MANAGED_USERS_NOT_SUPPORTED, STATUS_SYSTEM_USER, + STATUS_CANNOT_ADD_MANAGED_PROFILE, STATUS_DEVICE_ADMIN_NOT_SUPPORTED, + STATUS_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER, + STATUS_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS }) - public @interface ProvisioningPreCondition {} + public @interface ProvisioningPrecondition {} /** * Disable all configurable SystemUI features during LockTask mode. This includes, @@ -11996,15 +11987,16 @@ public class DevicePolicyManager { * {@link #ACTION_PROVISION_MANAGED_PROFILE} * @param packageName The package of the component that would be set as device, user, or profile * owner. - * @return A {@link ProvisioningPreCondition} value indicating whether provisioning is allowed. + * @return An int constant value indicating whether provisioning is allowed. * @hide */ @SystemApi @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) - public @ProvisioningPreCondition int checkProvisioningPreCondition( + @ProvisioningPrecondition + public int checkProvisioningPrecondition( @NonNull String action, @NonNull String packageName) { try { - return mService.checkProvisioningPreCondition(action, packageName); + return mService.checkProvisioningPrecondition(action, packageName); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -14689,7 +14681,7 @@ public class DevicePolicyManager { * {@link ManagedProfileProvisioningParams#getProfileAdminComponentName()} as the profile * owner. * - * <p>The method {@link #checkProvisioningPreCondition} must be returning {@link #CODE_OK} + * <p>The method {@link #checkProvisioningPrecondition} must be returning {@link #STATUS_OK} * before calling this method. * * @param provisioningParams Params required to provision a managed profile, @@ -14733,7 +14725,7 @@ public class DevicePolicyManager { * Provisions a managed device and sets the {@code deviceAdminComponentName} as the device * owner. * - * <p>The method {@link #checkProvisioningPreCondition} must be returning {@link #CODE_OK} + * <p>The method {@link #checkProvisioningPrecondition} must be returning {@link #STATUS_OK} * before calling this method. * * @param provisioningParams Params required to provision a fully managed device, diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java index 7f2e5fde70f1..042e407becb4 100644 --- a/core/java/android/app/admin/DevicePolicyResources.java +++ b/core/java/android/app/admin/DevicePolicyResources.java @@ -317,6 +317,8 @@ import java.util.Set; */ public final class DevicePolicyResources { + private DevicePolicyResources() {} + /** * Resource identifiers used to update device management-related system drawable resources. * diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 0b9d51f0bdda..9d28ddefda7b 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -373,7 +373,7 @@ interface IDevicePolicyManager { String permission, int grantState, in RemoteCallback resultReceiver); int getPermissionGrantState(in ComponentName admin, in String callerPackage, String packageName, String permission); boolean isProvisioningAllowed(String action, String packageName); - int checkProvisioningPreCondition(String action, String packageName); + int checkProvisioningPrecondition(String action, String packageName); void setKeepUninstalledPackages(in ComponentName admin, in String callerPackage, in List<String> packageList); List<String> getKeepUninstalledPackages(in ComponentName admin, in String callerPackage); boolean isManagedProfile(in ComponentName admin); diff --git a/core/java/android/app/admin/ProvisioningException.java b/core/java/android/app/admin/ProvisioningException.java index 57a2c50165a0..a457b5fa3937 100644 --- a/core/java/android/app/admin/ProvisioningException.java +++ b/core/java/android/app/admin/ProvisioningException.java @@ -46,7 +46,7 @@ public class ProvisioningException extends AndroidException { /** * Service-specific error code for {@link DevicePolicyManager#provisionFullyManagedDevice} and * {@link DevicePolicyManager#createAndProvisionManagedProfile}: - * Indicates the call to {@link DevicePolicyManager#checkProvisioningPreCondition} returned an + * Indicates the call to {@link DevicePolicyManager#checkProvisioningPrecondition} returned an * error code. */ public static final int ERROR_PRE_CONDITION_FAILED = 1; diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java index ac1bcf3186be..1e4c95013205 100644 --- a/core/java/android/hardware/HardwareBuffer.java +++ b/core/java/android/hardware/HardwareBuffer.java @@ -63,6 +63,7 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable { D_FP32, DS_FP32UI8, S_UI8, + YCBCR_P010, }) public @interface Format { } @@ -96,6 +97,14 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable { public static final int DS_FP32UI8 = 0x34; /** Format: 8 bits stencil */ public static final int S_UI8 = 0x35; + /** + * <p>Android YUV P010 format.</p> + * + * P010 is a 4:2:0 YCbCr semiplanar format comprised of a WxH Y plane + * followed by a Wx(H/2) CbCr plane. Each sample is represented by a 16-bit + * little-endian value, with the lower 6 bits set to zero. + */ + public static final int YCBCR_P010 = 0x36; // Note: do not rename, this field is used by native code @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @@ -422,6 +431,7 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable { case D_FP32: case DS_FP32UI8: case S_UI8: + case YCBCR_P010: return true; } return false; diff --git a/core/java/android/hardware/SyncFence.java b/core/java/android/hardware/SyncFence.java index 99791ce3d8ec..cd4bf78f0879 100644 --- a/core/java/android/hardware/SyncFence.java +++ b/core/java/android/hardware/SyncFence.java @@ -17,6 +17,8 @@ package android.hardware; import android.annotation.NonNull; +import android.media.Image; +import android.media.ImageWriter; import android.opengl.EGLDisplay; import android.opengl.EGLSync; import android.os.Parcel; @@ -30,14 +32,29 @@ import java.io.IOException; import java.time.Duration; /** - * A SyncFence represents a synchronization primitive which signals when hardware buffers have - * completed work on a particular resource. + * A SyncFence represents a synchronization primitive which signals when hardware units have + * completed work on a particular resource. They initially start in an unsignaled state and make + * a one-time transition to either a signaled or error state. SyncFences are created by various + * device APIs in response to submitting tasks to the device. They cannot be created nor signaled + * by userspace. As a result, this means that a SyncFence will make always make forward progress. + * + * <p>SyncFence's generally come in one of two varieties. "Presentation fences" refer to + * a SyncFence when the writing to a buffer has finished. "Release fences" then refer + * to when the reading from a buffer has finished.</p> * * <p>For example, a GPU rendering to a framebuffer may generate a synchronization fence, - * e.g., a VkFence, which signals when rendering has completed. + * e.g., an EGLSync or VkFence, which signals when rendering has completed. Once the fence signals, + * then the backing storage for the framebuffer may be safely read from, such as for display or + * for media encoding. This would be referred to as a "presentation fence."</p> * - * Once the fence signals, then the backing storage for the framebuffer may be safely read from, - * such as for display or for media encoding.</p> + * <p>Similarly when using an {@link android.media.ImageWriter} it is possible that an + * {@link android.media.Image} returned by {@link ImageWriter#dequeueInputImage()} may already + * have a {@link Image#getFence() fence} set on it. This would be what is referred to as either + * a "release fence" or an "acqurie fence" and indicates the fence that the writer must wait + * on before writing to the underlying buffer. In the case of ImageWriter this is done + * automatically when eg {@link Image#getPlanes()} is called, however when using + * {@link Image#getHardwareBuffer()} it is the caller's responsibility to ensure the + * release fence has signaled before writing to the buffer.</p> * * @see android.opengl.EGLExt#eglDupNativeFenceFDANDROID(EGLDisplay, EGLSync) * @see android.media.Image#getFence() diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index 2fd79cf980c7..c38a847dfb9f 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -1079,16 +1079,24 @@ public final class InputManager { } /** - * Gets the key code produced by the specified location on a US keyboard layout. - * Key code as defined in {@link android.view.KeyEvent}. - * This API is only functional for devices with {@link InputDevice#SOURCE_KEYBOARD} available - * which can alter their key mapping using country specific keyboard layouts. - * - * @param deviceId The input device id. - * @param locationKeyCode The location of a key on a US keyboard layout. - * @return The key code produced when pressing the key at the specified location, given the - * active keyboard layout. Returns {@link KeyEvent#KEYCODE_UNKNOWN} if the requested - * mapping could not be determined, or if an error occurred. + * Gets the {@link android.view.KeyEvent key code} produced by the given location on a reference + * QWERTY keyboard layout. + * <p> + * This API is useful for querying the physical location of keys that change the character + * produced based on the current locale and keyboard layout. + * <p> + * @see InputDevice#getKeyCodeForKeyLocation(int) for examples. + * + * @param locationKeyCode The location of a key specified as a key code on the QWERTY layout. + * This provides a consistent way of referring to the physical location of a key independently + * of the current keyboard layout. Also see the + * <a href="https://www.w3.org/TR/2017/CR-uievents-code-20170601/#key-alphanumeric-writing-system"> + * hypothetical keyboard</a> provided by the W3C, which may be helpful for identifying the + * physical location of a key. + * @return The key code produced by the key at the specified location, given the current + * keyboard layout. Returns {@link KeyEvent#KEYCODE_UNKNOWN} if the device does not specify + * {@link InputDevice#SOURCE_KEYBOARD} or the requested mapping cannot be determined. + * * @hide */ public int getKeyCodeForKeyLocation(int deviceId, int locationKeyCode) { diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java index 0a9fe90f2524..9a780c8a2935 100644 --- a/core/java/android/nfc/cardemulation/CardEmulation.java +++ b/core/java/android/nfc/cardemulation/CardEmulation.java @@ -84,13 +84,6 @@ public final class CardEmulation { public static final String EXTRA_SERVICE_COMPONENT = "component"; /** - * The caller userId extra for {@link #ACTION_CHANGE_DEFAULT}. - * - * @see #ACTION_CHANGE_DEFAULT - */ - public static final String EXTRA_USERID = "android.nfc.cardemulation.extra.USERID"; - - /** * Category used for NFC payment services. */ public static final String CATEGORY_PAYMENT = "payment"; diff --git a/core/java/android/os/BadTypeParcelableException.java b/core/java/android/os/BadTypeParcelableException.java new file mode 100644 index 000000000000..2ca3bd2adca1 --- /dev/null +++ b/core/java/android/os/BadTypeParcelableException.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 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.os; + +/** Used by Parcel to signal that the type on the payload was not expected by the caller. */ +class BadTypeParcelableException extends BadParcelableException { + BadTypeParcelableException(String msg) { + super(msg); + } + BadTypeParcelableException(Exception cause) { + super(cause); + } + BadTypeParcelableException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java index 244335d3aa06..45812e551618 100644 --- a/core/java/android/os/BaseBundle.java +++ b/core/java/android/os/BaseBundle.java @@ -16,6 +16,8 @@ package android.os; +import static java.util.Objects.requireNonNull; + import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; @@ -31,7 +33,7 @@ import com.android.internal.util.IndentingPrintWriter; import java.io.Serializable; import java.util.ArrayList; import java.util.Set; -import java.util.function.Function; +import java.util.function.BiFunction; /** * A mapping from String keys to values of various types. In most cases, you @@ -254,8 +256,8 @@ public class BaseBundle { } try { return getValueAt(0, String.class); - } catch (ClassCastException | BadParcelableException e) { - typeWarning("getPairValue()", /* value */ null, "String", e); + } catch (ClassCastException | BadTypeParcelableException e) { + typeWarning("getPairValue()", "String", e); return null; } } @@ -320,28 +322,46 @@ public class BaseBundle { * This call should always be made after {@link #unparcel()} or inside a lock after making sure * {@code mMap} is not null. * + * @deprecated Use {@link #getValue(String, Class, Class[])}. This method should only be used in + * other deprecated APIs. + * * @hide */ + @Deprecated + @Nullable final Object getValue(String key) { return getValue(key, /* clazz */ null); } + /** Same as {@link #getValue(String, Class, Class[])} with no item types. */ + @Nullable + final <T> T getValue(String key, @Nullable Class<T> clazz) { + // Avoids allocating Class[0] array + return getValue(key, clazz, (Class<?>[]) null); + } + /** - * Returns the value for key {@code key} for expected return type {@param clazz} (or {@code + * Returns the value for key {@code key} for expected return type {@code clazz} (or pass {@code * null} for no type check). * + * For {@code itemTypes}, see {@link Parcel#readValue(int, ClassLoader, Class, Class[])}. + * * This call should always be made after {@link #unparcel()} or inside a lock after making sure * {@code mMap} is not null. * * @hide */ - final <T> T getValue(String key, @Nullable Class<T> clazz) { + @Nullable + final <T> T getValue(String key, @Nullable Class<T> clazz, @Nullable Class<?>... itemTypes) { int i = mMap.indexOfKey(key); - return (i >= 0) ? getValueAt(i, clazz) : null; + return (i >= 0) ? getValueAt(i, clazz, itemTypes) : null; } /** - * Returns the value for a certain position in the array map. + * Returns the value for a certain position in the array map for expected return type {@code + * clazz} (or pass {@code null} for no type check). + * + * For {@code itemTypes}, see {@link Parcel#readValue(int, ClassLoader, Class, Class[])}. * * This call should always be made after {@link #unparcel()} or inside a lock after making sure * {@code mMap} is not null. @@ -349,11 +369,12 @@ public class BaseBundle { * @hide */ @SuppressWarnings("unchecked") - final <T> T getValueAt(int i, @Nullable Class<T> clazz) { + @Nullable + final <T> T getValueAt(int i, @Nullable Class<T> clazz, @Nullable Class<?>... itemTypes) { Object object = mMap.valueAt(i); - if (object instanceof Function<?, ?>) { + if (object instanceof BiFunction<?, ?, ?>) { try { - object = ((Function<Class<?>, ?>) object).apply(clazz); + object = ((BiFunction<Class<?>, Class<?>[], ?>) object).apply(clazz, itemTypes); } catch (BadParcelableException e) { if (sShouldDefuse) { Log.w(TAG, "Failed to parse item " + mMap.keyAt(i) + ", returning null.", e); @@ -615,7 +636,11 @@ public class BaseBundle { * * @param key a String key * @return an Object, or null + * + * @deprecated Use the type-safe specific APIs depending on the type of the item to be + * retrieved, eg. {@link #getString(String)}. */ + @Deprecated @Nullable public Object get(String key) { unparcel(); @@ -623,6 +648,32 @@ public class BaseBundle { } /** + * Returns the object of type {@code clazz} for the given {@code key}, or {@code null} if: + * <ul> + * <li>No mapping of the desired type exists for the given key. + * <li>A {@code null} value is explicitly associated with the key. + * <li>The object is not of type {@code clazz}. + * </ul> + * + * <p>Use the more specific APIs where possible, especially in the case of containers such as + * lists, since those APIs allow you to specify the type of the items. + * + * @param key String key + * @param clazz The type of the object expected + * @return an Object, or null + */ + @Nullable + <T> T get(@Nullable String key, @NonNull Class<T> clazz) { + unparcel(); + try { + return getValue(key, requireNonNull(clazz)); + } catch (ClassCastException | BadTypeParcelableException e) { + typeWarning(key, clazz.getCanonicalName(), e); + return null; + } + } + + /** * Removes any entry with the given key from the mapping of this Bundle. * * @param key a String key @@ -1006,7 +1057,7 @@ public class BaseBundle { sb.append(" but value was a "); sb.append(value.getClass().getName()); } else { - sb.append(" but value was of a different type "); + sb.append(" but value was of a different type"); } sb.append(". The default value "); sb.append(defaultValue); @@ -1019,6 +1070,10 @@ public class BaseBundle { typeWarning(key, value, className, "<null>", e); } + void typeWarning(String key, String className, RuntimeException e) { + typeWarning(key, /* value */ null, className, "<null>", e); + } + /** * Returns the value associated with the given key, or defaultValue if * no mapping of the desired type exists for the given key. @@ -1358,7 +1413,11 @@ public class BaseBundle { * * @param key a String, or null * @return a Serializable value, or null + * + * @deprecated Use {@link #getSerializable(String, Class)}. This method should only be used in + * other deprecated APIs. */ + @Deprecated @Nullable Serializable getSerializable(@Nullable String key) { unparcel(); @@ -1375,6 +1434,36 @@ public class BaseBundle { } /** + * Returns the value associated with the given key, or {@code null} if: + * <ul> + * <li>No mapping of the desired type exists for the given key. + * <li>A {@code null} value is explicitly associated with the key. + * <li>The object is not of type {@code clazz}. + * </ul> + * + * @param key a String, or null + * @param clazz The expected class of the returned type + * @return a Serializable value, or null + */ + @Nullable + <T extends Serializable> T getSerializable(@Nullable String key, @NonNull Class<T> clazz) { + return get(key, clazz); + } + + + @SuppressWarnings("unchecked") + @Nullable + <T> ArrayList<T> getArrayList(@Nullable String key, @NonNull Class<T> clazz) { + unparcel(); + try { + return getValue(key, ArrayList.class, requireNonNull(clazz)); + } catch (ClassCastException | BadTypeParcelableException e) { + typeWarning(key, "ArrayList<" + clazz.getCanonicalName() + ">", e); + return null; + } + } + + /** * Returns the value associated with the given key, or null if * no mapping of the desired type exists for the given key or a null * value is explicitly associated with the key. @@ -1384,17 +1473,7 @@ public class BaseBundle { */ @Nullable ArrayList<Integer> getIntegerArrayList(@Nullable String key) { - unparcel(); - Object o = getValue(key); - if (o == null) { - return null; - } - try { - return (ArrayList<Integer>) o; - } catch (ClassCastException e) { - typeWarning(key, o, "ArrayList<Integer>", e); - return null; - } + return getArrayList(key, Integer.class); } /** @@ -1407,17 +1486,7 @@ public class BaseBundle { */ @Nullable ArrayList<String> getStringArrayList(@Nullable String key) { - unparcel(); - Object o = getValue(key); - if (o == null) { - return null; - } - try { - return (ArrayList<String>) o; - } catch (ClassCastException e) { - typeWarning(key, o, "ArrayList<String>", e); - return null; - } + return getArrayList(key, String.class); } /** @@ -1430,17 +1499,7 @@ public class BaseBundle { */ @Nullable ArrayList<CharSequence> getCharSequenceArrayList(@Nullable String key) { - unparcel(); - Object o = getValue(key); - if (o == null) { - return null; - } - try { - return (ArrayList<CharSequence>) o; - } catch (ClassCastException e) { - typeWarning(key, o, "ArrayList<CharSequence>", e); - return null; - } + return getArrayList(key, CharSequence.class); } /** diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java index 2b13f20ffe6d..edbbb59ac77d 100644 --- a/core/java/android/os/Bundle.java +++ b/core/java/android/os/Bundle.java @@ -20,6 +20,7 @@ import static java.util.Objects.requireNonNull; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.compat.annotation.UnsupportedAppUsage; import android.util.ArrayMap; import android.util.Size; @@ -876,7 +877,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { @Nullable public Bundle getBundle(@Nullable String key) { unparcel(); - Object o = getValue(key); + Object o = mMap.get(key); if (o == null) { return null; } @@ -899,7 +900,11 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { * * @param key a String, or {@code null} * @return a Parcelable value, or {@code null} + * + * @deprecated Use the type-safer {@link #getParcelable(String, Class)} starting from Android + * {@link Build.VERSION_CODES#TIRAMISU}. */ + @Deprecated @Nullable public <T extends Parcelable> T getParcelable(@Nullable String key) { unparcel(); @@ -916,30 +921,28 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { } /** - * Returns the value associated with the given key, or {@code null} if - * no mapping of the desired type exists for the given key or a {@code null} - * value is explicitly associated with the key. + * Returns the value associated with the given key or {@code null} if: + * <ul> + * <li>No mapping of the desired type exists for the given key. + * <li>A {@code null} value is explicitly associated with the key. + * <li>The object is not of type {@code clazz}. + * </ul> * * <p><b>Note: </b> if the expected value is not a class provided by the Android platform, * you must call {@link #setClassLoader(ClassLoader)} with the proper {@link ClassLoader} first. * Otherwise, this method might throw an exception or return {@code null}. * * @param key a String, or {@code null} - * @param clazz The type of the object expected or {@code null} for performing no checks. + * @param clazz The type of the object expected * @return a Parcelable value, or {@code null} - * - * @hide */ @SuppressWarnings("unchecked") @Nullable public <T> T getParcelable(@Nullable String key, @NonNull Class<T> clazz) { - unparcel(); - try { - return getValue(key, requireNonNull(clazz)); - } catch (ClassCastException | BadParcelableException e) { - typeWarning(key, /* value */ null, "Parcelable", e); - return null; - } + // The reason for not using <T extends Parcelable> is because the caller could provide a + // super class to restrict the children that doesn't implement Parcelable itself while the + // children do, more details at b/210800751 (same reasoning applies here). + return get(key, clazz); } /** @@ -953,7 +956,11 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { * * @param key a String, or {@code null} * @return a Parcelable[] value, or {@code null} + * + * @deprecated Use the type-safer {@link #getParcelableArray(String, Class)} starting from + * Android {@link Build.VERSION_CODES#TIRAMISU}. */ + @Deprecated @Nullable public Parcelable[] getParcelableArray(@Nullable String key) { unparcel(); @@ -970,6 +977,39 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { } /** + * Returns the value associated with the given key, or {@code null} if: + * <ul> + * <li>No mapping of the desired type exists for the given key. + * <li>A {@code null} value is explicitly associated with the key. + * <li>The object is not of type {@code clazz}. + * </ul> + * + * <p><b>Note: </b> if the expected value is not a class provided by the Android platform, + * you must call {@link #setClassLoader(ClassLoader)} with the proper {@link ClassLoader} first. + * Otherwise, this method might throw an exception or return {@code null}. + * + * @param key a String, or {@code null} + * @param clazz The type of the items inside the array + * @return a Parcelable[] value, or {@code null} + */ + @SuppressLint({"ArrayReturn", "NullableCollection"}) + @SuppressWarnings("unchecked") + @Nullable + public <T> T[] getParcelableArray(@Nullable String key, @NonNull Class<T> clazz) { + // The reason for not using <T extends Parcelable> is because the caller could provide a + // super class to restrict the children that doesn't implement Parcelable itself while the + // children do, more details at b/210800751 (same reasoning applies here). + unparcel(); + try { + // In Java 12, we can pass clazz.arrayType() instead of Parcelable[] and later casting. + return (T[]) getValue(key, Parcelable[].class, requireNonNull(clazz)); + } catch (ClassCastException | BadTypeParcelableException e) { + typeWarning(key, clazz.getCanonicalName() + "[]", e); + return null; + } + } + + /** * Returns the value associated with the given key, or {@code null} if * no mapping of the desired type exists for the given key or a {@code null} * value is explicitly associated with the key. @@ -980,7 +1020,11 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { * * @param key a String, or {@code null} * @return an ArrayList<T> value, or {@code null} + * + * @deprecated Use the type-safer {@link #getParcelable(String, Class)} starting from Android + * {@link Build.VERSION_CODES#TIRAMISU}. */ + @Deprecated @Nullable public <T extends Parcelable> ArrayList<T> getParcelableArrayList(@Nullable String key) { unparcel(); @@ -997,14 +1041,43 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { } /** + * Returns the value associated with the given key, or {@code null} if: + * <ul> + * <li>No mapping of the desired type exists for the given key. + * <li>A {@code null} value is explicitly associated with the key. + * <li>The object is not of type {@code clazz}. + * </ul> + * + * <p><b>Note: </b> if the expected value is not a class provided by the Android platform, + * you must call {@link #setClassLoader(ClassLoader)} with the proper {@link ClassLoader} first. + * Otherwise, this method might throw an exception or return {@code null}. + * + * @param key a String, or {@code null} + * @param clazz The type of the items inside the array list + * @return an ArrayList<T> value, or {@code null} + */ + @SuppressLint("NullableCollection") + @SuppressWarnings("unchecked") + @Nullable + public <T> ArrayList<T> getParcelableArrayList(@Nullable String key, @NonNull Class<T> clazz) { + // The reason for not using <T extends Parcelable> is because the caller could provide a + // super class to restrict the children that doesn't implement Parcelable itself while the + // children do, more details at b/210800751 (same reasoning applies here). + return getArrayList(key, clazz); + } + + /** * Returns the value associated with the given key, or null if * no mapping of the desired type exists for the given key or a null * value is explicitly associated with the key. * * @param key a String, or null - * * @return a SparseArray of T values, or null + * + * @deprecated Use the type-safer {@link #getSparseParcelableArray(String, Class)} starting from + * Android {@link Build.VERSION_CODES#TIRAMISU}. */ + @Deprecated @Nullable public <T extends Parcelable> SparseArray<T> getSparseParcelableArray(@Nullable String key) { unparcel(); @@ -1021,13 +1094,44 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { } /** + * Returns the value associated with the given key, or {@code null} if: + * <ul> + * <li>No mapping of the desired type exists for the given key. + * <li>A {@code null} value is explicitly associated with the key. + * <li>The object is not of type {@code clazz}. + * </ul> + * + * @param key a String, or null + * @return a SparseArray of T values, or null + */ + @SuppressWarnings("unchecked") + @Nullable + public <T> SparseArray<T> getSparseParcelableArray(@Nullable String key, + @NonNull Class<T> clazz) { + // The reason for not using <T extends Parcelable> is because the caller could provide a + // super class to restrict the children that doesn't implement Parcelable itself while the + // children do, more details at b/210800751 (same reasoning applies here). + unparcel(); + try { + return (SparseArray<T>) getValue(key, SparseArray.class, requireNonNull(clazz)); + } catch (ClassCastException | BadTypeParcelableException e) { + typeWarning(key, "SparseArray<" + clazz.getCanonicalName() + ">", e); + return null; + } + } + + /** * Returns the value associated with the given key, or null if * no mapping of the desired type exists for the given key or a null * value is explicitly associated with the key. * * @param key a String, or null * @return a Serializable value, or null + * + * @deprecated Use the type-safer {@link #getSerializable(String, Class)} starting from Android + * {@link Build.VERSION_CODES#TIRAMISU}. */ + @Deprecated @Override @Nullable public Serializable getSerializable(@Nullable String key) { @@ -1035,6 +1139,24 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { } /** + * Returns the value associated with the given key, or {@code null} if: + * <ul> + * <li>No mapping of the desired type exists for the given key. + * <li>A {@code null} value is explicitly associated with the key. + * <li>The object is not of type {@code clazz}. + * </ul> + * + * @param key a String, or null + * @param clazz The expected class of the returned type + * @return a Serializable value, or null + */ + @Nullable + public <T extends Serializable> T getSerializable(@Nullable String key, + @NonNull Class<T> clazz) { + return super.getSerializable(key, requireNonNull(clazz)); + } + + /** * Returns the value associated with the given key, or null if * no mapping of the desired type exists for the given key or a null * value is explicitly associated with the key. diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index ae923530b651..09cfb6e70cba 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -16,6 +16,8 @@ package android.os; +import static com.android.internal.util.Preconditions.checkArgument; + import static java.util.Objects.requireNonNull; import android.annotation.IntDef; @@ -65,6 +67,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.IntFunction; import java.util.function.Supplier; @@ -287,26 +290,26 @@ public final class Parcel { private static final int VAL_NULL = -1; private static final int VAL_STRING = 0; private static final int VAL_INTEGER = 1; - private static final int VAL_MAP = 2; + private static final int VAL_MAP = 2; // length-prefixed private static final int VAL_BUNDLE = 3; - private static final int VAL_PARCELABLE = 4; + private static final int VAL_PARCELABLE = 4; // length-prefixed private static final int VAL_SHORT = 5; private static final int VAL_LONG = 6; private static final int VAL_FLOAT = 7; private static final int VAL_DOUBLE = 8; private static final int VAL_BOOLEAN = 9; private static final int VAL_CHARSEQUENCE = 10; - private static final int VAL_LIST = 11; - private static final int VAL_SPARSEARRAY = 12; + private static final int VAL_LIST = 11; // length-prefixed + private static final int VAL_SPARSEARRAY = 12; // length-prefixed private static final int VAL_BYTEARRAY = 13; private static final int VAL_STRINGARRAY = 14; private static final int VAL_IBINDER = 15; - private static final int VAL_PARCELABLEARRAY = 16; - private static final int VAL_OBJECTARRAY = 17; + private static final int VAL_PARCELABLEARRAY = 16; // length-prefixed + private static final int VAL_OBJECTARRAY = 17; // length-prefixed private static final int VAL_INTARRAY = 18; private static final int VAL_LONGARRAY = 19; private static final int VAL_BYTE = 20; - private static final int VAL_SERIALIZABLE = 21; + private static final int VAL_SERIALIZABLE = 21; // length-prefixed private static final int VAL_SPARSEBOOLEANARRAY = 22; private static final int VAL_BOOLEANARRAY = 23; private static final int VAL_CHARSEQUENCEARRAY = 24; @@ -3179,8 +3182,7 @@ public final class Parcel { */ @Deprecated public final void readMap(@NonNull Map outVal, @Nullable ClassLoader loader) { - int n = readInt(); - readMapInternal(outVal, n, loader, /* clazzKey */ null, /* clazzValue */ null); + readMapInternal(outVal, loader, /* clazzKey */ null, /* clazzValue */ null); } /** @@ -3195,8 +3197,7 @@ public final class Parcel { @NonNull Class<V> clazzValue) { Objects.requireNonNull(clazzKey); Objects.requireNonNull(clazzValue); - int n = readInt(); - readMapInternal(outVal, n, loader, clazzKey, clazzValue); + readMapInternal(outVal, loader, clazzKey, clazzValue); } /** @@ -3245,13 +3246,7 @@ public final class Parcel { @Deprecated @Nullable public HashMap readHashMap(@Nullable ClassLoader loader) { - int n = readInt(); - if (n < 0) { - return null; - } - HashMap m = new HashMap(n); - readMapInternal(m, n, loader, /* clazzKey */ null, /* clazzValue */ null); - return m; + return readHashMapInternal(loader, /* clazzKey */ null, /* clazzValue */ null); } /** @@ -3267,13 +3262,7 @@ public final class Parcel { @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; + return readHashMapInternal(loader, clazzKey, clazzValue); } /** @@ -4296,16 +4285,17 @@ public final class Parcel { /** - * @param clazz The type of the object expected or {@code null} for performing no checks. + * @see #readValue(int, ClassLoader, Class, Class[]) */ @Nullable - private <T> T readValue(@Nullable ClassLoader loader, @Nullable Class<T> clazz) { + private <T> T readValue(@Nullable ClassLoader loader, @Nullable Class<T> clazz, + @Nullable Class<?>... itemTypes) { int type = readInt(); final T object; if (isLengthPrefixed(type)) { int length = readInt(); int start = dataPosition(); - object = readValue(type, loader, clazz); + object = readValue(type, loader, clazz, itemTypes); int actual = dataPosition() - start; if (actual != length) { Slog.wtfStack(TAG, @@ -4313,25 +4303,26 @@ public final class Parcel { + " consumed " + actual + " bytes, but " + length + " expected."); } } else { - object = readValue(type, loader, clazz); + object = readValue(type, loader, clazz, itemTypes); } return object; } /** - * This will return a {@link Function} for length-prefixed types that deserializes the object - * when {@link Function#apply} is called with the expected class of the return object (or {@code - * null} for no type check), for other types it will return the object itself. + * This will return a {@link BiFunction} for length-prefixed types that deserializes the object + * when {@link BiFunction#apply} is called (the arguments correspond to the ones of {@link + * #readValue(int, ClassLoader, Class, Class[])} after the class loader), for other types it + * will return the object itself. * - * <p>After calling {@link Function#apply(Object)} the parcel cursor will not change. Note that - * you shouldn't recycle the parcel, not at least until all objects have been retrieved. No + * <p>After calling {@link BiFunction#apply} the parcel cursor will not change. Note that you + * shouldn't recycle the parcel, not at least until all objects have been retrieved. No * synchronization attempts are made. * * </p>The function returned implements {@link #equals(Object)} and {@link #hashCode()}. Two * function objects are equal if either of the following is true: * <ul> - * <li>{@link Function#apply} has been called on both and both objects returned are equal. - * <li>{@link Function#apply} hasn't been called on either one and everything below is true: + * <li>{@link BiFunction#apply} has been called on both and both objects returned are equal. + * <li>{@link BiFunction#apply} hasn't been called on either one and everything below is true: * <ul> * <li>The {@code loader} parameters used to retrieve each are equal. * <li>They both have the same type. @@ -4358,7 +4349,7 @@ public final class Parcel { } - private static final class LazyValue implements Function<Class<?>, Object> { + private static final class LazyValue implements BiFunction<Class<?>, Class<?>[], Object> { /** * | 4B | 4B | * mSource = Parcel{... | type | length | object | ...} @@ -4390,7 +4381,7 @@ public final class Parcel { } @Override - public Object apply(@Nullable Class<?> clazz) { + public Object apply(@Nullable Class<?> clazz, @Nullable Class<?>[] itemTypes) { Parcel source = mSource; if (source != null) { synchronized (source) { @@ -4399,7 +4390,7 @@ public final class Parcel { int restore = source.dataPosition(); try { source.setDataPosition(mPosition); - mObject = source.readValue(mLoader, clazz); + mObject = source.readValue(mLoader, clazz, itemTypes); } finally { source.setDataPosition(restore); } @@ -4479,14 +4470,25 @@ public final class Parcel { } } + /** Same as {@link #readValue(ClassLoader, Class, Class[])} without any item types. */ + private <T> T readValue(int type, @Nullable ClassLoader loader, @Nullable Class<T> clazz) { + // Avoids allocating Class[0] array + return readValue(type, loader, clazz, (Class<?>[]) null); + } + /** * Reads a value from the parcel of type {@code type}. Does NOT read the int representing the * type first. + * * @param clazz The type of the object expected or {@code null} for performing no checks. + * @param itemTypes If the value is a container, these represent the item types (eg. for a list + * it's the item type, for a map, it's the key type, followed by the value + * type). */ @SuppressWarnings("unchecked") @Nullable - private <T> T readValue(int type, @Nullable ClassLoader loader, @Nullable Class<T> clazz) { + private <T> T readValue(int type, @Nullable ClassLoader loader, @Nullable Class<T> clazz, + @Nullable Class<?>... itemTypes) { final Object object; switch (type) { case VAL_NULL: @@ -4502,7 +4504,11 @@ public final class Parcel { break; case VAL_MAP: - object = readHashMap(loader); + checkTypeToUnparcel(clazz, HashMap.class); + Class<?> keyType = ArrayUtils.getOrNull(itemTypes, 0); + Class<?> valueType = ArrayUtils.getOrNull(itemTypes, 1); + checkArgument((keyType == null) == (valueType == null)); + object = readHashMapInternal(loader, keyType, valueType); break; case VAL_PARCELABLE: @@ -4533,10 +4539,12 @@ public final class Parcel { object = readCharSequence(); break; - case VAL_LIST: - object = readArrayList(loader); + case VAL_LIST: { + checkTypeToUnparcel(clazz, ArrayList.class); + Class<?> itemType = ArrayUtils.getOrNull(itemTypes, 0); + object = readArrayListInternal(loader, itemType); break; - + } case VAL_BOOLEANARRAY: object = createBooleanArray(); break; @@ -4557,10 +4565,12 @@ public final class Parcel { object = readStrongBinder(); break; - case VAL_OBJECTARRAY: - object = readArray(loader); + case VAL_OBJECTARRAY: { + Class<?> itemType = ArrayUtils.getOrNull(itemTypes, 0); + checkArrayTypeToUnparcel(clazz, (itemType != null) ? itemType : Object.class); + object = readArrayInternal(loader, itemType); break; - + } case VAL_INTARRAY: object = createIntArray(); break; @@ -4577,14 +4587,18 @@ public final class Parcel { object = readSerializableInternal(loader, clazz); break; - case VAL_PARCELABLEARRAY: - object = readParcelableArray(loader); + case VAL_PARCELABLEARRAY: { + Class<?> itemType = ArrayUtils.getOrNull(itemTypes, 0); + checkArrayTypeToUnparcel(clazz, (itemType != null) ? itemType : Parcelable.class); + object = readParcelableArrayInternal(loader, itemType); break; - - case VAL_SPARSEARRAY: - object = readSparseArray(loader); + } + case VAL_SPARSEARRAY: { + checkTypeToUnparcel(clazz, SparseArray.class); + Class<?> itemType = ArrayUtils.getOrNull(itemTypes, 0); + object = readSparseArrayInternal(loader, itemType); break; - + } case VAL_SPARSEBOOLEANARRAY: object = readSparseBooleanArray(); break; @@ -4632,7 +4646,7 @@ public final class Parcel { + " at offset " + off); } if (object != null && clazz != null && !clazz.isInstance(object)) { - throw new BadParcelableException("Unparcelled object " + object + throw new BadTypeParcelableException("Unparcelled object " + object + " is not an instance of required class " + clazz.getName() + " provided in the parameter"); } @@ -4659,6 +4673,38 @@ public final class Parcel { } /** + * Checks that an array of type T[], where T is {@code componentTypeToUnparcel}, is a subtype of + * {@code requiredArrayType}. + */ + private void checkArrayTypeToUnparcel(@Nullable Class<?> requiredArrayType, + Class<?> componentTypeToUnparcel) { + if (requiredArrayType != null) { + // In Java 12, we could use componentTypeToUnparcel.arrayType() for the check + Class<?> requiredComponentType = requiredArrayType.getComponentType(); + if (requiredComponentType == null) { + throw new BadTypeParcelableException( + "About to unparcel an array but type " + + requiredArrayType.getCanonicalName() + + " required by caller is not an array."); + } + checkTypeToUnparcel(requiredComponentType, componentTypeToUnparcel); + } + } + + /** + * Checks that {@code typeToUnparcel} is a subtype of {@code requiredType}, if {@code + * requiredType} is not {@code null}. + */ + private void checkTypeToUnparcel(@Nullable Class<?> requiredType, Class<?> typeToUnparcel) { + if (requiredType != null && !requiredType.isAssignableFrom(typeToUnparcel)) { + throw new BadTypeParcelableException( + "About to unparcel a " + typeToUnparcel.getCanonicalName() + + ", which is not a subtype of type " + requiredType.getCanonicalName() + + " required by caller."); + } + } + + /** * Read and return a new Parcelable from the parcel. The given class loader * will be used to load any enclosed Parcelables. If it is null, the default * class loader will be used. @@ -4788,7 +4834,7 @@ public final class Parcel { if (clazz != null) { Class<?> parcelableClass = creator.getClass().getEnclosingClass(); if (!clazz.isAssignableFrom(parcelableClass)) { - throw new BadParcelableException("Parcelable creator " + name + " is not " + throw new BadTypeParcelableException("Parcelable creator " + name + " is not " + "a subclass of required class " + clazz.getName() + " provided in the parameter"); } @@ -4811,7 +4857,7 @@ public final class Parcel { } if (clazz != null) { if (!clazz.isAssignableFrom(parcelableClass)) { - throw new BadParcelableException("Parcelable creator " + name + " is not " + throw new BadTypeParcelableException("Parcelable creator " + name + " is not " + "a subclass of required class " + clazz.getName() + " provided in the parameter"); } @@ -4872,15 +4918,7 @@ public final class Parcel { @Deprecated @Nullable public Parcelable[] readParcelableArray(@Nullable ClassLoader loader) { - int N = readInt(); - if (N < 0) { - return null; - } - Parcelable[] p = new Parcelable[N]; - for (int i = 0; i < N; i++) { - p[i] = readParcelable(loader); - } - return p; + return readParcelableArrayInternal(loader, /* clazz */ null); } /** @@ -4892,14 +4930,20 @@ public final class Parcel { * trying to instantiate an element. */ @SuppressLint({"ArrayReturn", "NullableCollection"}) - @SuppressWarnings("unchecked") @Nullable public <T> T[] readParcelableArray(@Nullable ClassLoader loader, @NonNull Class<T> clazz) { + return readParcelableArrayInternal(loader, requireNonNull(clazz)); + } + + @SuppressWarnings("unchecked") + @Nullable + private <T> T[] readParcelableArrayInternal(@Nullable ClassLoader loader, + @Nullable Class<T> clazz) { int n = readInt(); if (n < 0) { return null; } - T[] p = (T[]) Array.newInstance(clazz, n); + T[] p = (T[]) ((clazz == null) ? new Parcelable[n] : Array.newInstance(clazz, n)); for (int i = 0; i < n; i++) { p[i] = readParcelableInternal(loader, clazz); } @@ -4962,7 +5006,7 @@ public final class Parcel { // the class the same way as ObjectInputStream, using the provided classloader. Class<?> cl = Class.forName(name, false, loader); if (!clazz.isAssignableFrom(cl)) { - throw new BadParcelableException("Serializable object " + throw new BadTypeParcelableException("Serializable object " + cl.getName() + " is not a subclass of required class " + clazz.getName() + " provided in the parameter"); } @@ -4987,7 +5031,7 @@ public final class Parcel { // the deserialized object, as we cannot resolve the class the same way as // ObjectInputStream. if (!clazz.isAssignableFrom(object.getClass())) { - throw new BadParcelableException("Serializable object " + throw new BadTypeParcelableException("Serializable object " + object.getClass().getName() + " is not a subclass of required class " + clazz.getName() + " provided in the parameter"); } @@ -5097,7 +5141,26 @@ public final class Parcel { readMapInternal(outVal, n, loader, /* clazzKey */null, /* clazzValue */null); } - /* package */ <K, V> void readMapInternal(@NonNull Map<? super K, ? super V> outVal, int n, + @Nullable + private <K, V> HashMap<K, V> readHashMapInternal(@Nullable ClassLoader loader, + @NonNull Class<? extends K> clazzKey, @NonNull Class<? extends V> clazzValue) { + int n = readInt(); + if (n < 0) { + return null; + } + HashMap<K, V> map = new HashMap<>(n); + readMapInternal(map, n, loader, clazzKey, clazzValue); + return map; + } + + private <K, V> void readMapInternal(@NonNull Map<? super K, ? super V> outVal, + @Nullable ClassLoader loader, @Nullable Class<K> clazzKey, + @Nullable Class<V> clazzValue) { + int n = readInt(); + readMapInternal(outVal, n, loader, clazzKey, clazzValue); + } + + private <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) { @@ -5108,7 +5171,7 @@ public final class Parcel { } } - /* package */ void readArrayMapInternal(@NonNull ArrayMap<? super String, Object> outVal, + private void readArrayMapInternal(@NonNull ArrayMap<? super String, Object> outVal, int size, @Nullable ClassLoader loader) { readArrayMap(outVal, size, /* sorted */ true, /* lazy */ false, loader); } diff --git a/core/java/android/view/ImeFocusController.java b/core/java/android/view/ImeFocusController.java index 3fc9f6bcbdeb..b48b5258237f 100644 --- a/core/java/android/view/ImeFocusController.java +++ b/core/java/android/view/ImeFocusController.java @@ -54,13 +54,11 @@ public final class ImeFocusController { @NonNull private InputMethodManagerDelegate getImmDelegate() { - InputMethodManagerDelegate delegate = mDelegate; - if (delegate != null) { - return delegate; + if (mDelegate == null) { + mDelegate = mViewRootImpl.mContext.getSystemService( + InputMethodManager.class).getDelegate(); } - delegate = mViewRootImpl.mContext.getSystemService(InputMethodManager.class).getDelegate(); - mDelegate = delegate; - return delegate; + return mDelegate; } /** Called when the view root is moved to a different display. */ diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java index 188d7459f9a7..7d5603994efa 100644 --- a/core/java/android/view/InputDevice.java +++ b/core/java/android/view/InputDevice.java @@ -737,15 +737,47 @@ public final class InputDevice implements Parcelable { } /** - * Gets the key code produced by the specified location on a US keyboard layout. - * Key code as defined in {@link android.view.KeyEvent}. - * This API is only functional for devices with {@link InputDevice#SOURCE_KEYBOARD} available - * which can alter their key mapping using country specific keyboard layouts. - * - * @param locationKeyCode The location of a key on a US keyboard layout. - * @return The key code produced when pressing the key at the specified location, given the - * active keyboard layout. Returns {@link KeyEvent#KEYCODE_UNKNOWN} if the requested - * mapping could not be determined, or if an error occurred. + * Gets the {@link android.view.KeyEvent key code} produced by the given location on a reference + * QWERTY keyboard layout. + * <p> + * This API is useful for querying the physical location of keys that change the character + * produced based on the current locale and keyboard layout. + * <p> + * The following table provides a non-exhaustive list of examples: + * <table border="2" width="85%" align="center" cellpadding="5"> + * <thead> + * <tr><th>Active Keyboard Layout</th> <th>Input Parameter</th> + * <th>Return Value</th></tr> + * </thead> + * + * <tbody> + * <tr> + * <td>French AZERTY</td> + * <td><code>{@link KeyEvent#KEYCODE_Q}</code></td> + * <td><code>{@link KeyEvent#KEYCODE_A}</code></td> + * </tr> + * <tr> + * <td>German QWERTZ</td> + * <td><code>{@link KeyEvent#KEYCODE_Y}</code></td> + * <td><code>{@link KeyEvent#KEYCODE_Z}</code></td> + * </tr> + * <tr> + * <td>US QWERTY</td> + * <td><code>{@link KeyEvent#KEYCODE_B}</code></td> + * <td><code>{@link KeyEvent#KEYCODE_B}</code></td> + * </tr> + * </tbody> + * </table> + * + * @param locationKeyCode The location of a key specified as a key code on the QWERTY layout. + * This provides a consistent way of referring to the physical location of a key independently + * of the current keyboard layout. Also see the + * <a href="https://www.w3.org/TR/2017/CR-uievents-code-20170601/#key-alphanumeric-writing-system"> + * hypothetical keyboard</a> provided by the W3C, which may be helpful for identifying the + * physical location of a key. + * @return The key code produced by the key at the specified location, given the current + * keyboard layout. Returns {@link KeyEvent#KEYCODE_UNKNOWN} if the device does not specify + * {@link InputDevice#SOURCE_KEYBOARD} or the requested mapping cannot be determined. */ public int getKeyCodeForKeyLocation(int locationKeyCode) { return InputManager.getInstance().getKeyCodeForKeyLocation(mId, locationKeyCode); diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 98cef95885bd..632af2315bcd 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -3094,6 +3094,10 @@ public final class SurfaceControl implements Parcelable { * @param destFrame The destination rectangle in parent space. Or null for the source frame. * @param orientation The buffer rotation * @return This transaction object. + * @deprecated Use {@link #setCrop(SurfaceControl, Rect)}, + * {@link #setBufferTransform(SurfaceControl, int)}, + * {@link #setPosition(SurfaceControl, float, float)} and + * {@link #setScale(SurfaceControl, float, float)} instead. */ @NonNull public Transaction setGeometry(@NonNull SurfaceControl sc, @Nullable Rect sourceCrop, diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 286b502791c8..34a13868f4d7 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -14173,7 +14173,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, && isAccessibilityPane()) { // If the pane isn't visible, content changed events are sufficient unless we're // reporting that the view just disappeared - if ((getVisibility() == VISIBLE) + if ((isAggregatedVisible()) || (changeType == AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED)) { final AccessibilityEvent event = AccessibilityEvent.obtain(); onInitializeAccessibilityEvent(event); diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 1c2704632e51..8ee3e432cbbb 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -3222,7 +3222,8 @@ public interface WindowManager extends ViewManager { /** * The window is allowed to extend into the {@link DisplayCutout} area, only if the - * {@link DisplayCutout} is fully contained within a system bar. Otherwise, the window is + * {@link DisplayCutout} is fully contained within a system bar or the {@link DisplayCutout} + * is not deeper than 16 dp, but this depends on the OEM choice. Otherwise, the window is * laid out such that it does not overlap with the {@link DisplayCutout} area. * * <p> @@ -3237,6 +3238,13 @@ public interface WindowManager extends ViewManager { * The usual precautions for not overlapping with the status and navigation bar are * sufficient for ensuring that no important content overlaps with the DisplayCutout. * + * <p> + * Note: OEMs can have an option to allow the window to always extend into the + * {@link DisplayCutout} area, no matter the cutout flag set, when the {@link DisplayCutout} + * is on the different side from system bars, only if the {@link DisplayCutout} overlaps at + * most 16dp with the windows. + * In such case, OEMs must provide an opt-in/out affordance for users. + * * @see DisplayCutout * @see WindowInsets * @see #layoutInDisplayCutoutMode @@ -3249,8 +3257,16 @@ public interface WindowManager extends ViewManager { * The window is always allowed to extend into the {@link DisplayCutout} areas on the short * edges of the screen. * + * <p> * The window will never extend into a {@link DisplayCutout} area on the long edges of the - * screen. + * screen, unless the {@link DisplayCutout} is not deeper than 16 dp, but this depends on + * the OEM choice. + * + * <p> + * Note: OEMs can have an option to allow the window to extend into the + * {@link DisplayCutout} area on the long edge side, only if the cutout overlaps at most + * 16dp with the windows. In such case, OEMs must provide an opt-in/out affordance for + * users. * * <p> * The window must make sure that no important content overlaps with the diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 2359d8d67f67..27174630bfa3 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -2031,14 +2031,20 @@ public final class InputMethodManager { * @param inputConnection the connection to be invalidated. * @param textSnapshot {@link TextSnapshot} to be used to update {@link EditorInfo}. * @param sessionId the session ID to be sent. + * @return {@code true} if the operation is done. {@code false} if the caller needs to fall back + * to {@link InputMethodManager#restartInput(View)}. * @hide */ - public void doInvalidateInput(@NonNull RemoteInputConnectionImpl inputConnection, + public boolean doInvalidateInput(@NonNull RemoteInputConnectionImpl inputConnection, @NonNull TextSnapshot textSnapshot, int sessionId) { synchronized (mH) { if (mServedInputConnection != inputConnection || mCurrentTextBoxAttribute == null) { // OK to ignore because the calling InputConnection is already abandoned. - return; + return true; + } + if (mCurrentInputMethodSession == null) { + // IME is not yet bound to the client. Need to fall back to the restartInput(). + return false; } final EditorInfo editorInfo = mCurrentTextBoxAttribute.createCopyInternal(); editorInfo.initialSelStart = mCursorSelStart = textSnapshot.getSelectionStart(); @@ -2051,6 +2057,7 @@ public final class InputMethodManager { sessionId); forAccessibilitySessions(wrapper -> wrapper.invalidateInput(editorInfo, mServedInputConnection, sessionId)); + return true; } } diff --git a/core/java/android/webkit/UserPackage.java b/core/java/android/webkit/UserPackage.java index 5bcfa8bb6439..1da2af459348 100644 --- a/core/java/android/webkit/UserPackage.java +++ b/core/java/android/webkit/UserPackage.java @@ -34,7 +34,7 @@ public class UserPackage { private final UserInfo mUserInfo; private final PackageInfo mPackageInfo; - public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.S; + public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.TIRAMISU; public UserPackage(UserInfo user, PackageInfo packageInfo) { this.mUserInfo = user; diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java index cf6807e41e8a..0ef37d14420c 100644 --- a/core/java/android/webkit/WebViewFactory.java +++ b/core/java/android/webkit/WebViewFactory.java @@ -51,7 +51,7 @@ public final class WebViewFactory { // visible for WebViewZygoteInit to look up the class by reflection and call preloadInZygote. /** @hide */ private static final String CHROMIUM_WEBVIEW_FACTORY = - "com.android.webview.chromium.WebViewChromiumFactoryProviderForS"; + "com.android.webview.chromium.WebViewChromiumFactoryProviderForT"; private static final String CHROMIUM_WEBVIEW_FACTORY_METHOD = "create"; diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 3dfb4a5a084a..c207af53fab7 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -12774,7 +12774,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * Called when a context menu option for the text view is selected. Currently * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut}, - * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}. + * {@link android.R.id#copy}, {@link android.R.id#paste}, + * {@link android.R.id#pasteAsPlainText} (starting at API level 23) or + * {@link android.R.id#shareText}. * * @return true if the context menu item action was performed. */ @@ -12965,6 +12967,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * method. The default actions can also be removed from the menu using * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste}, + * {@link android.R.id#pasteAsPlainText} (starting at API level 23), * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters. * * <p>Returning false from @@ -13003,7 +13006,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode, * android.view.Menu)} method. The default actions can also be removed from the menu using * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, - * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p> + * {@link android.R.id#paste}, {@link android.R.id#pasteAsPlainText} (starting at API + * level 23) or {@link android.R.id#replaceText} ids as parameters.</p> * * <p>Returning false from * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode, diff --git a/core/java/com/android/internal/app/MediaRouteChooserDialog.java b/core/java/com/android/internal/app/MediaRouteChooserDialog.java index 7108d1443d3a..23d966fdbc0d 100644 --- a/core/java/com/android/internal/app/MediaRouteChooserDialog.java +++ b/core/java/com/android/internal/app/MediaRouteChooserDialog.java @@ -16,25 +16,26 @@ package com.android.internal.app; -import com.android.internal.R; - -import android.app.Dialog; +import android.app.AlertDialog; import android.content.Context; import android.media.MediaRouter; import android.media.MediaRouter.RouteInfo; import android.os.Bundle; import android.text.TextUtils; import android.util.TypedValue; +import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.view.Window; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; +import android.widget.LinearLayout; import android.widget.ListView; import android.widget.TextView; +import com.android.internal.R; + import java.util.Comparator; /** @@ -48,9 +49,10 @@ import java.util.Comparator; * * TODO: Move this back into the API, as in the support library media router. */ -public class MediaRouteChooserDialog extends Dialog { +public class MediaRouteChooserDialog extends AlertDialog { private final MediaRouter mRouter; private final MediaRouterCallback mCallback; + private final boolean mShowProgressBarWhenEmpty; private int mRouteTypes; private View.OnClickListener mExtendedSettingsClickListener; @@ -60,10 +62,15 @@ public class MediaRouteChooserDialog extends Dialog { private boolean mAttachedToWindow; public MediaRouteChooserDialog(Context context, int theme) { + this(context, theme, true); + } + + public MediaRouteChooserDialog(Context context, int theme, boolean showProgressBarWhenEmpty) { super(context, theme); mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE); mCallback = new MediaRouterCallback(); + mShowProgressBarWhenEmpty = showProgressBarWhenEmpty; } /** @@ -120,28 +127,38 @@ public class MediaRouteChooserDialog extends Dialog { @Override protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + // Note: setView must be called before super.onCreate(). + setView(LayoutInflater.from(getContext()).inflate(R.layout.media_route_chooser_dialog, + null)); - getWindow().requestFeature(Window.FEATURE_LEFT_ICON); - - setContentView(R.layout.media_route_chooser_dialog); setTitle(mRouteTypes == MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY ? R.string.media_route_chooser_title_for_remote_display : R.string.media_route_chooser_title); - // Must be called after setContentView. - getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, - isLightTheme(getContext()) ? R.drawable.ic_media_route_off_holo_light - : R.drawable.ic_media_route_off_holo_dark); + setIcon(isLightTheme(getContext()) ? R.drawable.ic_media_route_off_holo_light + : R.drawable.ic_media_route_off_holo_dark); + + super.onCreate(savedInstanceState); + View emptyView = findViewById(android.R.id.empty); mAdapter = new RouteAdapter(getContext()); - mListView = (ListView)findViewById(R.id.media_route_list); + mListView = (ListView) findViewById(R.id.media_route_list); mListView.setAdapter(mAdapter); mListView.setOnItemClickListener(mAdapter); - mListView.setEmptyView(findViewById(android.R.id.empty)); + mListView.setEmptyView(emptyView); - mExtendedSettingsButton = (Button)findViewById(R.id.media_route_extended_settings_button); + mExtendedSettingsButton = (Button) findViewById(R.id.media_route_extended_settings_button); updateExtendedSettingsButton(); + + if (!mShowProgressBarWhenEmpty) { + findViewById(R.id.media_route_progress_bar).setVisibility(View.GONE); + + // Center the empty view when the progress bar is not shown. + LinearLayout.LayoutParams params = + (LinearLayout.LayoutParams) emptyView.getLayoutParams(); + params.gravity = Gravity.CENTER; + emptyView.setLayoutParams(params); + } } private void updateExtendedSettingsButton() { diff --git a/core/java/com/android/internal/app/MediaRouteDialogPresenter.java b/core/java/com/android/internal/app/MediaRouteDialogPresenter.java index bb2d7fab9cc1..5628b7ed9d15 100644 --- a/core/java/com/android/internal/app/MediaRouteDialogPresenter.java +++ b/core/java/com/android/internal/app/MediaRouteDialogPresenter.java @@ -66,24 +66,37 @@ public abstract class MediaRouteDialogPresenter { } } + /** Create a media route dialog as appropriate. */ public static Dialog createDialog(Context context, int routeTypes, View.OnClickListener extendedSettingsClickListener) { - final MediaRouter router = (MediaRouter)context.getSystemService( - Context.MEDIA_ROUTER_SERVICE); - int theme = MediaRouteChooserDialog.isLightTheme(context) ? android.R.style.Theme_DeviceDefault_Light_Dialog : android.R.style.Theme_DeviceDefault_Dialog; + return createDialog(context, routeTypes, extendedSettingsClickListener, theme); + } + + /** Create a media route dialog as appropriate. */ + public static Dialog createDialog(Context context, + int routeTypes, View.OnClickListener extendedSettingsClickListener, int theme) { + return createDialog(context, routeTypes, extendedSettingsClickListener, theme, + true /* showProgressBarWhenEmpty */); + } + + /** Create a media route dialog as appropriate. */ + public static Dialog createDialog(Context context, int routeTypes, + View.OnClickListener extendedSettingsClickListener, int theme, + boolean showProgressBarWhenEmpty) { + final MediaRouter router = context.getSystemService(MediaRouter.class); MediaRouter.RouteInfo route = router.getSelectedRoute(); if (route.isDefault() || !route.matchesTypes(routeTypes)) { - final MediaRouteChooserDialog d = new MediaRouteChooserDialog(context, theme); + final MediaRouteChooserDialog d = new MediaRouteChooserDialog(context, theme, + showProgressBarWhenEmpty); d.setRouteTypes(routeTypes); d.setExtendedSettingsClickListener(extendedSettingsClickListener); return d; } else { - MediaRouteControllerDialog d = new MediaRouteControllerDialog(context, theme); - return d; + return new MediaRouteControllerDialog(context, theme); } } } diff --git a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java index d1942ac26aff..780de0eb4da2 100644 --- a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java +++ b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java @@ -282,9 +282,8 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { if (!alwaysTrueEndBatchEditDetected) { final TextSnapshot textSnapshot = ic.takeSnapshot(); - if (textSnapshot != null) { - mParentInputMethodManager.doInvalidateInput(this, textSnapshot, - nextSessionId); + if (textSnapshot != null && mParentInputMethodManager.doInvalidateInput( + this, textSnapshot, nextSessionId)) { return; } } diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java index 9bdcddf38c61..1fd04109ae45 100644 --- a/core/java/com/android/internal/util/ArrayUtils.java +++ b/core/java/com/android/internal/util/ArrayUtils.java @@ -888,6 +888,15 @@ public class ArrayUtils { } } + /** + * Returns the {@code i}-th item in {@code items}, if it exists and {@code items} is not {@code + * null}, otherwise returns {@code null}. + */ + @Nullable + public static <T> T getOrNull(@Nullable T[] items, int i) { + return (items != null && items.length > i) ? items[i] : null; + } + public static @Nullable <T> T firstOrNull(T[] items) { return items.length > 0 ? items[0] : null; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 3b2a24846d92..d4c03e412fcb 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4718,10 +4718,10 @@ <permission android:name="android.permission.READ_FRAME_BUFFER" android:protectionLevel="signature|recents" /> - <!-- @SystemApi Allows an application to change the touch mode state. + <!-- Allows an application to change the touch mode state. Without this permission, an app can only change the touch mode if it currently has focus. - @hide --> + @hide --> <permission android:name="android.permission.MODIFY_TOUCH_MODE_STATE" android:protectionLevel="signature" /> diff --git a/core/res/res/layout/media_route_chooser_dialog.xml b/core/res/res/layout/media_route_chooser_dialog.xml index cd1c74fd7d7d..bf73f4b26c9c 100644 --- a/core/res/res/layout/media_route_chooser_dialog.xml +++ b/core/res/res/layout/media_route_chooser_dialog.xml @@ -28,20 +28,21 @@ <!-- Content to show when list is empty. --> <LinearLayout android:id="@android:id/empty" - android:layout_width="match_parent" - android:layout_height="64dp" - android:orientation="horizontal" - android:paddingLeft="16dp" - android:paddingRight="16dp" - android:visibility="gone"> - <ProgressBar android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" /> + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:visibility="gone"> + <ProgressBar android:id="@+id/media_route_progress_bar" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" /> <TextView android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:paddingStart="16dp" - android:text="@string/media_route_chooser_searching" /> + android:layout_height="wrap_content" + android:layout_gravity="center" + android:paddingStart="16dp" + android:text="@string/media_route_chooser_searching" /> </LinearLayout> <!-- Settings button. --> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 7150fca5ab99..689d37c91920 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -7013,30 +7013,42 @@ <!-- Defines the ExtendAnimation used to extend windows during animations --> <declare-styleable name="ExtendAnimation"> - <!-- Defines the amount a window should be extended outward from the left at - the start of the animation. --> - <attr name="fromExtendLeft" format="float|fraction" /> - <!-- Defines the amount a window should be extended outward from the top at - the start of the animation. --> - <attr name="fromExtendTop" format="float|fraction" /> - <!-- Defines the amount a window should be extended outward from the right at - the start of the animation. --> - <attr name="fromExtendRight" format="float|fraction" /> - <!-- Defines the amount a window should be extended outward from the bottom at - the start of the animation. --> - <attr name="fromExtendBottom" format="float|fraction" /> - <!-- Defines the amount a window should be extended outward from the left by - the end of the animation by transitioning from the fromExtendLeft amount. --> - <attr name="toExtendLeft" format="float|fraction" /> - <!-- Defines the amount a window should be extended outward from the top by - the end of the animation by transitioning from the fromExtendTop amount. --> - <attr name="toExtendTop" format="float|fraction" /> - <!-- Defines the amount a window should be extended outward from the right by - the end of the animation by transitioning from the fromExtendRight amount. --> - <attr name="toExtendRight" format="float|fraction" /> - <!-- Defines the amount a window should be extended outward from the bottom by - the end of the animation by transitioning from the fromExtendBottom amount. --> - <attr name="toExtendBottom" format="float|fraction" /> + <!-- Defines the amount a window should be extended outward from the left at the start of + the animation in an absolute dimension (interpreted as pixels if no dimension unit is + provided) or as a percentage of the animation target's width. --> + <attr name="fromExtendLeft" format="float|fraction|dimension" /> + <!-- Defines the amount a window should be extended outward from the top at the start of + the animation in an absolute dimension (interpreted as pixels if no dimension unit is + provided) or as a percentage of the animation target's height. --> + <attr name="fromExtendTop" format="float|fraction|dimension" /> + <!-- Defines the amount a window should be extended outward from the right at the start of + the animation in an absolute dimension (interpreted as pixels if no dimension unit is + provided) or as a percentage of the animation target's width. --> + <attr name="fromExtendRight" format="float|fraction|dimension" /> + <!-- Defines the amount a window should be extended outward from the bottom at the start of + the animation in an absolute dimension (interpreted as pixels if no dimension unit is + provided) or as a percentage of the animation target's height. --> + <attr name="fromExtendBottom" format="float|fraction|dimension" /> + <!-- Defines the amount a window should be extended outward from the left by the end of the + animation by transitioning from the fromExtendLeft amount in an absolute dimension + (interpreted as pixels if no dimension unit is provided) or as a percentage of the + animation target's width. --> + <attr name="toExtendLeft" format="float|fraction|dimension" /> + <!-- Defines the amount a window should be extended outward from the top by the end of the + animation by transitioning from the fromExtendTop amount in an absolute dimension + (interpreted as pixels if no dimension unit is provided) or as a percentage of the + animation target's height. --> + <attr name="toExtendTop" format="float|fraction|dimension" /> + <!-- Defines the amount a window should be extended outward from the right by the end of + the animation by transitioning from the fromExtendRight amount in an absolute + dimension (interpreted as pixels if no dimension unit is provided) or as a percentage + of the animation target's width. --> + <attr name="toExtendRight" format="float|fraction|dimension" /> + <!-- Defines the amount a window should be extended outward from the bottom by the end of + the animation by transitioning from the fromExtendBottom amount in an absolute + dimension (interpreted as pixels if no dimension unit is provided) or as a percentage + of the animation target's height. --> + <attr name="toExtendBottom" format="float|fraction|dimension" /> </declare-styleable> <declare-styleable name="LayoutAnimation"> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index e7eeecc2b516..d4513d0c4a3d 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1676,6 +1676,7 @@ <java-symbol type="id" name="media_route_volume_slider" /> <java-symbol type="id" name="media_route_control_frame" /> <java-symbol type="id" name="media_route_extended_settings_button" /> + <java-symbol type="id" name="media_route_progress_bar" /> <java-symbol type="string" name="media_route_chooser_title" /> <java-symbol type="string" name="media_route_chooser_title_for_remote_display" /> <java-symbol type="string" name="media_route_controller_disconnect" /> diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java index 2f978fc1fc2d..582488ff8de3 100644 --- a/graphics/java/android/graphics/ColorSpace.java +++ b/graphics/java/android/graphics/ColorSpace.java @@ -22,6 +22,10 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; import android.annotation.SuppressAutoDoc; +import android.annotation.SuppressLint; +import android.hardware.DataSpace; +import android.hardware.DataSpace.NamedDataSpace; +import android.util.SparseIntArray; import libcore.util.NativeAllocationRegistry; @@ -207,6 +211,7 @@ public abstract class ColorSpace { // See static initialization block next to #get(Named) private static final ColorSpace[] sNamedColorSpaces = new ColorSpace[Named.values().length]; + private static final SparseIntArray sDataToColorSpaces = new SparseIntArray(); @NonNull private final String mName; @NonNull private final Model mModel; @@ -1389,6 +1394,47 @@ public abstract class ColorSpace { } /** + * Create a {@link ColorSpace} object using a {@link android.hardware.DataSpace DataSpace} + * value. + * + * <p>This function maps from a dataspace to a {@link Named} ColorSpace. + * If no {@link Named} ColorSpace object matching the {@code dataSpace} value can be created, + * {@code null} will return.</p> + * + * @param dataSpace The dataspace value + * @return the ColorSpace object or {@code null} if no matching colorspace can be found. + */ + @SuppressLint("MethodNameUnits") + @Nullable + public static ColorSpace getFromDataSpace(@NamedDataSpace int dataSpace) { + int index = sDataToColorSpaces.get(dataSpace, -1); + if (index != -1) { + return ColorSpace.get(index); + } else { + return null; + } + } + + /** + * Retrieve the {@link android.hardware.DataSpace DataSpace} value from a {@link ColorSpace} + * object. + * + * <p>If this {@link ColorSpace} object has no matching {@code dataSpace} value, + * {@link android.hardware.DataSpace#DATASPACE_UNKNOWN DATASPACE_UNKNOWN} will return.</p> + * + * @return the dataspace value. + */ + @SuppressLint("MethodNameUnits") + public @NamedDataSpace int getDataSpace() { + int index = sDataToColorSpaces.indexOfValue(getId()); + if (index != -1) { + return sDataToColorSpaces.keyAt(index); + } else { + return DataSpace.DATASPACE_UNKNOWN; + } + } + + /** * <p>Returns an instance of {@link ColorSpace} identified by the specified * name. The list of names provided in the {@link Named} enum gives access * to a variety of common RGB color spaces.</p> @@ -1445,6 +1491,7 @@ public abstract class ColorSpace { SRGB_TRANSFER_PARAMETERS, Named.SRGB.ordinal() ); + sDataToColorSpaces.put(DataSpace.DATASPACE_SRGB, Named.SRGB.ordinal()); sNamedColorSpaces[Named.LINEAR_SRGB.ordinal()] = new ColorSpace.Rgb( "sRGB IEC61966-2.1 (Linear)", SRGB_PRIMARIES, @@ -1453,6 +1500,7 @@ public abstract class ColorSpace { 0.0f, 1.0f, Named.LINEAR_SRGB.ordinal() ); + sDataToColorSpaces.put(DataSpace.DATASPACE_SRGB_LINEAR, Named.LINEAR_SRGB.ordinal()); sNamedColorSpaces[Named.EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb( "scRGB-nl IEC 61966-2-2:2003", SRGB_PRIMARIES, @@ -1464,6 +1512,7 @@ public abstract class ColorSpace { SRGB_TRANSFER_PARAMETERS, Named.EXTENDED_SRGB.ordinal() ); + sDataToColorSpaces.put(DataSpace.DATASPACE_SCRGB, Named.EXTENDED_SRGB.ordinal()); sNamedColorSpaces[Named.LINEAR_EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb( "scRGB IEC 61966-2-2:2003", SRGB_PRIMARIES, @@ -1472,6 +1521,8 @@ public abstract class ColorSpace { -0.5f, 7.499f, Named.LINEAR_EXTENDED_SRGB.ordinal() ); + sDataToColorSpaces.put( + DataSpace.DATASPACE_SCRGB_LINEAR, Named.LINEAR_EXTENDED_SRGB.ordinal()); sNamedColorSpaces[Named.BT709.ordinal()] = new ColorSpace.Rgb( "Rec. ITU-R BT.709-5", new float[] { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f }, @@ -1480,6 +1531,7 @@ public abstract class ColorSpace { new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45), Named.BT709.ordinal() ); + sDataToColorSpaces.put(DataSpace.DATASPACE_BT709, Named.BT709.ordinal()); sNamedColorSpaces[Named.BT2020.ordinal()] = new ColorSpace.Rgb( "Rec. ITU-R BT.2020-1", new float[] { 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f }, @@ -1488,6 +1540,7 @@ public abstract class ColorSpace { new Rgb.TransferParameters(1 / 1.0993, 0.0993 / 1.0993, 1 / 4.5, 0.08145, 1 / 0.45), Named.BT2020.ordinal() ); + sDataToColorSpaces.put(DataSpace.DATASPACE_BT2020, Named.BT2020.ordinal()); sNamedColorSpaces[Named.DCI_P3.ordinal()] = new ColorSpace.Rgb( "SMPTE RP 431-2-2007 DCI (P3)", new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f }, @@ -1496,6 +1549,7 @@ public abstract class ColorSpace { 0.0f, 1.0f, Named.DCI_P3.ordinal() ); + sDataToColorSpaces.put(DataSpace.DATASPACE_DCI_P3, Named.DCI_P3.ordinal()); sNamedColorSpaces[Named.DISPLAY_P3.ordinal()] = new ColorSpace.Rgb( "Display P3", new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f }, @@ -1504,6 +1558,7 @@ public abstract class ColorSpace { SRGB_TRANSFER_PARAMETERS, Named.DISPLAY_P3.ordinal() ); + sDataToColorSpaces.put(DataSpace.DATASPACE_DISPLAY_P3, Named.DISPLAY_P3.ordinal()); sNamedColorSpaces[Named.NTSC_1953.ordinal()] = new ColorSpace.Rgb( "NTSC (1953)", NTSC_1953_PRIMARIES, @@ -1528,6 +1583,7 @@ public abstract class ColorSpace { 0.0f, 1.0f, Named.ADOBE_RGB.ordinal() ); + sDataToColorSpaces.put(DataSpace.DATASPACE_ADOBE_RGB, Named.ADOBE_RGB.ordinal()); sNamedColorSpaces[Named.PRO_PHOTO_RGB.ordinal()] = new ColorSpace.Rgb( "ROMM RGB ISO 22028-2:2013", new float[] { 0.7347f, 0.2653f, 0.1596f, 0.8404f, 0.0366f, 0.0001f }, diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java index 9feb619b34e3..b341a4e27e67 100644 --- a/graphics/java/android/graphics/ImageFormat.java +++ b/graphics/java/android/graphics/ImageFormat.java @@ -428,7 +428,7 @@ public class ImageFormat { /** * <p>Private raw camera sensor image format, a single channel image with - * implementation depedent pixel layout.</p> + * implementation dependent pixel layout.</p> * * <p>RAW_PRIVATE is a format for unprocessed raw image buffers coming from an * image sensor. The actual structure of buffers of this format is diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java index 101295d246bc..11ecc9197be7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java @@ -35,6 +35,9 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMA import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.content.ClipDescription; import android.content.Context; import android.content.res.Configuration; @@ -54,6 +57,7 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.protolog.common.ProtoLog; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.R; +import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.protolog.ShellProtoLogGroup; @@ -205,6 +209,7 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange break; case ACTION_DRAG_ENTERED: pd.dragLayout.show(); + pd.dragLayout.update(event); break; case ACTION_DRAG_LOCATION: pd.dragLayout.update(event); @@ -250,10 +255,6 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange // Hide the window if another drag hasn't been started while animating the drop setDropTargetWindowVisibility(pd, View.INVISIBLE); } - - // Clean up the drag surface - mTransaction.reparent(dragSurface, null); - mTransaction.apply(); }); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java index e8bae0f94bf0..756831007c35 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java @@ -123,6 +123,13 @@ public class DragAndDropPolicy { } /** + * Returns the number of targets. + */ + int getNumTargets() { + return mTargets.size(); + } + + /** * Returns the target's regions based on the current state of the device and display. */ @NonNull diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java index d395f956a41c..25fe8b9e0da3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java @@ -17,6 +17,7 @@ package com.android.wm.shell.draganddrop; import static android.app.StatusBarManager.DISABLE_NONE; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; @@ -24,6 +25,7 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.StatusBarManager; @@ -44,6 +46,7 @@ import com.android.internal.logging.InstanceId; import com.android.internal.protolog.common.ProtoLog; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.R; +import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -135,6 +138,12 @@ public class DragLayout extends LinearLayout { } } + private void updateContainerMarginsForSingleTask() { + mDropZoneView1.setContainerMargin( + mDisplayMargin, mDisplayMargin, mDisplayMargin, mDisplayMargin); + mDropZoneView2.setContainerMargin(0, 0, 0, 0); + } + private void updateContainerMargins(int orientation) { final float halfMargin = mDisplayMargin / 2f; if (orientation == Configuration.ORIENTATION_LANDSCAPE) { @@ -165,11 +174,20 @@ public class DragLayout extends LinearLayout { if (!alreadyInSplit) { ActivityManager.RunningTaskInfo taskInfo1 = mPolicy.getLatestRunningTask(); if (taskInfo1 != null) { - Drawable icon1 = mIconProvider.getIcon(taskInfo1.topActivityInfo); - int bgColor1 = getResizingBackgroundColor(taskInfo1); - mDropZoneView1.setAppInfo(bgColor1, icon1); - mDropZoneView2.setAppInfo(bgColor1, icon1); - updateDropZoneSizes(null, null); // passing null splits the views evenly + final int activityType = taskInfo1.getActivityType(); + if (activityType == ACTIVITY_TYPE_STANDARD) { + Drawable icon1 = mIconProvider.getIcon(taskInfo1.topActivityInfo); + int bgColor1 = getResizingBackgroundColor(taskInfo1); + mDropZoneView1.setAppInfo(bgColor1, icon1); + mDropZoneView2.setAppInfo(bgColor1, icon1); + updateDropZoneSizes(null, null); // passing null splits the views evenly + } else { + // We use the first drop zone to show the fullscreen highlight, and don't need + // to set additional info + mDropZoneView1.setForceIgnoreBottomMargin(true); + updateDropZoneSizesForSingleTask(); + updateContainerMarginsForSingleTask(); + } } } else { // We're already in split so get taskInfo from the controller to populate icon / color. @@ -195,6 +213,21 @@ public class DragLayout extends LinearLayout { } } + private void updateDropZoneSizesForSingleTask() { + final LinearLayout.LayoutParams dropZoneView1 = + (LayoutParams) mDropZoneView1.getLayoutParams(); + final LinearLayout.LayoutParams dropZoneView2 = + (LayoutParams) mDropZoneView2.getLayoutParams(); + dropZoneView1.width = MATCH_PARENT; + dropZoneView1.height = MATCH_PARENT; + dropZoneView2.width = 0; + dropZoneView2.height = 0; + dropZoneView1.weight = 1; + dropZoneView2.weight = 0; + mDropZoneView1.setLayoutParams(dropZoneView1); + mDropZoneView2.setLayoutParams(dropZoneView2); + } + /** * Sets the size of the two drop zones based on the provided bounds. The divider sits between * the views and its size is included in the calculations. @@ -265,9 +298,12 @@ public class DragLayout extends LinearLayout { // Animating to no target animateSplitContainers(false, null /* animCompleteCallback */); } else if (mCurrentTarget == null) { - // Animating to first target - animateSplitContainers(true, null /* animCompleteCallback */); - animateHighlight(target); + if (mPolicy.getNumTargets() == 1) { + animateFullscreenContainer(true); + } else { + animateSplitContainers(true, null /* animCompleteCallback */); + animateHighlight(target); + } } else { // Switching between targets mDropZoneView1.animateSwitch(); @@ -283,6 +319,10 @@ public class DragLayout extends LinearLayout { public void hide(DragEvent event, Runnable hideCompleteCallback) { mIsShowing = false; animateSplitContainers(false, hideCompleteCallback); + // Reset the state if we previously force-ignore the bottom margin + mDropZoneView1.setForceIgnoreBottomMargin(false); + mDropZoneView2.setForceIgnoreBottomMargin(false); + updateContainerMargins(getResources().getConfiguration().orientation); mCurrentTarget = null; } @@ -297,11 +337,63 @@ public class DragLayout extends LinearLayout { // Process the drop mPolicy.handleDrop(mCurrentTarget, event.getClipData()); - // TODO(b/169894807): Coordinate with dragSurface + // Start animating the drop UI out with the drag surface hide(event, dropCompleteCallback); + hideDragSurface(dragSurface); return handledDrop; } + private void hideDragSurface(SurfaceControl dragSurface) { + final SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); + final ValueAnimator dragSurfaceAnimator = ValueAnimator.ofFloat(0f, 1f); + // Currently the splash icon animation runs with the default ValueAnimator duration of + // 300ms + dragSurfaceAnimator.setDuration(300); + dragSurfaceAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); + dragSurfaceAnimator.addUpdateListener(animation -> { + float t = animation.getAnimatedFraction(); + float alpha = 1f - t; + // TODO: Scale the drag surface as well once we make all the source surfaces + // consistent + tx.setAlpha(dragSurface, alpha); + tx.apply(); + }); + dragSurfaceAnimator.addListener(new AnimatorListenerAdapter() { + private boolean mCanceled = false; + + @Override + public void onAnimationCancel(Animator animation) { + cleanUpSurface(); + mCanceled = true; + } + + @Override + public void onAnimationEnd(Animator animation) { + if (mCanceled) { + // Already handled above + return; + } + cleanUpSurface(); + } + + private void cleanUpSurface() { + // Clean up the drag surface + tx.remove(dragSurface); + tx.apply(); + } + }); + dragSurfaceAnimator.start(); + } + + private void animateFullscreenContainer(boolean visible) { + mStatusBarManager.disable(visible + ? HIDE_STATUS_BAR_FLAGS + : DISABLE_NONE); + // We're only using the first drop zone if there is one fullscreen target + mDropZoneView1.setShowingMargin(visible); + mDropZoneView1.setShowingHighlight(visible); + } + private void animateSplitContainers(boolean visible, Runnable animCompleteCallback) { mStatusBarManager.disable(visible ? HIDE_STATUS_BAR_FLAGS diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java index a3ee8aed204d..38870bcb3383 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java @@ -65,6 +65,7 @@ public class DropZoneView extends FrameLayout { private final float[] mContainerMargin = new float[4]; private float mCornerRadius; private float mBottomInset; + private boolean mIgnoreBottomMargin; private int mMarginColor; // i.e. color used for negative space like the container insets private boolean mShowingHighlight; @@ -141,6 +142,14 @@ public class DropZoneView extends FrameLayout { } } + /** Ignores the bottom margin provided by the insets. */ + public void setForceIgnoreBottomMargin(boolean ignoreBottomMargin) { + mIgnoreBottomMargin = ignoreBottomMargin; + if (mMarginPercent > 0) { + mMarginView.invalidate(); + } + } + /** Sets the bottom inset so the drop zones are above bottom navigation. */ public void setBottomInset(float bottom) { mBottomInset = bottom; @@ -257,7 +266,8 @@ public class DropZoneView extends FrameLayout { mPath.addRoundRect(mContainerMargin[0] * mMarginPercent, mContainerMargin[1] * mMarginPercent, getWidth() - (mContainerMargin[2] * mMarginPercent), - getHeight() - (mContainerMargin[3] * mMarginPercent) - mBottomInset, + getHeight() - (mContainerMargin[3] * mMarginPercent) + - (mIgnoreBottomMargin ? 0 : mBottomInset), mCornerRadius * mMarginPercent, mCornerRadius * mMarginPercent, Path.Direction.CW); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java index 5996acd269d2..50691803e8f9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java @@ -43,6 +43,7 @@ import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.pip.PinnedStackListenerForwarder; import com.android.wm.shell.pip.Pip; +import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; @@ -289,7 +290,6 @@ public class TvPipController implements PipTransitionController.PipTransitionCal if (DEBUG) Log.d(TAG, "checkIfPinnedTaskAppeared(), task=" + pinnedTask); if (pinnedTask == null || pinnedTask.topActivity == null) return; mPinnedTaskId = pinnedTask.taskId; - setState(STATE_PIP); mPipMediaController.onActivityPinned(); mPipNotificationController.show(pinnedTask.topActivity.getPackageName()); @@ -326,6 +326,9 @@ public class TvPipController implements PipTransitionController.PipTransitionCal @Override public void onPipTransitionFinished(int direction) { + if (PipAnimationController.isInPipDirection(direction) && mState == STATE_NO_PIP) { + setState(STATE_PIP); + } if (DEBUG) Log.d(TAG, "onPipTransition_Finished(), state=" + stateToName(mState)); } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt index c22d3f65b201..0b4bc761838d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt @@ -93,15 +93,4 @@ abstract class ExitPipTransition(testSpec: FlickerTestParameter) : PipTransition .isVisible(LAUNCHER_COMPONENT) } } - - /** - * Checks that the focus doesn't change between windows during the transition - */ - @Presubmit - @Test - open fun focusDoesNotChange() { - testSpec.assertEventLog { - this.focusDoesNotChange() - } - } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt index 5214daa0ec44..9c095a2a039f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.flicker.pip +import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice @@ -72,10 +73,17 @@ class ExitPipWithDismissButtonTest(testSpec: FlickerTestParameter) : ExitPipTran @Test override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() - /** {@inheritDoc} */ - @FlakyTest(bugId = 215869110) + /** + * Checks that the focus changes between the pip menu window and the launcher when clicking the + * dismiss button on pip menu to close the pip window. + */ + @Presubmit @Test - override fun focusDoesNotChange() = super.focusDoesNotChange() + fun focusDoesNotChange() { + testSpec.assertEventLog { + this.focusChanges("PipMenuView", "NexusLauncherActivity") + } + } companion object { /** diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt index 332bba6ad6ef..ab07ede5bb32 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.flicker.pip +import android.platform.test.annotations.Presubmit import android.view.Surface import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice @@ -81,6 +82,17 @@ class ExitPipWithSwipeDownTest(testSpec: FlickerTestParameter) : ExitPipTransiti @Test override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales() + /** + * Checks that the focus doesn't change between windows during the transition + */ + @Presubmit + @Test + fun focusDoesNotChange() { + testSpec.assertEventLog { + this.focusDoesNotChange() + } + } + companion object { /** * Creates the test configurations. diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index 96809bda7c31..69fe5ee49872 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -1863,12 +1863,9 @@ public final class TvInputManager { * Returns a priority for the given use case type and the client's foreground or background * status. * - * @param useCase the use case type of the client. When the given use case type is invalid, - * the default use case type will be used. {@see TvInputService#PriorityHintUseCaseType}. - * @param sessionId the unique id of the session owned by the client. When {@code null}, - * the caller will be used as a client. When the session is invalid, background status - * will be used as a client's status. Otherwise, TV app corresponding to the given - * session id will be used as a client. + * @param useCase the use case type of the client. + * {@see TvInputService#PriorityHintUseCaseType}. + * @param sessionId the unique id of the session owned by the client. * {@see TvInputService#onCreateSession(String, String)}. * * @return the use case priority value for the given use case type and the client's foreground @@ -1879,11 +1876,35 @@ public final class TvInputManager { @SystemApi @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS) public int getClientPriority(@TvInputService.PriorityHintUseCaseType int useCase, - @Nullable String sessionId) { + @NonNull String sessionId) { + Preconditions.checkNotNull(sessionId); + if (!isValidUseCase(useCase)) { + throw new IllegalArgumentException("Invalid use case: " + useCase); + } return getClientPriorityInternal(useCase, sessionId); }; /** + * Returns a priority for the given use case type and the caller's foreground or background + * status. + * + * @param useCase the use case type of the caller. + * {@see TvInputService#PriorityHintUseCaseType}. + * + * @return the use case priority value for the given use case type and the caller's foreground + * or background status. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS) + public int getClientPriority(@TvInputService.PriorityHintUseCaseType int useCase) { + if (!isValidUseCase(useCase)) { + throw new IllegalArgumentException("Invalid use case: " + useCase); + } + return getClientPriorityInternal(useCase, null); + }; + /** * Creates a recording {@link Session} for a given TV input. * * <p>The number of sessions that can be created at the same time is limited by the capability @@ -1935,6 +1956,14 @@ public final class TvInputManager { } } + private boolean isValidUseCase(int useCase) { + return useCase == TvInputService.PRIORITY_HINT_USE_CASE_TYPE_BACKGROUND + || useCase == TvInputService.PRIORITY_HINT_USE_CASE_TYPE_SCAN + || useCase == TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK + || useCase == TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE + || useCase == TvInputService.PRIORITY_HINT_USE_CASE_TYPE_RECORD; + } + /** * Returns the TvStreamConfig list of the given TV input. * diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp index d0acc53f1fe7..397c70485d5b 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -1839,7 +1839,7 @@ jobject JTuner::getAvSyncTime(jint id) { if (mDemuxClient == nullptr) { return nullptr; } - long time = mDemuxClient->getAvSyncTime((int)id); + int64_t time = mDemuxClient->getAvSyncTime((int)id); if (time >= 0) { JNIEnv *env = AndroidRuntime::getJNIEnv(); jclass longClazz = env->FindClass("java/lang/Long"); diff --git a/packages/CompanionDeviceManager/Android.bp b/packages/CompanionDeviceManager/Android.bp index 6ded16371e8a..9f5bfd40e7e3 100644 --- a/packages/CompanionDeviceManager/Android.bp +++ b/packages/CompanionDeviceManager/Android.bp @@ -34,6 +34,7 @@ license { android_app { name: "CompanionDeviceManager", defaults: ["platform_app_defaults"], + certificate: "platform", srcs: ["src/**/*.java"], static_libs: [ diff --git a/packages/CompanionDeviceManager/AndroidManifest.xml b/packages/CompanionDeviceManager/AndroidManifest.xml index 06f2d9d0f0c8..8b5d214f7a10 100644 --- a/packages/CompanionDeviceManager/AndroidManifest.xml +++ b/packages/CompanionDeviceManager/AndroidManifest.xml @@ -31,6 +31,7 @@ <uses-permission android:name="android.permission.RADIO_SCAN_WITHOUT_LOCATION"/> <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/> <uses-permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"/> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/> <application android:allowClearUserData="true" diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java index b51d3103caec..a6a8fcf9af62 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java @@ -37,6 +37,7 @@ import android.companion.AssociationRequest; import android.companion.CompanionDeviceManager; import android.companion.IAssociationRequestCallback; import android.content.Intent; +import android.content.pm.PackageManager; import android.net.MacAddress; import android.os.Bundle; import android.os.Handler; @@ -71,6 +72,9 @@ public class CompanionDeviceActivity extends AppCompatActivity { private static final String EXTRA_ASSOCIATION_REQUEST = "association_request"; private static final String EXTRA_RESULT_RECEIVER = "result_receiver"; + // Activity result: Internal Error. + private static final int RESULT_INTERNAL_ERROR = 2; + // AssociationRequestsProcessor -> UI private static final int RESULT_CODE_ASSOCIATION_CREATED = 0; private static final String EXTRA_ASSOCIATION = "association"; @@ -191,6 +195,20 @@ public class CompanionDeviceActivity extends AppCompatActivity { private void initUI() { if (DEBUG) Log.d(TAG, "initUI(), request=" + mRequest); + final String packageName = mRequest.getPackageName(); + final int userId = mRequest.getUserId(); + final CharSequence appLabel; + + try { + appLabel = getApplicationLabel(this, packageName, userId); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Package u" + userId + "/" + packageName + " not found."); + + CompanionDeviceDiscoveryService.stop(this); + setResultAndFinish(null, RESULT_INTERNAL_ERROR); + return; + } + setContentView(R.layout.activity_confirmation); mTitle = findViewById(R.id.title); @@ -203,8 +221,6 @@ public class CompanionDeviceActivity extends AppCompatActivity { mButtonAllow.setOnClickListener(this::onPositiveButtonClick); findViewById(R.id.btn_negative).setOnClickListener(this::onNegativeButtonClick); - final CharSequence appLabel = getApplicationLabel(this, mRequest.getPackageName()); - if (mRequest.isSelfManaged()) { initUiForSelfManagedAssociation(appLabel); } else if (mRequest.isSingleDevice()) { @@ -257,7 +273,7 @@ public class CompanionDeviceActivity extends AppCompatActivity { if (DEBUG) Log.i(TAG, "onAssociationCreated(), association=" + association); // Don't need to notify the app, CdmService has already done that. Just finish. - setResultAndFinish(association); + setResultAndFinish(association, RESULT_OK); } private void cancel(boolean discoveryTimeout) { @@ -284,10 +300,10 @@ public class CompanionDeviceActivity extends AppCompatActivity { } // ... then set result and finish ("sending" onActivityResult()). - setResultAndFinish(null); + setResultAndFinish(null, RESULT_CANCELED); } - private void setResultAndFinish(@Nullable AssociationInfo association) { + private void setResultAndFinish(@Nullable AssociationInfo association, int resultCode) { if (DEBUG) Log.i(TAG, "setResultAndFinish(), association=" + association); final Intent data = new Intent(); @@ -297,7 +313,7 @@ public class CompanionDeviceActivity extends AppCompatActivity { data.putExtra(CompanionDeviceManager.EXTRA_DEVICE, mSelectedDevice.getDevice()); } } - setResult(association != null ? RESULT_OK : RESULT_CANCELED, data); + setResult(resultCode, data); finish(); } diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java index eab421e48446..e3e563d56e8a 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java @@ -50,14 +50,13 @@ class Utils { } static @NonNull CharSequence getApplicationLabel( - @NonNull Context context, @NonNull String packageName) { + @NonNull Context context, @NonNull String packageName, int userId) + throws PackageManager.NameNotFoundException { final PackageManager packageManager = context.getPackageManager(); - final ApplicationInfo appInfo; - try { - appInfo = packageManager.getApplicationInfo(packageName, 0); - } catch (PackageManager.NameNotFoundException e) { - throw new RuntimeException(e); - } + + final ApplicationInfo appInfo = packageManager.getApplicationInfoAsUser( + packageName, PackageManager.ApplicationInfoFlags.of(0), userId); + return packageManager.getApplicationLabel(appInfo); } diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java index 9175809d9c7c..f681ba1c3853 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java @@ -28,6 +28,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.Process; import android.os.SystemClock; +import android.text.TextUtils; import android.util.SparseBooleanArray; import com.android.internal.annotations.VisibleForTesting; @@ -500,7 +501,7 @@ public final class NetworkStats implements Parcelable, Iterable<NetworkStats.Ent && roaming == e.roaming && defaultNetwork == e.defaultNetwork && rxBytes == e.rxBytes && rxPackets == e.rxPackets && txBytes == e.txBytes && txPackets == e.txPackets - && operations == e.operations && iface.equals(e.iface); + && operations == e.operations && TextUtils.equals(iface, e.iface); } return false; } diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java index ef9ebb50b642..ef6f39a5c040 100644 --- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java +++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java @@ -1887,7 +1887,13 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private void deleteKernelTagData(int uid) { try { mCookieTagMap.forEach((key, value) -> { - if (value.uid == uid) { + // If SkDestroyListener deletes the socket tag while this code is running, + // forEach will either restart iteration from the beginning or return null, + // depending on when the deletion happens. + // If it returns null, continue iteration to delete the data and in fact it would + // just iterate from first key because BpfMap#getNextKey would return first key + // if the current key is not exist. + if (value != null && value.uid == uid) { try { mCookieTagMap.deleteEntry(key); } catch (ErrnoException e) { diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/SparseInputStream.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/SparseInputStream.java index 72230b4062e1..4117d0f07e0f 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/SparseInputStream.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/SparseInputStream.java @@ -177,7 +177,7 @@ public class SparseInputStream extends InputStream { ret = 0; break; case SparseChunk.FILL: - ret = mCur.fill[(4 - ((int) mLeft & 0x3)) & 0x3]; + ret = Byte.toUnsignedInt(mCur.fill[(4 - ((int) mLeft & 0x3)) & 0x3]); break; default: throw new IOException("Unsupported Chunk:" + mCur.toString()); diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 8965144e7351..014a033a4c01 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1160,6 +1160,8 @@ <string name="battery_info_status_not_charging">Connected, not charging</string> <!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed --> <string name="battery_info_status_full">Charged</string> + <!-- [CHAR_LIMIT=40] Battery Info screen. Value for a status item. A state which device is fully charged --> + <string name="battery_info_status_full_charged">Fully Charged</string> <!-- Summary for settings preference disabled by administrator [CHAR LIMIT=50] --> <string name="disabled_by_admin_summary_text">Controlled by admin</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index f6e3557b5a49..19114cf147e4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -194,9 +194,11 @@ public class Utils { * @param context the context * @param batteryChangedIntent battery broadcast intent received from {@link * Intent.ACTION_BATTERY_CHANGED}. + * @param compactStatus to present compact battery charging string if {@code true} * @return battery status string */ - public static String getBatteryStatus(Context context, Intent batteryChangedIntent) { + public static String getBatteryStatus(Context context, Intent batteryChangedIntent, + boolean compactStatus) { final int status = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN); final Resources res = context.getResources(); @@ -205,10 +207,14 @@ public class Utils { final BatteryStatus batteryStatus = new BatteryStatus(batteryChangedIntent); if (batteryStatus.isCharged()) { - statusString = res.getString(R.string.battery_info_status_full); + statusString = res.getString(compactStatus + ? R.string.battery_info_status_full_charged + : R.string.battery_info_status_full); } else { if (status == BatteryManager.BATTERY_STATUS_CHARGING) { - if (batteryStatus.isPluggedInWired()) { + if (compactStatus) { + statusString = res.getString(R.string.battery_info_status_charging); + } else if (batteryStatus.isPluggedInWired()) { switch (batteryStatus.getChargingSpeed(context)) { case BatteryStatus.CHARGING_FAST: statusString = res.getString( diff --git a/packages/SettingsLib/src/com/android/settingslib/devicestate/AndroidSecureSettings.java b/packages/SettingsLib/src/com/android/settingslib/devicestate/AndroidSecureSettings.java new file mode 100644 index 000000000000..8aee576c3d04 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/devicestate/AndroidSecureSettings.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2022 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.settingslib.devicestate; + +import android.content.ContentResolver; +import android.database.ContentObserver; +import android.provider.Settings; + +/** + * Implementation of {@link SecureSettings} that uses Android's {@link Settings.Secure} + * implementation. + */ +class AndroidSecureSettings implements SecureSettings { + + private final ContentResolver mContentResolver; + + AndroidSecureSettings(ContentResolver contentResolver) { + mContentResolver = contentResolver; + } + + @Override + public void putStringForUser(String name, String value, int userHandle) { + Settings.Secure.putStringForUser(mContentResolver, name, value, userHandle); + } + + @Override + public String getStringForUser(String name, int userHandle) { + return Settings.Secure.getStringForUser(mContentResolver, name, userHandle); + } + + @Override + public void registerContentObserver(String name, boolean notifyForDescendants, + ContentObserver observer, int userHandle) { + mContentResolver.registerContentObserver( + Settings.Secure.getUriFor(name), + notifyForDescendants, + observer, + userHandle); + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java b/packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java index afd3626ab889..961fab32fc2c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java @@ -24,6 +24,7 @@ import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; import android.os.Handler; +import android.os.Looper; import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; @@ -47,28 +48,33 @@ public final class DeviceStateRotationLockSettingsManager { private static DeviceStateRotationLockSettingsManager sSingleton; - private final ContentResolver mContentResolver; private final String[] mDeviceStateRotationLockDefaults; - private final Handler mMainHandler = Handler.getMain(); + private final Handler mMainHandler = new Handler(Looper.getMainLooper()); private final Set<DeviceStateRotationLockSettingsListener> mListeners = new HashSet<>(); + private final SecureSettings mSecureSettings; private SparseIntArray mDeviceStateRotationLockSettings; private SparseIntArray mDeviceStateRotationLockFallbackSettings; + private String mLastSettingValue; - private DeviceStateRotationLockSettingsManager(Context context) { - mContentResolver = context.getContentResolver(); + @VisibleForTesting + DeviceStateRotationLockSettingsManager(Context context, SecureSettings secureSettings) { + this.mSecureSettings = secureSettings; mDeviceStateRotationLockDefaults = context.getResources() .getStringArray(R.array.config_perDeviceStateRotationLockDefaults); loadDefaults(); initializeInMemoryMap(); - listenForSettingsChange(context); + listenForSettingsChange(); } /** Returns a singleton instance of this class */ public static synchronized DeviceStateRotationLockSettingsManager getInstance(Context context) { if (sSingleton == null) { + Context applicationContext = context.getApplicationContext(); + ContentResolver contentResolver = applicationContext.getContentResolver(); + SecureSettings secureSettings = new AndroidSecureSettings(contentResolver); sSingleton = - new DeviceStateRotationLockSettingsManager(context.getApplicationContext()); + new DeviceStateRotationLockSettingsManager(applicationContext, secureSettings); } return sSingleton; } @@ -81,11 +87,11 @@ public final class DeviceStateRotationLockSettingsManager { > 0; } - private void listenForSettingsChange(Context context) { - context.getContentResolver() + private void listenForSettingsChange() { + mSecureSettings .registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.DEVICE_STATE_ROTATION_LOCK), - /* notifyForDescendents= */ false, //NOTYPO + Settings.Secure.DEVICE_STATE_ROTATION_LOCK, + /* notifyForDescendants= */ false, new ContentObserver(mMainHandler) { @Override public void onChange(boolean selfChange) { @@ -182,8 +188,7 @@ public final class DeviceStateRotationLockSettingsManager { private void initializeInMemoryMap() { String serializedSetting = - Settings.Secure.getStringForUser( - mContentResolver, + mSecureSettings.getStringForUser( Settings.Secure.DEVICE_STATE_ROTATION_LOCK, UserHandle.USER_CURRENT); if (TextUtils.isEmpty(serializedSetting)) { @@ -222,11 +227,7 @@ public final class DeviceStateRotationLockSettingsManager { private void persistSettings() { if (mDeviceStateRotationLockSettings.size() == 0) { - Settings.Secure.putStringForUser( - mContentResolver, - Settings.Secure.DEVICE_STATE_ROTATION_LOCK, - /* value= */ "", - UserHandle.USER_CURRENT); + persistSettingIfChanged(/* newSettingValue= */ ""); return; } @@ -243,10 +244,17 @@ public final class DeviceStateRotationLockSettingsManager { .append(SEPARATOR_REGEX) .append(mDeviceStateRotationLockSettings.valueAt(i)); } - Settings.Secure.putStringForUser( - mContentResolver, + persistSettingIfChanged(stringBuilder.toString()); + } + + private void persistSettingIfChanged(String newSettingValue) { + if (TextUtils.equals(mLastSettingValue, newSettingValue)) { + return; + } + mLastSettingValue = newSettingValue; + mSecureSettings.putStringForUser( Settings.Secure.DEVICE_STATE_ROTATION_LOCK, - stringBuilder.toString(), + /* value= */ newSettingValue, UserHandle.USER_CURRENT); } diff --git a/packages/SettingsLib/src/com/android/settingslib/devicestate/SecureSettings.java b/packages/SettingsLib/src/com/android/settingslib/devicestate/SecureSettings.java new file mode 100644 index 000000000000..10528739b2b0 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/devicestate/SecureSettings.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 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.settingslib.devicestate; + +import android.database.ContentObserver; + +/** Minimal wrapper interface around {@link android.provider.Settings.Secure} for easier testing. */ +interface SecureSettings { + + void putStringForUser(String name, String value, int userHandle); + + String getStringForUser(String name, int userHandle); + + void registerContentObserver(String name, boolean notifyForDescendants, + ContentObserver settingsObserver, int userHandle); +} diff --git a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java index 93be66ad4882..1e1dfae9f7ac 100644 --- a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java +++ b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java @@ -81,6 +81,7 @@ public class AvatarPickerActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + setTheme(R.style.SudThemeGlifV3_DayNight); ThemeHelper.trySetDynamicColor(this); setContentView(R.layout.avatar_picker); setUpButtons(); diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java new file mode 100644 index 000000000000..1a45384bc768 --- /dev/null +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2022 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.settingslib.devicestate; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.os.UserHandle; +import android.provider.Settings; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class DeviceStateRotationLockSettingsManagerTest { + + @Mock private Context mMockContext; + @Mock private Resources mMockResources; + + private DeviceStateRotationLockSettingsManager mManager; + private int mNumSettingsChanges = 0; + private final ContentObserver mContentObserver = new ContentObserver(null) { + @Override + public void onChange(boolean selfChange) { + mNumSettingsChanges++; + } + }; + private final FakeSecureSettings mFakeSecureSettings = new FakeSecureSettings(); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + Context context = InstrumentationRegistry.getTargetContext(); + when(mMockContext.getApplicationContext()).thenReturn(mMockContext); + when(mMockContext.getResources()).thenReturn(mMockResources); + when(mMockContext.getContentResolver()).thenReturn(context.getContentResolver()); + mFakeSecureSettings.registerContentObserver( + Settings.Secure.DEVICE_STATE_ROTATION_LOCK, + /* notifyForDescendents= */ false, //NOTYPO + mContentObserver, + UserHandle.USER_CURRENT); + mManager = new DeviceStateRotationLockSettingsManager(context, mFakeSecureSettings); + } + + @Test + public void initialization_settingsAreChangedOnce() { + assertThat(mNumSettingsChanges).isEqualTo(1); + } + + @Test + public void updateSetting_multipleTimes_sameValue_settingsAreChangedOnlyOnce() { + mNumSettingsChanges = 0; + + mManager.updateSetting(/* deviceState= */ 1, /* rotationLocked= */ true); + mManager.updateSetting(/* deviceState= */ 1, /* rotationLocked= */ true); + mManager.updateSetting(/* deviceState= */ 1, /* rotationLocked= */ true); + + assertThat(mNumSettingsChanges).isEqualTo(1); + } + + @Test + public void updateSetting_multipleTimes_differentValues_settingsAreChangedMultipleTimes() { + mNumSettingsChanges = 0; + + mManager.updateSetting(/* deviceState= */ 1, /* rotationLocked= */ true); + mManager.updateSetting(/* deviceState= */ 1, /* rotationLocked= */ false); + mManager.updateSetting(/* deviceState= */ 1, /* rotationLocked= */ true); + + assertThat(mNumSettingsChanges).isEqualTo(3); + } +} diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/FakeSecureSettings.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/FakeSecureSettings.java new file mode 100644 index 000000000000..91baa68a1c49 --- /dev/null +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/FakeSecureSettings.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2022 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.settingslib.devicestate; + +import android.database.ContentObserver; +import android.util.Pair; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; + +import java.util.HashMap; +import java.util.Map; + +/** Fake implementation of {@link SecureSettings} that stores everything in memory. */ +class FakeSecureSettings implements SecureSettings { + + private final Map<SettingsKey, String> mValues = new HashMap<>(); + private final Multimap<SettingsKey, ContentObserver> mContentObservers = HashMultimap.create(); + + @Override + public void putStringForUser(String name, String value, int userHandle) { + SettingsKey settingsKey = new SettingsKey(userHandle, name); + mValues.put(settingsKey, value); + for (ContentObserver observer : mContentObservers.get(settingsKey)) { + observer.onChange(/* selfChange= */ false); + } + } + + @Override + public String getStringForUser(String name, int userHandle) { + return mValues.getOrDefault(new SettingsKey(userHandle, name), ""); + } + + @Override + public void registerContentObserver(String name, boolean notifyForDescendants, + ContentObserver settingsObserver, int userHandle) { + mContentObservers.put(new SettingsKey(userHandle, name), settingsObserver); + } + + private static class SettingsKey extends Pair<Integer, String> { + + SettingsKey(Integer userHandle, String settingName) { + super(userHandle, settingName); + } + } +} diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/OWNERS b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/OWNERS new file mode 100644 index 000000000000..98f41234feb1 --- /dev/null +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/OWNERS @@ -0,0 +1,3 @@ +# Default reviewers for this and subdirectories. +alexflo@google.com +chrisgollner@google.com diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java index 62de66ea56fb..09b2a2e73c5b 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java @@ -320,28 +320,47 @@ public class UtilsTest { final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100); final Resources resources = mContext.getResources(); - assertThat(Utils.getBatteryStatus(mContext, intent)).isEqualTo( + assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo( resources.getString(R.string.battery_info_status_full)); } @Test + public void getBatteryStatus_statusIsFullAndUseCompactStatus_returnFullyChargedString() { + final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100); + final Resources resources = mContext.getResources(); + + assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo( + resources.getString(R.string.battery_info_status_full_charged)); + } + + @Test public void getBatteryStatus_batteryLevelIs100_returnFullString() { final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_FULL); final Resources resources = mContext.getResources(); - assertThat(Utils.getBatteryStatus(mContext, intent)).isEqualTo( + assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo( resources.getString(R.string.battery_info_status_full)); } @Test + public void getBatteryStatus_batteryLevelIs100AndUseCompactStatus_returnFullyString() { + final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_STATUS, + BatteryManager.BATTERY_STATUS_FULL); + final Resources resources = mContext.getResources(); + + assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo( + resources.getString(R.string.battery_info_status_full_charged)); + } + + @Test public void getBatteryStatus_batteryLevel99_returnChargingString() { final Intent intent = new Intent(); intent.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_CHARGING); intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_USB); final Resources resources = mContext.getResources(); - assertThat(Utils.getBatteryStatus(mContext, intent)).isEqualTo( + assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo( resources.getString(R.string.battery_info_status_charging)); } @@ -352,7 +371,29 @@ public class UtilsTest { intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_WIRELESS); final Resources resources = mContext.getResources(); - assertThat(Utils.getBatteryStatus(mContext, intent)).isEqualTo( + assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo( resources.getString(R.string.battery_info_status_charging_wireless)); } + + @Test + public void getBatteryStatus_chargingAndUseCompactStatus_returnCompactString() { + final Intent intent = new Intent(); + intent.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_CHARGING); + intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_USB); + final Resources resources = mContext.getResources(); + + assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo( + resources.getString(R.string.battery_info_status_charging)); + } + + @Test + public void getBatteryStatus_chargingWirelessAndUseCompactStatus_returnCompactString() { + final Intent intent = new Intent(); + intent.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_CHARGING); + intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_WIRELESS); + final Resources resources = mContext.getResources(); + + assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo( + resources.getString(R.string.battery_info_status_charging)); + } } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt index c3f6a5d18b74..0da60f0b3d66 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt @@ -298,6 +298,9 @@ class ActivityLaunchAnimator( * * Important: The view must be attached to a [ViewGroup] when calling this function and * during the animation. For safety, this method will return null when it is not. + * + * Note: The background of [view] should be a (rounded) rectangle so that it can be + * properly animated. */ @JvmStatic fun fromView(view: View, cujType: Int? = null): Controller? { diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt index a3c5649e3fec..50178f470fd8 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt @@ -79,6 +79,9 @@ class DialogLaunchAnimator @JvmOverloads constructor( * If [animateBackgroundBoundsChange] is true, then the background of the dialog will be * animated when the dialog bounds change. * + * Note: The background of [view] should be a (rounded) rectangle so that it can be properly + * animated. + * * Caveats: When calling this function and [dialog] is not a fullscreen dialog, then it will be * made fullscreen and 2 views will be inserted between the dialog DecorView and its children. */ @@ -153,6 +156,9 @@ class DialogLaunchAnimator @JvmOverloads constructor( * activity started, when the dialog to app animation is done (or when it is cancelled). If this * method returns null, then the dialog won't be dismissed. * + * Note: The background of [view] should be a (rounded) rectangle so that it can be properly + * animated. + * * @param view any view inside the dialog to animate. */ @JvmOverloads diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml new file mode 100644 index 000000000000..0544b871fa06 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:color="?android:attr/colorControlHighlight"> + <item android:id="@android:id/mask"> + <shape android:shape="rectangle"> + <solid android:color="@android:color/white"/> + <corners android:radius="18dp"/> + </shape> + </item> + <item> + <shape android:shape="rectangle"> + <corners android:radius="18dp"/> + <solid android:color="?androidprv:attr/colorAccentPrimary"/> + </shape> + </item> +</ripple> diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml index d057f5f32dc4..31a8c3bc2397 100644 --- a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml +++ b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml @@ -19,13 +19,17 @@ <ripple android:color="?android:attr/colorControlHighlight"> <item android:id="@android:id/mask"> - <shape android:shape="oval"> + <!-- We make this shape a rounded rectangle instead of a oval so that it can animate --> + <!-- properly into an app/dialog. --> + <shape android:shape="rectangle"> <solid android:color="@android:color/white"/> + <corners android:radius="@dimen/qs_footer_action_corner_radius"/> </shape> </item> <item> - <shape android:shape="oval"> + <shape android:shape="rectangle"> <solid android:color="?attr/offStateColor"/> + <corners android:radius="@dimen/qs_footer_action_corner_radius"/> </shape> </item> diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml index 944061cc8e70..021a85f6a244 100644 --- a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml +++ b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml @@ -19,13 +19,17 @@ <ripple android:color="?android:attr/colorControlHighlight"> <item android:id="@android:id/mask"> - <shape android:shape="oval"> + <!-- We make this shape a rounded rectangle instead of a oval so that it can animate --> + <!-- properly into an app/dialog. --> + <shape android:shape="rectangle"> <solid android:color="@android:color/white"/> + <corners android:radius="@dimen/qs_footer_action_corner_radius"/> </shape> </item> <item> - <shape android:shape="oval"> + <shape android:shape="rectangle"> <solid android:color="?android:attr/colorAccent"/> + <corners android:radius="@dimen/qs_footer_action_corner_radius"/> </shape> </item> diff --git a/packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml b/packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml index a3e289a42d05..e06bfdc500da 100644 --- a/packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml +++ b/packages/SystemUI/res/layout/alert_dialog_button_bar_systemui.xml @@ -22,10 +22,6 @@ android:scrollbarAlwaysDrawVerticalTrack="true" android:scrollIndicators="top|bottom" android:fillViewport="true" - android:paddingTop="@dimen/dialog_button_bar_top_padding" - android:paddingStart="@dimen/dialog_side_padding" - android:paddingEnd="@dimen/dialog_side_padding" - android:paddingBottom="@dimen/dialog_bottom_padding" style="?android:attr/buttonBarStyle"> <com.android.internal.widget.ButtonBarLayout android:layout_width="match_parent" diff --git a/packages/SystemUI/res/layout/alert_dialog_systemui.xml b/packages/SystemUI/res/layout/alert_dialog_systemui.xml index f280cbd16a0f..ca8fadd9c7da 100644 --- a/packages/SystemUI/res/layout/alert_dialog_systemui.xml +++ b/packages/SystemUI/res/layout/alert_dialog_systemui.xml @@ -83,9 +83,15 @@ android:layout_height="wrap_content" /> </FrameLayout> - <include + <FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content" - layout="@layout/alert_dialog_button_bar_systemui" /> + android:paddingStart="@dimen/dialog_side_padding" + android:paddingEnd="@dimen/dialog_side_padding"> + <include + android:layout_width="match_parent" + android:layout_height="wrap_content" + layout="@layout/alert_dialog_button_bar_systemui" /> + </FrameLayout> </com.android.internal.widget.AlertDialogLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/dream_overlay_container.xml b/packages/SystemUI/res/layout/dream_overlay_container.xml index 330f515a29fc..8e83b4abe0dc 100644 --- a/packages/SystemUI/res/layout/dream_overlay_container.xml +++ b/packages/SystemUI/res/layout/dream_overlay_container.xml @@ -34,34 +34,5 @@ app:layout_constraintBottom_toBottomOf="parent" /> - <com.android.systemui.dreams.DreamOverlayStatusBarView - android:id="@+id/dream_overlay_status_bar" - android:layout_width="match_parent" - android:layout_height="@dimen/dream_overlay_status_bar_height" - android:paddingEnd="@dimen/dream_overlay_status_bar_margin" - android:paddingStart="@dimen/dream_overlay_status_bar_margin" - app:layout_constraintTop_toTopOf="parent"> - - <androidx.constraintlayout.widget.ConstraintLayout - android:id="@+id/dream_overlay_system_status" - android:layout_width="wrap_content" - android:layout_height="match_parent" - app:layout_constraintEnd_toEndOf="parent"> - - <com.android.systemui.statusbar.AlphaOptimizedImageView - android:id="@+id/dream_overlay_wifi_status" - android:layout_width="@dimen/status_bar_wifi_signal_size" - android:layout_height="match_parent" - android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin" - android:visibility="gone" - app:layout_constraintEnd_toStartOf="@id/dream_overlay_battery" /> - - <com.android.systemui.battery.BatteryMeterView - android:id="@+id/dream_overlay_battery" - android:layout_width="wrap_content" - android:layout_height="match_parent" - app:layout_constraintEnd_toEndOf="parent" /> - - </androidx.constraintlayout.widget.ConstraintLayout> - </com.android.systemui.dreams.DreamOverlayStatusBarView> + <include layout="@layout/dream_overlay_status_bar_view" /> </com.android.systemui.dreams.DreamOverlayContainerView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml new file mode 100644 index 000000000000..813787e8f9d0 --- /dev/null +++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml @@ -0,0 +1,96 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 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. + --> +<com.android.systemui.dreams.DreamOverlayStatusBarView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/dream_overlay_status_bar" + android:layout_width="match_parent" + android:layout_height="@dimen/dream_overlay_status_bar_height" + android:paddingEnd="@dimen/dream_overlay_status_bar_margin" + android:paddingStart="@dimen/dream_overlay_status_bar_margin" + app:layout_constraintTop_toTopOf="parent"> + + <com.android.systemui.dreams.DreamOverlayDotImageView + android:id="@+id/dream_overlay_notification_indicator" + android:layout_width="@dimen/dream_overlay_notification_indicator_size" + android:layout_height="@dimen/dream_overlay_notification_indicator_size" + android:visibility="gone" + android:contentDescription="@string/dream_overlay_status_bar_notification_indicator" + app:dotColor="@android:color/white" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" /> + + <LinearLayout + android:id="@+id/dream_overlay_system_status" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:orientation="horizontal" + app:layout_constraintEnd_toEndOf="parent"> + + <com.android.systemui.statusbar.AlphaOptimizedImageView + android:id="@+id/dream_overlay_assistant_guest_mode_enabled" + android:layout_width="@dimen/dream_overlay_status_bar_icon_size" + android:layout_height="match_parent" + android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin" + android:src="@drawable/ic_account_circle" + android:tint="@android:color/white" + android:visibility="gone" + android:contentDescription= + "@string/dream_overlay_status_bar_assistant_guest_mode_enabled" /> + + <com.android.systemui.statusbar.AlphaOptimizedImageView + android:id="@+id/dream_overlay_alarm_set" + android:layout_width="@dimen/dream_overlay_status_bar_icon_size" + android:layout_height="match_parent" + android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin" + android:src="@drawable/ic_alarm" + android:tint="@android:color/white" + android:visibility="gone" + android:contentDescription="@string/dream_overlay_status_bar_alarm_set" /> + + <com.android.systemui.statusbar.AlphaOptimizedImageView + android:id="@+id/dream_overlay_priority_mode" + android:layout_width="@dimen/dream_overlay_status_bar_icon_size" + android:layout_height="match_parent" + android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin" + android:src="@drawable/ic_remove_circle" + android:tint="@android:color/white" + android:visibility="gone" + android:contentDescription="@string/dream_overlay_status_bar_priority_mode" /> + + <com.android.systemui.statusbar.AlphaOptimizedImageView + android:id="@+id/dream_overlay_wifi_status" + android:layout_width="@dimen/dream_overlay_status_bar_icon_size" + android:layout_height="match_parent" + android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin" + android:src="@drawable/ic_signal_wifi_off" + android:visibility="gone" + android:contentDescription="@string/dream_overlay_status_bar_wifi_off" /> + + <com.android.systemui.dreams.DreamOverlayDotImageView + android:id="@+id/dream_overlay_camera_mic_off" + android:layout_width="@dimen/dream_overlay_camera_mic_off_indicator_size" + android:layout_height="@dimen/dream_overlay_camera_mic_off_indicator_size" + android:layout_gravity="center_vertical" + android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin" + android:visibility="gone" + android:contentDescription="@string/dream_overlay_status_bar_camera_mic_off" + app:dotColor="@color/dream_overlay_camera_mic_off_dot_color" /> + + </LinearLayout> +</com.android.systemui.dreams.DreamOverlayStatusBarView> diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml index 2992859b1ce7..c5e005c556b2 100644 --- a/packages/SystemUI/res/values/attrs.xml +++ b/packages/SystemUI/res/values/attrs.xml @@ -199,5 +199,9 @@ </declare-styleable> <attr name="overlayButtonTextColor" format="color" /> + + <declare-styleable name="DreamOverlayDotImageView"> + <attr name="dotColor" format="color" /> + </declare-styleable> </resources> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index f4e7cf3fcf40..dc7470081da2 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -225,4 +225,6 @@ <color name="settingslib_track_off_color">@color/settingslib_track_off</color> <color name="connected_network_primary_color">#191C18</color> <color name="connected_network_secondary_color">#41493D</color> + + <color name="dream_overlay_camera_mic_off_dot_color">#FCBE03</color> </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 370413400766..fcf60bf43c62 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1332,12 +1332,16 @@ <dimen name="fgs_manager_min_width_minor">100%</dimen> <!-- Dream overlay related dimensions --> - <dimen name="dream_overlay_status_bar_height">80dp</dimen> + <dimen name="dream_overlay_status_bar_height">60dp</dimen> <dimen name="dream_overlay_status_bar_margin">40dp</dimen> <dimen name="dream_overlay_status_icon_margin">8dp</dimen> + <dimen name="dream_overlay_status_bar_icon_size"> + @*android:dimen/status_bar_system_icon_size</dimen> <!-- Height of the area at the top of the dream overlay to allow dragging down the notifications shade. --> <dimen name="dream_overlay_notifications_drag_area_height">100dp</dimen> + <dimen name="dream_overlay_camera_mic_off_indicator_size">8dp</dimen> + <dimen name="dream_overlay_notification_indicator_size">6dp</dimen> <!-- Dream overlay complications related dimensions --> <dimen name="dream_overlay_complication_clock_time_text_size">72sp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 3f80647886f4..df16b0d45228 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2407,4 +2407,20 @@ <string name="add_user_supervised" translatable="false">@*android:string/supervised_user_creation_label</string> <!-- Manage users - For system user management [CHAR LIMIT=40] --> <string name="manage_users">Manage users</string> + + <!-- Toast shown when a notification does not support dragging to split [CHAR LIMIT=NONE] --> + <string name="drag_split_not_supported">This notification does not support dragging to Splitscreen.</string> + + <!-- Content description for the Wi-Fi off icon in the dream overlay status bar [CHAR LIMIT=NONE] --> + <string name="dream_overlay_status_bar_wifi_off">Wi\u2011Fi unavailable</string> + <!-- Content description for the priority mode icon in the dream overlay status bar [CHAR LIMIT=NONE] --> + <string name="dream_overlay_status_bar_priority_mode">Priority mode</string> + <!-- Content description for the alarm set icon in the dream overlay status bar [CHAR LIMIT=NONE] --> + <string name="dream_overlay_status_bar_alarm_set">Alarm set</string> + <!-- Content description for the assistant guest mode enabled icon in the dream overlay status bar [CHAR LIMIT=NONE] --> + <string name="dream_overlay_status_bar_assistant_guest_mode_enabled">Assistant guest mode enabled</string> + <!-- Content description for the camera and mic off icon in the dream overlay status bar [CHAR LIMIT=NONE] --> + <string name="dream_overlay_status_bar_camera_mic_off">Camera and mic are off</string> + <!-- Content description for the camera and mic off icon in the dream overlay status bar [CHAR LIMIT=NONE] --> + <string name="dream_overlay_status_bar_notification_indicator">There are notifications</string> </resources> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 9d65c3895827..f2eaa75496e8 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -381,12 +381,19 @@ <item name="android:buttonBarNeutralButtonStyle">@style/Widget.Dialog.Button.BorderButton</item> <item name="android:colorBackground">?androidprv:attr/colorSurface</item> <item name="android:alertDialogStyle">@style/AlertDialogStyle</item> + <item name="android:buttonBarStyle">@style/ButtonBarStyle</item> + <item name="android:buttonBarButtonStyle">@style/Widget.Dialog.Button.Large</item> </style> <style name="AlertDialogStyle" parent="@androidprv:style/AlertDialog.DeviceDefault"> <item name="android:layout">@layout/alert_dialog_systemui</item> </style> + <style name="ButtonBarStyle" parent="@androidprv:style/DeviceDefault.ButtonBar.AlertDialog"> + <item name="android:paddingTop">@dimen/dialog_button_bar_top_padding</item> + <item name="android:paddingBottom">@dimen/dialog_bottom_padding</item> + </style> + <style name="Theme.SystemUI.Dialog.Alert" parent="@*android:style/Theme.DeviceDefault.Light.Dialog.Alert" /> <style name="Theme.SystemUI.Dialog.GlobalActions" parent="@android:style/Theme.DeviceDefault.Light.NoActionBar.Fullscreen"> @@ -962,6 +969,11 @@ <item name="android:textColor">?android:attr/textColorPrimary</item> </style> + <style name="Widget.Dialog.Button.Large"> + <item name="android:background">@drawable/qs_dialog_btn_filled_large</item> + <item name="android:minHeight">56dp</item> + </style> + <style name="MainSwitch.Settingslib" parent="@android:style/Theme.DeviceDefault"> <item name="android:switchMinWidth">@dimen/settingslib_min_switch_width</item> </style> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index 08b4d3f68a87..2b1c47f60070 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -103,8 +103,8 @@ public class QuickStepContract { // enabled (since it's used to navigate back within the bubbled app, or to collapse the bubble // stack. public static final int SYSUI_STATE_BUBBLES_EXPANDED = 1 << 14; - // The global actions dialog is showing - public static final int SYSUI_STATE_GLOBAL_ACTIONS_SHOWING = 1 << 15; + // A SysUI dialog is showing. + public static final int SYSUI_STATE_DIALOG_SHOWING = 1 << 15; // The one-handed mode is active public static final int SYSUI_STATE_ONE_HANDED_ACTIVE = 1 << 16; // Allow system gesture no matter the system bar(s) is visible or not @@ -140,7 +140,7 @@ public class QuickStepContract { SYSUI_STATE_TRACING_ENABLED, SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED, SYSUI_STATE_BUBBLES_EXPANDED, - SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, + SYSUI_STATE_DIALOG_SHOWING, SYSUI_STATE_ONE_HANDED_ACTIVE, SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY, SYSUI_STATE_IME_SHOWING, @@ -166,7 +166,7 @@ public class QuickStepContract { str.add((flags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED) != 0 ? "keygrd_occluded" : ""); str.add((flags & SYSUI_STATE_BOUNCER_SHOWING) != 0 ? "bouncer_visible" : ""); - str.add((flags & SYSUI_STATE_GLOBAL_ACTIONS_SHOWING) != 0 ? "global_actions" : ""); + str.add((flags & SYSUI_STATE_DIALOG_SHOWING) != 0 ? "dialog_showing" : ""); str.add((flags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0 ? "a11y_click" : ""); str.add((flags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0 ? "a11y_long_click" : ""); str.add((flags & SYSUI_STATE_TRACING_ENABLED) != 0 ? "tracing" : ""); @@ -256,7 +256,7 @@ public class QuickStepContract { public static boolean isBackGestureDisabled(int sysuiStateFlags) { // Always allow when the bouncer/global actions is showing (even on top of the keyguard) if ((sysuiStateFlags & SYSUI_STATE_BOUNCER_SHOWING) != 0 - || (sysuiStateFlags & SYSUI_STATE_GLOBAL_ACTIONS_SHOWING) != 0) { + || (sysuiStateFlags & SYSUI_STATE_DIALOG_SHOWING) != 0) { return false; } if ((sysuiStateFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0) { diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index b32c2b639f16..84b6ace17ab9 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -38,6 +38,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.systemui.accessibility.AccessibilityButtonModeObserver; import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver; import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController; +import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.appops.AppOpsController; import com.android.systemui.assist.AssistManager; import com.android.systemui.broadcast.BroadcastDispatcher; @@ -107,6 +108,7 @@ import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider; import com.android.systemui.statusbar.phone.StatusBarIconController; +import com.android.systemui.statusbar.phone.SystemUIDialogManager; import com.android.systemui.statusbar.policy.AccessibilityController; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.BatteryController; @@ -372,6 +374,8 @@ public class Dependency { @Inject Lazy<AmbientState> mAmbientStateLazy; @Inject Lazy<GroupMembershipManager> mGroupMembershipManagerLazy; @Inject Lazy<GroupExpansionManager> mGroupExpansionManagerLazy; + @Inject Lazy<SystemUIDialogManager> mSystemUIDialogManagerLazy; + @Inject Lazy<DialogLaunchAnimator> mDialogLaunchAnimatorLazy; @Inject public Dependency() { @@ -592,6 +596,8 @@ public class Dependency { mProviders.put(AmbientState.class, mAmbientStateLazy::get); mProviders.put(GroupMembershipManager.class, mGroupMembershipManagerLazy::get); mProviders.put(GroupExpansionManager.class, mGroupExpansionManagerLazy::get); + mProviders.put(SystemUIDialogManager.class, mSystemUIDialogManagerLazy::get); + mProviders.put(DialogLaunchAnimator.class, mDialogLaunchAnimatorLazy::get); Dependency.setInstance(this); } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayDotImageView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayDotImageView.java new file mode 100644 index 000000000000..02a8b39a106a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayDotImageView.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2022 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.dreams; + +import android.annotation.ColorInt; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.systemui.R; +import com.android.systemui.statusbar.AlphaOptimizedImageView; + +/** + * An {@link AlphaOptimizedImageView} that is responsible for rendering a dot. Used by + * {@link DreamOverlayStatusBarView}. + */ +public class DreamOverlayDotImageView extends AlphaOptimizedImageView { + private final @ColorInt int mDotColor; + + public DreamOverlayDotImageView(Context context) { + this(context, null); + } + + public DreamOverlayDotImageView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public DreamOverlayDotImageView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public DreamOverlayDotImageView(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + TypedArray a = context.getTheme().obtainStyledAttributes(attrs, + R.styleable.DreamOverlayDotImageView, 0, 0); + + try { + mDotColor = a.getColor(R.styleable.DreamOverlayDotImageView_dotColor, Color.WHITE); + } finally { + a.recycle(); + } + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + setImageDrawable(new DotDrawable(mDotColor)); + } + + private static class DotDrawable extends Drawable { + private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private Bitmap mDotBitmap; + private final Rect mBounds = new Rect(); + private final @ColorInt int mDotColor; + + DotDrawable(@ColorInt int color) { + mDotColor = color; + } + + @Override + public void draw(@NonNull Canvas canvas) { + if (mBounds.isEmpty()) { + return; + } + + if (mDotBitmap == null) { + mDotBitmap = createBitmap(mBounds.width(), mBounds.height()); + } + + canvas.drawBitmap(mDotBitmap, null, mBounds, mPaint); + } + + @Override + protected void onBoundsChange(Rect bounds) { + super.onBoundsChange(bounds); + mBounds.set(bounds.left, bounds.top, bounds.right, bounds.bottom); + // Make sure to regenerate the dot bitmap when the bounds change. + mDotBitmap = null; + } + + @Override + public void setAlpha(int alpha) { + } + + @Override + public void setColorFilter(@Nullable ColorFilter colorFilter) { + } + + @Override + public int getOpacity() { + return 0; + } + + private Bitmap createBitmap(int width, int height) { + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + paint.setColor(mDotColor); + canvas.drawCircle(width / 2.f, height / 2.f, Math.min(width, height) / 2.f, paint); + return bitmap; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java index 9847ef633bc1..2d969206b468 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java @@ -25,17 +25,13 @@ import androidx.constraintlayout.widget.ConstraintLayout; import com.android.internal.util.Preconditions; import com.android.systemui.R; -import com.android.systemui.battery.BatteryMeterView; -import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback; /** * {@link DreamOverlayStatusBarView} is the view responsible for displaying the status bar in a - * dream. The status bar includes status icons such as battery and wifi. + * dream. The status bar displays conditional status icons such as "priority mode" and "no wifi". */ -public class DreamOverlayStatusBarView extends ConstraintLayout implements - BatteryStateChangeCallback { +public class DreamOverlayStatusBarView extends ConstraintLayout { - private BatteryMeterView mBatteryView; private ImageView mWifiStatusView; public DreamOverlayStatusBarView(Context context) { @@ -59,20 +55,8 @@ public class DreamOverlayStatusBarView extends ConstraintLayout implements protected void onFinishInflate() { super.onFinishInflate(); - mBatteryView = Preconditions.checkNotNull(findViewById(R.id.dream_overlay_battery), - "R.id.dream_overlay_battery must not be null"); mWifiStatusView = Preconditions.checkNotNull(findViewById(R.id.dream_overlay_wifi_status), "R.id.dream_overlay_wifi_status must not be null"); - - mWifiStatusView.setImageDrawable(getContext().getDrawable(R.drawable.ic_signal_wifi_off)); - } - - /** - * Whether to show the battery percent text next to the battery status icons. - * @param show True if the battery percent text should be shown. - */ - void showBatteryPercentText(boolean show) { - mBatteryView.setForceShowPercent(show); } /** diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java index 5674b9f3f9fd..ed82ab0e308f 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java @@ -17,24 +17,19 @@ package com.android.systemui.dreams; import android.annotation.IntDef; -import android.content.Context; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; -import com.android.systemui.battery.BatteryMeterViewController; import com.android.systemui.dreams.dagger.DreamOverlayComponent; -import com.android.systemui.dreams.dagger.DreamOverlayModule; -import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.util.ViewController; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import javax.inject.Inject; -import javax.inject.Named; /** * View controller for {@link DreamOverlayStatusBarView}. @@ -52,21 +47,7 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve private static final int WIFI_STATUS_UNAVAILABLE = 1; private static final int WIFI_STATUS_AVAILABLE = 2; - @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = { "BATTERY_STATUS_" }, value = { - BATTERY_STATUS_UNKNOWN, - BATTERY_STATUS_NOT_CHARGING, - BATTERY_STATUS_CHARGING - }) - private @interface BatteryStatus {} - private static final int BATTERY_STATUS_UNKNOWN = 0; - private static final int BATTERY_STATUS_NOT_CHARGING = 1; - private static final int BATTERY_STATUS_CHARGING = 2; - - private final BatteryController mBatteryController; - private final BatteryMeterViewController mBatteryMeterViewController; private final ConnectivityManager mConnectivityManager; - private final boolean mShowPercentAvailable; private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder() .clearCapabilities() @@ -91,43 +72,18 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve } }; - private final BatteryController.BatteryStateChangeCallback mBatteryStateChangeCallback = - new BatteryController.BatteryStateChangeCallback() { - @Override - public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) { - DreamOverlayStatusBarViewController.this.onBatteryLevelChanged(charging); - } - }; - private @WifiStatus int mWifiStatus = WIFI_STATUS_UNKNOWN; - private @BatteryStatus int mBatteryStatus = BATTERY_STATUS_UNKNOWN; @Inject public DreamOverlayStatusBarViewController( - Context context, DreamOverlayStatusBarView view, - BatteryController batteryController, - @Named(DreamOverlayModule.DREAM_OVERLAY_BATTERY_CONTROLLER) - BatteryMeterViewController batteryMeterViewController, ConnectivityManager connectivityManager) { super(view); - mBatteryController = batteryController; - mBatteryMeterViewController = batteryMeterViewController; mConnectivityManager = connectivityManager; - - mShowPercentAvailable = context.getResources().getBoolean( - com.android.internal.R.bool.config_battery_percentage_setting_available); - } - - @Override - protected void onInit() { - super.onInit(); - mBatteryMeterViewController.init(); } @Override protected void onViewAttached() { - mBatteryController.addCallback(mBatteryStateChangeCallback); mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback); NetworkCapabilities capabilities = @@ -140,7 +96,6 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve @Override protected void onViewDetached() { - mBatteryController.removeCallback(mBatteryStateChangeCallback); mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); } @@ -155,18 +110,4 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve mView.showWifiStatus(mWifiStatus == WIFI_STATUS_UNAVAILABLE); } } - - /** - * The battery level has changed. Update the battery status icon as appropriate. - * @param charging Whether the battery is currently charging. - */ - private void onBatteryLevelChanged(boolean charging) { - final int newBatteryStatus = - charging ? BATTERY_STATUS_CHARGING : BATTERY_STATUS_NOT_CHARGING; - if (mBatteryStatus != newBatteryStatus) { - mBatteryStatus = newBatteryStatus; - mView.showBatteryPercentText( - mBatteryStatus == BATTERY_STATUS_CHARGING && mShowPercentAvailable); - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java index 4eb5cb97607a..839a05e6e78f 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java @@ -16,9 +16,7 @@ package com.android.systemui.dreams.dagger; -import android.content.ContentResolver; import android.content.res.Resources; -import android.os.Handler; import android.view.LayoutInflater; import android.view.ViewGroup; @@ -28,15 +26,9 @@ import androidx.lifecycle.LifecycleRegistry; import com.android.internal.util.Preconditions; import com.android.systemui.R; -import com.android.systemui.battery.BatteryMeterView; -import com.android.systemui.battery.BatteryMeterViewController; -import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dreams.DreamOverlayContainerView; import com.android.systemui.dreams.DreamOverlayStatusBarView; -import com.android.systemui.statusbar.policy.BatteryController; -import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.tuner.TunerService; import javax.inject.Named; @@ -47,9 +39,6 @@ import dagger.Provides; /** Dagger module for {@link DreamOverlayComponent}. */ @Module public abstract class DreamOverlayModule { - private static final String DREAM_OVERLAY_BATTERY_VIEW = "dream_overlay_battery_view"; - public static final String DREAM_OVERLAY_BATTERY_CONTROLLER = - "dream_overlay_battery_controller"; public static final String DREAM_OVERLAY_CONTENT_VIEW = "dream_overlay_content_view"; public static final String MAX_BURN_IN_OFFSET = "max_burn_in_offset"; public static final String BURN_IN_PROTECTION_UPDATE_INTERVAL = @@ -86,37 +75,6 @@ public abstract class DreamOverlayModule { /** */ @Provides @DreamOverlayComponent.DreamOverlayScope - @Named(DREAM_OVERLAY_BATTERY_VIEW) - static BatteryMeterView providesBatteryMeterView(DreamOverlayContainerView view) { - return Preconditions.checkNotNull(view.findViewById(R.id.dream_overlay_battery), - "R.id.battery must not be null"); - } - - /** */ - @Provides - @DreamOverlayComponent.DreamOverlayScope - @Named(DREAM_OVERLAY_BATTERY_CONTROLLER) - static BatteryMeterViewController providesBatteryMeterViewController( - @Named(DREAM_OVERLAY_BATTERY_VIEW) BatteryMeterView batteryMeterView, - ConfigurationController configurationController, - TunerService tunerService, - BroadcastDispatcher broadcastDispatcher, - @Main Handler mainHandler, - ContentResolver contentResolver, - BatteryController batteryController) { - return new BatteryMeterViewController( - batteryMeterView, - configurationController, - tunerService, - broadcastDispatcher, - mainHandler, - contentResolver, - batteryController); - } - - /** */ - @Provides - @DreamOverlayComponent.DreamOverlayScope @Named(MAX_BURN_IN_OFFSET) static int providesMaxBurnInOffset(@Main Resources resources) { return resources.getDimensionPixelSize(R.dimen.default_burn_in_prevention_offset); diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java index 84fa6a6772fe..e3886cd80a42 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java @@ -27,7 +27,6 @@ import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; -import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -117,7 +116,6 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.GlobalActions.GlobalActionsManager; import com.android.systemui.plugins.GlobalActionsPanelPlugin; import com.android.systemui.scrim.ScrimDrawable; @@ -125,7 +123,6 @@ import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.SystemUIDialog; -import com.android.systemui.statusbar.phone.SystemUIDialogManager; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.telephony.TelephonyListenerManager; @@ -200,7 +197,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene private final TelecomManager mTelecomManager; private final MetricsLogger mMetricsLogger; private final UiEventLogger mUiEventLogger; - private final SysUiState mSysUiState; // Used for RingerModeTracker private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this); @@ -241,7 +237,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene protected Handler mMainHandler; private int mSmallestScreenWidthDp; private final Optional<StatusBar> mStatusBarOptional; - private final SystemUIDialogManager mDialogManager; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final DialogLaunchAnimator mDialogLaunchAnimator; @@ -347,13 +342,11 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene @Background Executor backgroundExecutor, UiEventLogger uiEventLogger, RingerModeTracker ringerModeTracker, - SysUiState sysUiState, @Main Handler handler, PackageManager packageManager, Optional<StatusBar> statusBarOptional, KeyguardUpdateMonitor keyguardUpdateMonitor, - DialogLaunchAnimator dialogLaunchAnimator, - SystemUIDialogManager dialogManager) { + DialogLaunchAnimator dialogLaunchAnimator) { mContext = context; mWindowManagerFuncs = windowManagerFuncs; mAudioManager = audioManager; @@ -379,13 +372,11 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mIWindowManager = iWindowManager; mBackgroundExecutor = backgroundExecutor; mRingerModeTracker = ringerModeTracker; - mSysUiState = sysUiState; mMainHandler = handler; mSmallestScreenWidthDp = resources.getConfiguration().smallestScreenWidthDp; mStatusBarOptional = statusBarOptional; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mDialogLaunchAnimator = dialogLaunchAnimator; - mDialogManager = dialogManager; // receive broadcasts IntentFilter filter = new IntentFilter(); @@ -682,11 +673,10 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene ActionsDialogLite dialog = new ActionsDialogLite(mContext, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActionsLite, - mAdapter, mOverflowAdapter, mSysuiColorExtractor, - mStatusBarService, mNotificationShadeWindowController, - mSysUiState, this::onRefresh, mKeyguardShowing, mPowerAdapter, mUiEventLogger, - mStatusBarOptional, mKeyguardUpdateMonitor, mLockPatternUtils, - mDialogManager); + mAdapter, mOverflowAdapter, mSysuiColorExtractor, mStatusBarService, + mNotificationShadeWindowController, this::onRefresh, mKeyguardShowing, + mPowerAdapter, mUiEventLogger, mStatusBarOptional, mKeyguardUpdateMonitor, + mLockPatternUtils); dialog.setOnDismissListener(this); dialog.setOnShowListener(this); @@ -2165,7 +2155,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene private boolean mKeyguardShowing; protected float mScrimAlpha; protected final NotificationShadeWindowController mNotificationShadeWindowController; - protected final SysUiState mSysUiState; private ListPopupWindow mOverflowPopup; private Dialog mPowerOptionsDialog; protected final Runnable mOnRefreshCallback; @@ -2226,15 +2215,13 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene MyOverflowAdapter overflowAdapter, SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService, NotificationShadeWindowController notificationShadeWindowController, - SysUiState sysuiState, Runnable onRefreshCallback, boolean keyguardShowing, + Runnable onRefreshCallback, boolean keyguardShowing, MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger, - Optional<StatusBar> statusBarOptional, - KeyguardUpdateMonitor keyguardUpdateMonitor, LockPatternUtils lockPatternUtils, - SystemUIDialogManager systemUiDialogManager) { + Optional<StatusBar> statusBarOptional, KeyguardUpdateMonitor keyguardUpdateMonitor, + LockPatternUtils lockPatternUtils) { // We set dismissOnDeviceLock to false because we have a custom broadcast receiver to // dismiss this dialog when the device is locked. - super(context, themeRes, false /* dismissOnDeviceLock */, - systemUiDialogManager); + super(context, themeRes, false /* dismissOnDeviceLock */); mContext = context; mAdapter = adapter; mOverflowAdapter = overflowAdapter; @@ -2242,7 +2229,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mColorExtractor = sysuiColorExtractor; mStatusBarService = statusBarService; mNotificationShadeWindowController = notificationShadeWindowController; - mSysUiState = sysuiState; mOnRefreshCallback = onRefreshCallback; mKeyguardShowing = keyguardShowing; mUiEventLogger = uiEventLogger; @@ -2463,8 +2449,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene public void show() { super.show(); mNotificationShadeWindowController.setRequestTopUi(true, TAG); - mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, true) - .commitUpdate(mContext.getDisplayId()); // By default this dialog windowAnimationStyle is null, and therefore windowAnimations // should be equal to 0 which means we need to animate the dialog in-window. If it's not @@ -2563,9 +2547,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene dismissPowerOptions(); mNotificationShadeWindowController.setRequestTopUi(false, TAG); - mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, false) - .commitUpdate(mContext.getDisplayId()); - super.dismiss(); } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java index 7bb5454112c4..04a324b87382 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java @@ -56,7 +56,6 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.systemui.R; import com.android.systemui.statusbar.phone.SystemUIDialog; -import com.android.systemui.statusbar.phone.SystemUIDialogManager; /** * Base dialog for media output UI @@ -99,9 +98,8 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements } }; - public MediaOutputBaseDialog(Context context, MediaOutputController mediaOutputController, - SystemUIDialogManager dialogManager) { - super(context, R.style.Theme_SystemUI_Dialog_Media, dialogManager); + public MediaOutputBaseDialog(Context context, MediaOutputController mediaOutputController) { + super(context, R.style.Theme_SystemUI_Dialog_Media); // Save the context that is wrapped with our theme. mContext = getContext(); 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 7bc0f5202c91..e929b5e21053 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -66,7 +66,6 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; import com.android.systemui.statusbar.phone.ShadeController; -import com.android.systemui.statusbar.phone.SystemUIDialogManager; import java.util.ArrayList; import java.util.Collection; @@ -91,7 +90,6 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { private final ShadeController mShadeController; private final ActivityStarter mActivityStarter; private final DialogLaunchAnimator mDialogLaunchAnimator; - private final SystemUIDialogManager mDialogManager; private final List<MediaDevice> mGroupMediaDevices = new CopyOnWriteArrayList<>(); private final boolean mAboveStatusbar; private final boolean mVolumeAdjustmentForRemoteGroupSessions; @@ -119,7 +117,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { boolean aboveStatusbar, MediaSessionManager mediaSessionManager, LocalBluetoothManager lbm, ShadeController shadeController, ActivityStarter starter, CommonNotifCollection notifCollection, UiEventLogger uiEventLogger, - DialogLaunchAnimator dialogLaunchAnimator, SystemUIDialogManager dialogManager) { + DialogLaunchAnimator dialogLaunchAnimator) { mContext = context; mPackageName = packageName; mMediaSessionManager = mediaSessionManager; @@ -135,7 +133,6 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { mDialogLaunchAnimator = dialogLaunchAnimator; mVolumeAdjustmentForRemoteGroupSessions = mContext.getResources().getBoolean( com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions); - mDialogManager = dialogManager; mColorActiveItem = Utils.getColorStateListDefaultColor(mContext, R.color.media_dialog_active_item_main_content); mColorInactiveItem = Utils.getColorStateListDefaultColor(mContext, @@ -610,10 +607,9 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { // We show the output group dialog from the output dialog. MediaOutputController controller = new MediaOutputController(mContext, mPackageName, mAboveStatusbar, mMediaSessionManager, mLocalBluetoothManager, mShadeController, - mActivityStarter, mNotifCollection, mUiEventLogger, mDialogLaunchAnimator, - mDialogManager); + mActivityStarter, mNotifCollection, mUiEventLogger, mDialogLaunchAnimator); MediaOutputGroupDialog dialog = new MediaOutputGroupDialog(mContext, mAboveStatusbar, - controller, mDialogManager); + controller); mDialogLaunchAnimator.showFromView(dialog, mediaOutputDialog); } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java index 4e9da55ffbcb..7696a1f63c01 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java @@ -29,7 +29,6 @@ import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.statusbar.phone.SystemUIDialogManager; /** * Dialog for media output transferring. @@ -39,9 +38,8 @@ public class MediaOutputDialog extends MediaOutputBaseDialog { final UiEventLogger mUiEventLogger; MediaOutputDialog(Context context, boolean aboveStatusbar, MediaOutputController - mediaOutputController, UiEventLogger uiEventLogger, - SystemUIDialogManager dialogManager) { - super(context, mediaOutputController, dialogManager); + mediaOutputController, UiEventLogger uiEventLogger) { + super(context, mediaOutputController); mUiEventLogger = uiEventLogger; mAdapter = new MediaOutputAdapter(mMediaOutputController, this); if (!aboveStatusbar) { diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt index e1e7fa3ebbe0..9e252ea1eddc 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt @@ -25,7 +25,6 @@ import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.plugins.ActivityStarter import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection import com.android.systemui.statusbar.phone.ShadeController -import com.android.systemui.statusbar.phone.SystemUIDialogManager import javax.inject.Inject /** @@ -39,8 +38,7 @@ class MediaOutputDialogFactory @Inject constructor( private val starter: ActivityStarter, private val notifCollection: CommonNotifCollection, private val uiEventLogger: UiEventLogger, - private val dialogLaunchAnimator: DialogLaunchAnimator, - private val dialogManager: SystemUIDialogManager + private val dialogLaunchAnimator: DialogLaunchAnimator ) { companion object { var mediaOutputDialog: MediaOutputDialog? = null @@ -53,9 +51,8 @@ class MediaOutputDialogFactory @Inject constructor( val controller = MediaOutputController(context, packageName, aboveStatusBar, mediaSessionManager, lbm, shadeController, starter, notifCollection, - uiEventLogger, dialogLaunchAnimator, dialogManager) - val dialog = MediaOutputDialog(context, aboveStatusBar, controller, uiEventLogger, - dialogManager) + uiEventLogger, dialogLaunchAnimator) + val dialog = MediaOutputDialog(context, aboveStatusBar, controller, uiEventLogger) mediaOutputDialog = dialog // Show the dialog. diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java index 9f752b92a85e..f1c66016a49a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java @@ -25,7 +25,6 @@ import android.view.WindowManager; import androidx.core.graphics.drawable.IconCompat; import com.android.systemui.R; -import com.android.systemui.statusbar.phone.SystemUIDialogManager; /** * Dialog for media output group. @@ -34,8 +33,8 @@ import com.android.systemui.statusbar.phone.SystemUIDialogManager; public class MediaOutputGroupDialog extends MediaOutputBaseDialog { MediaOutputGroupDialog(Context context, boolean aboveStatusbar, MediaOutputController - mediaOutputController, SystemUIDialogManager dialogManager) { - super(context, mediaOutputController, dialogManager); + mediaOutputController) { + super(context, mediaOutputController); mMediaOutputController.resetGroupMediaDevices(); mAdapter = new MediaOutputGroupAdapter(mMediaOutputController); if (!aboveStatusbar) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java index e088f548a356..5d6bbae1f14a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -202,11 +202,12 @@ public class CastTile extends QSTileImpl<BooleanState> { mActivityStarter .postStartActivityDismissingKeyguard(getLongClickIntent(), 0, controller); - }); + }, R.style.Theme_SystemUI_Dialog, false /* showProgressBarWhenEmpty */); holder.init(dialog); SystemUIDialog.setShowForAllUsers(dialog, true); SystemUIDialog.registerDismissListener(dialog); SystemUIDialog.setWindowOnTop(dialog, mKeyguard.isShowing()); + SystemUIDialog.setDialogSize(dialog); mUiHandler.post(() -> { if (view != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java index 06b739b33e77..c2c40d84bd36 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java @@ -17,6 +17,11 @@ package com.android.systemui.statusbar.notification.row; +import static android.widget.Toast.LENGTH_SHORT; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.annotation.NonNull; import android.app.Notification; import android.app.PendingIntent; @@ -33,13 +38,15 @@ import android.service.notification.StatusBarNotification; import android.util.Log; import android.view.DragEvent; import android.view.HapticFeedbackConstants; +import android.view.SurfaceControl; import android.view.View; import android.widget.ImageView; +import android.widget.Toast; import androidx.annotation.VisibleForTesting; -import com.android.systemui.Dependency; import com.android.systemui.R; +import com.android.systemui.animation.Interpolators; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -55,12 +62,15 @@ public class ExpandableNotificationRowDragController { private final Context mContext; private final HeadsUpManager mHeadsUpManager; + private final ShadeController mShadeController; @Inject public ExpandableNotificationRowDragController(Context context, - HeadsUpManager headsUpManager) { + HeadsUpManager headsUpManager, + ShadeController shadeController) { mContext = context; mHeadsUpManager = headsUpManager; + mShadeController = shadeController; init(); } @@ -87,6 +97,16 @@ public class ExpandableNotificationRowDragController { final PendingIntent contentIntent = notification.contentIntent != null ? notification.contentIntent : notification.fullScreenIntent; + if (contentIntent == null) { + if (!enr.isPinned()) { + // We dismiss the shade for consistency, but also because toasts currently don't + // show above the shade + dismissShade(); + } + Toast.makeText(mContext, R.string.drag_split_not_supported, LENGTH_SHORT) + .show(); + return; + } Bitmap iconBitmap = getBitmapFromDrawable( getPkgIcon(enr.getEntry().getSbn().getPackageName())); @@ -97,15 +117,30 @@ public class ExpandableNotificationRowDragController { ClipDescription clipDescription = new ClipDescription("Drag And Drop", new String[]{ClipDescription.MIMETYPE_APPLICATION_ACTIVITY}); Intent dragIntent = new Intent(); - dragIntent.putExtra("android.intent.extra.PENDING_INTENT", contentIntent); + dragIntent.putExtra(ClipDescription.EXTRA_PENDING_INTENT, contentIntent); dragIntent.putExtra(Intent.EXTRA_USER, android.os.Process.myUserHandle()); ClipData.Item item = new ClipData.Item(dragIntent); ClipData dragData = new ClipData(clipDescription, item); View.DragShadowBuilder myShadow = new View.DragShadowBuilder(snapshot); view.setOnDragListener(getDraggedViewDragListener()); - view.startDragAndDrop(dragData, myShadow, null, View.DRAG_FLAG_GLOBAL); + boolean result = view.startDragAndDrop(dragData, myShadow, null, View.DRAG_FLAG_GLOBAL + | View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION); + if (result) { + view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + if (enr.isPinned()) { + mHeadsUpManager.releaseAllImmediately(); + } else { + dismissShade(); + } + } } + private void dismissShade() { + // Speed up dismissing the shade since the drag needs to be handled by + // the shell layer underneath + mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */, + false /* delayed */, 1.1f /* speedUpFactor */); + } private Drawable getPkgIcon(String pkgName) { Drawable pkgicon = null; @@ -145,16 +180,6 @@ public class ExpandableNotificationRowDragController { return (view, dragEvent) -> { switch (dragEvent.getAction()) { case DragEvent.ACTION_DRAG_STARTED: - view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - if (view instanceof ExpandableNotificationRow) { - ExpandableNotificationRow enr = (ExpandableNotificationRow) view; - if (enr.isPinned()) { - mHeadsUpManager.releaseAllImmediately(); - } else { - Dependency.get(ShadeController.class).animateCollapsePanels( - CommandQueue.FLAG_EXCLUDE_NONE, true /* force */); - } - } return true; case DragEvent.ACTION_DRAG_ENDED: if (dragEvent.getResult()) { @@ -162,10 +187,55 @@ public class ExpandableNotificationRowDragController { ExpandableNotificationRow enr = (ExpandableNotificationRow) view; enr.dragAndDropSuccess(); } + } else { + // Fade out the drag surface in place instead of animating back to the + // start position now that the shade is closed + fadeOutAndRemoveDragSurface(dragEvent); } + // Clear the drag listener set above + view.setOnDragListener(null); return true; } return false; }; } + + private void fadeOutAndRemoveDragSurface(DragEvent dragEvent) { + SurfaceControl dragSurface = dragEvent.getDragSurface(); + SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); + ValueAnimator returnAnimator = ValueAnimator.ofFloat(0f, 1f); + returnAnimator.setDuration(200); + returnAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); + returnAnimator.addUpdateListener(animation -> { + float t = animation.getAnimatedFraction(); + float alpha = 1f - t; + tx.setAlpha(dragSurface, alpha); + tx.apply(); + }); + returnAnimator.addListener(new AnimatorListenerAdapter() { + private boolean mCanceled = false; + + @Override + public void onAnimationCancel(Animator animation) { + cleanUpSurface(); + mCanceled = true; + } + + @Override + public void onAnimationEnd(Animator animation) { + if (mCanceled) { + // Already handled above + return; + } + cleanUpSurface(); + } + + private void cleanUpSurface() { + tx.remove(dragSurface); + tx.apply(); + tx.close(); + } + }); + returnAnimator.start(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java index 6d7748388e14..05fba5498ea7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java @@ -28,6 +28,8 @@ import android.util.Log; import android.view.MotionEvent; import android.view.View; +import androidx.annotation.Nullable; + import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.assist.AssistManager; @@ -46,8 +48,11 @@ import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.unfold.FoldAodAnimationController; +import com.android.systemui.unfold.SysUIUnfoldComponent; import java.util.ArrayList; +import java.util.Optional; import javax.inject.Inject; @@ -73,6 +78,8 @@ public final class DozeServiceHost implements DozeHost { private final WakefulnessLifecycle mWakefulnessLifecycle; private final SysuiStatusBarStateController mStatusBarStateController; private final DeviceProvisionedController mDeviceProvisionedController; + @Nullable + private final FoldAodAnimationController mFoldAodAnimationController; private final HeadsUpManagerPhone mHeadsUpManagerPhone; private final BatteryController mBatteryController; private final ScrimController mScrimController; @@ -105,6 +112,7 @@ public final class DozeServiceHost implements DozeHost { Lazy<AssistManager> assistManagerLazy, DozeScrimController dozeScrimController, KeyguardUpdateMonitor keyguardUpdateMonitor, PulseExpansionHandler pulseExpansionHandler, + Optional<SysUIUnfoldComponent> sysUIUnfoldComponent, NotificationShadeWindowController notificationShadeWindowController, NotificationWakeUpCoordinator notificationWakeUpCoordinator, AuthController authController, @@ -128,6 +136,8 @@ public final class DozeServiceHost implements DozeHost { mNotificationWakeUpCoordinator = notificationWakeUpCoordinator; mAuthController = authController; mNotificationIconAreaController = notificationIconAreaController; + mFoldAodAnimationController = sysUIUnfoldComponent + .map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null); } // TODO: we should try to not pass status bar in here if we can avoid it. @@ -215,6 +225,9 @@ public final class DozeServiceHost implements DozeHost { mStatusBarStateController.setIsDozing(dozing); mNotificationShadeWindowViewController.setDozing(dozing); + if (mFoldAodAnimationController != null) { + mFoldAodAnimationController.setIsDozing(dozing); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java index 97225284f208..79d646cdbd13 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java @@ -42,7 +42,10 @@ import androidx.annotation.Nullable; import com.android.systemui.Dependency; import com.android.systemui.R; +import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.model.SysUiState; +import com.android.systemui.shared.system.QuickStepContract; import java.util.ArrayList; import java.util.List; @@ -64,7 +67,8 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh private final Context mContext; @Nullable private final DismissReceiver mDismissReceiver; private final Handler mHandler = new Handler(); - @Nullable private final SystemUIDialogManager mDialogManager; + private final SystemUIDialogManager mDialogManager; + private final SysUiState mSysUiState; private int mLastWidth = Integer.MIN_VALUE; private int mLastHeight = Integer.MIN_VALUE; @@ -77,24 +81,11 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh this(context, R.style.Theme_SystemUI_Dialog); } - public SystemUIDialog(Context context, SystemUIDialogManager dialogManager) { - this(context, R.style.Theme_SystemUI_Dialog, true, dialogManager); - } - public SystemUIDialog(Context context, int theme) { this(context, theme, true /* dismissOnDeviceLock */); } - public SystemUIDialog(Context context, int theme, SystemUIDialogManager dialogManager) { - this(context, theme, true /* dismissOnDeviceLock */, dialogManager); - } - public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock) { - this(context, theme, dismissOnDeviceLock, null); - } - - public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock, - @Nullable SystemUIDialogManager dialogManager) { super(context, theme); mContext = context; @@ -104,7 +95,12 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh getWindow().setAttributes(attrs); mDismissReceiver = dismissOnDeviceLock ? new DismissReceiver(this) : null; - mDialogManager = dialogManager; + + // TODO(b/219008720): Remove those calls to Dependency.get by introducing a + // SystemUIDialogFactory and make all other dialogs create a SystemUIDialog to which we set + // the content and attach listeners. + mDialogManager = Dependency.get(SystemUIDialogManager.class); + mSysUiState = Dependency.get(SysUiState.class); } @Override @@ -174,13 +170,11 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh mDismissReceiver.register(); } - if (mDialogManager != null) { - mDialogManager.setShowing(this, true); - } - // Listen for configuration changes to resize this dialog window. This is mostly necessary // for foldables that often go from large <=> small screen when folding/unfolding. ViewRootImpl.addConfigCallback(this); + mDialogManager.setShowing(this, true); + mSysUiState.setFlag(QuickStepContract.SYSUI_STATE_DIALOG_SHOWING, true); } @Override @@ -191,11 +185,9 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh mDismissReceiver.unregister(); } - if (mDialogManager != null) { - mDialogManager.setShowing(this, false); - } - ViewRootImpl.removeConfigCallback(this); + mDialogManager.setShowing(this, false); + mSysUiState.setFlag(QuickStepContract.SYSUI_STATE_DIALOG_SHOWING, false); } public void setShowForAllUsers(boolean show) { @@ -401,10 +393,13 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh private final Dialog mDialog; private boolean mRegistered; private final BroadcastDispatcher mBroadcastDispatcher; + private final DialogLaunchAnimator mDialogLaunchAnimator; DismissReceiver(Dialog dialog) { mDialog = dialog; + // TODO(b/219008720): Remove those calls to Dependency.get. mBroadcastDispatcher = Dependency.get(BroadcastDispatcher.class); + mDialogLaunchAnimator = Dependency.get(DialogLaunchAnimator.class); } void register() { @@ -421,6 +416,10 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh @Override public void onReceive(Context context, Intent intent) { + // These broadcast are usually received when locking the device, swiping up to home + // (which collapses the shade), etc. In those cases, we usually don't want to animate + // back into the view. + mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); mDialog.dismiss(); } } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt index 2a9076e8f848..e2374ada15d5 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt @@ -16,9 +16,12 @@ package com.android.systemui.unfold +import android.content.Context +import android.hardware.devicestate.DeviceStateManager import android.os.Handler import android.os.PowerManager import android.provider.Settings +import androidx.core.view.OneShotPreDrawListener import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.statusbar.LightRevealScrim @@ -27,6 +30,8 @@ import com.android.systemui.statusbar.phone.StatusBar import com.android.systemui.statusbar.policy.CallbackController import com.android.systemui.unfold.FoldAodAnimationController.FoldAodAnimationStatus import com.android.systemui.util.settings.GlobalSettings +import java.util.concurrent.Executor +import java.util.function.Consumer import javax.inject.Inject /** @@ -38,13 +43,21 @@ class FoldAodAnimationController @Inject constructor( @Main private val handler: Handler, + @Main private val executor: Executor, + private val context: Context, + private val deviceStateManager: DeviceStateManager, private val wakefulnessLifecycle: WakefulnessLifecycle, private val globalSettings: GlobalSettings ) : CallbackController<FoldAodAnimationStatus>, ScreenOffAnimation, WakefulnessLifecycle.Observer { + private lateinit var statusBar: StatusBar + + private var isFolded = false + private var isFoldHandled = true + private var alwaysOnEnabled: Boolean = false + private var isDozing: Boolean = false private var isScrimOpaque: Boolean = false - private lateinit var statusBar: StatusBar private var pendingScrimReadyCallback: Runnable? = null private var shouldPlayAnimation = false @@ -62,6 +75,7 @@ constructor( override fun initialize(statusBar: StatusBar, lightRevealScrim: LightRevealScrim) { this.statusBar = statusBar + deviceStateManager.registerCallback(executor, FoldListener()) wakefulnessLifecycle.addObserver(this) } @@ -84,7 +98,7 @@ constructor( override fun onStartedWakingUp() { if (isAnimationPlaying) { handler.removeCallbacks(startAnimationRunnable) - statusBar.notificationPanelViewController.cancelFoldToAodAnimation(); + statusBar.notificationPanelViewController.cancelFoldToAodAnimation() } setAnimationState(playing = false) @@ -105,11 +119,24 @@ constructor( */ fun onScreenTurningOn(onReady: Runnable) { if (shouldPlayAnimation) { + // The device was not dozing and going to sleep after folding, play the animation + if (isScrimOpaque) { onReady.run() } else { pendingScrimReadyCallback = onReady } + } else if (isFolded && !isFoldHandled && alwaysOnEnabled && isDozing) { + // Screen turning on for the first time after folding and we are already dozing + // We should play the folding to AOD animation + + setAnimationState(playing = true) + statusBar.notificationPanelViewController.prepareFoldToAodAnimation() + + // We don't need to wait for the scrim as it is already displayed + // but we should wait for the initial animation preparations to be drawn + // (setting initial alpha/translation) + OneShotPreDrawListener.add(statusBar.notificationPanelViewController.view, onReady) } else { // No animation, call ready callback immediately onReady.run() @@ -136,6 +163,10 @@ constructor( } } + fun setIsDozing(dozing: Boolean) { + isDozing = dozing + } + override fun isAnimationPlaying(): Boolean = isAnimationPlaying override fun isKeyguardHideDelayed(): Boolean = isAnimationPlaying() @@ -166,4 +197,15 @@ constructor( interface FoldAodAnimationStatus { fun onFoldToAodAnimationChanged() } + + private inner class FoldListener : + DeviceStateManager.FoldStateListener( + context, + Consumer { isFolded -> + if (!isFolded) { + // We are unfolded now, reset the fold handle status + isFoldHandled = false + } + this.isFolded = isFolded + }) } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index 6fefce2be19b..b2a79b01fb74 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -21,7 +21,7 @@ import static android.view.Display.DEFAULT_DISPLAY; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED; -import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED; @@ -97,7 +97,7 @@ 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 = - SYSUI_STATE_GLOBAL_ACTIONS_SHOWING + SYSUI_STATE_DIALOG_SHOWING | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED | SYSUI_STATE_BOUNCER_SHOWING diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java index 40632a85d722..7a0db1fd975c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java +++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java @@ -42,6 +42,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.SmartReplyController; +import com.android.systemui.statusbar.phone.SystemUIDialogManager; import org.junit.After; import org.junit.AfterClass; @@ -112,6 +113,11 @@ public abstract class SysuiTestCase { // KeyguardUpdateMonitor to be created (injected). // TODO(b/1531701009) Clean up NotificationContentView creation to prevent this mDependency.injectMockDependency(SmartReplyController.class); + + // Make sure that all tests on any SystemUIDialog does not crash because this dependency + // is missing (constructing the actual one would throw). + // TODO(b/219008720): Remove this. + mDependency.injectMockDependency(SystemUIDialogManager.class); } @After diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java index 7f72dda441d2..658702929d7b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java @@ -29,8 +29,6 @@ import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.battery.BatteryMeterViewController; -import com.android.systemui.statusbar.policy.BatteryController; import org.junit.Before; import org.junit.Test; @@ -46,10 +44,6 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { @Mock DreamOverlayStatusBarView mView; @Mock - BatteryController mBatteryController; - @Mock - BatteryMeterViewController mBatteryMeterViewController; - @Mock ConnectivityManager mConnectivityManager; @Mock NetworkCapabilities mNetworkCapabilities; @@ -61,22 +55,7 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { @Before public void setup() { MockitoAnnotations.initMocks(this); - mController = new DreamOverlayStatusBarViewController( - mContext, mView, mBatteryController, mBatteryMeterViewController, - mConnectivityManager); - } - - @Test - public void testOnInitInitializesControllers() { - mController.onInit(); - verify(mBatteryMeterViewController).init(); - } - - @Test - public void testOnViewAttachedAddsBatteryControllerCallback() { - mController.onViewAttached(); - verify(mBatteryController) - .addCallback(any(BatteryController.BatteryStateChangeCallback.class)); + mController = new DreamOverlayStatusBarViewController(mView, mConnectivityManager); } @Test @@ -113,13 +92,6 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { } @Test - public void testOnViewDetachedRemovesBatteryControllerCallback() { - mController.onViewDetached(); - verify(mBatteryController) - .removeCallback(any(BatteryController.BatteryStateChangeCallback.class)); - } - - @Test public void testOnViewDetachedUnregistersNetworkCallback() { mController.onViewDetached(); verify(mConnectivityManager) @@ -127,26 +99,6 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase { } @Test - public void testBatteryPercentTextShownWhenBatteryLevelChangesWhileCharging() { - final ArgumentCaptor<BatteryController.BatteryStateChangeCallback> callbackCapture = - ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback.class); - mController.onViewAttached(); - verify(mBatteryController).addCallback(callbackCapture.capture()); - callbackCapture.getValue().onBatteryLevelChanged(1, true, true); - verify(mView).showBatteryPercentText(true); - } - - @Test - public void testBatteryPercentTextHiddenWhenBatteryLevelChangesWhileNotCharging() { - final ArgumentCaptor<BatteryController.BatteryStateChangeCallback> callbackCapture = - ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback.class); - mController.onViewAttached(); - verify(mBatteryController).addCallback(callbackCapture.capture()); - callbackCapture.getValue().onBatteryLevelChanged(1, true, false); - verify(mView).showBatteryPercentText(false); - } - - @Test public void testWifiStatusHiddenWhenWifiBecomesAvailable() { // Make sure wifi starts out unavailable when onViewAttached is called. when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java index 71fc8ee6cce8..953be7d6f002 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java @@ -19,7 +19,6 @@ package com.android.systemui.globalactions; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; @@ -57,13 +56,11 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.colorextraction.SysuiColorExtractor; -import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.GlobalActions; import com.android.systemui.settings.UserContextProvider; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.phone.StatusBar; -import com.android.systemui.statusbar.phone.SystemUIDialogManager; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.telephony.TelephonyListenerManager; @@ -112,7 +109,6 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { @Mock private UiEventLogger mUiEventLogger; @Mock private RingerModeTracker mRingerModeTracker; @Mock private RingerModeLiveData mRingerModeLiveData; - @Mock private SysUiState mSysUiState; @Mock private PackageManager mPackageManager; @Mock private Handler mHandler; @Mock private UserContextProvider mUserContextProvider; @@ -120,7 +116,6 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { @Mock private StatusBar mStatusBar; @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Mock private DialogLaunchAnimator mDialogLaunchAnimator; - @Mock private SystemUIDialogManager mDialogManager; private TestableLooper mTestableLooper; @@ -161,19 +156,16 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { mBackgroundExecutor, mUiEventLogger, mRingerModeTracker, - mSysUiState, mHandler, mPackageManager, Optional.of(mStatusBar), mKeyguardUpdateMonitor, - mDialogLaunchAnimator, - mDialogManager); + mDialogLaunchAnimator); mGlobalActionsDialogLite.setZeroDialogPressDelayForTesting(); ColorExtractor.GradientColors backdropColors = new ColorExtractor.GradientColors(); backdropColors.setMainColor(Color.BLACK); when(mColorExtractor.getNeutralColors()).thenReturn(backdropColors); - when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java index c5c4d79e043d..2be30b39763e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java @@ -45,7 +45,6 @@ import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.phone.ShadeController; -import com.android.systemui.statusbar.phone.SystemUIDialogManager; import org.junit.Before; import org.junit.Test; @@ -68,7 +67,6 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { mock(NotificationEntryManager.class); private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class); private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class); - private final SystemUIDialogManager mDialogManager = mock(SystemUIDialogManager.class); private MediaOutputBaseDialogImpl mMediaOutputBaseDialogImpl; private MediaOutputController mMediaOutputController; @@ -82,7 +80,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { public void setUp() { mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, false, mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter, - mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator, mDialogManager); + mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator); mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mMediaOutputController); mMediaOutputBaseDialogImpl.onCreate(new Bundle()); @@ -175,7 +173,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { class MediaOutputBaseDialogImpl extends MediaOutputBaseDialog { MediaOutputBaseDialogImpl(Context context, MediaOutputController mediaOutputController) { - super(context, mediaOutputController, mDialogManager); + super(context, mediaOutputController); mAdapter = mMediaOutputBaseAdapter; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java index bdc311725880..789822e262d5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java @@ -55,7 +55,6 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; import com.android.systemui.statusbar.phone.ShadeController; -import com.android.systemui.statusbar.phone.SystemUIDialogManager; import org.junit.Before; import org.junit.Test; @@ -94,7 +93,6 @@ public class MediaOutputControllerTest extends SysuiTestCase { private CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class); private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class); private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class); - private final SystemUIDialogManager mDialogManager = mock(SystemUIDialogManager.class); private Context mSpyContext; private MediaOutputController mMediaOutputController; @@ -117,7 +115,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mMediaOutputController = new MediaOutputController(mSpyContext, TEST_PACKAGE_NAME, false, mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter, - mNotifCollection, mUiEventLogger, mDialogLaunchAnimator, mDialogManager); + mNotifCollection, mUiEventLogger, mDialogLaunchAnimator); mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager); mMediaOutputController.mLocalMediaManager = mLocalMediaManager; MediaDescription.Builder builder = new MediaDescription.Builder(); @@ -161,7 +159,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { public void start_withoutPackageName_verifyMediaControllerInit() { mMediaOutputController = new MediaOutputController(mSpyContext, null, false, mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter, - mNotifCollection, mUiEventLogger, mDialogLaunchAnimator, mDialogManager); + mNotifCollection, mUiEventLogger, mDialogLaunchAnimator); mMediaOutputController.start(mCb); @@ -182,7 +180,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { public void stop_withoutPackageName_verifyMediaControllerDeinit() { mMediaOutputController = new MediaOutputController(mSpyContext, null, false, mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter, - mNotifCollection, mUiEventLogger, mDialogLaunchAnimator, mDialogManager); + mNotifCollection, mUiEventLogger, mDialogLaunchAnimator); mMediaOutputController.start(mCb); @@ -453,7 +451,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { public void getNotificationLargeIcon_withoutPackageName_returnsNull() { mMediaOutputController = new MediaOutputController(mSpyContext, null, false, mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter, - mNotifCollection, mUiEventLogger, mDialogLaunchAnimator, mDialogManager); + mNotifCollection, mUiEventLogger, mDialogLaunchAnimator); assertThat(mMediaOutputController.getNotificationIcon()).isNull(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java index ada8d3592012..8a3ea562269d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java @@ -40,7 +40,6 @@ import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.phone.ShadeController; -import com.android.systemui.statusbar.phone.SystemUIDialogManager; import org.junit.After; import org.junit.Before; @@ -68,7 +67,6 @@ public class MediaOutputDialogTest extends SysuiTestCase { mock(NotificationEntryManager.class); private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class); private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class); - private final SystemUIDialogManager mDialogManager = mock(SystemUIDialogManager.class); private MediaOutputDialog mMediaOutputDialog; private MediaOutputController mMediaOutputController; @@ -78,10 +76,10 @@ public class MediaOutputDialogTest extends SysuiTestCase { public void setUp() { mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, false, mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter, - mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator, mDialogManager); + mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator); mMediaOutputController.mLocalMediaManager = mLocalMediaManager; mMediaOutputDialog = new MediaOutputDialog(mContext, false, - mMediaOutputController, mUiEventLogger, mDialogManager); + mMediaOutputController, mUiEventLogger); mMediaOutputDialog.show(); when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice); @@ -127,7 +125,7 @@ public class MediaOutputDialogTest extends SysuiTestCase { // and verify if the calling times increases. public void onCreate_ShouldLogVisibility() { MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, - mMediaOutputController, mUiEventLogger, mDialogManager); + mMediaOutputController, mUiEventLogger); testDialog.show(); testDialog.dismissDialog(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java index b114452facc3..e8cd6c88956d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java @@ -38,7 +38,6 @@ import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.phone.ShadeController; -import com.android.systemui.statusbar.phone.SystemUIDialogManager; import org.junit.After; import org.junit.Before; @@ -67,7 +66,6 @@ public class MediaOutputGroupDialogTest extends SysuiTestCase { mock(NotificationEntryManager.class); private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class); private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class); - private final SystemUIDialogManager mDialogManager = mock(SystemUIDialogManager.class); private MediaOutputGroupDialog mMediaOutputGroupDialog; private MediaOutputController mMediaOutputController; @@ -77,10 +75,10 @@ public class MediaOutputGroupDialogTest extends SysuiTestCase { public void setUp() { mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, false, mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter, - mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator, mDialogManager); + mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator); mMediaOutputController.mLocalMediaManager = mLocalMediaManager; mMediaOutputGroupDialog = new MediaOutputGroupDialog(mContext, false, - mMediaOutputController, mDialogManager); + mMediaOutputController); mMediaOutputGroupDialog.show(); when(mLocalMediaManager.getSelectedMediaDevice()).thenReturn(mMediaDevices); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java index 24a0ad3de196..bc54bf8ebb7e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java @@ -18,6 +18,8 @@ package com.android.systemui.statusbar.notification.row; import static android.view.DragEvent.ACTION_DRAG_STARTED; +import android.app.Notification; +import android.app.PendingIntent; import android.content.Context; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -36,7 +38,12 @@ import org.junit.Test; import org.junit.runner.RunWith; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -58,6 +65,7 @@ public class ExpandableNotificationRowDragControllerTest extends SysuiTestCase { private NotificationMenuRow mMenuRow = mock(NotificationMenuRow.class); private NotificationMenuRowPlugin.MenuItem mMenuItem = mock(NotificationMenuRowPlugin.MenuItem.class); + private ShadeController mShadeController = mock(ShadeController.class); @Before public void setUp() throws Exception { @@ -69,11 +77,15 @@ public class ExpandableNotificationRowDragControllerTest extends SysuiTestCase { mContext, mDependency, TestableLooper.get(this)); - mRow = mNotificationTestHelper.createRow(); + mRow = spy(mNotificationTestHelper.createRow()); + Notification notification = mRow.getEntry().getSbn().getNotification(); + notification.contentIntent = mock(PendingIntent.class); + doReturn(true).when(mRow).startDragAndDrop(any(), any(), any(), anyInt()); mGroupRow = mNotificationTestHelper.createGroup(4); when(mMenuRow.getLongpressMenuItem(any(Context.class))).thenReturn(mMenuItem); - mController = new ExpandableNotificationRowDragController(mContext, mHeadsUpManager); + mController = new ExpandableNotificationRowDragController(mContext, mHeadsUpManager, + mShadeController); } @Test @@ -86,10 +98,6 @@ public class ExpandableNotificationRowDragControllerTest extends SysuiTestCase { mRow.doLongClickCallback(0, 0); mRow.doDragCallback(0, 0); verify(controller).startDragAndDrop(mRow); - - // Simulate the drag start - mRow.dispatchDragEvent(DragEvent.obtain(ACTION_DRAG_STARTED, 0, 0, 0, 0, null, null, null, - null, null, false)); verify(mHeadsUpManager, times(1)).releaseAllImmediately(); } @@ -98,14 +106,27 @@ public class ExpandableNotificationRowDragControllerTest extends SysuiTestCase { ExpandableNotificationRowDragController controller = createSpyController(); mRow.setDragController(controller); - mDependency.get(ShadeController.class).instantExpandNotificationsPanel(); + mRow.doDragCallback(0, 0); + verify(controller).startDragAndDrop(mRow); + verify(mShadeController).animateCollapsePanels(eq(0), eq(true), + eq(false), anyFloat()); + } + + @Test + public void testDoStartDrag_noLaunchIntent() throws Exception { + ExpandableNotificationRowDragController controller = createSpyController(); + mRow.setDragController(controller); + + // Clear the intents + Notification notification = mRow.getEntry().getSbn().getNotification(); + notification.contentIntent = null; + notification.fullScreenIntent = null; + mRow.doDragCallback(0, 0); verify(controller).startDragAndDrop(mRow); - // Simulate the drag start - mRow.dispatchDragEvent(DragEvent.obtain(ACTION_DRAG_STARTED, 0, 0, 0, 0, null, null, null, - null, null, false)); - verify(mDependency.get(ShadeController.class)).animateCollapsePanels(0, true); + // Verify that we never start the actual drag since there is no content + verify(mRow, never()).startDragAndDrop(any(), any(), any(), anyInt()); } private ExpandableNotificationRowDragController createSpyController() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java index 38d7ce76f1b9..6ce3b4b7b6f9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java @@ -59,6 +59,7 @@ import org.mockito.MockitoAnnotations; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.Optional; @SmallTest @RunWith(AndroidTestingRunner.class) @@ -97,7 +98,7 @@ public class DozeServiceHostTest extends SysuiTestCase { mStatusBarStateController, mDeviceProvisionedController, mHeadsUpManager, mBatteryController, mScrimController, () -> mBiometricUnlockController, mKeyguardViewMediator, () -> mAssistManager, mDozeScrimController, - mKeyguardUpdateMonitor, mPulseExpansionHandler, + mKeyguardUpdateMonitor, mPulseExpansionHandler, Optional.empty(), mNotificationShadeWindowController, mNotificationWakeUpCoordinator, mAuthController, mNotificationIconAreaController); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 62da981bd9a7..0e9926590511 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -1363,8 +1363,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub * </p> * * @param displayId The logical display id - * @param region the new magnified region, may be empty if - * magnification is not enabled (e.g. scale is 1) + * @param region The magnification region. + * If the config mode is + * {@link MagnificationConfig#MAGNIFICATION_MODE_FULLSCREEN}, + * it is the region of the screen currently active for magnification. + * the returned region will be empty if the magnification is not active + * (e.g. scale is 1. And the magnification is active if magnification + * gestures are enabled or if a service is running that can control + * magnification. + * If the config mode is + * {@link MagnificationConfig#MAGNIFICATION_MODE_WINDOW}, + * it is the region of screen projected on the magnification window. + * The region will be empty if magnification is not activated. * @param config The magnification config. That has magnification mode, the new scale and the * new screen-relative center position */ diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java index fe97a46e12f9..a95820966926 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java @@ -50,7 +50,6 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.LocalServices; -import com.android.server.accessibility.AccessibilityManagerService; import com.android.server.accessibility.AccessibilityTraceManager; import com.android.server.wm.WindowManagerInternal; @@ -374,9 +373,8 @@ public class FullScreenMagnificationController implements .setScale(getScale()) .setCenterX(getCenterX()) .setCenterY(getCenterY()).build(); - mControllerCtx.getAms().notifyMagnificationChanged(mDisplayId, - mMagnificationRegion, - config); + mMagnificationInfoChangedCallback.onFullScreenMagnificationChanged(mDisplayId, + mMagnificationRegion, config); if (mUnregisterPending && !isMagnifying()) { unregister(mDeleteAfterUnregister); } @@ -665,10 +663,10 @@ public class FullScreenMagnificationController implements * FullScreenMagnificationController Constructor */ public FullScreenMagnificationController(@NonNull Context context, - @NonNull AccessibilityManagerService ams, @NonNull Object lock, + @NonNull AccessibilityTraceManager traceManager, @NonNull Object lock, @NonNull MagnificationInfoChangedCallback magnificationInfoChangedCallback, @NonNull MagnificationScaleProvider scaleProvider) { - this(new ControllerContext(context, ams, + this(new ControllerContext(context, traceManager, LocalServices.getService(WindowManagerInternal.class), new Handler(context.getMainLooper()), context.getResources().getInteger(R.integer.config_longAnimTime)), lock, @@ -1521,7 +1519,6 @@ public class FullScreenMagnificationController implements @VisibleForTesting public static class ControllerContext { private final Context mContext; - private final AccessibilityManagerService mAms; private final AccessibilityTraceManager mTrace; private final WindowManagerInternal mWindowManager; private final Handler mHandler; @@ -1531,13 +1528,12 @@ public class FullScreenMagnificationController implements * Constructor for ControllerContext. */ public ControllerContext(@NonNull Context context, - @NonNull AccessibilityManagerService ams, + @NonNull AccessibilityTraceManager traceManager, @NonNull WindowManagerInternal windowManager, @NonNull Handler handler, long animationDuration) { mContext = context; - mAms = ams; - mTrace = ams.getTraceManager(); + mTrace = traceManager; mWindowManager = windowManager; mHandler = handler; mAnimationDuration = animationDuration; @@ -1552,14 +1548,6 @@ public class FullScreenMagnificationController implements } /** - * @return AccessibilityManagerService - */ - @NonNull - public AccessibilityManagerService getAms() { - return mAms; - } - - /** * @return AccessibilityTraceManager */ @NonNull @@ -1632,5 +1620,17 @@ public class FullScreenMagnificationController implements * hidden. */ void onImeWindowVisibilityChanged(boolean shown); + + /** + * Called when the magnification spec changed. + * + * @param displayId The logical display id + * @param region The region of the screen currently active for magnification. + * The returned region will be empty if the magnification is not active. + * @param config The magnification config. That has magnification mode, the new scale and + * the new screen-relative center position + */ + void onFullScreenMagnificationChanged(int displayId, @NonNull Region region, + @NonNull MagnificationConfig config); } } 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 c376bf873dc8..09e82c787c90 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java @@ -405,6 +405,12 @@ public class MagnificationController implements WindowMagnificationManager.Callb mAms.notifyMagnificationChanged(displayId, new Region(bounds), config); } + @Override + public void onFullScreenMagnificationChanged(int displayId, @NonNull Region region, + @NonNull MagnificationConfig config) { + mAms.notifyMagnificationChanged(displayId, region, config); + } + private void disableFullScreenMagnificationIfNeeded(int displayId) { final FullScreenMagnificationController fullScreenMagnificationController = getFullScreenMagnificationController(); @@ -590,7 +596,7 @@ public class MagnificationController implements WindowMagnificationManager.Callb synchronized (mLock) { if (mFullScreenMagnificationController == null) { mFullScreenMagnificationController = new FullScreenMagnificationController(mContext, - mAms, mLock, this, mScaleProvider); + mAms.getTraceManager(), mLock, this, mScaleProvider); } } return mFullScreenMagnificationController; diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java b/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java index 89633373b152..7e3ede15aa04 100644 --- a/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java +++ b/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java @@ -22,20 +22,18 @@ import android.app.backup.RestoreDescription; import android.app.backup.RestoreSet; import android.content.Intent; import android.content.pm.PackageInfo; -import android.os.Binder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.util.Slog; -import com.android.internal.annotations.GuardedBy; import com.android.internal.backup.IBackupTransport; import com.android.internal.infra.AndroidFuture; import java.util.ArrayDeque; -import java.util.Deque; +import java.util.HashSet; import java.util.List; import java.util.Queue; -import java.util.concurrent.ArrayBlockingQueue; +import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -49,17 +47,19 @@ public class BackupTransportClient { private final IBackupTransport mTransportBinder; private final TransportStatusCallbackPool mCallbackPool; + private final TransportFutures mTransportFutures; BackupTransportClient(IBackupTransport transportBinder) { mTransportBinder = transportBinder; mCallbackPool = new TransportStatusCallbackPool(); + mTransportFutures = new TransportFutures(); } /** * See {@link IBackupTransport#name()}. */ public String name() throws RemoteException { - AndroidFuture<String> resultFuture = new AndroidFuture<>(); + AndroidFuture<String> resultFuture = mTransportFutures.newFuture(); mTransportBinder.name(resultFuture); return getFutureResult(resultFuture); } @@ -68,7 +68,7 @@ public class BackupTransportClient { * See {@link IBackupTransport#configurationIntent()} */ public Intent configurationIntent() throws RemoteException { - AndroidFuture<Intent> resultFuture = new AndroidFuture<>(); + AndroidFuture<Intent> resultFuture = mTransportFutures.newFuture(); mTransportBinder.configurationIntent(resultFuture); return getFutureResult(resultFuture); } @@ -77,7 +77,7 @@ public class BackupTransportClient { * See {@link IBackupTransport#currentDestinationString()} */ public String currentDestinationString() throws RemoteException { - AndroidFuture<String> resultFuture = new AndroidFuture<>(); + AndroidFuture<String> resultFuture = mTransportFutures.newFuture(); mTransportBinder.currentDestinationString(resultFuture); return getFutureResult(resultFuture); } @@ -86,7 +86,7 @@ public class BackupTransportClient { * See {@link IBackupTransport#dataManagementIntent()} */ public Intent dataManagementIntent() throws RemoteException { - AndroidFuture<Intent> resultFuture = new AndroidFuture<>(); + AndroidFuture<Intent> resultFuture = mTransportFutures.newFuture(); mTransportBinder.dataManagementIntent(resultFuture); return getFutureResult(resultFuture); } @@ -96,7 +96,7 @@ public class BackupTransportClient { */ @Nullable public CharSequence dataManagementIntentLabel() throws RemoteException { - AndroidFuture<CharSequence> resultFuture = new AndroidFuture<>(); + AndroidFuture<CharSequence> resultFuture = mTransportFutures.newFuture(); mTransportBinder.dataManagementIntentLabel(resultFuture); return getFutureResult(resultFuture); } @@ -105,7 +105,7 @@ public class BackupTransportClient { * See {@link IBackupTransport#transportDirName()} */ public String transportDirName() throws RemoteException { - AndroidFuture<String> resultFuture = new AndroidFuture<>(); + AndroidFuture<String> resultFuture = mTransportFutures.newFuture(); mTransportBinder.transportDirName(resultFuture); return getFutureResult(resultFuture); } @@ -153,7 +153,7 @@ public class BackupTransportClient { * See {@link IBackupTransport#requestBackupTime()} */ public long requestBackupTime() throws RemoteException { - AndroidFuture<Long> resultFuture = new AndroidFuture<>(); + AndroidFuture<Long> resultFuture = mTransportFutures.newFuture(); mTransportBinder.requestBackupTime(resultFuture); Long result = getFutureResult(resultFuture); return result == null ? BackupTransport.TRANSPORT_ERROR : result; @@ -177,7 +177,7 @@ public class BackupTransportClient { * See {@link IBackupTransport#getAvailableRestoreSets()} */ public RestoreSet[] getAvailableRestoreSets() throws RemoteException { - AndroidFuture<List<RestoreSet>> resultFuture = new AndroidFuture<>(); + AndroidFuture<List<RestoreSet>> resultFuture = mTransportFutures.newFuture(); mTransportBinder.getAvailableRestoreSets(resultFuture); List<RestoreSet> result = getFutureResult(resultFuture); return result == null ? null : result.toArray(new RestoreSet[] {}); @@ -187,7 +187,7 @@ public class BackupTransportClient { * See {@link IBackupTransport#getCurrentRestoreSet()} */ public long getCurrentRestoreSet() throws RemoteException { - AndroidFuture<Long> resultFuture = new AndroidFuture<>(); + AndroidFuture<Long> resultFuture = mTransportFutures.newFuture(); mTransportBinder.getCurrentRestoreSet(resultFuture); Long result = getFutureResult(resultFuture); return result == null ? BackupTransport.TRANSPORT_ERROR : result; @@ -210,7 +210,7 @@ public class BackupTransportClient { * See {@link IBackupTransport#nextRestorePackage()} */ public RestoreDescription nextRestorePackage() throws RemoteException { - AndroidFuture<RestoreDescription> resultFuture = new AndroidFuture<>(); + AndroidFuture<RestoreDescription> resultFuture = mTransportFutures.newFuture(); mTransportBinder.nextRestorePackage(resultFuture); return getFutureResult(resultFuture); } @@ -245,7 +245,7 @@ public class BackupTransportClient { * See {@link IBackupTransport#requestFullBackupTime()} */ public long requestFullBackupTime() throws RemoteException { - AndroidFuture<Long> resultFuture = new AndroidFuture<>(); + AndroidFuture<Long> resultFuture = mTransportFutures.newFuture(); mTransportBinder.requestFullBackupTime(resultFuture); Long result = getFutureResult(resultFuture); return result == null ? BackupTransport.TRANSPORT_ERROR : result; @@ -309,7 +309,7 @@ public class BackupTransportClient { */ public boolean isAppEligibleForBackup(PackageInfo targetPackage, boolean isFullBackup) throws RemoteException { - AndroidFuture<Boolean> resultFuture = new AndroidFuture<>(); + AndroidFuture<Boolean> resultFuture = mTransportFutures.newFuture(); mTransportBinder.isAppEligibleForBackup(targetPackage, isFullBackup, resultFuture); Boolean result = getFutureResult(resultFuture); return result != null && result; @@ -319,7 +319,7 @@ public class BackupTransportClient { * See {@link IBackupTransport#getBackupQuota(String, boolean)} */ public long getBackupQuota(String packageName, boolean isFullBackup) throws RemoteException { - AndroidFuture<Long> resultFuture = new AndroidFuture<>(); + AndroidFuture<Long> resultFuture = mTransportFutures.newFuture(); mTransportBinder.getBackupQuota(packageName, isFullBackup, resultFuture); Long result = getFutureResult(resultFuture); return result == null ? BackupTransport.TRANSPORT_ERROR : result; @@ -355,18 +355,58 @@ public class BackupTransportClient { * See {@link IBackupTransport#getTransportFlags()} */ public int getTransportFlags() throws RemoteException { - AndroidFuture<Integer> resultFuture = new AndroidFuture<>(); + AndroidFuture<Integer> resultFuture = mTransportFutures.newFuture(); mTransportBinder.getTransportFlags(resultFuture); Integer result = getFutureResult(resultFuture); return result == null ? BackupTransport.TRANSPORT_ERROR : result; } + /** + * Allows the {@link TransportConnection} to notify this client + * if the underlying transport has become unusable. If that happens + * we want to cancel all active futures or callbacks. + */ + void onBecomingUnusable() { + mCallbackPool.cancelActiveCallbacks(); + mTransportFutures.cancelActiveFutures(); + } + private <T> T getFutureResult(AndroidFuture<T> future) { try { return future.get(600, TimeUnit.SECONDS); } catch (InterruptedException | ExecutionException | TimeoutException e) { Slog.w(TAG, "Failed to get result from transport:", e); return null; + } finally { + mTransportFutures.remove(future); + } + } + + private static class TransportFutures { + private final Object mActiveFuturesLock = new Object(); + private final Set<AndroidFuture<?>> mActiveFutures = new HashSet<>(); + + <T> AndroidFuture<T> newFuture() { + AndroidFuture<T> future = new AndroidFuture<>(); + synchronized (mActiveFuturesLock) { + mActiveFutures.add(future); + } + return future; + } + + <T> void remove(AndroidFuture<T> future) { + synchronized (mActiveFuturesLock) { + mActiveFutures.remove(future); + } + } + + void cancelActiveFutures() { + synchronized (mActiveFuturesLock) { + for (AndroidFuture<?> future : mActiveFutures) { + future.cancel(true); + } + mActiveFutures.clear(); + } } } @@ -375,27 +415,47 @@ public class BackupTransportClient { private final Object mPoolLock = new Object(); private final Queue<TransportStatusCallback> mCallbackPool = new ArrayDeque<>(); + private final Set<TransportStatusCallback> mActiveCallbacks = new HashSet<>(); TransportStatusCallback acquire() { synchronized (mPoolLock) { - if (mCallbackPool.isEmpty()) { - return new TransportStatusCallback(); - } else { - return mCallbackPool.poll(); + TransportStatusCallback callback = mCallbackPool.poll(); + if (callback == null) { + callback = new TransportStatusCallback(); } + callback.reset(); + mActiveCallbacks.add(callback); + return callback; } } void recycle(TransportStatusCallback callback) { synchronized (mPoolLock) { + mActiveCallbacks.remove(callback); if (mCallbackPool.size() > MAX_POOL_SIZE) { Slog.d(TAG, "TransportStatusCallback pool size exceeded"); return; } - - callback.reset(); mCallbackPool.add(callback); } } + + void cancelActiveCallbacks() { + synchronized (mPoolLock) { + for (TransportStatusCallback callback : mActiveCallbacks) { + try { + callback.onOperationCompleteWithStatus(BackupTransport.TRANSPORT_ERROR); + // This waits for status to propagate before the callback is reset. + callback.getOperationStatus(); + } catch (RemoteException ex) { + // Nothing we can do. + } + if (mCallbackPool.size() < MAX_POOL_SIZE) { + mCallbackPool.add(callback); + } + } + mActiveCallbacks.clear(); + } + } } } diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnection.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnection.java index f9a3c36a3220..1009787bebe5 100644 --- a/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnection.java +++ b/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnection.java @@ -449,6 +449,9 @@ public class TransportConnection { private void onServiceDisconnected() { synchronized (mStateLock) { log(Priority.ERROR, "Service disconnected: client UNUSABLE"); + if (mTransport != null) { + mTransport.onBecomingUnusable(); + } setStateLocked(State.UNUSABLE, null); try { // After unbindService() no calls back to mConnection @@ -473,6 +476,9 @@ public class TransportConnection { checkStateIntegrityLocked(); log(Priority.ERROR, "Binding died: client UNUSABLE"); + if (mTransport != null) { + mTransport.onBecomingUnusable(); + } // After unbindService() no calls back to mConnection switch (mState) { case State.UNUSABLE: diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/TransportStatusCallback.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportStatusCallback.java index a55178c27eef..bc5cb0250d56 100644 --- a/services/backup/backuplib/java/com/android/server/backup/transport/TransportStatusCallback.java +++ b/services/backup/backuplib/java/com/android/server/backup/transport/TransportStatusCallback.java @@ -75,13 +75,11 @@ public class TransportStatusCallback extends ITransportStatusCallback.Stub { } Slog.w(TAG, "Couldn't get operation status from transport"); - return BackupTransport.TRANSPORT_ERROR; } catch (InterruptedException e) { Slog.w(TAG, "Couldn't get operation status from transport: ", e); - return BackupTransport.TRANSPORT_ERROR; - } finally { - reset(); } + + return BackupTransport.TRANSPORT_ERROR; } synchronized void reset() { diff --git a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java index 21a677b8383c..cb28254f70db 100644 --- a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java +++ b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java @@ -171,12 +171,20 @@ class AssociationStoreImpl implements AssociationStore { broadcastChange(CHANGE_TYPE_REMOVED, association); } + /** + * @return a "snapshot" of the current state of the existing associations. + */ public @NonNull Collection<AssociationInfo> getAssociations() { - final Collection<AssociationInfo> allAssociations; synchronized (mLock) { - allAssociations = mIdMap.values(); + // IMPORTANT: make and return a COPY of the mIdMap.values(), NOT a "direct" reference. + // The HashMap.values() returns a collection which is backed by the HashMap, so changes + // to the HashMap are reflected in this collection. + // For us this means that if mIdMap is modified while the iteration over mIdMap.values() + // is in progress it may lead to "undefined results" (according to the HashMap's + // documentation) or cause ConcurrentModificationExceptions in the iterator (according + // to the bugreports...). + return List.copyOf(mIdMap.values()); } - return Collections.unmodifiableCollection(allAssociations); } public @NonNull List<AssociationInfo> getAssociationsForUser(@UserIdInt int userId) { diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 09ef03c38188..111bd340a3d1 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -67,7 +67,7 @@ import java.util.function.Consumer; * * @hide Only for use within the system server. */ -public abstract class PackageManagerInternal implements PackageSettingsSnapshotProvider { +public abstract class PackageManagerInternal { @IntDef(prefix = "PACKAGE_", value = { PACKAGE_SYSTEM, PACKAGE_SETUP_WIZARD, diff --git a/services/core/java/android/content/pm/PackageSettingsSnapshotProvider.java b/services/core/java/android/content/pm/PackageSettingsSnapshotProvider.java deleted file mode 100644 index 221f172c3463..000000000000 --- a/services/core/java/android/content/pm/PackageSettingsSnapshotProvider.java +++ /dev/null @@ -1,77 +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 android.content.pm; - -import android.annotation.NonNull; - -import com.android.internal.util.FunctionalUtils; -import com.android.server.pm.PackageManagerService; -import com.android.server.pm.PackageSetting; -import com.android.server.pm.pkg.PackageStateInternal; - -import java.util.function.Consumer; -import java.util.function.Function; - -/** @hide */ -public interface PackageSettingsSnapshotProvider { - - /** - * Run a function block that requires access to {@link PackageStateInternal} data. This will - * ensure the {@link PackageManagerService} lock is taken before any caller's internal lock - * to avoid deadlock. Note that this method may or may not lock. If a snapshot is available - * and valid, it will iterate the snapshot set of data. - */ - void withPackageSettingsSnapshot( - @NonNull Consumer<Function<String, PackageStateInternal>> block); - - /** - * Variant which returns a value to the caller. - * @see #withPackageSettingsSnapshot(Consumer) - */ - <Output> Output withPackageSettingsSnapshotReturning( - @NonNull FunctionalUtils.ThrowingFunction<Function<String, PackageStateInternal>, - Output> block); - - /** - * Variant which throws. - * @see #withPackageSettingsSnapshot(Consumer) - */ - <ExceptionType extends Exception> void withPackageSettingsSnapshotThrowing( - @NonNull FunctionalUtils.ThrowingCheckedConsumer<Function<String, PackageStateInternal>, - ExceptionType> block) throws ExceptionType; - - /** - * Variant which throws 2 exceptions. - * @see #withPackageSettingsSnapshot(Consumer) - */ - <ExceptionOne extends Exception, ExceptionTwo extends Exception> void - withPackageSettingsSnapshotThrowing2( - @NonNull FunctionalUtils.ThrowingChecked2Consumer< - Function<String, PackageStateInternal>, - ExceptionOne, ExceptionTwo> block) - throws ExceptionOne, ExceptionTwo; - - /** - * Variant which returns a value to the caller and throws. - * @see #withPackageSettingsSnapshot(Consumer) - */ - <Output, ExceptionType extends Exception> Output - withPackageSettingsSnapshotReturningThrowing( - @NonNull FunctionalUtils.ThrowingCheckedFunction< - Function<String, PackageStateInternal>, Output, ExceptionType> block) - throws ExceptionType; -} diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index 9ba9d78591dd..b813bc48118a 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -159,7 +159,7 @@ public final class GameManagerService extends IGameManagerService.Stub { mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class); if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_GAME_SERVICE)) { mGameServiceController = new GameServiceController( - BackgroundThread.getExecutor(), + context, BackgroundThread.getExecutor(), new GameServiceProviderSelectorImpl( context.getResources(), context.getPackageManager()), @@ -376,9 +376,10 @@ public final class GameManagerService extends IGameManagerService.Stub { /** * Called by games to communicate the current state to the platform. + * * @param packageName The client package name. - * @param gameState An object set to the current state. - * @param userId The user associated with this state. + * @param gameState An object set to the current state. + * @param userId The user associated with this state. */ public void setGameState(String packageName, @NonNull GameState gameState, @UserIdInt int userId) { @@ -1373,7 +1374,7 @@ public final class GameManagerService extends IGameManagerService.Stub { * @hide */ @VisibleForTesting - void updateConfigsForUser(@UserIdInt int userId, String ...packageNames) { + void updateConfigsForUser(@UserIdInt int userId, String... packageNames) { try { synchronized (mDeviceConfigLock) { for (final String packageName : packageNames) { @@ -1442,7 +1443,7 @@ public final class GameManagerService extends IGameManagerService.Stub { final List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(0, userId); return packages.stream().filter(e -> e.applicationInfo != null && e.applicationInfo.category - == ApplicationInfo.CATEGORY_GAME) + == ApplicationInfo.CATEGORY_GAME) .map(e -> e.packageName) .toArray(String[]::new); } diff --git a/services/core/java/com/android/server/app/GameServiceConfiguration.java b/services/core/java/com/android/server/app/GameServiceConfiguration.java new file mode 100644 index 000000000000..1f31a87e157f --- /dev/null +++ b/services/core/java/com/android/server/app/GameServiceConfiguration.java @@ -0,0 +1,154 @@ +/* + * 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.app; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.os.UserHandle; +import android.text.TextUtils; + +import java.util.Objects; + +/** + * Representation of a {@link android.service.games.GameService} provider configuration. + */ +final class GameServiceConfiguration { + private final String mPackageName; + @Nullable + private final GameServiceComponentConfiguration mGameServiceComponentConfiguration; + + GameServiceConfiguration( + @NonNull String packageName, + @Nullable GameServiceComponentConfiguration gameServiceComponentConfiguration) { + Objects.requireNonNull(packageName); + + mPackageName = packageName; + mGameServiceComponentConfiguration = gameServiceComponentConfiguration; + } + + @NonNull + public String getPackageName() { + return mPackageName; + } + + @Nullable + public GameServiceComponentConfiguration getGameServiceComponentConfiguration() { + return mGameServiceComponentConfiguration; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof GameServiceConfiguration)) { + return false; + } + + GameServiceConfiguration that = (GameServiceConfiguration) o; + return TextUtils.equals(mPackageName, that.mPackageName) + && Objects.equals(mGameServiceComponentConfiguration, + that.mGameServiceComponentConfiguration); + } + + @Override + public int hashCode() { + return Objects.hash(mPackageName, mGameServiceComponentConfiguration); + } + + @Override + public String toString() { + return "GameServiceConfiguration{" + + "packageName=" + + mPackageName + + ", gameServiceComponentConfiguration=" + + mGameServiceComponentConfiguration + + '}'; + } + + static final class GameServiceComponentConfiguration { + private final UserHandle mUserHandle; + private final ComponentName mGameServiceComponentName; + private final ComponentName mGameSessionServiceComponentName; + + GameServiceComponentConfiguration( + @NonNull UserHandle userHandle, @NonNull ComponentName gameServiceComponentName, + @NonNull ComponentName gameSessionServiceComponentName) { + Objects.requireNonNull(userHandle); + Objects.requireNonNull(gameServiceComponentName); + Objects.requireNonNull(gameSessionServiceComponentName); + + mUserHandle = userHandle; + mGameServiceComponentName = gameServiceComponentName; + mGameSessionServiceComponentName = gameSessionServiceComponentName; + } + + @NonNull + public UserHandle getUserHandle() { + return mUserHandle; + } + + @NonNull + public ComponentName getGameServiceComponentName() { + return mGameServiceComponentName; + } + + @NonNull + public ComponentName getGameSessionServiceComponentName() { + return mGameSessionServiceComponentName; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof GameServiceComponentConfiguration)) { + return false; + } + + GameServiceComponentConfiguration that = + (GameServiceComponentConfiguration) o; + return mUserHandle.equals(that.mUserHandle) && mGameServiceComponentName.equals( + that.mGameServiceComponentName) + && mGameSessionServiceComponentName.equals( + that.mGameSessionServiceComponentName); + } + + @Override + public int hashCode() { + return Objects.hash(mUserHandle, + mGameServiceComponentName, + mGameSessionServiceComponentName); + } + + @Override + public String toString() { + return "GameServiceComponentConfiguration{" + + "userHandle=" + + mUserHandle + + ", gameServiceComponentName=" + + mGameServiceComponentName + + ", gameSessionServiceComponentName=" + + mGameSessionServiceComponentName + + "}"; + } + } +} diff --git a/services/core/java/com/android/server/app/GameServiceController.java b/services/core/java/com/android/server/app/GameServiceController.java index 397439a356f8..db1ca97df888 100644 --- a/services/core/java/com/android/server/app/GameServiceController.java +++ b/services/core/java/com/android/server/app/GameServiceController.java @@ -19,10 +19,17 @@ package com.android.server.app; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.WorkerThread; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.PatternMatcher; +import android.text.TextUtils; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.server.SystemService; +import com.android.server.app.GameServiceConfiguration.GameServiceComponentConfiguration; import java.util.Objects; import java.util.concurrent.Executor; @@ -36,8 +43,8 @@ import java.util.concurrent.Executor; final class GameServiceController { private static final String TAG = "GameServiceController"; - private final Object mLock = new Object(); + private final Context mContext; private final Executor mBackgroundExecutor; private final GameServiceProviderSelector mGameServiceProviderSelector; private final GameServiceProviderInstanceFactory mGameServiceProviderInstanceFactory; @@ -46,18 +53,24 @@ final class GameServiceController { @Nullable private volatile String mGameServiceProviderOverride; @Nullable + private BroadcastReceiver mGameServicePackageChangedReceiver; + @Nullable private volatile SystemService.TargetUser mCurrentForegroundUser; @GuardedBy("mLock") @Nullable - private volatile GameServiceProviderConfiguration mActiveGameServiceProviderConfiguration; + private volatile GameServiceComponentConfiguration mActiveGameServiceComponentConfiguration; @GuardedBy("mLock") @Nullable private volatile GameServiceProviderInstance mGameServiceProviderInstance; + @GuardedBy("mLock") + @Nullable + private volatile String mActiveGameServiceProviderPackage; GameServiceController( - @NonNull Executor backgroundExecutor, + @NonNull Context context, @NonNull Executor backgroundExecutor, @NonNull GameServiceProviderSelector gameServiceProviderSelector, @NonNull GameServiceProviderInstanceFactory gameServiceProviderInstanceFactory) { + mContext = context; mGameServiceProviderInstanceFactory = gameServiceProviderInstanceFactory; mBackgroundExecutor = backgroundExecutor; mGameServiceProviderSelector = gameServiceProviderSelector; @@ -139,35 +152,92 @@ final class GameServiceController { } synchronized (mLock) { - GameServiceProviderConfiguration selectedGameServiceProviderConfiguration = + final GameServiceConfiguration selectedGameServiceConfiguration = mGameServiceProviderSelector.get(mCurrentForegroundUser, mGameServiceProviderOverride); - - boolean didActiveGameServiceProviderChanged = - !Objects.equals(selectedGameServiceProviderConfiguration, - mActiveGameServiceProviderConfiguration); - if (!didActiveGameServiceProviderChanged) { + final String gameServicePackage = + selectedGameServiceConfiguration == null ? null : + selectedGameServiceConfiguration.getPackageName(); + final GameServiceComponentConfiguration gameServiceComponentConfiguration = + selectedGameServiceConfiguration == null ? null + : selectedGameServiceConfiguration + .getGameServiceComponentConfiguration(); + + evaluateGameServiceProviderPackageChangedListenerLocked(gameServicePackage); + + boolean didActiveGameServiceProviderChange = + !Objects.equals(gameServiceComponentConfiguration, + mActiveGameServiceComponentConfiguration); + if (!didActiveGameServiceProviderChange) { return; } if (mGameServiceProviderInstance != null) { Slog.i(TAG, "Stopping Game Service provider: " - + mActiveGameServiceProviderConfiguration); + + mActiveGameServiceComponentConfiguration); mGameServiceProviderInstance.stop(); + mGameServiceProviderInstance = null; } - mActiveGameServiceProviderConfiguration = selectedGameServiceProviderConfiguration; - - if (mActiveGameServiceProviderConfiguration == null) { + mActiveGameServiceComponentConfiguration = gameServiceComponentConfiguration; + if (mActiveGameServiceComponentConfiguration == null) { return; } Slog.i(TAG, - "Starting Game Service provider: " + mActiveGameServiceProviderConfiguration); + "Starting Game Service provider: " + mActiveGameServiceComponentConfiguration); mGameServiceProviderInstance = mGameServiceProviderInstanceFactory.create( - mActiveGameServiceProviderConfiguration); + mActiveGameServiceComponentConfiguration); mGameServiceProviderInstance.start(); } } + + @GuardedBy("mLock") + private void evaluateGameServiceProviderPackageChangedListenerLocked( + @Nullable String gameServicePackage) { + if (TextUtils.equals(mActiveGameServiceProviderPackage, gameServicePackage)) { + return; + } + + if (mGameServicePackageChangedReceiver != null) { + mContext.unregisterReceiver(mGameServicePackageChangedReceiver); + mGameServicePackageChangedReceiver = null; + } + + mActiveGameServiceProviderPackage = gameServicePackage; + + if (TextUtils.isEmpty(mActiveGameServiceProviderPackage)) { + return; + } + + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); + intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + intentFilter.addDataScheme("package"); + intentFilter.addDataSchemeSpecificPart(gameServicePackage, PatternMatcher.PATTERN_LITERAL); + mGameServicePackageChangedReceiver = new PackageChangedBroadcastReceiver( + gameServicePackage); + mContext.registerReceiver( + mGameServicePackageChangedReceiver, + intentFilter); + } + + private final class PackageChangedBroadcastReceiver extends BroadcastReceiver { + private final String mPackageName; + + PackageChangedBroadcastReceiver(String packageName) { + mPackageName = packageName; + } + + @Override + public void onReceive(Context context, Intent intent) { + if (!TextUtils.equals(intent.getData().getSchemeSpecificPart(), mPackageName)) { + return; + } + mBackgroundExecutor.execute( + GameServiceController.this::evaluateActiveGameServiceProvider); + } + } } diff --git a/services/core/java/com/android/server/app/GameServiceProviderConfiguration.java b/services/core/java/com/android/server/app/GameServiceProviderConfiguration.java deleted file mode 100644 index 7c8f251f35fe..000000000000 --- a/services/core/java/com/android/server/app/GameServiceProviderConfiguration.java +++ /dev/null @@ -1,94 +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.server.app; - -import android.annotation.NonNull; -import android.content.ComponentName; -import android.os.UserHandle; - -import java.util.Objects; - -/** - * Representation of a {@link android.service.games.GameService} provider configuration. - */ -final class GameServiceProviderConfiguration { - private final UserHandle mUserHandle; - private final ComponentName mGameServiceComponentName; - private final ComponentName mGameSessionServiceComponentName; - - GameServiceProviderConfiguration( - @NonNull UserHandle userHandle, - @NonNull ComponentName gameServiceComponentName, - @NonNull ComponentName gameSessionServiceComponentName) { - Objects.requireNonNull(userHandle); - Objects.requireNonNull(gameServiceComponentName); - Objects.requireNonNull(gameSessionServiceComponentName); - - this.mUserHandle = userHandle; - this.mGameServiceComponentName = gameServiceComponentName; - this.mGameSessionServiceComponentName = gameSessionServiceComponentName; - } - - @NonNull - public UserHandle getUserHandle() { - return mUserHandle; - } - - @NonNull - public ComponentName getGameServiceComponentName() { - return mGameServiceComponentName; - } - - @NonNull - public ComponentName getGameSessionServiceComponentName() { - return mGameSessionServiceComponentName; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - - if (!(o instanceof GameServiceProviderConfiguration)) { - return false; - } - - GameServiceProviderConfiguration that = (GameServiceProviderConfiguration) o; - return mUserHandle.equals(that.mUserHandle) - && mGameServiceComponentName.equals(that.mGameServiceComponentName) - && mGameSessionServiceComponentName.equals(that.mGameSessionServiceComponentName); - } - - @Override - public int hashCode() { - return Objects.hash(mUserHandle, mGameServiceComponentName, - mGameSessionServiceComponentName); - } - - @Override - public String toString() { - return "GameServiceProviderConfiguration{" - + "mUserHandle=" - + mUserHandle - + ", gameServiceComponentName=" - + mGameServiceComponentName - + ", gameSessionServiceComponentName=" - + mGameSessionServiceComponentName - + '}'; - } -} diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceFactory.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactory.java index 7640cc555446..7dfaec0fc7ab 100644 --- a/services/core/java/com/android/server/app/GameServiceProviderInstanceFactory.java +++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactory.java @@ -18,12 +18,13 @@ package com.android.server.app; import android.annotation.NonNull; +import com.android.server.app.GameServiceConfiguration.GameServiceComponentConfiguration; + /** * Factory for creating {@link GameServiceProviderInstance}. */ interface GameServiceProviderInstanceFactory { @NonNull - GameServiceProviderInstance create(@NonNull - GameServiceProviderConfiguration gameServiceProviderConfiguration); + GameServiceProviderInstance create(@NonNull GameServiceComponentConfiguration configuration); } diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java index 73278e471062..0abab6aafe73 100644 --- a/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java +++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java @@ -30,6 +30,7 @@ import android.service.games.IGameSessionService; import com.android.internal.infra.ServiceConnector; import com.android.internal.os.BackgroundThread; import com.android.server.LocalServices; +import com.android.server.app.GameServiceConfiguration.GameServiceComponentConfiguration; import com.android.server.wm.WindowManagerInternal; import com.android.server.wm.WindowManagerService; @@ -43,9 +44,9 @@ final class GameServiceProviderInstanceFactoryImpl implements GameServiceProvide @NonNull @Override public GameServiceProviderInstance create( - @NonNull GameServiceProviderConfiguration gameServiceProviderConfiguration) { + @NonNull GameServiceComponentConfiguration configuration) { return new GameServiceProviderInstanceImpl( - gameServiceProviderConfiguration.getUserHandle(), + configuration.getUserHandle(), BackgroundThread.getExecutor(), mContext, new GameClassifierImpl(mContext.getPackageManager()), @@ -53,8 +54,8 @@ final class GameServiceProviderInstanceFactoryImpl implements GameServiceProvide ActivityTaskManager.getService(), (WindowManagerService) ServiceManager.getService(Context.WINDOW_SERVICE), LocalServices.getService(WindowManagerInternal.class), - new GameServiceConnector(mContext, gameServiceProviderConfiguration), - new GameSessionServiceConnector(mContext, gameServiceProviderConfiguration)); + new GameServiceConnector(mContext, configuration), + new GameSessionServiceConnector(mContext, configuration)); } private static final class GameServiceConnector extends ServiceConnector.Impl<IGameService> { @@ -63,7 +64,7 @@ final class GameServiceProviderInstanceFactoryImpl implements GameServiceProvide GameServiceConnector( @NonNull Context context, - @NonNull GameServiceProviderConfiguration configuration) { + @NonNull GameServiceComponentConfiguration configuration) { super(context, new Intent(GameService.ACTION_GAME_SERVICE) .setComponent(configuration.getGameServiceComponentName()), BINDING_FLAGS, configuration.getUserHandle().getIdentifier(), @@ -86,7 +87,7 @@ final class GameServiceProviderInstanceFactoryImpl implements GameServiceProvide GameSessionServiceConnector( @NonNull Context context, - @NonNull GameServiceProviderConfiguration configuration) { + @NonNull GameServiceComponentConfiguration configuration) { super(context, new Intent(GameSessionService.ACTION_GAME_SESSION_SERVICE) .setComponent(configuration.getGameSessionServiceComponentName()), BINDING_FLAGS, configuration.getUserHandle().getIdentifier(), diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java index 4eba77168b8e..e8d9dadcb17a 100644 --- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java +++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java @@ -244,11 +244,11 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan // TODO(b/204503192): It is possible that the game service is disconnected. In this // case we should avoid rebinding just to shut it down again. - AndroidFuture<Void> unusedPostDisconnectedFuture = - mGameServiceConnector.post(gameService -> { - gameService.disconnected(); - }); - mGameServiceConnector.unbind(); + mGameServiceConnector.post(gameService -> { + gameService.disconnected(); + }).whenComplete((result, t) -> { + mGameServiceConnector.unbind(); + }); mGameSessionServiceConnector.unbind(); } diff --git a/services/core/java/com/android/server/app/GameServiceProviderSelector.java b/services/core/java/com/android/server/app/GameServiceProviderSelector.java index 0f55b9ff31f4..d125f214751b 100644 --- a/services/core/java/com/android/server/app/GameServiceProviderSelector.java +++ b/services/core/java/com/android/server/app/GameServiceProviderSelector.java @@ -26,10 +26,10 @@ import com.android.server.SystemService; interface GameServiceProviderSelector { /** - * Returns the {@link GameServiceProviderConfiguration} associated with the selected Game + * Returns the {@link GameServiceConfiguration} associated with the selected Game * Service provider for the given user or {@code null} if none should be used. */ @Nullable - GameServiceProviderConfiguration get(@Nullable SystemService.TargetUser user, + GameServiceConfiguration get(@Nullable SystemService.TargetUser user, @Nullable String packageNameOverride); } diff --git a/services/core/java/com/android/server/app/GameServiceProviderSelectorImpl.java b/services/core/java/com/android/server/app/GameServiceProviderSelectorImpl.java index c1ad6685fbbb..fc8530874a6f 100644 --- a/services/core/java/com/android/server/app/GameServiceProviderSelectorImpl.java +++ b/services/core/java/com/android/server/app/GameServiceProviderSelectorImpl.java @@ -34,6 +34,7 @@ import android.util.Slog; import android.util.Xml; import com.android.server.SystemService; +import com.android.server.app.GameServiceConfiguration.GameServiceComponentConfiguration; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -57,7 +58,7 @@ final class GameServiceProviderSelectorImpl implements GameServiceProviderSelect @Override @Nullable - public GameServiceProviderConfiguration get(@Nullable SystemService.TargetUser user, + public GameServiceConfiguration get(@Nullable SystemService.TargetUser user, @Nullable String packageNameOverride) { if (user == null) { return null; @@ -98,10 +99,10 @@ final class GameServiceProviderSelectorImpl implements GameServiceProviderSelect if (gameServiceResolveInfos == null || gameServiceResolveInfos.isEmpty()) { Slog.w(TAG, "No available game service found for user id: " + userId); - return null; + return new GameServiceConfiguration(gameServicePackage, null); } - GameServiceProviderConfiguration selectedProvider = null; + GameServiceConfiguration selectedProvider = null; for (ResolveInfo resolveInfo : gameServiceResolveInfos) { if (resolveInfo.serviceInfo == null) { continue; @@ -115,16 +116,18 @@ final class GameServiceProviderSelectorImpl implements GameServiceProviderSelect } selectedProvider = - new GameServiceProviderConfiguration( - new UserHandle(userId), - gameServiceServiceInfo.getComponentName(), - gameSessionServiceComponentName); + new GameServiceConfiguration( + gameServicePackage, + new GameServiceComponentConfiguration( + new UserHandle(userId), + gameServiceServiceInfo.getComponentName(), + gameSessionServiceComponentName)); break; } if (selectedProvider == null) { Slog.w(TAG, "No valid game service found for user id: " + userId); - return null; + return new GameServiceConfiguration(gameServicePackage, null); } return selectedProvider; diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index 3491cd59ebb7..49a935ebadc9 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -479,6 +479,8 @@ public class BtHelper { } if (profile == BluetoothProfile.A2DP) { mA2dp = (BluetoothA2dp) proxy; + } else if (profile == BluetoothProfile.HEARING_AID) { + mHearingAid = (BluetoothHearingAid) proxy; } else if (profile == BluetoothProfile.LE_AUDIO) { mLeAudio = (BluetoothLeAudio) proxy; } diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java index eb2f80b96dce..9f46bd6cf28a 100644 --- a/services/core/java/com/android/server/clipboard/ClipboardService.java +++ b/services/core/java/com/android/server/clipboard/ClipboardService.java @@ -389,7 +389,7 @@ public class ClipboardService extends SystemService { final long oldIdentity = Binder.clearCallingIdentity(); try { if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CLIPBOARD, - PROPERTY_AUTO_CLEAR_ENABLED, false)) { + PROPERTY_AUTO_CLEAR_ENABLED, true)) { mClipboardClearHandler.removeEqualMessages(ClipboardClearHandler.MSG_CLEAR, userId); Message clearMessage = Message.obtain(mClipboardClearHandler, diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index ea054a5b280c..682f0dfe067c 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -2020,7 +2020,8 @@ public class Vpn { .setCategory(Notification.CATEGORY_SYSTEM) .setVisibility(Notification.VISIBILITY_PUBLIC) .setOngoing(true) - .setColor(mContext.getColor(R.color.system_notification_accent_color)); + .setColor(mContext.getColor( + android.R.color.system_notification_accent_color)); notificationManager.notify(TAG, SystemMessage.NOTE_VPN_DISCONNECTED, builder.build()); } finally { Binder.restoreCallingIdentity(token); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java index b2f500a59ba9..d2d80ffde532 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java @@ -408,6 +408,11 @@ final class InputMethodBindingController { @GuardedBy("ImfLock.class") @NonNull InputBindResult bindCurrentMethod() { + if (mSelectedMethodId == null) { + Slog.e(TAG, "mSelectedMethodId is null!"); + return InputBindResult.NO_IME; + } + InputMethodInfo info = mMethodMap.get(mSelectedMethodId); if (info == null) { throw new IllegalArgumentException("Unknown id: " + mSelectedMethodId); diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index 0c3f9f0e26c6..45d9822205ec 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -1428,6 +1428,7 @@ public class LocationManagerService extends ILocationManager.Stub implements ipw.println("Location Settings:"); ipw.increaseIndent(); mInjector.getSettingsHelper().dump(fd, ipw, args); + mInjector.getLocationSettings().dump(fd, ipw, args); ipw.decreaseIndent(); synchronized (mLock) { diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java index 8fdde2486401..e9bf90f1a82e 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java @@ -16,8 +16,6 @@ package com.android.server.location.contexthub; -import static android.content.pm.PackageManager.PERMISSION_GRANTED; - import android.Manifest; import android.content.Context; import android.hardware.contexthub.V1_0.AsyncEventType; @@ -297,19 +295,14 @@ import java.util.List; } /** - * Checks for location hardware permissions. + * Checks for ACCESS_CONTEXT_HUB permissions. * * @param context the context of the service */ /* package */ static void checkPermissions(Context context) { - boolean hasAccessContextHubPermission = (context.checkCallingPermission( - CONTEXT_HUB_PERMISSION) == PERMISSION_GRANTED); - - if (!hasAccessContextHubPermission) { - throw new SecurityException( - "ACCESS_CONTEXT_HUB permission required to use Context Hub"); - } + context.enforceCallingOrSelfPermission(CONTEXT_HUB_PERMISSION, + "ACCESS_CONTEXT_HUB permission required to use Context Hub"); } /** diff --git a/services/core/java/com/android/server/location/settings/LocationSettings.java b/services/core/java/com/android/server/location/settings/LocationSettings.java index d52153893970..be0e7acd2401 100644 --- a/services/core/java/com/android/server/location/settings/LocationSettings.java +++ b/services/core/java/com/android/server/location/settings/LocationSettings.java @@ -18,8 +18,11 @@ package com.android.server.location.settings; import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE; +import android.app.ActivityManager; import android.content.Context; import android.os.Environment; +import android.os.RemoteException; +import android.util.IndentingPrintWriter; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; @@ -29,6 +32,7 @@ import com.android.server.FgThread; import java.io.DataInput; import java.io.DataOutput; import java.io.File; +import java.io.FileDescriptor; import java.io.IOException; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Function; @@ -104,6 +108,33 @@ public class LocationSettings { getUserSettingsStore(userId).update(updater); } + /** Dumps info for debugging. */ + public final void dump(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) { + int[] userIds; + try { + userIds = ActivityManager.getService().getRunningUserIds(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + if (mContext.getPackageManager().hasSystemFeature(FEATURE_AUTOMOTIVE)) { + ipw.print("ADAS Location Setting: "); + ipw.increaseIndent(); + if (userIds.length > 1) { + ipw.println(); + for (int userId : userIds) { + ipw.print("[u"); + ipw.print(userId); + ipw.print("] "); + ipw.println(getUserSettings(userId).isAdasGnssLocationEnabled()); + } + } else { + ipw.println(getUserSettings(userIds[0]).isAdasGnssLocationEnabled()); + } + ipw.decreaseIndent(); + } + } + @VisibleForTesting final void flushFiles() throws InterruptedException { synchronized (mUserSettings) { diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 96391ac62530..074d891a9974 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -647,18 +647,17 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR if (mDestroyed) { return; } - toSend = new ArrayList<>(); - if (mQueue != null) { - toSend.ensureCapacity(mQueue.size()); - toSend.addAll(mQueue); - } + toSend = mQueue == null ? null : new ArrayList<>(mQueue); } Collection<ISessionControllerCallbackHolder> deadCallbackHolders = null; for (ISessionControllerCallbackHolder holder : mControllerCallbackHolders) { - ParceledListSlice<QueueItem> parcelableQueue = new ParceledListSlice<>(toSend); - // Limit the size of initial Parcel to prevent binder buffer overflow - // as onQueueChanged is an async binder call. - parcelableQueue.setInlineCountLimit(1); + ParceledListSlice<QueueItem> parcelableQueue = null; + if (toSend != null) { + parcelableQueue = new ParceledListSlice<>(toSend); + // Limit the size of initial Parcel to prevent binder buffer overflow + // as onQueueChanged is an async binder call. + parcelableQueue.setInlineCountLimit(1); + } try { holder.mCallback.onQueueChanged(parcelableQueue); diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java index 851ea3d01085..1b7d1ba59b06 100644 --- a/services/core/java/com/android/server/net/LockdownVpnTracker.java +++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java @@ -293,7 +293,7 @@ public class LockdownVpnTracker { .addAction(R.drawable.ic_menu_refresh, mContext.getString(R.string.reset), mResetIntent) .setColor(mContext.getColor( - com.android.internal.R.color.system_notification_accent_color)); + android.R.color.system_notification_accent_color)); mNotificationManager.notify(null /* tag */, SystemMessage.NOTE_VPN_STATUS, builder.build()); diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 0052df37eaf7..60962b122fe7 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -4157,7 +4157,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { if (mRestrictedNetworkingMode) { // Note: setUidFirewallRule also updates mUidFirewallRestrictedModeRules. // In this case, default firewall rules can also be added. - setUidFirewallRule(FIREWALL_CHAIN_RESTRICTED, uid, + setUidFirewallRuleUL(FIREWALL_CHAIN_RESTRICTED, uid, getRestrictedModeFirewallRule(uidBlockedState)); } } @@ -4321,10 +4321,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { && (uidBlockedState.effectiveBlockedReasons & BLOCKED_REASON_LOW_POWER_STANDBY) == 0) { mUidFirewallLowPowerStandbyModeRules.put(uid, FIREWALL_RULE_ALLOW); - setUidFirewallRule(FIREWALL_CHAIN_LOW_POWER_STANDBY, uid, FIREWALL_RULE_ALLOW); + setUidFirewallRuleUL(FIREWALL_CHAIN_LOW_POWER_STANDBY, uid, FIREWALL_RULE_ALLOW); } else { mUidFirewallLowPowerStandbyModeRules.delete(uid); - setUidFirewallRule(FIREWALL_CHAIN_LOW_POWER_STANDBY, uid, FIREWALL_RULE_DEFAULT); + setUidFirewallRuleUL(FIREWALL_CHAIN_LOW_POWER_STANDBY, uid, FIREWALL_RULE_DEFAULT); } } @@ -4373,9 +4373,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final boolean isWhitelisted = isWhitelistedFromPowerSaveUL(uid, chain == FIREWALL_CHAIN_DOZABLE); if (isWhitelisted || isUidForegroundOnRestrictPowerUL(uid)) { - setUidFirewallRule(chain, uid, FIREWALL_RULE_ALLOW); + setUidFirewallRuleUL(chain, uid, FIREWALL_RULE_ALLOW); } else { - setUidFirewallRule(chain, uid, FIREWALL_RULE_DEFAULT); + setUidFirewallRuleUL(chain, uid, FIREWALL_RULE_DEFAULT); } } } @@ -4421,10 +4421,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { int appId = UserHandle.getAppId(uid); if (!mPowerSaveTempWhitelistAppIds.get(appId) && isUidIdle(uid) && !isUidForegroundOnRestrictPowerUL(uid)) { - setUidFirewallRule(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DENY); + setUidFirewallRuleUL(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DENY); if (LOGD) Log.d(TAG, "updateRuleForAppIdleUL DENY " + uid); } else { - setUidFirewallRule(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DEFAULT); + setUidFirewallRuleUL(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DEFAULT); if (LOGD) Log.d(TAG, "updateRuleForAppIdleUL " + uid + " to DEFAULT"); } } finally { @@ -5495,10 +5495,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { /** * Add or remove a uid to the firewall denylist for all network ifaces. */ - private void setUidFirewallRule(int chain, int uid, int rule) { + @GuardedBy("mUidRulesFirstLock") + private void setUidFirewallRuleUL(int chain, int uid, int rule) { if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) { Trace.traceBegin(Trace.TRACE_TAG_NETWORK, - "setUidFirewallRule: " + chain + "/" + uid + "/" + rule); + "setUidFirewallRuleUL: " + chain + "/" + uid + "/" + rule); } try { if (chain == FIREWALL_CHAIN_DOZABLE) { diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 9e87898a7491..30ac1b85e7c3 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -3134,8 +3134,8 @@ public class ComputerEngine implements Computer { writer.println("Domain verification status:"); writer.increaseIndent(); try { - mDomainVerificationManager.printState(writer, packageName, - UserHandle.USER_ALL, mSettings::getPackage); + mDomainVerificationManager.printState(this, writer, packageName, + UserHandle.USER_ALL); } catch (Exception e) { pw.println("Failure printing domain verification information"); Slog.e(TAG, "Failure printing domain verification information", e); diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java index 3220b3171178..58f9bb2c1902 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -453,7 +453,8 @@ final class DeletePackageHelper { } for (final int affectedUserId : affectedUserIds) { if (hadSuspendAppsPermission.get(affectedUserId)) { - mPm.unsuspendForSuspendingPackage(packageName, affectedUserId); + mPm.unsuspendForSuspendingPackage(mPm.snapshotComputer(), packageName, + affectedUserId); mPm.removeAllDistractingPackageRestrictions(affectedUserId); } } diff --git a/services/core/java/com/android/server/pm/DomainVerificationConnection.java b/services/core/java/com/android/server/pm/DomainVerificationConnection.java index d24435e12593..db8c6dc60b5e 100644 --- a/services/core/java/com/android/server/pm/DomainVerificationConnection.java +++ b/services/core/java/com/android/server/pm/DomainVerificationConnection.java @@ -26,17 +26,12 @@ import android.os.Binder; import android.os.Message; import android.os.UserHandle; -import com.android.internal.util.FunctionalUtils; import com.android.server.DeviceIdleInternal; import com.android.server.pm.parsing.pkg.AndroidPackage; -import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.verify.domain.DomainVerificationService; import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV1; import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV2; -import java.util.function.Consumer; -import java.util.function.Function; - public final class DomainVerificationConnection implements DomainVerificationService.Connection, DomainVerificationProxyV1.Connection, DomainVerificationProxyV2.Connection { final PackageManagerService mPm; @@ -111,42 +106,8 @@ public final class DomainVerificationConnection implements DomainVerificationSer return mUmInternal.exists(userId); } - @Override - public void withPackageSettingsSnapshot( - @NonNull Consumer<Function<String, PackageStateInternal>> block) { - mPmInternal.withPackageSettingsSnapshot(block); - } - - @Override - public <Output> Output withPackageSettingsSnapshotReturning( - @NonNull FunctionalUtils.ThrowingFunction<Function<String, PackageStateInternal>, - Output> block) { - return mPmInternal.withPackageSettingsSnapshotReturning(block); - } - - @Override - public <ExceptionType extends Exception> void withPackageSettingsSnapshotThrowing( - @NonNull FunctionalUtils.ThrowingCheckedConsumer<Function<String, PackageStateInternal>, - ExceptionType> block) throws ExceptionType { - mPmInternal.withPackageSettingsSnapshotThrowing(block); - } - - @Override - public <ExceptionOne extends Exception, ExceptionTwo extends Exception> void - withPackageSettingsSnapshotThrowing2( - @NonNull FunctionalUtils.ThrowingChecked2Consumer< - Function<String, PackageStateInternal>, ExceptionOne, - ExceptionTwo> block) - throws ExceptionOne, ExceptionTwo { - mPmInternal.withPackageSettingsSnapshotThrowing2(block); - } - - @Override - public <Output, ExceptionType extends Exception> Output - withPackageSettingsSnapshotReturningThrowing( - @NonNull FunctionalUtils.ThrowingCheckedFunction< - Function<String, PackageStateInternal>, Output, ExceptionType> block) - throws ExceptionType { - return mPmInternal.withPackageSettingsSnapshotReturningThrowing(block); + @NonNull + public Computer snapshot() { + return (Computer) mPmInternal.snapshot(); } } diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 234a2309d76b..c09c904ff931 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -36,7 +36,6 @@ import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_RESTORE; import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_SETUP; import static android.content.pm.PackageManager.INSTALL_SUCCEEDED; import static android.content.pm.PackageManager.UNINSTALL_REASON_UNKNOWN; -import static android.content.pm.PackageManagerInternal.PACKAGE_SETUP_WIZARD; import static android.content.pm.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4; import static android.content.pm.parsing.ApkLiteParseUtils.isApkFile; import static android.os.PowerExemptionManager.REASON_PACKAGE_REPLACED; @@ -145,6 +144,7 @@ import com.android.internal.content.F2fsUtils; import com.android.internal.content.InstallLocationUtils; import com.android.internal.security.VerityUtils; import com.android.internal.util.ArrayUtils; +import com.android.internal.util.CollectionUtils; import com.android.internal.util.FrameworkStatsLog; import com.android.server.EventLogTags; import com.android.server.pm.dex.ArtManagerService; @@ -2563,7 +2563,9 @@ final class InstallPackageHelper { Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); synchronized (mPm.mLock) { - size = mPm.mPendingBroadcasts.size(); + final SparseArray<ArrayMap<String, ArrayList<String>>> userIdToPackagesToComponents = + mPm.mPendingBroadcasts.copiedMap(); + size = userIdToPackagesToComponents.size(); if (size <= 0) { // Nothing to be done. Just return return; @@ -2573,11 +2575,11 @@ final class InstallPackageHelper { uids = new int[size]; int i = 0; // filling out the above arrays - for (int n = 0; n < mPm.mPendingBroadcasts.userIdCount(); n++) { - final int packageUserId = mPm.mPendingBroadcasts.userIdAt(n); + for (int n = 0; n < size; n++) { + final int packageUserId = userIdToPackagesToComponents.keyAt(n); final ArrayMap<String, ArrayList<String>> componentsToBroadcast = - mPm.mPendingBroadcasts.packagesForUserId(packageUserId); - final int numComponents = componentsToBroadcast.size(); + userIdToPackagesToComponents.valueAt(n); + final int numComponents = CollectionUtils.size(componentsToBroadcast); for (int index = 0; i < size && index < numComponents; index++) { packages[i] = componentsToBroadcast.keyAt(index); components[i] = componentsToBroadcast.valueAt(index); diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java index 46e2aa3ff841..b028a2cef2a5 100644 --- a/services/core/java/com/android/server/pm/PackageHandler.java +++ b/services/core/java/com/android/server/pm/PackageHandler.java @@ -382,6 +382,7 @@ final class PackageHandler extends Handler { case PRUNE_UNUSED_STATIC_SHARED_LIBRARIES: { try { mPm.mInjector.getSharedLibrariesImpl().pruneUnusedStaticSharedLibraries( + mPm.snapshotComputer(), Long.MAX_VALUE, Settings.Global.getLong(mPm.mContext.getContentResolver(), Settings.Global.UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD, diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 0b7a6a7cec45..c05faf14cae4 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -293,7 +293,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; -import java.util.function.Function; /** * Keep track of all those APKs everywhere. @@ -1898,7 +1897,8 @@ public class PackageManagerService extends IPackageManager.Stub t.traceEnd(); t.traceBegin("read user settings"); - mFirstBoot = !mSettings.readLPw(mInjector.getUserManagerInternal().getUsers( + mFirstBoot = !mSettings.readLPw(mLiveComputer, + mInjector.getUserManagerInternal().getUsers( /* excludePartial= */ true, /* excludeDying= */ false, /* excludePreCreated= */ false)); @@ -2841,8 +2841,9 @@ public class PackageManagerService extends IPackageManager.Stub } if (file.getUsableSpace() >= bytes) return; + Computer computer = snapshotComputer(); // 5. Consider shared libraries with refcount=0 and age>min cache period - if (internalVolume && mSharedLibraries.pruneUnusedStaticSharedLibraries(bytes, + if (internalVolume && mSharedLibraries.pruneUnusedStaticSharedLibraries(computer, bytes, android.provider.Settings.Global.getLong(mContext.getContentResolver(), Global.UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD, FREE_STORAGE_UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD))) { @@ -2854,14 +2855,12 @@ public class PackageManagerService extends IPackageManager.Stub // 7. Consider installed instant apps unused longer than min cache period if (internalVolume) { - if (executeWithConsistentComputerReturning(computer -> - mInstantAppRegistry.pruneInstalledInstantApps(computer, bytes, - android.provider.Settings.Global.getLong( - mContext.getContentResolver(), - Global.INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD, - InstantAppRegistry - .DEFAULT_INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD))) - ) { + if (mInstantAppRegistry.pruneInstalledInstantApps(computer, bytes, + android.provider.Settings.Global.getLong( + mContext.getContentResolver(), + Global.INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD, + InstantAppRegistry + .DEFAULT_INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD))) { return; } } @@ -2881,14 +2880,12 @@ public class PackageManagerService extends IPackageManager.Stub // 10. Consider instant meta-data (uninstalled apps) older that min cache period if (internalVolume) { - if (executeWithConsistentComputerReturning(computer -> - mInstantAppRegistry.pruneUninstalledInstantApps(computer, bytes, - android.provider.Settings.Global.getLong( - mContext.getContentResolver(), - Global.UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD, - InstantAppRegistry - .DEFAULT_UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD))) - ) { + if (mInstantAppRegistry.pruneUninstalledInstantApps(computer, bytes, + android.provider.Settings.Global.getLong( + mContext.getContentResolver(), + Global.UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD, + InstantAppRegistry + .DEFAULT_UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD))) { return; } } @@ -3493,8 +3490,8 @@ public class PackageManagerService extends IPackageManager.Stub enforceCrossUserPermission(Binder.getCallingUid(), userId, true /* requireFullPermission */, false /* checkShell */, "getEphemeralApplications"); - List<InstantAppInfo> instantApps = executeWithConsistentComputerReturning(computer -> - mInstantAppRegistry.getInstantApps(computer, userId)); + Computer computer = snapshotComputer(); + List<InstantAppInfo> instantApps = mInstantAppRegistry.getInstantApps(computer, userId); if (instantApps != null) { return new ParceledListSlice<>(instantApps); } @@ -3710,13 +3707,13 @@ public class PackageManagerService extends IPackageManager.Stub public void notifyPackageUse(String packageName, int reason) { final int callingUid = Binder.getCallingUid(); final int callingUserId = UserHandle.getUserId(callingUid); - boolean notify = executeWithConsistentComputerReturning(computer -> { - if (getInstantAppPackageName(callingUid) != null) { - return isCallerSameApp(packageName, callingUid); - } else { - return !isInstantAppInternal(packageName, callingUserId, Process.SYSTEM_UID); - } - }); + Computer computer = snapshotComputer(); + final boolean notify; + if (getInstantAppPackageName(callingUid) != null) { + notify = isCallerSameApp(packageName, callingUid); + } else { + notify = !isInstantAppInternal(packageName, callingUserId, Process.SYSTEM_UID); + } if (!notify) { return; } @@ -4255,34 +4252,33 @@ public class PackageManagerService extends IPackageManager.Stub final List<String> unactionedPackages = new ArrayList<>(packageNames.length); ArraySet<String> changesToCommit = new ArraySet<>(); - executeWithConsistentComputer(computer -> { - final boolean[] canRestrict = (restrictionFlags != 0) - ? mSuspendPackageHelper.canSuspendPackageForUser(computer, packageNames, userId, - callingUid) : null; - for (int i = 0; i < packageNames.length; i++) { - final String packageName = packageNames[i]; - final PackageStateInternal packageState = - computer.getPackageStateInternal(packageName); - if (packageState == null - || shouldFilterApplication(packageState, callingUid, userId)) { - Slog.w(TAG, "Could not find package setting for package: " + packageName - + ". Skipping..."); - unactionedPackages.add(packageName); - continue; - } - if (canRestrict != null && !canRestrict[i]) { - unactionedPackages.add(packageName); - continue; - } - final int oldDistractionFlags = packageState.getUserStateOrDefault(userId) - .getDistractionFlags(); - if (restrictionFlags != oldDistractionFlags) { - changedPackagesList.add(packageName); - changedUids.add(UserHandle.getUid(userId, packageState.getAppId())); - changesToCommit.add(packageName); - } + Computer computer = snapshotComputer(); + final boolean[] canRestrict = (restrictionFlags != 0) + ? mSuspendPackageHelper.canSuspendPackageForUser(computer, packageNames, userId, + callingUid) : null; + for (int i = 0; i < packageNames.length; i++) { + final String packageName = packageNames[i]; + final PackageStateInternal packageState = + computer.getPackageStateInternal(packageName); + if (packageState == null + || computer.shouldFilterApplication(packageState, callingUid, userId)) { + Slog.w(TAG, "Could not find package setting for package: " + packageName + + ". Skipping..."); + unactionedPackages.add(packageName); + continue; } - }); + if (canRestrict != null && !canRestrict[i]) { + unactionedPackages.add(packageName); + continue; + } + final int oldDistractionFlags = packageState.getUserStateOrDefault(userId) + .getDistractionFlags(); + if (restrictionFlags != oldDistractionFlags) { + changedPackagesList.add(packageName); + changedUids.add(UserHandle.getUid(userId, packageState.getAppId())); + changesToCommit.add(packageName); + } + } commitPackageStateMutation(null, mutator -> { final int size = changesToCommit.size(); @@ -4341,8 +4337,9 @@ public class PackageManagerService extends IPackageManager.Stub final int callingUid = Binder.getCallingUid(); enforceCanSetPackagesSuspendedAsUser(callingPackage, callingUid, userId, "setPackagesSuspendedAsUser"); - return mSuspendPackageHelper.setPackagesSuspended(packageNames, suspended, appExtras, - launcherExtras, dialogInfo, callingPackage, userId, callingUid); + return mSuspendPackageHelper.setPackagesSuspended(snapshotComputer(), packageNames, + suspended, appExtras, launcherExtras, dialogInfo, callingPackage, userId, + callingUid); } @Override @@ -4361,11 +4358,12 @@ public class PackageManagerService extends IPackageManager.Stub return mComputer.isPackageSuspendedForUser(packageName, userId); } - void unsuspendForSuspendingPackage(String suspendingPackage, int userId) { + void unsuspendForSuspendingPackage(@NonNull Computer computer, String suspendingPackage, + @UserIdInt int userId) { // TODO: This can be replaced by a special parameter to iterate all packages, rather than // this weird pre-collect of all packages. - final String[] allPackages = getPackageStates().keySet().toArray(new String[0]); - mSuspendPackageHelper.removeSuspensionsBySuspendingPackage( + final String[] allPackages = computer.getPackageStates().keySet().toArray(new String[0]); + mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(computer, allPackages, suspendingPackage::equals, userId); } @@ -4424,7 +4422,7 @@ public class PackageManagerService extends IPackageManager.Stub throw new SecurityException("Calling uid " + callingUid + " cannot query getUnsuspendablePackagesForUser for user " + userId); } - return mSuspendPackageHelper.getUnsuspendablePackagesForUser( + return mSuspendPackageHelper.getUnsuspendablePackagesForUser(snapshotComputer(), packageNames, userId, callingUid); } @@ -4625,7 +4623,7 @@ public class PackageManagerService extends IPackageManager.Stub return true; }; PackageStateMutator.InitialState initialState = recordInitialState(); - boolean allowed = executeWithConsistentComputerReturningThrowing(implementation); + boolean allowed = implementation.apply(snapshotComputer()); if (allowed) { // TODO: Need to lock around here to handle mSettings.addInstallerPackageNames, // should find an alternative which avoids any race conditions @@ -4635,7 +4633,7 @@ public class PackageManagerService extends IPackageManager.Stub targetPackage, state -> state.setInstaller(installerPackageName)); if (result.isPackagesChanged() || result.isStateChanged()) { synchronized (mPackageStateWriteLock) { - allowed = executeWithConsistentComputerReturningThrowing(implementation); + allowed = implementation.apply(snapshotComputer()); if (allowed) { commitPackageStateMutation(null, targetPackage, state -> state.setInstaller(installerPackageName)); @@ -4685,12 +4683,12 @@ public class PackageManagerService extends IPackageManager.Stub } }; - PackageStateMutator.Result result = executeWithConsistentComputerReturning(implementation); + PackageStateMutator.Result result = implementation.apply(snapshotComputer()); if (result != null && result.isStateChanged() && !result.isSpecificPackageNull()) { // TODO: Specific return value of what state changed? // The installer on record might have changed, retry with lock synchronized (mPackageStateWriteLock) { - result = executeWithConsistentComputerReturning(implementation); + result = implementation.apply(snapshotComputer()); } } @@ -4988,7 +4986,7 @@ public class PackageManagerService extends IPackageManager.Stub } if (checkPermission(Manifest.permission.SUSPEND_APPS, packageName, userId) == PERMISSION_GRANTED) { - unsuspendForSuspendingPackage(packageName, userId); + unsuspendForSuspendingPackage(snapshotComputer(), packageName, userId); removeAllDistractingPackageRestrictions(userId); flushPackageRestrictionsAsUserInternalLocked(userId); } @@ -5080,17 +5078,7 @@ public class PackageManagerService extends IPackageManager.Stub updateInstantAppInstallerLocked(packageName); scheduleWritePackageRestrictions(userId); - final ArrayList<String> pendingComponents = mPendingBroadcasts.get(userId, packageName); - if (pendingComponents == null) { - mPendingBroadcasts.put(userId, packageName, updatedComponents); - } else { - for (int i = 0; i < updatedComponents.size(); i++) { - final String updatedComponent = updatedComponents.get(i); - if (!pendingComponents.contains(updatedComponent)) { - pendingComponents.add(updatedComponent); - } - } - } + mPendingBroadcasts.addComponents(userId, packageName, updatedComponents); if (!mHandler.hasMessages(SEND_PENDING_BROADCAST)) { mHandler.sendEmptyMessageDelayed(SEND_PENDING_BROADCAST, BROADCAST_DELAY); } @@ -5278,7 +5266,8 @@ public class PackageManagerService extends IPackageManager.Stub try { try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { TypedXmlSerializer serializer = Xml.resolveSerializer(output); - mDomainVerificationManager.writeSettings(serializer, true, userId); + mDomainVerificationManager.writeSettings(snapshotComputer(), serializer, true, + userId); return output.toByteArray(); } } catch (Exception e) { @@ -5301,7 +5290,7 @@ public class PackageManagerService extends IPackageManager.Stub // User ID input isn't necessary here as it assumes the user integers match and that // the only states inside the backup XML are for the target user. - mDomainVerificationManager.restoreSettings(parser); + mDomainVerificationManager.restoreSettings(snapshotComputer(), parser); input.close(); } catch (Exception e) { if (DEBUG_BACKUP) { @@ -5689,48 +5678,48 @@ public class PackageManagerService extends IPackageManager.Stub int callingUid = Binder.getCallingUid(); String componentPkgName = componentName.getPackageName(); - boolean changed = executeWithConsistentComputerReturning(computer -> { - int componentUid = getPackageUid(componentPkgName, 0, userId); - if (!UserHandle.isSameApp(callingUid, componentUid)) { - throw new SecurityException("The calling UID (" + callingUid + ")" - + " does not match the target UID"); - } + Computer computer = snapshotComputer(); - String allowedCallerPkg = - mContext.getString(R.string.config_overrideComponentUiPackage); - if (TextUtils.isEmpty(allowedCallerPkg)) { - throw new SecurityException( "There is no package defined as allowed to change a " - + "component's label or icon"); - } + int componentUid = computer.getPackageUid(componentPkgName, 0, userId); + if (!UserHandle.isSameApp(callingUid, componentUid)) { + throw new SecurityException("The calling UID (" + callingUid + ")" + + " does not match the target UID"); + } - int allowedCallerUid = getPackageUid(allowedCallerPkg, PackageManager.MATCH_SYSTEM_ONLY, - userId); - if (allowedCallerUid == -1 || !UserHandle.isSameApp(callingUid, allowedCallerUid)) { - throw new SecurityException("The calling UID (" + callingUid + ")" - + " is not allowed to change a component's label or icon"); - } - PackageStateInternal packageState = computer.getPackageStateInternal(componentPkgName); - if (packageState == null || packageState.getPkg() == null - || (!packageState.isSystem() - && !packageState.getTransientState().isUpdatedSystemApp())) { - throw new SecurityException( - "Changing the label is not allowed for " + componentName); - } + String allowedCallerPkg = + mContext.getString(R.string.config_overrideComponentUiPackage); + if (TextUtils.isEmpty(allowedCallerPkg)) { + throw new SecurityException( "There is no package defined as allowed to change a " + + "component's label or icon"); + } - if (!mComponentResolver.componentExists(componentName)) { - throw new IllegalArgumentException("Component " + componentName + " not found"); - } + int allowedCallerUid = computer.getPackageUid(allowedCallerPkg, + PackageManager.MATCH_SYSTEM_ONLY, userId); + if (allowedCallerUid == -1 || !UserHandle.isSameApp(callingUid, allowedCallerUid)) { + throw new SecurityException("The calling UID (" + callingUid + ")" + + " is not allowed to change a component's label or icon"); + } + PackageStateInternal packageState = computer.getPackageStateInternal(componentPkgName); + if (packageState == null || packageState.getPkg() == null + || (!packageState.isSystem() + && !packageState.getTransientState().isUpdatedSystemApp())) { + throw new SecurityException( + "Changing the label is not allowed for " + componentName); + } + + if (!computer.getComponentResolver().componentExists(componentName)) { + throw new IllegalArgumentException("Component " + componentName + " not found"); + } - Pair<String, Integer> overrideLabelIcon = packageState.getUserStateOrDefault(userId) - .getOverrideLabelIconForComponent(componentName); + Pair<String, Integer> overrideLabelIcon = packageState.getUserStateOrDefault(userId) + .getOverrideLabelIconForComponent(componentName); - String existingLabel = overrideLabelIcon == null ? null : overrideLabelIcon.first; - Integer existingIcon = overrideLabelIcon == null ? null : overrideLabelIcon.second; + String existingLabel = overrideLabelIcon == null ? null : overrideLabelIcon.first; + Integer existingIcon = overrideLabelIcon == null ? null : overrideLabelIcon.second; - return !TextUtils.equals(existingLabel, nonLocalizedLabel) - || !Objects.equals(existingIcon, icon); - }); - if (!changed) { + if (TextUtils.equals(existingLabel, nonLocalizedLabel) + && Objects.equals(existingIcon, icon)) { + // Nothing changed return; } @@ -5738,16 +5727,7 @@ public class PackageManagerService extends IPackageManager.Stub state -> state.userState(userId) .setComponentLabelIcon(componentName, nonLocalizedLabel, icon)); - ArrayList<String> components = mPendingBroadcasts.get(userId, componentPkgName); - if (components == null) { - components = new ArrayList<>(); - mPendingBroadcasts.put(userId, componentPkgName, components); - } - - String className = componentName.getClassName(); - if (!components.contains(className)) { - components.add(className); - } + mPendingBroadcasts.addComponent(userId, componentPkgName, componentName.getClassName()); if (!mHandler.hasMessages(SEND_PENDING_BROADCAST)) { mHandler.sendEmptyMessageDelayed(SEND_PENDING_BROADCAST, BROADCAST_DELAY); @@ -5956,6 +5936,7 @@ public class PackageManagerService extends IPackageManager.Stub // packageName -> list of components to send broadcasts now final ArrayMap<String, ArrayList<String>> sendNowBroadcasts = new ArrayMap<>(targetSize); synchronized (mLock) { + Computer computer = snapshotComputer(); boolean scheduleBroadcastMessage = false; boolean isSynchronous = false; boolean anyChanged = false; @@ -5967,8 +5948,8 @@ public class PackageManagerService extends IPackageManager.Stub // update enabled settings final ComponentEnabledSetting setting = settings.get(i); final String packageName = setting.getPackageName(); - if (!setEnabledSettingInternalLocked(pkgSettings.get(packageName), setting, - userId, callingPackage)) { + if (!setEnabledSettingInternalLocked(computer, pkgSettings.get(packageName), + setting, userId, callingPackage)) { continue; } anyChanged = true; @@ -5979,26 +5960,18 @@ public class PackageManagerService extends IPackageManager.Stub // collect broadcast list for the package final String componentName = setting.isComponent() ? setting.getClassName() : packageName; - ArrayList<String> componentList = sendNowBroadcasts.get(packageName); - if (componentList == null) { - componentList = mPendingBroadcasts.get(userId, packageName); - } - final boolean newPackage = componentList == null; - if (newPackage) { - componentList = new ArrayList<>(); - } - if (!componentList.contains(componentName)) { - componentList.add(componentName); - } if ((setting.getEnabledFlags() & PackageManager.DONT_KILL_APP) == 0) { + ArrayList<String> componentList = sendNowBroadcasts.get(packageName); + componentList = componentList == null ? new ArrayList<>() : componentList; + if (!componentList.contains(componentName)) { + componentList.add(componentName); + } sendNowBroadcasts.put(packageName, componentList); // Purge entry from pending broadcast list if another one exists already // since we are sending one right away. mPendingBroadcasts.remove(userId, packageName); } else { - if (newPackage) { - mPendingBroadcasts.put(userId, packageName, componentList); - } + mPendingBroadcasts.addComponent(userId, packageName, componentName); scheduleBroadcastMessage = true; } } @@ -6041,8 +6014,9 @@ public class PackageManagerService extends IPackageManager.Stub } } - private boolean setEnabledSettingInternalLocked(PackageSetting pkgSetting, - ComponentEnabledSetting setting, int userId, String callingPackage) { + private boolean setEnabledSettingInternalLocked(@NonNull Computer computer, + PackageSetting pkgSetting, ComponentEnabledSetting setting, @UserIdInt int userId, + String callingPackage) { final int newState = setting.getEnabledState(); final String packageName = setting.getPackageName(); boolean success = false; @@ -6061,7 +6035,7 @@ public class PackageManagerService extends IPackageManager.Stub // This app should not generally be allowed to get disabled by the UI, but // if it ever does, we don't want to end up with some of the user's apps // permanently suspended. - unsuspendForSuspendingPackage(packageName, userId); + unsuspendForSuspendingPackage(computer, packageName, userId); removeAllDistractingPackageRestrictions(userId); } success = true; @@ -6152,11 +6126,8 @@ public class PackageManagerService extends IPackageManager.Stub public void setPackageStoppedState(String packageName, boolean stopped, int userId) { if (!mUserManager.exists(userId)) return; final int callingUid = Binder.getCallingUid(); - Pair<Boolean, String> wasNotLaunchedAndInstallerPackageName = - executeWithConsistentComputerReturningThrowing(computer -> { - if (computer.getInstantAppPackageName(callingUid) != null) { - return null; - } + final Computer computer = snapshotComputer(); + if (computer.getInstantAppPackageName(callingUid) == null) { final int permission = mContext.checkCallingOrSelfPermission( android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE); final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED); @@ -6171,36 +6142,30 @@ public class PackageManagerService extends IPackageManager.Stub true /* requireFullPermission */, true /* checkShell */, "stop package"); final PackageStateInternal packageState = computer.getPackageStateInternal(packageName); - final PackageUserState PackageUserState = packageState == null + final PackageUserState packageUserState = packageState == null ? null : packageState.getUserStateOrDefault(userId); - if (packageState == null - || computer.shouldFilterApplication(packageState, callingUid, userId) - || PackageUserState.isStopped() == stopped) { - return null; - } - - return Pair.create(PackageUserState.isNotLaunched(), - packageState.getInstallSource().installerPackageName); - }); - if (wasNotLaunchedAndInstallerPackageName != null) { - boolean wasNotLaunched = wasNotLaunchedAndInstallerPackageName.first; + if (packageState != null + && !computer.shouldFilterApplication(packageState, callingUid, userId) + && packageUserState.isStopped() != stopped) { + boolean wasNotLaunched = packageUserState.isNotLaunched(); + commitPackageStateMutation(null, packageName, state -> { + PackageUserStateWrite userState = state.userState(userId); + userState.setStopped(stopped); + if (wasNotLaunched) { + userState.setNotLaunched(false); + } + }); - commitPackageStateMutation(null, packageName, packageState -> { - PackageUserStateWrite userState = packageState.userState(userId); - userState.setStopped(stopped); if (wasNotLaunched) { - userState.setNotLaunched(false); + final String installerPackageName = + packageState.getInstallSource().installerPackageName; + if (installerPackageName != null) { + notifyFirstLaunch(packageName, installerPackageName, userId); + } } - }); - if (wasNotLaunched) { - final String installerPackageName = wasNotLaunchedAndInstallerPackageName.second; - if (installerPackageName != null) { - notifyFirstLaunch(packageName, installerPackageName, userId); - } + scheduleWritePackageRestrictions(userId); } - - scheduleWritePackageRestrictions(userId); } // If this would cause the app to leave force-stop, then also make sure to unhibernate the @@ -6687,7 +6652,7 @@ public class PackageManagerService extends IPackageManager.Stub "Only package verification agents can read the verifier device identity"); synchronized (mLock) { - return mSettings.getVerifierDeviceIdentityLPw(); + return mSettings.getVerifierDeviceIdentityLPw(mLiveComputer); } } @@ -7026,15 +6991,16 @@ public class PackageManagerService extends IPackageManager.Stub @Override public void removeAllNonSystemPackageSuspensions(int userId) { - final String[] allPackages = mComputer.getAllAvailablePackageNames(); - mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(allPackages, + final Computer computer = snapshotComputer(); + final String[] allPackages = computer.getAllAvailablePackageNames(); + mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(computer, allPackages, (suspendingPackage) -> !PLATFORM_PACKAGE_NAME.equals(suspendingPackage), userId); } @Override public void removeNonSystemPackageSuspensions(String packageName, int userId) { - mSuspendPackageHelper.removeSuspensionsBySuspendingPackage( + mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(snapshotComputer(), new String[]{packageName}, (suspendingPackage) -> !PLATFORM_PACKAGE_NAME.equals(suspendingPackage), userId); @@ -7233,29 +7199,29 @@ public class PackageManagerService extends IPackageManager.Stub @Override public void grantImplicitAccess(int userId, Intent intent, int recipientAppId, int visibleUid, boolean direct, boolean retainOnUpdate) { - boolean accessGranted = executeWithConsistentComputerReturning(computer -> { - final AndroidPackage visiblePackage = computer.getPackage(visibleUid); - final int recipientUid = UserHandle.getUid(userId, recipientAppId); - if (visiblePackage == null || computer.getPackage(recipientUid) == null) { - return false; - } + Computer computer = snapshotComputer(); + final AndroidPackage visiblePackage = computer.getPackage(visibleUid); + final int recipientUid = UserHandle.getUid(userId, recipientAppId); + if (visiblePackage == null || computer.getPackage(recipientUid) == null) { + return; + } - final boolean instantApp = computer.isInstantAppInternal( - visiblePackage.getPackageName(), userId, visibleUid); - if (instantApp) { - if (!direct) { - // if the interaction that lead to this granting access to an instant app - // was indirect (i.e.: URI permission grant), do not actually execute the - // grant. - return false; - } - return mInstantAppRegistry.grantInstantAccess(userId, intent, - recipientAppId, UserHandle.getAppId(visibleUid) /*instantAppId*/); - } else { - return mAppsFilter.grantImplicitAccess(recipientUid, visibleUid, - retainOnUpdate); + final boolean instantApp = computer.isInstantAppInternal( + visiblePackage.getPackageName(), userId, visibleUid); + final boolean accessGranted; + if (instantApp) { + if (!direct) { + // if the interaction that lead to this granting access to an instant app + // was indirect (i.e.: URI permission grant), do not actually execute the + // grant. + return; } - }); + accessGranted = mInstantAppRegistry.grantInstantAccess(userId, intent, + recipientAppId, UserHandle.getAppId(visibleUid) /*instantAppId*/); + } else { + accessGranted = mAppsFilter.grantImplicitAccess(recipientUid, visibleUid, + retainOnUpdate); + } if (accessGranted) { ApplicationPackageManager.invalidateGetPackagesForUidCache(); @@ -7271,8 +7237,7 @@ public class PackageManagerService extends IPackageManager.Stub @Override public void pruneInstantApps() { - executeWithConsistentComputer(computer -> - mInstantAppRegistry.pruneInstantApps(computer)); + mInstantAppRegistry.pruneInstantApps(snapshotComputer()); } @Override @@ -7680,7 +7645,8 @@ public class PackageManagerService extends IPackageManager.Stub @Override public void unsuspendForSuspendingPackage(final String packageName, int affectedUser) { - PackageManagerService.this.unsuspendForSuspendingPackage(packageName, affectedUser); + PackageManagerService.this.unsuspendForSuspendingPackage(snapshotComputer(), + packageName, affectedUser); } @Override @@ -7744,52 +7710,6 @@ public class PackageManagerService extends IPackageManager.Stub } @Override - public void withPackageSettingsSnapshot( - @NonNull Consumer<Function<String, PackageStateInternal>> block) { - executeWithConsistentComputer(computer -> - block.accept(computer::getPackageStateInternal)); - } - - @Override - public <Output> Output withPackageSettingsSnapshotReturning( - @NonNull FunctionalUtils.ThrowingFunction<Function<String, PackageStateInternal>, - Output> block) { - return executeWithConsistentComputerReturning(computer -> - block.apply(computer::getPackageStateInternal)); - } - - @Override - public <ExceptionType extends Exception> void withPackageSettingsSnapshotThrowing( - @NonNull FunctionalUtils.ThrowingCheckedConsumer<Function<String, - PackageStateInternal>, ExceptionType> block) throws ExceptionType { - executeWithConsistentComputerThrowing(computer -> - block.accept(computer::getPackageStateInternal)); - } - - @Override - public <ExceptionOne extends Exception, ExceptionTwo extends Exception> void - withPackageSettingsSnapshotThrowing2( - @NonNull FunctionalUtils.ThrowingChecked2Consumer< - Function<String, PackageStateInternal>, ExceptionOne, - ExceptionTwo> block) - throws ExceptionOne, ExceptionTwo { - executeWithConsistentComputerThrowing2( - (FunctionalUtils.ThrowingChecked2Consumer<Computer, ExceptionOne, - ExceptionTwo>) computer -> block.accept(computer::getPackageStateInternal)); - } - - @Override - public <Output, ExceptionType extends Exception> Output - withPackageSettingsSnapshotReturningThrowing( - @NonNull FunctionalUtils.ThrowingCheckedFunction< - Function<String, PackageStateInternal>, Output, - ExceptionType> block) - throws ExceptionType { - return executeWithConsistentComputerReturningThrowing(computer -> - block.apply(computer::getPackageStateInternal)); - } - - @Override public void reconcileAppsData(int userId, @StorageManager.StorageFlags int flags, boolean migrateAppsData) { PackageManagerService.this.mAppDataHelper.reconcileAppsData(userId, flags, @@ -7834,70 +7754,59 @@ public class PackageManagerService extends IPackageManager.Stub @NonNull Set<String> outUpdatedPackageNames) { synchronized (mOverlayPathsLock) { final ArrayMap<String, ArraySet<String>> libNameToModifiedDependents = new ArrayMap<>(); - Boolean targetModified = executeWithConsistentComputerReturning(computer -> { - final PackageStateInternal packageState = computer.getPackageStateInternal( - targetPackageName); - final AndroidPackage targetPkg = - packageState == null ? null : packageState.getPkg(); - if (targetPackageName == null || targetPkg == null) { - Slog.e(TAG, "failed to find package " + targetPackageName); - return null; - } + Computer computer = snapshotComputer(); + final PackageStateInternal packageState = computer.getPackageStateInternal( + targetPackageName); + final AndroidPackage targetPkg = packageState == null ? null : packageState.getPkg(); + if (targetPackageName == null || targetPkg == null) { + Slog.e(TAG, "failed to find package " + targetPackageName); + return false; + } - if (Objects.equals(packageState.getUserStateOrDefault(userId).getOverlayPaths(), - newOverlayPaths)) { - return false; - } + if (Objects.equals(packageState.getUserStateOrDefault(userId).getOverlayPaths(), + newOverlayPaths)) { + return true; + } - if (targetPkg.getLibraryNames() != null) { - // Set the overlay paths for dependencies of the shared library. - for (final String libName : targetPkg.getLibraryNames()) { - ArraySet<String> modifiedDependents = null; + if (targetPkg.getLibraryNames() != null) { + // Set the overlay paths for dependencies of the shared library. + for (final String libName : targetPkg.getLibraryNames()) { + ArraySet<String> modifiedDependents = null; - final SharedLibraryInfo info = computer.getSharedLibraryInfo(libName, - SharedLibraryInfo.VERSION_UNDEFINED); - if (info == null) { - continue; - } - final List<VersionedPackage> dependents = computer - .getPackagesUsingSharedLibrary(info, 0, Process.SYSTEM_UID, userId); - if (dependents == null) { + final SharedLibraryInfo info = computer.getSharedLibraryInfo(libName, + SharedLibraryInfo.VERSION_UNDEFINED); + if (info == null) { + continue; + } + final List<VersionedPackage> dependents = computer + .getPackagesUsingSharedLibrary(info, 0, Process.SYSTEM_UID, userId); + if (dependents == null) { + continue; + } + for (final VersionedPackage dependent : dependents) { + final PackageStateInternal dependentState = + computer.getPackageStateInternal(dependent.getPackageName()); + if (dependentState == null) { continue; } - for (final VersionedPackage dependent : dependents) { - final PackageStateInternal dependentState = - computer.getPackageStateInternal(dependent.getPackageName()); - if (dependentState == null) { - continue; - } - if (!Objects.equals(dependentState.getUserStateOrDefault(userId) - .getSharedLibraryOverlayPaths() - .get(libName), newOverlayPaths)) { - String dependentPackageName = dependent.getPackageName(); - modifiedDependents = ArrayUtils.add(modifiedDependents, - dependentPackageName); - outUpdatedPackageNames.add(dependentPackageName); - } + if (!Objects.equals(dependentState.getUserStateOrDefault(userId) + .getSharedLibraryOverlayPaths() + .get(libName), newOverlayPaths)) { + String dependentPackageName = dependent.getPackageName(); + modifiedDependents = ArrayUtils.add(modifiedDependents, + dependentPackageName); + outUpdatedPackageNames.add(dependentPackageName); } + } - if (modifiedDependents != null) { - libNameToModifiedDependents.put(libName, modifiedDependents); - } + if (modifiedDependents != null) { + libNameToModifiedDependents.put(libName, modifiedDependents); } } - - outUpdatedPackageNames.add(targetPackageName); - return true; - }); - - if (targetModified == null) { - // Null indicates error - return false; - } else if (!targetModified) { - // Treat non-modification as a successful commit - return true; } + outUpdatedPackageNames.add(targetPackageName); + commitPackageStateMutation(null, mutator -> { mutator.forPackage(targetPackageName) .userState(userId) @@ -8062,36 +7971,6 @@ public class PackageManagerService extends IPackageManager.Stub forEachPackageState(mComputer.getPackageStates(), actionWrapped); } - // TODO: Make private - void executeWithConsistentComputer( - @NonNull FunctionalUtils.ThrowingConsumer<Computer> consumer) { - consumer.accept(snapshotComputer()); - } - - private <T> T executeWithConsistentComputerReturning( - @NonNull FunctionalUtils.ThrowingFunction<Computer, T> function) { - return function.apply(snapshotComputer()); - } - - private <ExceptionType extends Exception> void executeWithConsistentComputerThrowing( - @NonNull FunctionalUtils.ThrowingCheckedConsumer<Computer, ExceptionType> consumer) - throws ExceptionType { - consumer.accept(snapshotComputer()); - } - - private <ExceptionOne extends Exception, ExceptionTwo extends Exception> void - executeWithConsistentComputerThrowing2( - @NonNull FunctionalUtils.ThrowingChecked2Consumer<Computer, ExceptionOne, - ExceptionTwo> consumer) throws ExceptionOne, ExceptionTwo { - consumer.accept(snapshotComputer()); - } - - private <T, ExceptionType extends Exception> T executeWithConsistentComputerReturningThrowing( - @NonNull FunctionalUtils.ThrowingCheckedFunction<Computer, T, ExceptionType> function) - throws ExceptionType { - return function.apply(snapshotComputer()); - } - boolean isHistoricalPackageUsageAvailable() { return mPackageUsage.isHistoricalPackageUsageAvailable(); } @@ -8357,7 +8236,7 @@ public class PackageManagerService extends IPackageManager.Stub */ void writeSettingsLPrTEMP() { mPermissionManager.writeLegacyPermissionsTEMP(mSettings.mPermissions); - mSettings.writeLPr(); + mSettings.writeLPr(mLiveComputer); } @Override @@ -8856,7 +8735,6 @@ public class PackageManagerService extends IPackageManager.Stub } void notifyInstantAppPackageInstalled(String packageName, int[] newUsers) { - executeWithConsistentComputer(computer -> - mInstantAppRegistry.onPackageInstalled(computer, packageName, newUsers)); + mInstantAppRegistry.onPackageInstalled(snapshotComputer(), packageName, newUsers); } } diff --git a/services/core/java/com/android/server/pm/PendingPackageBroadcasts.java b/services/core/java/com/android/server/pm/PendingPackageBroadcasts.java index 4e9a06a329a7..6e2f756cad27 100644 --- a/services/core/java/com/android/server/pm/PendingPackageBroadcasts.java +++ b/services/core/java/com/android/server/pm/PendingPackageBroadcasts.java @@ -16,12 +16,17 @@ package com.android.server.pm; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.util.ArrayMap; import android.util.SparseArray; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; +import java.util.List; /** * Set of pending broadcasts for aggregating enable/disable of components. @@ -29,65 +34,111 @@ import java.util.ArrayList; @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public final class PendingPackageBroadcasts { + private final Object mLock = new PackageManagerTracedLock(); + // for each user id, a map of <package name -> components within that package> + @GuardedBy("mLock") final SparseArray<ArrayMap<String, ArrayList<String>>> mUidMap; public PendingPackageBroadcasts() { mUidMap = new SparseArray<>(2); } - public ArrayList<String> get(int userId, String packageName) { - ArrayMap<String, ArrayList<String>> packages = getOrAllocate(userId); - return packages.get(packageName); + public boolean hasPackage(@UserIdInt int userId, @NonNull String packageName) { + synchronized (mLock) { + final ArrayMap<String, ArrayList<String>> packages = mUidMap.get(userId); + return packages != null && packages.containsKey(packageName); + } } public void put(int userId, String packageName, ArrayList<String> components) { - ArrayMap<String, ArrayList<String>> packages = getOrAllocate(userId); - packages.put(packageName, components); - } - - public void remove(int userId, String packageName) { - ArrayMap<String, ArrayList<String>> packages = mUidMap.get(userId); - if (packages != null) { - packages.remove(packageName); + synchronized (mLock) { + ArrayMap<String, ArrayList<String>> packages = getOrAllocate(userId); + packages.put(packageName, components); } } - public void remove(int userId) { - mUidMap.remove(userId); + public void addComponent(@UserIdInt int userId, @NonNull String packageName, + @NonNull String componentClassName) { + synchronized (mLock) { + ArrayList<String> components = getOrAllocate(userId, packageName); + if (!components.contains(componentClassName)) { + components.add(componentClassName); + } + } } - public int userIdCount() { - return mUidMap.size(); + public void addComponents(@UserIdInt int userId, @NonNull String packageName, + @NonNull List<String> componentClassNames) { + synchronized (mLock) { + ArrayList<String> components = getOrAllocate(userId, packageName); + for (int index = 0; index < componentClassNames.size(); index++) { + String componentClassName = componentClassNames.get(index); + if (!components.contains(componentClassName)) { + components.add(componentClassName); + } + } + } } - public int userIdAt(int n) { - return mUidMap.keyAt(n); + public void remove(int userId, String packageName) { + synchronized (mLock) { + ArrayMap<String, ArrayList<String>> packages = mUidMap.get(userId); + if (packages != null) { + packages.remove(packageName); + } + } } - public ArrayMap<String, ArrayList<String>> packagesForUserId(int userId) { - return mUidMap.get(userId); + public void remove(int userId) { + synchronized (mLock) { + mUidMap.remove(userId); + } } - public int size() { - // total number of pending broadcast entries across all userIds - int num = 0; - for (int i = 0; i < mUidMap.size(); i++) { - num += mUidMap.valueAt(i).size(); + @Nullable + public SparseArray<ArrayMap<String, ArrayList<String>>> copiedMap() { + synchronized (mLock) { + SparseArray<ArrayMap<String, ArrayList<String>>> copy = new SparseArray<>(); + for (int userIdIndex = 0; userIdIndex < mUidMap.size(); userIdIndex++) { + final ArrayMap<String, ArrayList<String>> packages = mUidMap.valueAt(userIdIndex); + ArrayMap<String, ArrayList<String>> packagesCopy = new ArrayMap<>(); + for (int packagesIndex = 0; packagesIndex < packages.size(); packagesIndex++) { + packagesCopy.put(packages.keyAt(packagesIndex), + new ArrayList<>(packages.valueAt(packagesIndex))); + } + copy.put(mUidMap.keyAt(userIdIndex), packagesCopy); + } + return copy; } - return num; } public void clear() { - mUidMap.clear(); + synchronized (mLock) { + mUidMap.clear(); + } } private ArrayMap<String, ArrayList<String>> getOrAllocate(int userId) { - ArrayMap<String, ArrayList<String>> map = mUidMap.get(userId); - if (map == null) { - map = new ArrayMap<>(); - mUidMap.put(userId, map); + synchronized (mLock) { + ArrayMap<String, ArrayList<String>> map = mUidMap.get(userId); + if (map == null) { + map = new ArrayMap<>(); + mUidMap.put(userId, map); + } + return map; + } + } + + private ArrayList<String> getOrAllocate(int userId, @NonNull String packageName) { + synchronized (mLock) { + ArrayMap<String, ArrayList<String>> map = mUidMap.get(userId); + if (map == null) { + map = new ArrayMap<>(); + mUidMap.put(userId, map); + } + + return map.computeIfAbsent(packageName, k -> new ArrayList<>()); } - return map; } } diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 2ad35b7acf50..394c8fb73156 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -2432,7 +2432,7 @@ public final class Settings implements Watchable, Snappable { } } - void writeLPr() { + void writeLPr(@NonNull Computer computer) { //Debug.startMethodTracing("/data/system/packageprof", 8 * 1024 * 1024); final long startTime = SystemClock.uptimeMillis(); @@ -2524,8 +2524,8 @@ public final class Settings implements Watchable, Snappable { } } - mDomainVerificationManager.writeSettings(serializer, false /* includeSignatures */, - UserHandle.USER_ALL); + mDomainVerificationManager.writeSettings(computer, serializer, + false /* includeSignatures */, UserHandle.USER_ALL); mKeySetManagerService.writeKeySetManagerServiceLPr(serializer); @@ -2967,7 +2967,7 @@ public final class Settings implements Watchable, Snappable { } } - boolean readLPw(@NonNull List<UserInfo> users) { + boolean readLPw(@NonNull Computer computer, @NonNull List<UserInfo> users) { FileInputStream str = null; if (mBackupSettingsFilename.exists()) { try { @@ -3111,7 +3111,7 @@ public final class Settings implements Watchable, Snappable { ver.databaseVersion = parser.getAttributeInt(null, ATTR_DATABASE_VERSION); ver.fingerprint = XmlUtils.readStringAttribute(parser, ATTR_FINGERPRINT); } else if (tagName.equals(DomainVerificationPersistence.TAG_DOMAIN_VERIFICATIONS)) { - mDomainVerificationManager.readSettings(parser); + mDomainVerificationManager.readSettings(computer, parser); } else if (tagName.equals( DomainVerificationLegacySettings.TAG_DOMAIN_VERIFICATIONS_LEGACY)) { mDomainVerificationManager.readLegacySettings(parser); @@ -4287,11 +4287,11 @@ public final class Settings implements Watchable, Snappable { return Process.FIRST_APPLICATION_UID + size; } - public VerifierDeviceIdentity getVerifierDeviceIdentityLPw() { + public VerifierDeviceIdentity getVerifierDeviceIdentityLPw(@NonNull Computer computer) { if (mVerifierDeviceIdentity == null) { mVerifierDeviceIdentity = VerifierDeviceIdentity.generate(); - writeLPr(); + writeLPr(computer); } return mVerifierDeviceIdentity; diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java index 0638d5eee98b..3fe079034cf2 100644 --- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java +++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java @@ -321,7 +321,8 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable * on the device. * @return {@code true} if the available storage space is reached. */ - boolean pruneUnusedStaticSharedLibraries(long neededSpace, long maxCachePeriod) + boolean pruneUnusedStaticSharedLibraries(@NonNull Computer computer, long neededSpace, + long maxCachePeriod) throws IOException { final StorageManager storage = mInjector.getSystemService(StorageManager.class); final File volume = storage.findPathForUuid(StorageManager.UUID_PRIVATE_INTERNAL); @@ -332,38 +333,36 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable // Important: We skip shared libs used for some user since // in such a case we need to keep the APK on the device. The check for // a lib being used for any user is performed by the uninstall call. - mPm.executeWithConsistentComputer(computer -> { - final WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> - sharedLibraries = computer.getSharedLibraries(); - final int libCount = sharedLibraries.size(); - for (int i = 0; i < libCount; i++) { - final WatchedLongSparseArray<SharedLibraryInfo> versionedLib = - sharedLibraries.valueAt(i); - if (versionedLib == null) { + final WatchedArrayMap<String, WatchedLongSparseArray<SharedLibraryInfo>> + sharedLibraries = computer.getSharedLibraries(); + final int libCount = sharedLibraries.size(); + for (int i = 0; i < libCount; i++) { + final WatchedLongSparseArray<SharedLibraryInfo> versionedLib = + sharedLibraries.valueAt(i); + if (versionedLib == null) { + continue; + } + final int versionCount = versionedLib.size(); + for (int j = 0; j < versionCount; j++) { + SharedLibraryInfo libInfo = versionedLib.valueAt(j); + final PackageStateInternal ps = getLibraryPackage(computer, libInfo); + if (ps == null) { + continue; + } + // Skip unused libs cached less than the min period to prevent pruning a lib + // needed by a subsequently installed package. + if (now - ps.getLastUpdateTime() < maxCachePeriod) { continue; } - final int versionCount = versionedLib.size(); - for (int j = 0; j < versionCount; j++) { - SharedLibraryInfo libInfo = versionedLib.valueAt(j); - final PackageStateInternal ps = getLibraryPackage(computer, libInfo); - if (ps == null) { - continue; - } - // Skip unused libs cached less than the min period to prevent pruning a lib - // needed by a subsequently installed package. - if (now - ps.getLastUpdateTime() < maxCachePeriod) { - continue; - } - - if (ps.getPkg().isSystem()) { - continue; - } - packagesToDelete.add(new VersionedPackage(ps.getPkg().getPackageName(), - libInfo.getDeclaringPackage().getLongVersionCode())); + if (ps.getPkg().isSystem()) { + continue; } + + packagesToDelete.add(new VersionedPackage(ps.getPkg().getPackageName(), + libInfo.getDeclaringPackage().getLongVersionCode())); } - }); + } final int packageCount = packagesToDelete.size(); for (int i = 0; i < packageCount; i++) { diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java index bd1c9c702bcb..3ef5599385ce 100644 --- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java +++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java @@ -27,6 +27,7 @@ import static com.android.server.pm.PackageManagerService.TAG; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.IActivityManager; import android.content.Intent; @@ -99,10 +100,10 @@ public final class SuspendPackageHelper { * @return The names of failed packages. */ @Nullable - String[] setPackagesSuspended(@Nullable String[] packageNames, boolean suspended, - @Nullable PersistableBundle appExtras, @Nullable PersistableBundle launcherExtras, - @Nullable SuspendDialogInfo dialogInfo, @NonNull String callingPackage, - int userId, int callingUid) { + String[] setPackagesSuspended(@NonNull Computer computer, @Nullable String[] packageNames, + boolean suspended, @Nullable PersistableBundle appExtras, + @Nullable PersistableBundle launcherExtras, @Nullable SuspendDialogInfo dialogInfo, + @NonNull String callingPackage, @UserIdInt int userId, int callingUid) { if (ArrayUtils.isEmpty(packageNames)) { return packageNames; } @@ -121,62 +122,60 @@ public final class SuspendPackageHelper { ArraySet<String> modifiedPackages = new ArraySet<>(); - mPm.executeWithConsistentComputer(computer -> { - final boolean[] canSuspend = suspended - ? canSuspendPackageForUser(computer, packageNames, userId, callingUid) : null; - for (int i = 0; i < packageNames.length; i++) { - final String packageName = packageNames[i]; - if (callingPackage.equals(packageName)) { - Slog.w(TAG, "Calling package: " + callingPackage + " trying to " - + (suspended ? "" : "un") + "suspend itself. Ignoring"); - unmodifiablePackages.add(packageName); - continue; - } - final PackageStateInternal packageState = - computer.getPackageStateInternal(packageName); - if (packageState == null - || computer.shouldFilterApplication(packageState, callingUid, userId)) { - Slog.w(TAG, "Could not find package setting for package: " + packageName - + ". Skipping suspending/un-suspending."); - unmodifiablePackages.add(packageName); - continue; - } - if (canSuspend != null && !canSuspend[i]) { - unmodifiablePackages.add(packageName); - continue; - } + final boolean[] canSuspend = suspended + ? canSuspendPackageForUser(computer, packageNames, userId, callingUid) : null; + for (int i = 0; i < packageNames.length; i++) { + final String packageName = packageNames[i]; + if (callingPackage.equals(packageName)) { + Slog.w(TAG, "Calling package: " + callingPackage + " trying to " + + (suspended ? "" : "un") + "suspend itself. Ignoring"); + unmodifiablePackages.add(packageName); + continue; + } + final PackageStateInternal packageState = + computer.getPackageStateInternal(packageName); + if (packageState == null + || computer.shouldFilterApplication(packageState, callingUid, userId)) { + Slog.w(TAG, "Could not find package setting for package: " + packageName + + ". Skipping suspending/un-suspending."); + unmodifiablePackages.add(packageName); + continue; + } + if (canSuspend != null && !canSuspend[i]) { + unmodifiablePackages.add(packageName); + continue; + } - final WatchedArrayMap<String, SuspendParams> suspendParamsMap = - packageState.getUserStateOrDefault(userId).getSuspendParams(); - if (suspended) { - if (suspendParamsMap != null && suspendParamsMap.containsKey(packageName)) { - final SuspendParams suspendParams = suspendParamsMap.get(packageName); - // Skip if there's no changes - if (suspendParams != null - && Objects.equals(suspendParams.getDialogInfo(), dialogInfo) - && Objects.equals(suspendParams.getAppExtras(), appExtras) - && Objects.equals(suspendParams.getLauncherExtras(), - launcherExtras)) { - // Carried over API behavior, must notify change even if no change - changedPackagesList.add(packageName); - changedUids.add(UserHandle.getUid(userId, packageState.getAppId())); - continue; - } + final WatchedArrayMap<String, SuspendParams> suspendParamsMap = + packageState.getUserStateOrDefault(userId).getSuspendParams(); + if (suspended) { + if (suspendParamsMap != null && suspendParamsMap.containsKey(packageName)) { + final SuspendParams suspendParams = suspendParamsMap.get(packageName); + // Skip if there's no changes + if (suspendParams != null + && Objects.equals(suspendParams.getDialogInfo(), dialogInfo) + && Objects.equals(suspendParams.getAppExtras(), appExtras) + && Objects.equals(suspendParams.getLauncherExtras(), + launcherExtras)) { + // Carried over API behavior, must notify change even if no change + changedPackagesList.add(packageName); + changedUids.add(UserHandle.getUid(userId, packageState.getAppId())); + continue; } } + } - // If size one, the package will be unsuspended from this call - boolean packageUnsuspended = - !suspended && CollectionUtils.size(suspendParamsMap) <= 1; - if (suspended || packageUnsuspended) { - changedPackagesList.add(packageName); - changedUids.add(UserHandle.getUid(userId, packageState.getAppId())); - } - - modifiedPackages.add(packageName); - modifiedUids.add(UserHandle.getUid(userId, packageState.getAppId())); + // If size one, the package will be unsuspended from this call + boolean packageUnsuspended = + !suspended && CollectionUtils.size(suspendParamsMap) <= 1; + if (suspended || packageUnsuspended) { + changedPackagesList.add(packageName); + changedUids.add(UserHandle.getUid(userId, packageState.getAppId())); } - }); + + modifiedPackages.add(packageName); + modifiedUids.add(UserHandle.getUid(userId, packageState.getAppId())); + } mPm.commitPackageStateMutation(null, mutator -> { final int size = modifiedPackages.size(); @@ -218,29 +217,27 @@ public final class SuspendPackageHelper { * @return The names of packages which are Unsuspendable. */ @NonNull - String[] getUnsuspendablePackagesForUser(@NonNull String[] packageNames, int userId, - int callingUid) { + String[] getUnsuspendablePackagesForUser(@NonNull Computer computer, + @NonNull String[] packageNames, @UserIdInt int userId, int callingUid) { if (!isSuspendAllowedForUser(userId, callingUid)) { Slog.w(TAG, "Cannot suspend due to restrictions on user " + userId); return packageNames; } final ArraySet<String> unactionablePackages = new ArraySet<>(); - mPm.executeWithConsistentComputer(computer -> { - final boolean[] canSuspend = canSuspendPackageForUser(computer, packageNames, userId, - callingUid); - for (int i = 0; i < packageNames.length; i++) { - if (!canSuspend[i]) { - unactionablePackages.add(packageNames[i]); - continue; - } - final PackageStateInternal packageState = - computer.getPackageStateFiltered(packageNames[i], callingUid, userId); - if (packageState == null) { - Slog.w(TAG, "Could not find package setting for package: " + packageNames[i]); - unactionablePackages.add(packageNames[i]); - } + final boolean[] canSuspend = canSuspendPackageForUser(computer, packageNames, userId, + callingUid); + for (int i = 0; i < packageNames.length; i++) { + if (!canSuspend[i]) { + unactionablePackages.add(packageNames[i]); + continue; } - }); + final PackageStateInternal packageState = + computer.getPackageStateFiltered(packageNames[i], callingUid, userId); + if (packageState == null) { + Slog.w(TAG, "Could not find package setting for package: " + packageNames[i]); + unactionablePackages.add(packageNames[i]); + } + } return unactionablePackages.toArray(new String[unactionablePackages.size()]); } @@ -282,45 +279,44 @@ public final class SuspendPackageHelper { * suspensions will be removed. * @param userId The user for which the changes are taking place. */ - void removeSuspensionsBySuspendingPackage(@NonNull String[] packagesToChange, + void removeSuspensionsBySuspendingPackage(@NonNull Computer computer, + @NonNull String[] packagesToChange, @NonNull Predicate<String> suspendingPackagePredicate, int userId) { final List<String> unsuspendedPackages = new ArrayList<>(); final IntArray unsuspendedUids = new IntArray(); final ArrayMap<String, ArraySet<String>> pkgToSuspendingPkgsToCommit = new ArrayMap<>(); - mPm.executeWithConsistentComputer(computer -> { - for (String packageName : packagesToChange) { - final PackageStateInternal packageState = - computer.getPackageStateInternal(packageName); - final PackageUserStateInternal packageUserState = packageState == null - ? null : packageState.getUserStateOrDefault(userId); - if (packageUserState == null || !packageUserState.isSuspended()) { - continue; - } + for (String packageName : packagesToChange) { + final PackageStateInternal packageState = + computer.getPackageStateInternal(packageName); + final PackageUserStateInternal packageUserState = packageState == null + ? null : packageState.getUserStateOrDefault(userId); + if (packageUserState == null || !packageUserState.isSuspended()) { + continue; + } - WatchedArrayMap<String, SuspendParams> suspendParamsMap = - packageUserState.getSuspendParams(); - int countRemoved = 0; - for (int index = 0; index < suspendParamsMap.size(); index++) { - String suspendingPackage = suspendParamsMap.keyAt(index); - if (suspendingPackagePredicate.test(suspendingPackage)) { - ArraySet<String> suspendingPkgsToCommit = - pkgToSuspendingPkgsToCommit.get(packageName); - if (suspendingPkgsToCommit == null) { - suspendingPkgsToCommit = new ArraySet<>(); - pkgToSuspendingPkgsToCommit.put(packageName, suspendingPkgsToCommit); - } - suspendingPkgsToCommit.add(suspendingPackage); - countRemoved++; + WatchedArrayMap<String, SuspendParams> suspendParamsMap = + packageUserState.getSuspendParams(); + int countRemoved = 0; + for (int index = 0; index < suspendParamsMap.size(); index++) { + String suspendingPackage = suspendParamsMap.keyAt(index); + if (suspendingPackagePredicate.test(suspendingPackage)) { + ArraySet<String> suspendingPkgsToCommit = + pkgToSuspendingPkgsToCommit.get(packageName); + if (suspendingPkgsToCommit == null) { + suspendingPkgsToCommit = new ArraySet<>(); + pkgToSuspendingPkgsToCommit.put(packageName, suspendingPkgsToCommit); } + suspendingPkgsToCommit.add(suspendingPackage); + countRemoved++; } + } - // Everything would be removed and package unsuspended - if (countRemoved == suspendParamsMap.size()) { - unsuspendedPackages.add(packageState.getPackageName()); - unsuspendedUids.add(UserHandle.getUid(userId, packageState.getAppId())); - } + // Everything would be removed and package unsuspended + if (countRemoved == suspendParamsMap.size()) { + unsuspendedPackages.add(packageState.getPackageName()); + unsuspendedUids.add(UserHandle.getUid(userId, packageState.getAppId())); } - }); + } mPm.commitPackageStateMutation(null, mutator -> { for (int mapIndex = 0; mapIndex < pkgToSuspendingPkgsToCommit.size(); mapIndex++) { diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java index b730ab27c70f..e06b6081fd1b 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationDebug.java @@ -30,6 +30,7 @@ import android.util.PackageUtils; import android.util.SparseArray; import com.android.internal.util.CollectionUtils; +import com.android.server.pm.Computer; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState; @@ -38,7 +39,6 @@ import com.android.server.pm.verify.domain.models.DomainVerificationStateMap; import java.util.Arrays; import java.util.List; -import java.util.function.Function; @SuppressWarnings("PointlessBooleanExpression") public class DomainVerificationDebug { @@ -62,8 +62,7 @@ public class DomainVerificationDebug { } public void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName, - @Nullable @UserIdInt Integer userId, - @NonNull Function<String, PackageStateInternal> pkgSettingFunction, + @Nullable @UserIdInt Integer userId, @NonNull Computer snapshot, @NonNull DomainVerificationStateMap<DomainVerificationPkgState> stateMap) throws NameNotFoundException { ArrayMap<String, Integer> reusedMap = new ArrayMap<>(); @@ -74,7 +73,7 @@ public class DomainVerificationDebug { for (int index = 0; index < size; index++) { DomainVerificationPkgState pkgState = stateMap.valueAt(index); String pkgName = pkgState.getPackageName(); - PackageStateInternal pkgSetting = pkgSettingFunction.apply(pkgName); + PackageStateInternal pkgSetting = snapshot.getPackageStateInternal(pkgName); if (pkgSetting == null || pkgSetting.getPkg() == null) { continue; } @@ -90,7 +89,7 @@ public class DomainVerificationDebug { throw DomainVerificationUtils.throwPackageUnavailable(packageName); } - PackageStateInternal pkgSetting = pkgSettingFunction.apply(packageName); + PackageStateInternal pkgSetting = snapshot.getPackageStateInternal(packageName); if (pkgSetting == null || pkgSetting.getPkg() == null) { throw DomainVerificationUtils.throwPackageUnavailable(packageName); } diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java index 25147d092066..17140865a9d5 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java @@ -25,7 +25,6 @@ import android.content.Intent; import android.content.pm.IntentFilterVerificationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.PackageSettingsSnapshotProvider; import android.content.pm.ResolveInfo; import android.content.pm.verify.domain.DomainVerificationInfo; import android.content.pm.verify.domain.DomainVerificationManager; @@ -37,7 +36,7 @@ import android.util.Pair; import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; -import com.android.server.pm.PackageManagerService; +import com.android.server.pm.Computer; import com.android.server.pm.PackageSetting; import com.android.server.pm.Settings; import com.android.server.pm.pkg.PackageStateInternal; @@ -227,14 +226,14 @@ public interface DomainVerificationManagerInternal { * assumed nothing has changed since the device rebooted. * <p> * If this is a new install, state will be restored from a previous call to {@link - * #restoreSettings(TypedXmlPullParser)}, or a new one will be generated. In either case, a + * #restoreSettings(Computer, TypedXmlPullParser)}, or a new one will be generated. In either case, a * broadcast will be sent to the domain verification agent so it may re-run any verification * logic for the newly associated domains. * <p> * This will mutate internal {@link DomainVerificationPkgState} and so will hold the internal * lock. This should never be called from within the domain verification classes themselves. * <p> - * This will NOT call {@link #writeSettings(TypedXmlSerializer, boolean, int)}. That must be + * This will NOT call {@link #writeSettings(Computer, TypedXmlSerializer, boolean, int)}. That must be * handled by the caller. */ void addPackage(@NonNull PackageStateInternal newPkgSetting); @@ -249,7 +248,7 @@ public interface DomainVerificationManagerInternal { * This will mutate internal {@link DomainVerificationPkgState} and so will hold the internal * lock. This should never be called from within the domain verification classes themselves. * <p> - * This will NOT call {@link #writeSettings(TypedXmlSerializer, boolean, int)}. That must be + * This will NOT call {@link #writeSettings(Computer, TypedXmlSerializer, boolean, int)}. That must be * handled by the caller. */ void migrateState(@NonNull PackageStateInternal oldPkgSetting, @@ -259,24 +258,24 @@ public interface DomainVerificationManagerInternal { * Serializes the entire internal state. This is equivalent to a full backup of the existing * verification state. This write includes legacy state, as a sibling tag the modern state. * + * @param snapshot * @param includeSignatures Whether to include the package signatures in the output, mainly * used for backing up the user settings and ensuring they're * re-attached to the same package. * @param userId The user to write out. Supports {@link UserHandle#USER_ALL} if all users - * should be written. */ - void writeSettings(@NonNull TypedXmlSerializer serializer, boolean includeSignatures, - @UserIdInt int userId) throws IOException; + void writeSettings(@NonNull Computer snapshot, @NonNull TypedXmlSerializer serializer, + boolean includeSignatures, @UserIdInt int userId) throws IOException; /** * Read back a list of {@link DomainVerificationPkgState}s previously written by {@link - * #writeSettings(TypedXmlSerializer, boolean, int)}. Assumes that the + * #writeSettings(Computer, TypedXmlSerializer, boolean, int)}. Assumes that the * {@link DomainVerificationPersistence#TAG_DOMAIN_VERIFICATIONS} tag has already been entered. * <p> * This is expected to only be used to re-attach states for packages already known to be on the - * device. If restoring from a backup, use {@link #restoreSettings(TypedXmlPullParser)}. + * device. If restoring from a backup, use {@link #restoreSettings(Computer, TypedXmlPullParser)}. */ - void readSettings(@NonNull TypedXmlPullParser parser) + void readSettings(@NonNull Computer snapshot, @NonNull TypedXmlPullParser parser) throws IOException, XmlPullParserException; /** @@ -306,7 +305,7 @@ public interface DomainVerificationManagerInternal { /** * Restore a list of {@link DomainVerificationPkgState}s previously written by {@link - * #writeSettings(TypedXmlSerializer, boolean, int)}. Assumes that the + * #writeSettings(Computer, TypedXmlSerializer, boolean, int)}. Assumes that the * {@link DomainVerificationPersistence#TAG_DOMAIN_VERIFICATIONS} * tag has already been entered. * <p> @@ -321,7 +320,7 @@ public interface DomainVerificationManagerInternal { * TODO(b/170746586): Figure out how to verify that package signatures match at snapshot time * and restore time. */ - void restoreSettings(@NonNull TypedXmlPullParser parser) + void restoreSettings(@NonNull Computer snapshot, @NonNull TypedXmlPullParser parser) throws IOException, XmlPullParserException; /** @@ -349,17 +348,14 @@ public interface DomainVerificationManagerInternal { /** * Print the verification state and user selection state of a package. * + * @param snapshot * @param packageName the package whose state to change, or all packages if none is * specified * @param userId the specific user to print, or null to skip printing user selection - * states, supports {@link android.os.UserHandle#USER_ALL} - * @param pkgSettingFunction the method by which to retrieve package data; if this is called - * from {@link PackageManagerService}, it is expected to pass in the - * snapshot of {@link PackageStateInternal} objects + * states, supports {@link UserHandle#USER_ALL} */ - void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName, - @Nullable @UserIdInt Integer userId, - @NonNull Function<String, PackageStateInternal> pkgSettingFunction) + void printState(@NonNull Computer snapshot, @NonNull IndentingPrintWriter writer, + @Nullable String packageName, @Nullable @UserIdInt Integer userId) throws NameNotFoundException; @NonNull @@ -406,12 +402,11 @@ public interface DomainVerificationManagerInternal { @NonNull Set<String> domains, int state) throws NameNotFoundException; - interface Connection extends DomainVerificationEnforcer.Callback, - PackageSettingsSnapshotProvider { + interface Connection extends DomainVerificationEnforcer.Callback { /** * Notify that a settings change has been made and that eventually - * {@link #writeSettings(TypedXmlSerializer, boolean, int)} should be invoked by the parent. + * {@link #writeSettings(Computer, TypedXmlSerializer, boolean, int)} should be invoked by the parent. */ void scheduleWriteSettings(); @@ -433,5 +428,8 @@ public interface DomainVerificationManagerInternal { @UserIdInt int[] getAllUserIds(); + + @NonNull + Computer snapshot(); } } 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 d6c89f7209e6..13218eaf2ded 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 @@ -37,7 +37,6 @@ import android.content.pm.verify.domain.DomainVerificationManager; import android.content.pm.verify.domain.DomainVerificationState; import android.content.pm.verify.domain.DomainVerificationUserState; import android.content.pm.verify.domain.IDomainVerificationManager; -import android.os.Build; import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; @@ -52,11 +51,10 @@ import android.util.TypedXmlSerializer; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.CollectionUtils; -import com.android.internal.util.FunctionalUtils; import com.android.server.SystemConfig; import com.android.server.SystemService; import com.android.server.compat.PlatformCompat; -import com.android.server.pm.Settings; +import com.android.server.pm.Computer; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageStateUtils; @@ -80,7 +78,6 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.UUID; -import java.util.function.Consumer; import java.util.function.Function; @SuppressLint("MissingPermission") @@ -109,9 +106,9 @@ public class DomainVerificationService extends SystemService * immediately attached once its available. * <p> * Generally this should be not accessed directly. Prefer calling {@link - * #getAndValidateAttachedLocked(UUID, Set, boolean, int, Integer, Function)}. + * #getAndValidateAttachedLocked(UUID, Set, boolean, int, Integer, Computer)}. * - * @see #getAndValidateAttachedLocked(UUID, Set, boolean, int, Integer, Function) + * @see #getAndValidateAttachedLocked(UUID, Set, boolean, int, Integer, Computer) **/ @GuardedBy("mLock") @NonNull @@ -178,12 +175,7 @@ public class DomainVerificationService extends SystemService @Override public void setConnection(@NonNull Connection connection) { - if (Build.IS_USERDEBUG || Build.IS_ENG) { - mConnection = new LockSafeConnection(connection); - } else { - mConnection = connection; - } - + mConnection = connection; mEnforcer.setCallback(mConnection); } @@ -264,44 +256,43 @@ public class DomainVerificationService extends SystemService public DomainVerificationInfo getDomainVerificationInfo(@NonNull String packageName) throws NameNotFoundException { mEnforcer.assertApprovedQuerent(mConnection.getCallingUid(), mProxy); - return mConnection.withPackageSettingsSnapshotReturningThrowing(pkgSettings -> { - synchronized (mLock) { - PackageStateInternal pkgSetting = pkgSettings.apply(packageName); - AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg(); - if (pkg == null) { - throw DomainVerificationUtils.throwPackageUnavailable(packageName); - } - - DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName); - if (pkgState == null) { - return null; - } + synchronized (mLock) { + final Computer snapshot = mConnection.snapshot(); + PackageStateInternal pkgSetting = snapshot.getPackageStateInternal(packageName); + AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg(); + if (pkg == null) { + throw DomainVerificationUtils.throwPackageUnavailable(packageName); + } - ArrayMap<String, Integer> hostToStateMap = new ArrayMap<>(pkgState.getStateMap()); + DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName); + if (pkgState == null) { + return null; + } - // TODO(b/159952358): Should the domain list be cached? - ArraySet<String> domains = mCollector.collectValidAutoVerifyDomains(pkg); - if (domains.isEmpty()) { - return null; - } + ArrayMap<String, Integer> hostToStateMap = new ArrayMap<>(pkgState.getStateMap()); - int size = domains.size(); - for (int index = 0; index < size; index++) { - hostToStateMap.putIfAbsent(domains.valueAt(index), - DomainVerificationState.STATE_NO_RESPONSE); - } + // TODO(b/159952358): Should the domain list be cached? + ArraySet<String> domains = mCollector.collectValidAutoVerifyDomains(pkg); + if (domains.isEmpty()) { + return null; + } - final int mapSize = hostToStateMap.size(); - for (int index = 0; index < mapSize; index++) { - int internalValue = hostToStateMap.valueAt(index); - int publicValue = DomainVerificationState.convertToInfoState(internalValue); - hostToStateMap.setValueAt(index, publicValue); - } + int size = domains.size(); + for (int index = 0; index < size; index++) { + hostToStateMap.putIfAbsent(domains.valueAt(index), + DomainVerificationState.STATE_NO_RESPONSE); + } - // TODO(b/159952358): Do not return if no values are editable (all ignored states)? - return new DomainVerificationInfo(pkgState.getId(), packageName, hostToStateMap); + final int mapSize = hostToStateMap.size(); + for (int index = 0; index < mapSize; index++) { + int internalValue = hostToStateMap.valueAt(index); + int publicValue = DomainVerificationState.convertToInfoState(internalValue); + hostToStateMap.setValueAt(index, publicValue); } - }); + + // TODO(b/159952358): Do not return if no values are editable (all ignored states)? + return new DomainVerificationInfo(pkgState.getId(), packageName, hostToStateMap); + } } @DomainVerificationManager.Error @@ -324,42 +315,40 @@ public class DomainVerificationService extends SystemService @NonNull Set<String> domains, int state) throws NameNotFoundException { mEnforcer.assertApprovedVerifier(callingUid, mProxy); - return mConnection.withPackageSettingsSnapshotReturningThrowing(pkgSettings -> { - synchronized (mLock) { - List<String> verifiedDomains = new ArrayList<>(); - - GetAttachedResult result = getAndValidateAttachedLocked(domainSetId, domains, - true /* forAutoVerify */, callingUid, null /* userId */, - pkgSettings); - if (result.isError()) { - return result.getErrorCode(); - } - - DomainVerificationPkgState pkgState = result.getPkgState(); - ArrayMap<String, Integer> stateMap = pkgState.getStateMap(); - for (String domain : domains) { - Integer previousState = stateMap.get(domain); - if (previousState != null - && !DomainVerificationState.isModifiable(previousState)) { - continue; - } + synchronized (mLock) { + final Computer snapshot = mConnection.snapshot(); + List<String> verifiedDomains = new ArrayList<>(); - if (DomainVerificationState.isVerified(state)) { - verifiedDomains.add(domain); - } + GetAttachedResult result = getAndValidateAttachedLocked(domainSetId, domains, + true /* forAutoVerify */, callingUid, null /* userId */, snapshot); + if (result.isError()) { + return result.getErrorCode(); + } - stateMap.put(domain, state); + DomainVerificationPkgState pkgState = result.getPkgState(); + ArrayMap<String, Integer> stateMap = pkgState.getStateMap(); + for (String domain : domains) { + Integer previousState = stateMap.get(domain); + if (previousState != null + && !DomainVerificationState.isModifiable(previousState)) { + continue; } - int size = verifiedDomains.size(); - for (int index = 0; index < size; index++) { - removeUserStatesForDomain(verifiedDomains.get(index)); + if (DomainVerificationState.isVerified(state)) { + verifiedDomains.add(domain); } + + stateMap.put(domain, state); + } + + int size = verifiedDomains.size(); + for (int index = 0; index < size; index++) { + removeUserStatesForDomain(verifiedDomains.get(index)); } + } - mConnection.scheduleWriteSettings(); - return DomainVerificationManager.STATUS_OK; - }); + mConnection.scheduleWriteSettings(); + return DomainVerificationManager.STATUS_OK; } @Override @@ -380,60 +369,30 @@ public class DomainVerificationService extends SystemService ArraySet<String> verifiedDomains = new ArraySet<>(); if (packageName == null) { - mConnection.withPackageSettingsSnapshot(pkgSettings -> { - synchronized (mLock) { - ArraySet<String> validDomains = new ArraySet<>(); - - int size = mAttachedPkgStates.size(); - for (int index = 0; index < size; index++) { - DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index); - String pkgName = pkgState.getPackageName(); - PackageStateInternal pkgSetting = pkgSettings.apply(pkgName); - if (pkgSetting == null || pkgSetting.getPkg() == null) { - continue; - } - - AndroidPackage pkg = pkgSetting.getPkg(); - - validDomains.clear(); - - ArraySet<String> autoVerifyDomains = - mCollector.collectValidAutoVerifyDomains(pkg); - if (domains == null) { - validDomains.addAll(autoVerifyDomains); - } else { - validDomains.addAll(domains); - validDomains.retainAll(autoVerifyDomains); - } - - if (DomainVerificationState.isVerified(state)) { - verifiedDomains.addAll(validDomains); - } - - setDomainVerificationStatusInternal(pkgState, state, validDomains); - } - } - }); - } else { - mConnection.withPackageSettingsSnapshotThrowing(pkgSettings -> { - synchronized (mLock) { - DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName); - if (pkgState == null) { - throw DomainVerificationUtils.throwPackageUnavailable(packageName); - } + synchronized (mLock) { + final Computer snapshot = mConnection.snapshot(); + ArraySet<String> validDomains = new ArraySet<>(); - PackageStateInternal pkgSetting = pkgSettings.apply(packageName); + int size = mAttachedPkgStates.size(); + for (int index = 0; index < size; index++) { + DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index); + String pkgName = pkgState.getPackageName(); + PackageStateInternal pkgSetting = snapshot.getPackageStateInternal(pkgName); if (pkgSetting == null || pkgSetting.getPkg() == null) { - throw DomainVerificationUtils.throwPackageUnavailable(packageName); + continue; } AndroidPackage pkg = pkgSetting.getPkg(); - final ArraySet<String> validDomains; + + validDomains.clear(); + + ArraySet<String> autoVerifyDomains = + mCollector.collectValidAutoVerifyDomains(pkg); if (domains == null) { - validDomains = mCollector.collectValidAutoVerifyDomains(pkg); + validDomains.addAll(autoVerifyDomains); } else { - validDomains = domains; - validDomains.retainAll(mCollector.collectValidAutoVerifyDomains(pkg)); + validDomains.addAll(domains); + validDomains.retainAll(autoVerifyDomains); } if (DomainVerificationState.isVerified(state)) { @@ -442,7 +401,35 @@ public class DomainVerificationService extends SystemService setDomainVerificationStatusInternal(pkgState, state, validDomains); } - }); + } + } else { + synchronized (mLock) { + final Computer snapshot = mConnection.snapshot(); + DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName); + if (pkgState == null) { + throw DomainVerificationUtils.throwPackageUnavailable(packageName); + } + + PackageStateInternal pkgSetting = snapshot.getPackageStateInternal(packageName); + if (pkgSetting == null || pkgSetting.getPkg() == null) { + throw DomainVerificationUtils.throwPackageUnavailable(packageName); + } + + AndroidPackage pkg = pkgSetting.getPkg(); + final ArraySet<String> validDomains; + if (domains == null) { + validDomains = mCollector.collectValidAutoVerifyDomains(pkg); + } else { + validDomains = domains; + validDomains.retainAll(mCollector.collectValidAutoVerifyDomains(pkg)); + } + + if (DomainVerificationState.isVerified(state)) { + verifiedDomains.addAll(validDomains); + } + + setDomainVerificationStatusInternal(pkgState, state, validDomains); + } } // Mirror SystemApi behavior of revoking user selection for approved domains. @@ -552,39 +539,38 @@ public class DomainVerificationService extends SystemService return DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID; } - return mConnection.withPackageSettingsSnapshotReturningThrowing(pkgSettings -> { - synchronized (mLock) { - GetAttachedResult result = getAndValidateAttachedLocked(domainSetId, domains, - false /* forAutoVerify */, callingUid, userId, pkgSettings); - if (result.isError()) { - return result.getErrorCode(); - } - - DomainVerificationPkgState pkgState = result.getPkgState(); - DomainVerificationInternalUserState userState = pkgState.getOrCreateUserState( - userId); - - // Disable other packages if approving this one. Note that this check is only done - // for enabling. This allows an escape hatch in case multiple packages somehow get - // selected. They can be disabled without blocking in a circular dependency. - if (enabled) { - int statusCode = revokeOtherUserSelectionsLocked(userState, userId, domains, - pkgSettings); - if (statusCode != DomainVerificationManager.STATUS_OK) { - return statusCode; - } + synchronized (mLock) { + final Computer snapshot = mConnection.snapshot(); + GetAttachedResult result = getAndValidateAttachedLocked(domainSetId, domains, + false /* forAutoVerify */, callingUid, userId, snapshot); + if (result.isError()) { + return result.getErrorCode(); + } + + DomainVerificationPkgState pkgState = result.getPkgState(); + DomainVerificationInternalUserState userState = pkgState.getOrCreateUserState( + userId); + + // Disable other packages if approving this one. Note that this check is only done + // for enabling. This allows an escape hatch in case multiple packages somehow get + // selected. They can be disabled without blocking in a circular dependency. + if (enabled) { + int statusCode = revokeOtherUserSelectionsLocked(userState, userId, domains, + snapshot); + if (statusCode != DomainVerificationManager.STATUS_OK) { + return statusCode; } + } - if (enabled) { - userState.addHosts(domains); - } else { - userState.removeHosts(domains); - } + if (enabled) { + userState.addHosts(domains); + } else { + userState.removeHosts(domains); } + } - mConnection.scheduleWriteSettings(); - return DomainVerificationManager.STATUS_OK; - }); + mConnection.scheduleWriteSettings(); + return DomainVerificationManager.STATUS_OK; } @Override @@ -592,48 +578,47 @@ public class DomainVerificationService extends SystemService @NonNull String packageName, boolean enabled, @Nullable ArraySet<String> domains) throws NameNotFoundException { mEnforcer.assertInternal(mConnection.getCallingUid()); - mConnection.withPackageSettingsSnapshotThrowing(pkgSettings -> { - synchronized (mLock) { - DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName); - if (pkgState == null) { - throw DomainVerificationUtils.throwPackageUnavailable(packageName); - } + synchronized (mLock) { + final Computer snapshot = mConnection.snapshot(); + DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName); + if (pkgState == null) { + throw DomainVerificationUtils.throwPackageUnavailable(packageName); + } - PackageStateInternal pkgSetting = pkgSettings.apply(packageName); - AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg(); - if (pkg == null) { - throw DomainVerificationUtils.throwPackageUnavailable(packageName); - } + PackageStateInternal pkgSetting = snapshot.getPackageStateInternal(packageName); + AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg(); + if (pkg == null) { + throw DomainVerificationUtils.throwPackageUnavailable(packageName); + } - Set<String> validDomains = - domains == null ? mCollector.collectAllWebDomains(pkg) : domains; + Set<String> validDomains = + domains == null ? mCollector.collectAllWebDomains(pkg) : domains; - validDomains.retainAll(mCollector.collectAllWebDomains(pkg)); + validDomains.retainAll(mCollector.collectAllWebDomains(pkg)); - if (userId == UserHandle.USER_ALL) { - for (int aUserId : mConnection.getAllUserIds()) { - DomainVerificationInternalUserState userState = - pkgState.getOrCreateUserState(aUserId); - revokeOtherUserSelectionsLocked(userState, aUserId, validDomains, - pkgSettings); - if (enabled) { - userState.addHosts(validDomains); - } else { - userState.removeHosts(validDomains); - } - } - } else { + if (userId == UserHandle.USER_ALL) { + for (int aUserId : mConnection.getAllUserIds()) { DomainVerificationInternalUserState userState = - pkgState.getOrCreateUserState(userId); - revokeOtherUserSelectionsLocked(userState, userId, validDomains, pkgSettings); + pkgState.getOrCreateUserState(aUserId); + revokeOtherUserSelectionsLocked(userState, aUserId, validDomains, + snapshot); if (enabled) { userState.addHosts(validDomains); } else { userState.removeHosts(validDomains); } } + } else { + DomainVerificationInternalUserState userState = + pkgState.getOrCreateUserState(userId); + revokeOtherUserSelectionsLocked(userState, userId, validDomains, snapshot); + if (enabled) { + userState.addHosts(validDomains); + } else { + userState.removeHosts(validDomains); + } } - }); + } mConnection.scheduleWriteSettings(); } @@ -641,8 +626,7 @@ public class DomainVerificationService extends SystemService @GuardedBy("mLock") private int revokeOtherUserSelectionsLocked( @NonNull DomainVerificationInternalUserState userState, @UserIdInt int userId, - @NonNull Set<String> domains, - @NonNull Function<String, PackageStateInternal> pkgSettingFunction) { + @NonNull Set<String> domains, @NonNull Computer snapshot) { // Cache the approved packages from the 1st pass because the search is expensive ArrayMap<String, List<String>> domainToApprovedPackages = new ArrayMap<>(); @@ -652,7 +636,7 @@ public class DomainVerificationService extends SystemService } Pair<List<String>, Integer> packagesToLevel = getApprovedPackagesLocked(domain, - userId, APPROVAL_LEVEL_NONE + 1, pkgSettingFunction); + userId, APPROVAL_LEVEL_NONE + 1, snapshot); int highestApproval = packagesToLevel.second; if (highestApproval > APPROVAL_LEVEL_SELECTION) { return DomainVerificationManager.ERROR_UNABLE_TO_APPROVE; @@ -698,51 +682,50 @@ public class DomainVerificationService extends SystemService throw DomainVerificationUtils.throwPackageUnavailable(packageName); } - return mConnection.withPackageSettingsSnapshotReturningThrowing(pkgSettings -> { - synchronized (mLock) { - PackageStateInternal pkgSetting = pkgSettings.apply(packageName); - AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg(); - if (pkg == null) { - throw DomainVerificationUtils.throwPackageUnavailable(packageName); - } - - DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName); - if (pkgState == null) { - return null; - } + synchronized (mLock) { + final Computer snapshot = mConnection.snapshot(); + PackageStateInternal pkgSetting = snapshot.getPackageStateInternal(packageName); + AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg(); + if (pkg == null) { + throw DomainVerificationUtils.throwPackageUnavailable(packageName); + } - ArraySet<String> webDomains = mCollector.collectAllWebDomains(pkg); - int webDomainsSize = webDomains.size(); + DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName); + if (pkgState == null) { + return null; + } - Map<String, Integer> domains = new ArrayMap<>(webDomainsSize); - ArrayMap<String, Integer> stateMap = pkgState.getStateMap(); - DomainVerificationInternalUserState userState = pkgState.getUserState(userId); - Set<String> enabledHosts = - userState == null ? emptySet() : userState.getEnabledHosts(); - - for (int index = 0; index < webDomainsSize; index++) { - String host = webDomains.valueAt(index); - Integer state = stateMap.get(host); - - int domainState; - if (state != null && DomainVerificationState.isVerified(state)) { - domainState = DomainVerificationUserState.DOMAIN_STATE_VERIFIED; - } else if (enabledHosts.contains(host)) { - domainState = DomainVerificationUserState.DOMAIN_STATE_SELECTED; - } else { - domainState = DomainVerificationUserState.DOMAIN_STATE_NONE; - } + ArraySet<String> webDomains = mCollector.collectAllWebDomains(pkg); + int webDomainsSize = webDomains.size(); - domains.put(host, domainState); + Map<String, Integer> domains = new ArrayMap<>(webDomainsSize); + ArrayMap<String, Integer> stateMap = pkgState.getStateMap(); + DomainVerificationInternalUserState userState = pkgState.getUserState(userId); + Set<String> enabledHosts = + userState == null ? emptySet() : userState.getEnabledHosts(); + + for (int index = 0; index < webDomainsSize; index++) { + String host = webDomains.valueAt(index); + Integer state = stateMap.get(host); + + int domainState; + if (state != null && DomainVerificationState.isVerified(state)) { + domainState = DomainVerificationUserState.DOMAIN_STATE_VERIFIED; + } else if (enabledHosts.contains(host)) { + domainState = DomainVerificationUserState.DOMAIN_STATE_SELECTED; + } else { + domainState = DomainVerificationUserState.DOMAIN_STATE_NONE; } - boolean linkHandlingAllowed = - userState == null || userState.isLinkHandlingAllowed(); - - return new DomainVerificationUserState(pkgState.getId(), packageName, - UserHandle.of(userId), linkHandlingAllowed, domains); + domains.put(host, domainState); } - }); + + boolean linkHandlingAllowed = + userState == null || userState.isLinkHandlingAllowed(); + + return new DomainVerificationUserState(pkgState.getId(), packageName, + UserHandle.of(userId), linkHandlingAllowed, domains); + } } @NonNull @@ -751,27 +734,26 @@ public class DomainVerificationService extends SystemService mEnforcer.assertOwnerQuerent(mConnection.getCallingUid(), mConnection.getCallingUserId(), userId); - return mConnection.withPackageSettingsSnapshotReturningThrowing(pkgSettings -> { - SparseArray<List<String>> levelToPackages = getOwnersForDomainInternal(domain, false, - userId, pkgSettings); - if (levelToPackages.size() == 0) { - return emptyList(); - } + final Computer snapshot = mConnection.snapshot(); + SparseArray<List<String>> levelToPackages = getOwnersForDomainInternal(domain, false, + userId, snapshot); + if (levelToPackages.size() == 0) { + return emptyList(); + } - List<DomainOwner> owners = new ArrayList<>(); - int size = levelToPackages.size(); - for (int index = 0; index < size; index++) { - int level = levelToPackages.keyAt(index); - boolean overrideable = level <= APPROVAL_LEVEL_SELECTION; - List<String> packages = levelToPackages.valueAt(index); - int packagesSize = packages.size(); - for (int packageIndex = 0; packageIndex < packagesSize; packageIndex++) { - owners.add(new DomainOwner(packages.get(packageIndex), overrideable)); - } + List<DomainOwner> owners = new ArrayList<>(); + int size = levelToPackages.size(); + for (int index = 0; index < size; index++) { + int level = levelToPackages.keyAt(index); + boolean overrideable = level <= APPROVAL_LEVEL_SELECTION; + List<String> packages = levelToPackages.valueAt(index); + int packagesSize = packages.size(); + for (int packageIndex = 0; packageIndex < packagesSize; packageIndex++) { + owners.add(new DomainOwner(packages.get(packageIndex), overrideable)); } + } - return owners; - }); + return owners; } /** @@ -782,8 +764,7 @@ public class DomainVerificationService extends SystemService */ @NonNull private SparseArray<List<String>> getOwnersForDomainInternal(@NonNull String domain, - boolean includeNegative, @UserIdInt int userId, - @NonNull Function<String, PackageStateInternal> pkgSettingFunction) { + boolean includeNegative, @UserIdInt int userId, @NonNull Computer snapshot) { SparseArray<List<String>> levelToPackages = new SparseArray<>(); // First, collect the raw approval level values synchronized (mLock) { @@ -791,7 +772,7 @@ public class DomainVerificationService extends SystemService for (int index = 0; index < size; index++) { DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index); String packageName = pkgState.getPackageName(); - PackageStateInternal pkgSetting = pkgSettingFunction.apply(packageName); + PackageStateInternal pkgSetting = snapshot.getPackageStateInternal(packageName); if (pkgSetting == null) { continue; } @@ -818,8 +799,8 @@ public class DomainVerificationService extends SystemService // Then sort them ascending by first installed time, with package name as tie breaker for (int index = 0; index < size; index++) { levelToPackages.valueAt(index).sort((first, second) -> { - PackageStateInternal firstPkgSetting = pkgSettingFunction.apply(first); - PackageStateInternal secondPkgSetting = pkgSettingFunction.apply(second); + PackageStateInternal firstPkgSetting = snapshot.getPackageStateInternal(first); + PackageStateInternal secondPkgSetting = snapshot.getPackageStateInternal(second); long firstInstallTime = firstPkgSetting == null ? -1L : firstPkgSetting.getUserStateOrDefault(userId).getFirstInstallTime(); @@ -1060,44 +1041,38 @@ public class DomainVerificationService extends SystemService } @Override - public void writeSettings(@NonNull TypedXmlSerializer serializer, boolean includeSignatures, - @UserIdInt int userId) - throws IOException { - mConnection.withPackageSettingsSnapshotThrowing(pkgSettings -> { - synchronized (mLock) { - Function<String, String> pkgNameToSignature = null; - if (includeSignatures) { - pkgNameToSignature = pkgName -> { - PackageStateInternal pkgSetting = pkgSettings.apply(pkgName); - if (pkgSetting == null) { - // If querying for a user restored package that isn't installed on the - // device yet, there will be no signature to write out. In that case, - // it's expected that this returns null and it falls back to the - // restored state's stored signature if it exists. - return null; - } - - return PackageUtils.computeSignaturesSha256Digest( - pkgSetting.getSigningDetails().getSignatures()); - }; - } + public void writeSettings(Computer snapshot, @NonNull TypedXmlSerializer serializer, + boolean includeSignatures, @UserIdInt int userId) throws IOException { + synchronized (mLock) { + Function<String, String> pkgNameToSignature = null; + if (includeSignatures) { + pkgNameToSignature = pkgName -> { + PackageStateInternal pkgSetting = snapshot.getPackageStateInternal(pkgName); + if (pkgSetting == null) { + // If querying for a user restored package that isn't installed on the + // device yet, there will be no signature to write out. In that case, + // it's expected that this returns null and it falls back to the + // restored state's stored signature if it exists. + return null; + } - mSettings.writeSettings(serializer, mAttachedPkgStates, userId, pkgNameToSignature); + return PackageUtils.computeSignaturesSha256Digest( + pkgSetting.getSigningDetails().getSignatures()); + }; } - }); + + mSettings.writeSettings(serializer, mAttachedPkgStates, userId, pkgNameToSignature); + } mLegacySettings.writeSettings(serializer); } @Override - public void readSettings(@NonNull TypedXmlPullParser parser) throws IOException, - XmlPullParserException { - mConnection.<IOException, XmlPullParserException>withPackageSettingsSnapshotThrowing2( - pkgSettings -> { - synchronized (mLock) { - mSettings.readSettings(parser, mAttachedPkgStates, pkgSettings); - } - }); + public void readSettings(@NonNull Computer snapshot, @NonNull TypedXmlPullParser parser) + throws IOException, XmlPullParserException { + synchronized (mLock) { + mSettings.readSettings(parser, mAttachedPkgStates, snapshot); + } } @Override @@ -1107,14 +1082,11 @@ public class DomainVerificationService extends SystemService } @Override - public void restoreSettings(@NonNull TypedXmlPullParser parser) + public void restoreSettings(Computer snapshot, @NonNull TypedXmlPullParser parser) throws IOException, XmlPullParserException { - mConnection.<IOException, XmlPullParserException>withPackageSettingsSnapshotThrowing2( - pkgSettings -> { - synchronized (mLock) { - mSettings.restoreSettings(parser, mAttachedPkgStates, pkgSettings); - } - }); + synchronized (mLock) { + mSettings.restoreSettings(parser, mAttachedPkgStates, snapshot); + } } @Override @@ -1190,18 +1162,16 @@ public class DomainVerificationService extends SystemService @Override public void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName, @Nullable Integer userId) throws NameNotFoundException { - mConnection.withPackageSettingsSnapshotThrowing( - pkgSettings -> printState(writer, packageName, userId, pkgSettings)); + printState(mConnection.snapshot(), writer, packageName, userId); } @Override - public void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName, - @Nullable @UserIdInt Integer userId, - @NonNull Function<String, PackageStateInternal> pkgSettingFunction) + public void printState(@NonNull Computer snapshot, @NonNull IndentingPrintWriter writer, + @Nullable String packageName, @Nullable @UserIdInt Integer userId) throws NameNotFoundException { mEnforcer.assertApprovedQuerent(mConnection.getCallingUid(), mProxy); synchronized (mLock) { - mDebug.printState(writer, packageName, userId, pkgSettingFunction, mAttachedPkgStates); + mDebug.printState(writer, packageName, userId, snapshot, mAttachedPkgStates); } } @@ -1209,31 +1179,30 @@ public class DomainVerificationService extends SystemService public void printOwnersForPackage(@NonNull IndentingPrintWriter writer, @Nullable String packageName, @Nullable @UserIdInt Integer userId) throws NameNotFoundException { - mConnection.withPackageSettingsSnapshotThrowing(pkgSettings -> { - synchronized (mLock) { - if (packageName == null) { - int size = mAttachedPkgStates.size(); - for (int index = 0; index < size; index++) { - try { - printOwnersForPackage(writer, - mAttachedPkgStates.valueAt(index).getPackageName(), userId, - pkgSettings); - } catch (NameNotFoundException ignored) { - // When iterating packages, if one doesn't exist somehow, ignore - } + synchronized (mLock) { + final Computer snapshot = mConnection.snapshot(); + if (packageName == null) { + int size = mAttachedPkgStates.size(); + for (int index = 0; index < size; index++) { + try { + printOwnersForPackage(writer, + mAttachedPkgStates.valueAt(index).getPackageName(), userId, + snapshot); + } catch (NameNotFoundException ignored) { + // When iterating packages, if one doesn't exist somehow, ignore } - } else { - printOwnersForPackage(writer, packageName, userId, pkgSettings); } + } else { + printOwnersForPackage(writer, packageName, userId, snapshot); } - }); + } } private void printOwnersForPackage(@NonNull IndentingPrintWriter writer, @NonNull String packageName, @Nullable @UserIdInt Integer userId, - @NonNull Function<String, PackageStateInternal> pkgSettingFunction) + @NonNull Computer snapshot) throws NameNotFoundException { - PackageStateInternal pkgSetting = pkgSettingFunction.apply(packageName); + PackageStateInternal pkgSetting = snapshot.getPackageStateInternal(packageName); AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg(); if (pkg == null) { throw DomainVerificationUtils.throwPackageUnavailable(packageName); @@ -1249,7 +1218,7 @@ public class DomainVerificationService extends SystemService writer.increaseIndent(); for (int index = 0; index < size; index++) { - printOwnersForDomain(writer, domains.valueAt(index), userId, pkgSettingFunction); + printOwnersForDomain(writer, domains.valueAt(index), userId, snapshot); } writer.decreaseIndent(); @@ -1258,30 +1227,28 @@ public class DomainVerificationService extends SystemService @Override public void printOwnersForDomains(@NonNull IndentingPrintWriter writer, @NonNull List<String> domains, @Nullable @UserIdInt Integer userId) { - mConnection.withPackageSettingsSnapshot(pkgSettings -> { - synchronized (mLock) { - int size = domains.size(); - for (int index = 0; index < size; index++) { - printOwnersForDomain(writer, domains.get(index), userId, pkgSettings); - } + synchronized (mLock) { + final Computer snapshot = mConnection.snapshot(); + int size = domains.size(); + for (int index = 0; index < size; index++) { + printOwnersForDomain(writer, domains.get(index), userId, snapshot); } - }); + } } private void printOwnersForDomain(@NonNull IndentingPrintWriter writer, @NonNull String domain, - @Nullable @UserIdInt Integer userId, - @NonNull Function<String, PackageStateInternal> pkgSettingFunction) { + @Nullable @UserIdInt Integer userId, @NonNull Computer snapshot) { SparseArray<SparseArray<List<String>>> userIdToApprovalLevelToOwners = new SparseArray<>(); if (userId == null || userId == UserHandle.USER_ALL) { for (int aUserId : mConnection.getAllUserIds()) { userIdToApprovalLevelToOwners.put(aUserId, - getOwnersForDomainInternal(domain, true, aUserId, pkgSettingFunction)); + getOwnersForDomainInternal(domain, true, aUserId, snapshot)); } } else { userIdToApprovalLevelToOwners.put(userId, - getOwnersForDomainInternal(domain, true, userId, pkgSettingFunction)); + getOwnersForDomainInternal(domain, true, userId, snapshot)); } mDebug.printOwners(writer, domain, userIdToApprovalLevelToOwners); @@ -1330,8 +1297,7 @@ public class DomainVerificationService extends SystemService @GuardedBy("mLock") private GetAttachedResult getAndValidateAttachedLocked(@NonNull UUID domainSetId, @NonNull Set<String> domains, boolean forAutoVerify, int callingUid, - @Nullable Integer userIdForFilter, - @NonNull Function<String, PackageStateInternal> pkgSettingFunction) + @Nullable Integer userIdForFilter, @NonNull Computer snapshot) throws NameNotFoundException { if (domainSetId == null) { throw new IllegalArgumentException("domainSetId cannot be null"); @@ -1349,7 +1315,7 @@ public class DomainVerificationService extends SystemService return GetAttachedResult.error(DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID); } - PackageStateInternal pkgSetting = pkgSettingFunction.apply(pkgName); + PackageStateInternal pkgSetting = snapshot.getPackageStateInternal(pkgName); if (pkgSetting == null || pkgSetting.getPkg() == null) { throw DomainVerificationUtils.throwPackageUnavailable(pkgName); } @@ -1437,33 +1403,32 @@ public class DomainVerificationService extends SystemService @Override public void clearDomainVerificationState(@Nullable List<String> packageNames) { mEnforcer.assertInternal(mConnection.getCallingUid()); - mConnection.withPackageSettingsSnapshot(pkgSettings -> { - synchronized (mLock) { - if (packageNames == null) { - int size = mAttachedPkgStates.size(); - for (int index = 0; index < size; index++) { - DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index); - String pkgName = pkgState.getPackageName(); - PackageStateInternal pkgSetting = pkgSettings.apply(pkgName); - if (pkgSetting == null || pkgSetting.getPkg() == null) { - continue; - } - resetDomainState(pkgState.getStateMap(), pkgSetting); + synchronized (mLock) { + final Computer snapshot = mConnection.snapshot(); + if (packageNames == null) { + int size = mAttachedPkgStates.size(); + for (int index = 0; index < size; index++) { + DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index); + String pkgName = pkgState.getPackageName(); + PackageStateInternal pkgSetting = snapshot.getPackageStateInternal(pkgName); + if (pkgSetting == null || pkgSetting.getPkg() == null) { + continue; } - } else { - int size = packageNames.size(); - for (int index = 0; index < size; index++) { - String pkgName = packageNames.get(index); - DomainVerificationPkgState pkgState = mAttachedPkgStates.get(pkgName); - PackageStateInternal pkgSetting = pkgSettings.apply(pkgName); - if (pkgSetting == null || pkgSetting.getPkg() == null) { - continue; - } - resetDomainState(pkgState.getStateMap(), pkgSetting); + resetDomainState(pkgState.getStateMap(), pkgSetting); + } + } else { + int size = packageNames.size(); + for (int index = 0; index < size; index++) { + String pkgName = packageNames.get(index); + DomainVerificationPkgState pkgState = mAttachedPkgStates.get(pkgName); + PackageStateInternal pkgSetting = snapshot.getPackageStateInternal(pkgName); + if (pkgSetting == null || pkgSetting.getPkg() == null) { + continue; } + resetDomainState(pkgState.getStateMap(), pkgSetting); } } - }); + } mConnection.scheduleWriteSettings(); } @@ -1935,35 +1900,32 @@ public class DomainVerificationService extends SystemService @GuardedBy("mLock") @NonNull private Pair<List<String>, Integer> getApprovedPackagesLocked(@NonNull String domain, - @UserIdInt int userId, int minimumApproval, - @NonNull Function<String, PackageStateInternal> pkgSettingFunction) { + @UserIdInt int userId, int minimumApproval, @NonNull Computer snapshot) { boolean includeNegative = minimumApproval < APPROVAL_LEVEL_NONE; int highestApproval = minimumApproval; List<String> approvedPackages = emptyList(); - synchronized (mLock) { - final int size = mAttachedPkgStates.size(); - for (int index = 0; index < size; index++) { - DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index); - String packageName = pkgState.getPackageName(); - PackageStateInternal pkgSetting = pkgSettingFunction.apply(packageName); - if (pkgSetting == null) { - continue; - } + final int size = mAttachedPkgStates.size(); + for (int index = 0; index < size; index++) { + DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index); + String packageName = pkgState.getPackageName(); + PackageStateInternal pkgSetting = snapshot.getPackageStateInternal(packageName); + if (pkgSetting == null) { + continue; + } - int level = approvalLevelForDomain(pkgSetting, domain, includeNegative, userId, - domain); - if (level < minimumApproval) { - continue; - } + int level = approvalLevelForDomain(pkgSetting, domain, includeNegative, userId, + domain); + if (level < minimumApproval) { + continue; + } - if (level > highestApproval) { - approvedPackages.clear(); - approvedPackages = CollectionUtils.add(approvedPackages, packageName); - highestApproval = level; - } else if (level == highestApproval) { - approvedPackages = CollectionUtils.add(approvedPackages, packageName); - } + if (level > highestApproval) { + approvedPackages.clear(); + approvedPackages = CollectionUtils.add(approvedPackages, packageName); + highestApproval = level; + } else if (level == highestApproval) { + approvedPackages = CollectionUtils.add(approvedPackages, packageName); } } @@ -1976,7 +1938,7 @@ public class DomainVerificationService extends SystemService final int approvedSize = approvedPackages.size(); for (int index = 0; index < approvedSize; index++) { String packageName = approvedPackages.get(index); - PackageStateInternal pkgSetting = pkgSettingFunction.apply(packageName); + PackageStateInternal pkgSetting = snapshot.getPackageStateInternal(packageName); if (pkgSetting == null) { continue; } @@ -2035,108 +1997,4 @@ public class DomainVerificationService extends SystemService return mErrorCode; } } - - /** - * Wraps a {@link Connection} to verify that the {@link PackageStateInternal} calls do not hold - * {@link #mLock}, as that can cause deadlock when {@link Settings} tries to serialize state to - * disk. Only enabled if {@link Build#IS_USERDEBUG} or {@link Build#IS_ENG} is true. - */ - private class LockSafeConnection implements Connection { - - @NonNull - private final Connection mConnection; - - private LockSafeConnection(@NonNull Connection connection) { - mConnection = connection; - } - - private void enforceLocking() { - if (Thread.holdsLock(mLock)) { - Slog.wtf(TAG, "Method should not hold DVS lock when accessing package data"); - } - } - - @Override - public void withPackageSettingsSnapshot( - @NonNull Consumer<Function<String, PackageStateInternal>> block) { - enforceLocking(); - mConnection.withPackageSettingsSnapshot(block); - } - - @Override - public <Output> Output withPackageSettingsSnapshotReturning( - @NonNull FunctionalUtils.ThrowingFunction<Function<String, PackageStateInternal>, - Output> block) { - enforceLocking(); - return mConnection.withPackageSettingsSnapshotReturning(block); - } - - @Override - public <ExceptionType extends Exception> void withPackageSettingsSnapshotThrowing( - @NonNull FunctionalUtils.ThrowingCheckedConsumer< - Function<String, PackageStateInternal>, ExceptionType> block) - throws ExceptionType { - enforceLocking(); - mConnection.withPackageSettingsSnapshotThrowing(block); - } - - @Override - public <ExceptionOne extends Exception, ExceptionTwo extends Exception> void - withPackageSettingsSnapshotThrowing2( - @NonNull FunctionalUtils.ThrowingChecked2Consumer< - Function<String, PackageStateInternal>, ExceptionOne, - ExceptionTwo> block) - throws ExceptionOne, ExceptionTwo { - enforceLocking(); - mConnection.withPackageSettingsSnapshotThrowing2(block); - } - - @Override - public <Output, ExceptionType extends Exception> Output - withPackageSettingsSnapshotReturningThrowing( - @NonNull FunctionalUtils.ThrowingCheckedFunction< - Function<String, PackageStateInternal>, Output, - ExceptionType> block) - throws ExceptionType { - enforceLocking(); - return mConnection.withPackageSettingsSnapshotReturningThrowing(block); - } - - @Override - public void scheduleWriteSettings() { - mConnection.scheduleWriteSettings(); - } - - @Override - public int getCallingUid() { - return mConnection.getCallingUid(); - } - - @Override - @UserIdInt - public int getCallingUserId() { - return mConnection.getCallingUserId(); - } - - @Override - public void schedule(int code, @Nullable Object object) { - mConnection.schedule(code, object); - } - - @Override - @UserIdInt - public int[] getAllUserIds() { - return mConnection.getAllUserIds(); - } - - @Override - public boolean filterAppAccess(@NonNull String packageName, int callingUid, int userId) { - return mConnection.filterAppAccess(packageName, callingUid, userId); - } - - @Override - public boolean doesUserExist(int userId) { - return mConnection.doesUserExist(userId); - } - } } diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java index 015d4e9e9f16..8d1ae0bc20b0 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationSettings.java @@ -28,6 +28,7 @@ import android.util.TypedXmlSerializer; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.pm.Computer; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState; @@ -101,8 +102,7 @@ class DomainVerificationSettings { */ public void readSettings(@NonNull TypedXmlPullParser parser, @NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState, - @NonNull Function<String, PackageStateInternal> pkgSettingFunction) - throws IOException, XmlPullParserException { + @NonNull Computer snapshot) throws IOException, XmlPullParserException { DomainVerificationPersistence.ReadResult result = DomainVerificationPersistence.readFromXml(parser); ArrayMap<String, DomainVerificationPkgState> active = result.active; @@ -118,7 +118,7 @@ class DomainVerificationSettings { // This branch should never be possible. Settings should be read from disk // before any states are attached. But just in case, handle it. if (!existingState.getId().equals(pkgState.getId())) { - mergePkgState(existingState, pkgState, pkgSettingFunction); + mergePkgState(existingState, pkgState, snapshot); } } else { mPendingPkgStates.put(pkgName, pkgState); @@ -139,8 +139,7 @@ class DomainVerificationSettings { */ public void restoreSettings(@NonNull TypedXmlPullParser parser, @NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState, - @NonNull Function<String, PackageStateInternal> pkgSettingFunction) - throws IOException, XmlPullParserException { + @NonNull Computer snapshot) throws IOException, XmlPullParserException { // TODO(b/170746586): Restoration assumes user IDs match, which is probably not the case on // a new device. @@ -166,7 +165,7 @@ class DomainVerificationSettings { } if (existingState != null) { - mergePkgState(existingState, newState, pkgSettingFunction); + mergePkgState(existingState, newState, snapshot); } else { // If there's no existing state, that means the new state has to be transformed // in preparation for attaching to brand new package that may eventually be @@ -216,9 +215,9 @@ class DomainVerificationSettings { */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) public void mergePkgState(@NonNull DomainVerificationPkgState oldState, - @NonNull DomainVerificationPkgState newState, - @NonNull Function<String, PackageStateInternal> pkgSettingFunction) { - PackageStateInternal pkgSetting = pkgSettingFunction.apply(oldState.getPackageName()); + @NonNull DomainVerificationPkgState newState, @NonNull Computer snapshot) { + PackageStateInternal pkgSetting = + snapshot.getPackageStateInternal(oldState.getPackageName()); AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg(); Set<String> validDomains = pkg == null ? Collections.emptySet() : mCollector.collectValidAutoVerifyDomains(pkg); diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java index da2d162d46f9..4b0a8e2778c0 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java @@ -32,6 +32,7 @@ import android.util.ArraySet; import android.util.IndentingPrintWriter; import com.android.modules.utils.BasicShellCommandHandler; +import com.android.server.pm.Computer; import java.io.PrintWriter; import java.util.ArrayList; @@ -39,7 +40,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Locale; -import java.util.function.Function; public class DomainVerificationShell { @@ -599,8 +599,8 @@ public class DomainVerificationShell { void verifyPackages(@Nullable List<String> packageNames, boolean reVerify); /** - * @see DomainVerificationManagerInternal#printState(IndentingPrintWriter, String, Integer, - * Function) + * @see DomainVerificationManagerInternal#printState(Computer, IndentingPrintWriter, String, + * Integer) */ void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName, @Nullable @UserIdInt Integer userId) throws NameNotFoundException; diff --git a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java new file mode 100644 index 000000000000..3550bda282dd --- /dev/null +++ b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2022 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.vibrator; + +import android.os.SystemClock; +import android.os.VibrationEffect; +import android.util.Slog; + +import java.util.Arrays; +import java.util.List; + +/** + * Represent a step on a single vibrator that plays one or more segments from a + * {@link VibrationEffect.Composed} effect. + */ +abstract class AbstractVibratorStep extends Step { + public final VibratorController controller; + public final VibrationEffect.Composed effect; + public final int segmentIndex; + public final long previousStepVibratorOffTimeout; + + long mVibratorOnResult; + boolean mVibratorCompleteCallbackReceived; + + /** + * @param conductor The VibrationStepConductor for these steps. + * @param startTime The time to schedule this step in the + * {@link VibrationStepConductor}. + * @param controller The vibrator that is playing the effect. + * @param effect The effect being played in this step. + * @param index The index of the next segment to be played by this step + * @param previousStepVibratorOffTimeout The time the vibrator is expected to complete any + * previous vibration and turn off. This is used to allow this step to + * be triggered when the completion callback is received, and can + * be used to play effects back-to-back. + */ + AbstractVibratorStep(VibrationStepConductor conductor, long startTime, + VibratorController controller, VibrationEffect.Composed effect, int index, + long previousStepVibratorOffTimeout) { + super(conductor, startTime); + this.controller = controller; + this.effect = effect; + this.segmentIndex = index; + this.previousStepVibratorOffTimeout = previousStepVibratorOffTimeout; + } + + public int getVibratorId() { + return controller.getVibratorInfo().getId(); + } + + @Override + public long getVibratorOnDuration() { + return mVibratorOnResult; + } + + @Override + public boolean acceptVibratorCompleteCallback(int vibratorId) { + boolean isSameVibrator = controller.getVibratorInfo().getId() == vibratorId; + mVibratorCompleteCallbackReceived |= isSameVibrator; + // Only activate this step if a timeout was set to wait for the vibration to complete, + // otherwise we are waiting for the correct time to play the next step. + return isSameVibrator && (previousStepVibratorOffTimeout > SystemClock.uptimeMillis()); + } + + @Override + public List<Step> cancel() { + return Arrays.asList(new CompleteEffectVibratorStep(conductor, SystemClock.uptimeMillis(), + /* cancelled= */ true, controller, previousStepVibratorOffTimeout)); + } + + @Override + public void cancelImmediately() { + if (previousStepVibratorOffTimeout > SystemClock.uptimeMillis()) { + // Vibrator might be running from previous steps, so turn it off while canceling. + stopVibrating(); + } + } + + protected void stopVibrating() { + if (VibrationThread.DEBUG) { + Slog.d(VibrationThread.TAG, + "Turning off vibrator " + getVibratorId()); + } + controller.off(); + } + + protected void changeAmplitude(float amplitude) { + if (VibrationThread.DEBUG) { + Slog.d(VibrationThread.TAG, + "Amplitude changed on vibrator " + getVibratorId() + " to " + amplitude); + } + controller.setAmplitude(amplitude); + } + + /** + * Return the {@link VibrationStepConductor#nextVibrateStep} with same timings, only jumping + * the segments. + */ + protected List<Step> skipToNextSteps(int segmentsSkipped) { + return nextSteps(startTime, previousStepVibratorOffTimeout, segmentsSkipped); + } + + /** + * Return the {@link VibrationStepConductor#nextVibrateStep} with same start and off timings + * calculated from {@link #getVibratorOnDuration()}, jumping all played segments. + * + * <p>This method has same behavior as {@link #skipToNextSteps(int)} when the vibrator + * result is non-positive, meaning the vibrator has either ignored or failed to turn on. + */ + protected List<Step> nextSteps(int segmentsPlayed) { + if (mVibratorOnResult <= 0) { + // Vibration was not started, so just skip the played segments and keep timings. + return skipToNextSteps(segmentsPlayed); + } + long nextStartTime = SystemClock.uptimeMillis() + mVibratorOnResult; + long nextVibratorOffTimeout = + nextStartTime + VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT; + return nextSteps(nextStartTime, nextVibratorOffTimeout, segmentsPlayed); + } + + /** + * Return the {@link VibrationStepConductor#nextVibrateStep} with given start and off timings, + * which might be calculated independently, jumping all played segments. + * + * <p>This should be used when the vibrator on/off state is not responsible for the steps + * execution timings, e.g. while playing the vibrator amplitudes. + */ + protected List<Step> nextSteps(long nextStartTime, long vibratorOffTimeout, + int segmentsPlayed) { + Step nextStep = conductor.nextVibrateStep(nextStartTime, controller, effect, + segmentIndex + segmentsPlayed, vibratorOffTimeout); + return nextStep == null ? VibrationStepConductor.EMPTY_STEP_LIST : Arrays.asList(nextStep); + } +} diff --git a/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java b/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java new file mode 100644 index 000000000000..8585e3473ef3 --- /dev/null +++ b/services/core/java/com/android/server/vibrator/CompleteEffectVibratorStep.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2022 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.vibrator; + +import android.os.SystemClock; +import android.os.Trace; +import android.os.VibrationEffect; +import android.util.Slog; + +import java.util.Arrays; +import java.util.List; + +/** + * Represents a step to complete a {@link VibrationEffect}. + * + * <p>This runs right at the time the vibration is considered to end and will update the pending + * vibrators count. This can turn off the vibrator or slowly ramp it down to zero amplitude. + */ +final class CompleteEffectVibratorStep extends AbstractVibratorStep { + private final boolean mCancelled; + + CompleteEffectVibratorStep(VibrationStepConductor conductor, long startTime, boolean cancelled, + VibratorController controller, long previousStepVibratorOffTimeout) { + super(conductor, startTime, controller, /* effect= */ null, /* index= */ -1, + previousStepVibratorOffTimeout); + mCancelled = cancelled; + } + + @Override + public boolean isCleanUp() { + // If the vibration was cancelled then this is just a clean up to ramp off the vibrator. + // Otherwise this step is part of the vibration. + return mCancelled; + } + + @Override + public List<Step> cancel() { + if (mCancelled) { + // Double cancelling will just turn off the vibrator right away. + return Arrays.asList( + new TurnOffVibratorStep(conductor, SystemClock.uptimeMillis(), controller)); + } + return super.cancel(); + } + + @Override + public List<Step> play() { + Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "CompleteEffectVibratorStep"); + try { + if (VibrationThread.DEBUG) { + Slog.d(VibrationThread.TAG, + "Running " + (mCancelled ? "cancel" : "complete") + " vibration" + + " step on vibrator " + controller.getVibratorInfo().getId()); + } + if (mVibratorCompleteCallbackReceived) { + // Vibration completion callback was received by this step, just turn if off + // and skip any clean-up. + stopVibrating(); + return VibrationStepConductor.EMPTY_STEP_LIST; + } + + float currentAmplitude = controller.getCurrentAmplitude(); + long remainingOnDuration = + previousStepVibratorOffTimeout - VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT + - SystemClock.uptimeMillis(); + long rampDownDuration = + Math.min(remainingOnDuration, + conductor.vibrationSettings.getRampDownDuration()); + long stepDownDuration = conductor.vibrationSettings.getRampStepDuration(); + if (currentAmplitude < VibrationStepConductor.RAMP_OFF_AMPLITUDE_MIN + || rampDownDuration <= stepDownDuration) { + // No need to ramp down the amplitude, just wait to turn it off. + if (mCancelled) { + // Vibration is completing because it was cancelled, turn off right away. + stopVibrating(); + return VibrationStepConductor.EMPTY_STEP_LIST; + } else { + return Arrays.asList(new TurnOffVibratorStep( + conductor, previousStepVibratorOffTimeout, controller)); + } + } + + if (VibrationThread.DEBUG) { + Slog.d(VibrationThread.TAG, + "Ramping down vibrator " + controller.getVibratorInfo().getId() + + " from amplitude " + currentAmplitude + + " for " + rampDownDuration + "ms"); + } + float amplitudeDelta = currentAmplitude / (rampDownDuration / stepDownDuration); + float amplitudeTarget = currentAmplitude - amplitudeDelta; + long newVibratorOffTimeout = + mCancelled ? rampDownDuration : previousStepVibratorOffTimeout; + return Arrays.asList( + new RampOffVibratorStep(conductor, startTime, amplitudeTarget, amplitudeDelta, + controller, newVibratorOffTimeout)); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); + } + } +} diff --git a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java new file mode 100644 index 000000000000..d1ea80557419 --- /dev/null +++ b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2022 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.vibrator; + +import android.os.Trace; +import android.os.VibrationEffect; +import android.os.vibrator.PrimitiveSegment; +import android.os.vibrator.VibrationEffectSegment; +import android.util.Slog; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a step to turn the vibrator on using a composition of primitives. + * + * <p>This step will use the maximum supported number of consecutive segments of type + * {@link PrimitiveSegment} starting at the current index. + */ +final class ComposePrimitivesVibratorStep extends AbstractVibratorStep { + + ComposePrimitivesVibratorStep(VibrationStepConductor conductor, long startTime, + VibratorController controller, VibrationEffect.Composed effect, int index, + long previousStepVibratorOffTimeout) { + // This step should wait for the last vibration to finish (with the timeout) and for the + // intended step start time (to respect the effect delays). + super(conductor, Math.max(startTime, previousStepVibratorOffTimeout), controller, effect, + index, previousStepVibratorOffTimeout); + } + + @Override + public List<Step> play() { + Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "ComposePrimitivesStep"); + try { + // Load the next PrimitiveSegments to create a single compose call to the vibrator, + // limited to the vibrator composition maximum size. + int limit = controller.getVibratorInfo().getCompositionSizeMax(); + int segmentCount = limit > 0 + ? Math.min(effect.getSegments().size(), segmentIndex + limit) + : effect.getSegments().size(); + List<PrimitiveSegment> primitives = new ArrayList<>(); + for (int i = segmentIndex; i < segmentCount; i++) { + VibrationEffectSegment segment = effect.getSegments().get(i); + if (segment instanceof PrimitiveSegment) { + primitives.add((PrimitiveSegment) segment); + } else { + break; + } + } + + if (primitives.isEmpty()) { + Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposePrimitivesStep: " + + effect.getSegments().get(segmentIndex)); + return skipToNextSteps(/* segmentsSkipped= */ 1); + } + + if (VibrationThread.DEBUG) { + Slog.d(VibrationThread.TAG, "Compose " + primitives + " primitives on vibrator " + + controller.getVibratorInfo().getId()); + } + mVibratorOnResult = controller.on( + primitives.toArray(new PrimitiveSegment[primitives.size()]), + getVibration().id); + + return nextSteps(/* segmentsPlayed= */ primitives.size()); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); + } + } +} diff --git a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java new file mode 100644 index 000000000000..73bf933f8bc9 --- /dev/null +++ b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2022 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.vibrator; + +import android.os.Trace; +import android.os.VibrationEffect; +import android.os.vibrator.RampSegment; +import android.os.vibrator.StepSegment; +import android.os.vibrator.VibrationEffectSegment; +import android.util.Slog; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a step to turn the vibrator on using a composition of PWLE segments. + * + * <p>This step will use the maximum supported number of consecutive segments of type + * {@link StepSegment} or {@link RampSegment} starting at the current index. + */ +final class ComposePwleVibratorStep extends AbstractVibratorStep { + + ComposePwleVibratorStep(VibrationStepConductor conductor, long startTime, + VibratorController controller, VibrationEffect.Composed effect, int index, + long previousStepVibratorOffTimeout) { + // This step should wait for the last vibration to finish (with the timeout) and for the + // intended step start time (to respect the effect delays). + super(conductor, Math.max(startTime, previousStepVibratorOffTimeout), controller, effect, + index, previousStepVibratorOffTimeout); + } + + @Override + public List<Step> play() { + Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "ComposePwleStep"); + try { + // Load the next RampSegments to create a single composePwle call to the vibrator, + // limited to the vibrator PWLE maximum size. + int limit = controller.getVibratorInfo().getPwleSizeMax(); + int segmentCount = limit > 0 + ? Math.min(effect.getSegments().size(), segmentIndex + limit) + : effect.getSegments().size(); + List<RampSegment> pwles = new ArrayList<>(); + for (int i = segmentIndex; i < segmentCount; i++) { + VibrationEffectSegment segment = effect.getSegments().get(i); + if (segment instanceof RampSegment) { + pwles.add((RampSegment) segment); + } else { + break; + } + } + + if (pwles.isEmpty()) { + Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposePwleStep: " + + effect.getSegments().get(segmentIndex)); + return skipToNextSteps(/* segmentsSkipped= */ 1); + } + + if (VibrationThread.DEBUG) { + Slog.d(VibrationThread.TAG, "Compose " + pwles + " PWLEs on vibrator " + + controller.getVibratorInfo().getId()); + } + mVibratorOnResult = controller.on(pwles.toArray(new RampSegment[pwles.size()]), + getVibration().id); + + return nextSteps(/* segmentsPlayed= */ pwles.size()); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); + } + } +} diff --git a/services/core/java/com/android/server/vibrator/FinishSequentialEffectStep.java b/services/core/java/com/android/server/vibrator/FinishSequentialEffectStep.java new file mode 100644 index 000000000000..bbbca024214f --- /dev/null +++ b/services/core/java/com/android/server/vibrator/FinishSequentialEffectStep.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2022 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.vibrator; + +import android.os.Trace; +import android.util.Slog; + +import java.util.Arrays; +import java.util.List; + +/** + * Finish a sync vibration started by a {@link StartSequentialEffectStep}. + * + * <p>This only plays after all active vibrators steps have finished, and adds a {@link + * StartSequentialEffectStep} to the queue if the sequential effect isn't finished yet. + */ +final class FinishSequentialEffectStep extends Step { + public final StartSequentialEffectStep startedStep; + + FinishSequentialEffectStep(StartSequentialEffectStep startedStep) { + // No predefined startTime, just wait for all steps in the queue. + super(startedStep.conductor, Long.MAX_VALUE); + this.startedStep = startedStep; + } + + @Override + public boolean isCleanUp() { + // This step only notes that all the vibrators has been turned off. + return true; + } + + @Override + public List<Step> play() { + Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "FinishSequentialEffectStep"); + try { + if (VibrationThread.DEBUG) { + Slog.d(VibrationThread.TAG, + "FinishSequentialEffectStep for effect #" + startedStep.currentIndex); + } + conductor.vibratorManagerHooks.noteVibratorOff(conductor.getVibration().uid); + Step nextStep = startedStep.nextStep(); + return nextStep == null ? VibrationStepConductor.EMPTY_STEP_LIST + : Arrays.asList(nextStep); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); + } + } + + @Override + public List<Step> cancel() { + cancelImmediately(); + return VibrationStepConductor.EMPTY_STEP_LIST; + } + + @Override + public void cancelImmediately() { + conductor.vibratorManagerHooks.noteVibratorOff(conductor.getVibration().uid); + } +} diff --git a/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java new file mode 100644 index 000000000000..601ae978f637 --- /dev/null +++ b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2022 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.vibrator; + +import android.os.Trace; +import android.os.VibrationEffect; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.VibrationEffectSegment; +import android.util.Slog; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a step to turn the vibrator on with a single prebaked effect. + * + * <p>This step automatically falls back by replacing the prebaked segment with + * {@link VibrationSettings#getFallbackEffect(int)}, if available. + */ +final class PerformPrebakedVibratorStep extends AbstractVibratorStep { + + PerformPrebakedVibratorStep(VibrationStepConductor conductor, long startTime, + VibratorController controller, VibrationEffect.Composed effect, int index, + long previousStepVibratorOffTimeout) { + // This step should wait for the last vibration to finish (with the timeout) and for the + // intended step start time (to respect the effect delays). + super(conductor, Math.max(startTime, previousStepVibratorOffTimeout), controller, effect, + index, previousStepVibratorOffTimeout); + } + + @Override + public List<Step> play() { + Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "PerformPrebakedVibratorStep"); + try { + VibrationEffectSegment segment = effect.getSegments().get(segmentIndex); + if (!(segment instanceof PrebakedSegment)) { + Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a " + + "PerformPrebakedVibratorStep: " + segment); + return skipToNextSteps(/* segmentsSkipped= */ 1); + } + + PrebakedSegment prebaked = (PrebakedSegment) segment; + if (VibrationThread.DEBUG) { + Slog.d(VibrationThread.TAG, "Perform " + VibrationEffect.effectIdToString( + prebaked.getEffectId()) + " on vibrator " + + controller.getVibratorInfo().getId()); + } + + VibrationEffect fallback = getVibration().getFallback(prebaked.getEffectId()); + mVibratorOnResult = controller.on(prebaked, getVibration().id); + + if (mVibratorOnResult == 0 && prebaked.shouldFallback() + && (fallback instanceof VibrationEffect.Composed)) { + if (VibrationThread.DEBUG) { + Slog.d(VibrationThread.TAG, "Playing fallback for effect " + + VibrationEffect.effectIdToString(prebaked.getEffectId())); + } + AbstractVibratorStep fallbackStep = conductor.nextVibrateStep(startTime, controller, + replaceCurrentSegment((VibrationEffect.Composed) fallback), + segmentIndex, previousStepVibratorOffTimeout); + List<Step> fallbackResult = fallbackStep.play(); + // Update the result with the fallback result so this step is seamlessly + // replaced by the fallback to any outer application of this. + mVibratorOnResult = fallbackStep.getVibratorOnDuration(); + return fallbackResult; + } + + return nextSteps(/* segmentsPlayed= */ 1); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); + } + } + + /** + * Replace segment at {@link #segmentIndex} in {@link #effect} with given fallback segments. + * + * @return a copy of {@link #effect} with replaced segment. + */ + private VibrationEffect.Composed replaceCurrentSegment(VibrationEffect.Composed fallback) { + List<VibrationEffectSegment> newSegments = new ArrayList<>(effect.getSegments()); + int newRepeatIndex = effect.getRepeatIndex(); + newSegments.remove(segmentIndex); + newSegments.addAll(segmentIndex, fallback.getSegments()); + if (segmentIndex < effect.getRepeatIndex()) { + newRepeatIndex += fallback.getSegments().size() - 1; + } + return new VibrationEffect.Composed(newSegments, newRepeatIndex); + } +} diff --git a/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java b/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java new file mode 100644 index 000000000000..8cf5fb394d9d --- /dev/null +++ b/services/core/java/com/android/server/vibrator/RampOffVibratorStep.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2022 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.vibrator; + +import android.os.SystemClock; +import android.os.Trace; +import android.util.Slog; + +import java.util.Arrays; +import java.util.List; + +/** Represents a step to ramp down the vibrator amplitude before turning it off. */ +final class RampOffVibratorStep extends AbstractVibratorStep { + private final float mAmplitudeTarget; + private final float mAmplitudeDelta; + + RampOffVibratorStep(VibrationStepConductor conductor, long startTime, float amplitudeTarget, + float amplitudeDelta, VibratorController controller, + long previousStepVibratorOffTimeout) { + super(conductor, startTime, controller, /* effect= */ null, /* index= */ -1, + previousStepVibratorOffTimeout); + mAmplitudeTarget = amplitudeTarget; + mAmplitudeDelta = amplitudeDelta; + } + + @Override + public boolean isCleanUp() { + return true; + } + + @Override + public List<Step> cancel() { + return Arrays.asList( + new TurnOffVibratorStep(conductor, SystemClock.uptimeMillis(), controller)); + } + + @Override + public List<Step> play() { + Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "RampOffVibratorStep"); + try { + if (VibrationThread.DEBUG) { + long latency = SystemClock.uptimeMillis() - startTime; + Slog.d(VibrationThread.TAG, "Ramp down the vibrator amplitude, step with " + + latency + "ms latency."); + } + if (mVibratorCompleteCallbackReceived) { + // Vibration completion callback was received by this step, just turn if off + // and skip the rest of the steps to ramp down the vibrator amplitude. + stopVibrating(); + return VibrationStepConductor.EMPTY_STEP_LIST; + } + + changeAmplitude(mAmplitudeTarget); + + float newAmplitudeTarget = mAmplitudeTarget - mAmplitudeDelta; + if (newAmplitudeTarget < VibrationStepConductor.RAMP_OFF_AMPLITUDE_MIN) { + // Vibrator amplitude cannot go further down, just turn it off. + return Arrays.asList(new TurnOffVibratorStep( + conductor, previousStepVibratorOffTimeout, controller)); + } + return Arrays.asList(new RampOffVibratorStep( + conductor, + startTime + conductor.vibrationSettings.getRampStepDuration(), + newAmplitudeTarget, mAmplitudeDelta, controller, + previousStepVibratorOffTimeout)); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); + } + } +} diff --git a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java new file mode 100644 index 000000000000..d5c11161bdfa --- /dev/null +++ b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2022 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.vibrator; + +import android.os.SystemClock; +import android.os.Trace; +import android.os.VibrationEffect; +import android.os.vibrator.StepSegment; +import android.os.vibrator.VibrationEffectSegment; +import android.util.Slog; + +import java.util.Arrays; +import java.util.List; + +/** + * Represents a step to turn the vibrator on and change its amplitude. + * + * <p>This step ignores vibration completion callbacks and control the vibrator on/off state + * and amplitude to simulate waveforms represented by a sequence of {@link StepSegment}. + */ +final class SetAmplitudeVibratorStep extends AbstractVibratorStep { + private long mNextOffTime; + + SetAmplitudeVibratorStep(VibrationStepConductor conductor, long startTime, + VibratorController controller, VibrationEffect.Composed effect, int index, + long previousStepVibratorOffTimeout) { + // This step has a fixed startTime coming from the timings of the waveform it's playing. + super(conductor, startTime, controller, effect, index, previousStepVibratorOffTimeout); + mNextOffTime = previousStepVibratorOffTimeout; + } + + @Override + public boolean acceptVibratorCompleteCallback(int vibratorId) { + if (controller.getVibratorInfo().getId() == vibratorId) { + mVibratorCompleteCallbackReceived = true; + mNextOffTime = SystemClock.uptimeMillis(); + } + // Timings are tightly controlled here, so only trigger this step if the vibrator was + // supposed to be ON but has completed prematurely, to turn it back on as soon as + // possible. + return mNextOffTime < startTime && controller.getCurrentAmplitude() > 0; + } + + @Override + public List<Step> play() { + // TODO: consider separating the "on" steps at the start into a separate Step. + // TODO: consider instantiating the step with the required amplitude, rather than + // needing to dig into the effect. + Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "SetAmplitudeVibratorStep"); + try { + long now = SystemClock.uptimeMillis(); + long latency = now - startTime; + if (VibrationThread.DEBUG) { + Slog.d(VibrationThread.TAG, + "Running amplitude step with " + latency + "ms latency."); + } + + if (mVibratorCompleteCallbackReceived && latency < 0) { + // This step was run early because the vibrator turned off prematurely. + // Turn it back on and return this same step to run at the exact right time. + mNextOffTime = turnVibratorBackOn(/* remainingDuration= */ -latency); + return Arrays.asList(new SetAmplitudeVibratorStep(conductor, startTime, controller, + effect, segmentIndex, mNextOffTime)); + } + + VibrationEffectSegment segment = effect.getSegments().get(segmentIndex); + if (!(segment instanceof StepSegment)) { + Slog.w(VibrationThread.TAG, + "Ignoring wrong segment for a SetAmplitudeVibratorStep: " + segment); + return skipToNextSteps(/* segmentsSkipped= */ 1); + } + + StepSegment stepSegment = (StepSegment) segment; + if (stepSegment.getDuration() == 0) { + // Skip waveform entries with zero timing. + return skipToNextSteps(/* segmentsSkipped= */ 1); + } + + float amplitude = stepSegment.getAmplitude(); + if (amplitude == 0) { + if (previousStepVibratorOffTimeout > now) { + // Amplitude cannot be set to zero, so stop the vibrator. + stopVibrating(); + mNextOffTime = now; + } + } else { + if (startTime >= mNextOffTime) { + // Vibrator is OFF. Turn vibrator back on for the duration of another + // cycle before setting the amplitude. + long onDuration = getVibratorOnDuration(effect, segmentIndex); + if (onDuration > 0) { + mVibratorOnResult = startVibrating(onDuration); + mNextOffTime = now + onDuration + + VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT; + } + } + changeAmplitude(amplitude); + } + + // Use original startTime to avoid propagating latencies to the waveform. + long nextStartTime = startTime + segment.getDuration(); + return nextSteps(nextStartTime, mNextOffTime, /* segmentsPlayed= */ 1); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); + } + } + + private long turnVibratorBackOn(long remainingDuration) { + long onDuration = getVibratorOnDuration(effect, segmentIndex); + if (onDuration <= 0) { + // Vibrator is supposed to go back off when this step starts, so just leave it off. + return previousStepVibratorOffTimeout; + } + onDuration += remainingDuration; + float expectedAmplitude = controller.getCurrentAmplitude(); + mVibratorOnResult = startVibrating(onDuration); + if (mVibratorOnResult > 0) { + // Set the amplitude back to the value it was supposed to be playing at. + changeAmplitude(expectedAmplitude); + } + return SystemClock.uptimeMillis() + onDuration + + VibrationStepConductor.CALLBACKS_EXTRA_TIMEOUT; + } + + private long startVibrating(long duration) { + if (VibrationThread.DEBUG) { + Slog.d(VibrationThread.TAG, + "Turning on vibrator " + controller.getVibratorInfo().getId() + " for " + + duration + "ms"); + } + return controller.on(duration, getVibration().id); + } + + /** + * Get the duration the vibrator will be on for a waveform, starting at {@code startIndex} + * until the next time it's vibrating amplitude is zero or a different type of segment is + * found. + */ + private long getVibratorOnDuration(VibrationEffect.Composed effect, int startIndex) { + List<VibrationEffectSegment> segments = effect.getSegments(); + int segmentCount = segments.size(); + int repeatIndex = effect.getRepeatIndex(); + int i = startIndex; + long timing = 0; + while (i < segmentCount) { + VibrationEffectSegment segment = segments.get(i); + if (!(segment instanceof StepSegment) + || ((StepSegment) segment).getAmplitude() == 0) { + break; + } + timing += segment.getDuration(); + i++; + if (i == segmentCount && repeatIndex >= 0) { + i = repeatIndex; + // prevent infinite loop + repeatIndex = -1; + } + if (i == startIndex) { + // The repeating waveform keeps the vibrator ON all the time. Use a minimum + // of 1s duration to prevent short patterns from turning the vibrator ON too + // frequently. + return Math.max(timing, 1000); + } + } + if (i == segmentCount && effect.getRepeatIndex() < 0) { + // Vibration ending at non-zero amplitude, add extra timings to ramp down after + // vibration is complete. + timing += conductor.vibrationSettings.getRampDownDuration(); + } + return timing; + } +} diff --git a/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java b/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java new file mode 100644 index 000000000000..b8885e81dbe2 --- /dev/null +++ b/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java @@ -0,0 +1,369 @@ +/* + * Copyright (C) 2022 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.vibrator; + +import android.annotation.Nullable; +import android.hardware.vibrator.IVibratorManager; +import android.os.CombinedVibration; +import android.os.SystemClock; +import android.os.Trace; +import android.os.VibrationEffect; +import android.os.VibratorInfo; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.PrimitiveSegment; +import android.os.vibrator.StepSegment; +import android.os.vibrator.VibrationEffectSegment; +import android.util.Slog; +import android.util.SparseArray; + +import java.util.ArrayList; +import java.util.List; + +/** + * Starts a sync vibration. + * + * <p>If this step has successfully started playing a vibration on any vibrator, it will always + * add a {@link FinishSequentialEffectStep} to the queue, to be played after all vibrators + * have finished all their individual steps. + * + * <p>If this step does not start any vibrator, it will add a {@link StartSequentialEffectStep} if + * the sequential effect isn't finished yet. + * + * <p>TODO: this step actually does several things: multiple HAL calls to sync the vibrators, + * as well as dispatching the underlying vibrator instruction calls (which need to be done before + * triggering the synced effects). This role/encapsulation could probably be improved to split up + * the grouped HAL calls here, as well as to clarify the role of dispatching VibratorSteps between + * this class and the controller. + */ +final class StartSequentialEffectStep extends Step { + public final CombinedVibration.Sequential sequentialEffect; + public final int currentIndex; + + private long mVibratorsOnMaxDuration; + + StartSequentialEffectStep(VibrationStepConductor conductor, + CombinedVibration.Sequential effect) { + this(conductor, SystemClock.uptimeMillis() + effect.getDelays().get(0), effect, + /* index= */ 0); + } + + StartSequentialEffectStep(VibrationStepConductor conductor, long startTime, + CombinedVibration.Sequential effect, int index) { + super(conductor, startTime); + sequentialEffect = effect; + currentIndex = index; + } + + @Override + public long getVibratorOnDuration() { + return mVibratorsOnMaxDuration; + } + + @Override + public List<Step> play() { + Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "StartSequentialEffectStep"); + List<Step> nextSteps = new ArrayList<>(); + mVibratorsOnMaxDuration = -1; + try { + if (VibrationThread.DEBUG) { + Slog.d(VibrationThread.TAG, + "StartSequentialEffectStep for effect #" + currentIndex); + } + CombinedVibration effect = sequentialEffect.getEffects().get(currentIndex); + DeviceEffectMap effectMapping = createEffectToVibratorMapping(effect); + if (effectMapping == null) { + // Unable to map effects to vibrators, ignore this step. + return nextSteps; + } + + mVibratorsOnMaxDuration = startVibrating(effectMapping, nextSteps); + if (mVibratorsOnMaxDuration > 0) { + conductor.vibratorManagerHooks.noteVibratorOn(conductor.getVibration().uid, + mVibratorsOnMaxDuration); + } + } finally { + if (mVibratorsOnMaxDuration >= 0) { + // It least one vibrator was started then add a finish step to wait for all + // active vibrators to finish their individual steps before going to the next. + // Otherwise this step was ignored so just go to the next one. + Step nextStep = + mVibratorsOnMaxDuration > 0 ? new FinishSequentialEffectStep(this) + : nextStep(); + if (nextStep != null) { + nextSteps.add(nextStep); + } + } + Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); + } + return nextSteps; + } + + @Override + public List<Step> cancel() { + return VibrationStepConductor.EMPTY_STEP_LIST; + } + + @Override + public void cancelImmediately() { + } + + /** + * Create the next {@link StartSequentialEffectStep} to play this sequential effect, starting at + * the + * time this method is called, or null if sequence is complete. + */ + @Nullable + Step nextStep() { + int nextIndex = currentIndex + 1; + if (nextIndex >= sequentialEffect.getEffects().size()) { + return null; + } + long nextEffectDelay = sequentialEffect.getDelays().get(nextIndex); + long nextStartTime = SystemClock.uptimeMillis() + nextEffectDelay; + return new StartSequentialEffectStep(conductor, nextStartTime, sequentialEffect, + nextIndex); + } + + /** Create a mapping of individual {@link VibrationEffect} to available vibrators. */ + @Nullable + private DeviceEffectMap createEffectToVibratorMapping( + CombinedVibration effect) { + if (effect instanceof CombinedVibration.Mono) { + return new DeviceEffectMap((CombinedVibration.Mono) effect); + } + if (effect instanceof CombinedVibration.Stereo) { + return new DeviceEffectMap((CombinedVibration.Stereo) effect); + } + return null; + } + + /** + * Starts playing effects on designated vibrators, in sync. + * + * @param effectMapping The {@link CombinedVibration} mapped to this device vibrators + * @param nextSteps An output list to accumulate the future {@link Step + * Steps} created + * by this method, typically one for each vibrator that has + * successfully started vibrating on this step. + * @return The duration, in millis, of the {@link CombinedVibration}. Repeating + * waveforms return {@link Long#MAX_VALUE}. Zero or negative values indicate the vibrators + * have ignored all effects. + */ + private long startVibrating( + DeviceEffectMap effectMapping, List<Step> nextSteps) { + int vibratorCount = effectMapping.size(); + if (vibratorCount == 0) { + // No effect was mapped to any available vibrator. + return 0; + } + + AbstractVibratorStep[] steps = new AbstractVibratorStep[vibratorCount]; + long vibrationStartTime = SystemClock.uptimeMillis(); + for (int i = 0; i < vibratorCount; i++) { + steps[i] = conductor.nextVibrateStep(vibrationStartTime, + conductor.getVibrators().get(effectMapping.vibratorIdAt(i)), + effectMapping.effectAt(i), + /* segmentIndex= */ 0, /* vibratorOffTimeout= */ 0); + } + + if (steps.length == 1) { + // No need to prepare and trigger sync effects on a single vibrator. + return startVibrating(steps[0], nextSteps); + } + + // This synchronization of vibrators should be executed one at a time, even if we are + // vibrating different sets of vibrators in parallel. The manager can only prepareSynced + // one set of vibrators at a time. + // This property is guaranteed by there only being one thread (VibrationThread) executing + // one Step at a time, so there's no need to hold the state lock. Callbacks will be + // delivered asynchronously but enqueued until the step processing is finished. + boolean hasPrepared = false; + boolean hasTriggered = false; + long maxDuration = 0; + try { + hasPrepared = conductor.vibratorManagerHooks.prepareSyncedVibration( + effectMapping.getRequiredSyncCapabilities(), + effectMapping.getVibratorIds()); + + for (AbstractVibratorStep step : steps) { + long duration = startVibrating(step, nextSteps); + if (duration < 0) { + // One vibrator has failed, fail this entire sync attempt. + return maxDuration = -1; + } + maxDuration = Math.max(maxDuration, duration); + } + + // Check if sync was prepared and if any step was accepted by a vibrator, + // otherwise there is nothing to trigger here. + if (hasPrepared && maxDuration > 0) { + hasTriggered = conductor.vibratorManagerHooks.triggerSyncedVibration( + getVibration().id); + } + return maxDuration; + } finally { + if (hasPrepared && !hasTriggered) { + // Trigger has failed or all steps were ignored by the vibrators. + conductor.vibratorManagerHooks.cancelSyncedVibration(); + nextSteps.clear(); + } else if (maxDuration < 0) { + // Some vibrator failed without being prepared so other vibrators might be + // active. Cancel and remove every pending step from output list. + for (int i = nextSteps.size() - 1; i >= 0; i--) { + nextSteps.remove(i).cancelImmediately(); + } + } + } + } + + private long startVibrating(AbstractVibratorStep step, List<Step> nextSteps) { + nextSteps.addAll(step.play()); + long stepDuration = step.getVibratorOnDuration(); + if (stepDuration < 0) { + // Step failed, so return negative duration to propagate failure. + return stepDuration; + } + // Return the longest estimation for the entire effect. + return Math.max(stepDuration, step.effect.getDuration()); + } + + /** + * Map a {@link CombinedVibration} to the vibrators available on the device. + * + * <p>This contains the logic to find the capabilities required from {@link IVibratorManager} to + * play all of the effects in sync. + */ + final class DeviceEffectMap { + private final SparseArray<VibrationEffect.Composed> mVibratorEffects; + private final int[] mVibratorIds; + private final long mRequiredSyncCapabilities; + + DeviceEffectMap(CombinedVibration.Mono mono) { + SparseArray<VibratorController> vibrators = conductor.getVibrators(); + mVibratorEffects = new SparseArray<>(vibrators.size()); + mVibratorIds = new int[vibrators.size()]; + for (int i = 0; i < vibrators.size(); i++) { + int vibratorId = vibrators.keyAt(i); + VibratorInfo vibratorInfo = vibrators.valueAt(i).getVibratorInfo(); + VibrationEffect effect = conductor.deviceEffectAdapter.apply( + mono.getEffect(), vibratorInfo); + if (effect instanceof VibrationEffect.Composed) { + mVibratorEffects.put(vibratorId, (VibrationEffect.Composed) effect); + mVibratorIds[i] = vibratorId; + } + } + mRequiredSyncCapabilities = calculateRequiredSyncCapabilities(mVibratorEffects); + } + + DeviceEffectMap(CombinedVibration.Stereo stereo) { + SparseArray<VibratorController> vibrators = conductor.getVibrators(); + SparseArray<VibrationEffect> stereoEffects = stereo.getEffects(); + mVibratorEffects = new SparseArray<>(); + for (int i = 0; i < stereoEffects.size(); i++) { + int vibratorId = stereoEffects.keyAt(i); + if (vibrators.contains(vibratorId)) { + VibratorInfo vibratorInfo = vibrators.valueAt(i).getVibratorInfo(); + VibrationEffect effect = conductor.deviceEffectAdapter.apply( + stereoEffects.valueAt(i), vibratorInfo); + if (effect instanceof VibrationEffect.Composed) { + mVibratorEffects.put(vibratorId, (VibrationEffect.Composed) effect); + } + } + } + mVibratorIds = new int[mVibratorEffects.size()]; + for (int i = 0; i < mVibratorEffects.size(); i++) { + mVibratorIds[i] = mVibratorEffects.keyAt(i); + } + mRequiredSyncCapabilities = calculateRequiredSyncCapabilities(mVibratorEffects); + } + + /** + * Return the number of vibrators mapped to play the {@link CombinedVibration} on this + * device. + */ + public int size() { + return mVibratorIds.length; + } + + /** + * Return all capabilities required to play the {@link CombinedVibration} in + * between calls to {@link IVibratorManager#prepareSynced} and + * {@link IVibratorManager#triggerSynced}. + */ + public long getRequiredSyncCapabilities() { + return mRequiredSyncCapabilities; + } + + /** Return all vibrator ids mapped to play the {@link CombinedVibration}. */ + public int[] getVibratorIds() { + return mVibratorIds; + } + + /** Return the id of the vibrator at given index. */ + public int vibratorIdAt(int index) { + return mVibratorEffects.keyAt(index); + } + + /** Return the {@link VibrationEffect} at given index. */ + public VibrationEffect.Composed effectAt(int index) { + return mVibratorEffects.valueAt(index); + } + + /** + * Return all capabilities required from the {@link IVibratorManager} to prepare and + * trigger all given effects in sync. + * + * @return {@link IVibratorManager#CAP_SYNC} together with all required + * IVibratorManager.CAP_PREPARE_* and IVibratorManager.CAP_MIXED_TRIGGER_* capabilities. + */ + private long calculateRequiredSyncCapabilities( + SparseArray<VibrationEffect.Composed> effects) { + long prepareCap = 0; + for (int i = 0; i < effects.size(); i++) { + VibrationEffectSegment firstSegment = effects.valueAt(i).getSegments().get(0); + if (firstSegment instanceof StepSegment) { + prepareCap |= IVibratorManager.CAP_PREPARE_ON; + } else if (firstSegment instanceof PrebakedSegment) { + prepareCap |= IVibratorManager.CAP_PREPARE_PERFORM; + } else if (firstSegment instanceof PrimitiveSegment) { + prepareCap |= IVibratorManager.CAP_PREPARE_COMPOSE; + } + } + int triggerCap = 0; + if (requireMixedTriggerCapability(prepareCap, IVibratorManager.CAP_PREPARE_ON)) { + triggerCap |= IVibratorManager.CAP_MIXED_TRIGGER_ON; + } + if (requireMixedTriggerCapability(prepareCap, IVibratorManager.CAP_PREPARE_PERFORM)) { + triggerCap |= IVibratorManager.CAP_MIXED_TRIGGER_PERFORM; + } + if (requireMixedTriggerCapability(prepareCap, IVibratorManager.CAP_PREPARE_COMPOSE)) { + triggerCap |= IVibratorManager.CAP_MIXED_TRIGGER_COMPOSE; + } + return IVibratorManager.CAP_SYNC | prepareCap | triggerCap; + } + + /** + * Return true if {@code prepareCapabilities} contains this {@code capability} mixed with + * different ones, requiring a mixed trigger capability from the vibrator manager for + * syncing all effects. + */ + private boolean requireMixedTriggerCapability(long prepareCapabilities, long capability) { + return (prepareCapabilities & capability) != 0 + && (prepareCapabilities & ~capability) != 0; + } + } +} diff --git a/services/core/java/com/android/server/vibrator/Step.java b/services/core/java/com/android/server/vibrator/Step.java new file mode 100644 index 000000000000..042e8a0e6ad5 --- /dev/null +++ b/services/core/java/com/android/server/vibrator/Step.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2022 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.vibrator; + +import android.annotation.NonNull; +import android.os.CombinedVibration; +import android.os.SystemClock; +import android.os.VibrationEffect; + +import java.util.List; + +/** + * Represent a single step for playing a vibration. + * + * <p>Every step has a start time, which can be used to apply delays between steps while + * executing them in sequence. + */ +abstract class Step implements Comparable<Step> { + public final VibrationStepConductor conductor; + public final long startTime; + + Step(VibrationStepConductor conductor, long startTime) { + this.conductor = conductor; + this.startTime = startTime; + } + + protected Vibration getVibration() { + return conductor.getVibration(); + } + + /** + * Returns true if this step is a clean up step and not part of a {@link VibrationEffect} or + * {@link CombinedVibration}. + */ + public boolean isCleanUp() { + return false; + } + + /** Play this step, returning a (possibly empty) list of next steps. */ + @NonNull + public abstract List<Step> play(); + + /** + * Cancel this pending step and return a (possibly empty) list of clean-up steps that should + * be played to gracefully cancel this step. + */ + @NonNull + public abstract List<Step> cancel(); + + /** Cancel this pending step immediately, skipping any clean-up. */ + public abstract void cancelImmediately(); + + /** + * Return the duration the vibrator was turned on when this step was played. + * + * @return A positive duration that the vibrator was turned on for by this step; + * Zero if the segment is not supported, the step was not played yet or vibrator was never + * turned on by this step; A negative value if the vibrator call has failed. + */ + public long getVibratorOnDuration() { + return 0; + } + + /** + * Return true to run this step right after a vibrator has notified vibration completed, + * used to resume steps waiting on vibrator callbacks with a timeout. + */ + public boolean acceptVibratorCompleteCallback(int vibratorId) { + return false; + } + + /** + * Returns the time in millis to wait before playing this step. This is performed + * while holding the queue lock, so should not rely on potentially slow operations. + */ + public long calculateWaitTime() { + if (startTime == Long.MAX_VALUE) { + // This step don't have a predefined start time, it's just marked to be executed + // after all other steps have finished. + return 0; + } + return Math.max(0, startTime - SystemClock.uptimeMillis()); + } + + @Override + public int compareTo(Step o) { + return Long.compare(startTime, o.startTime); + } +} diff --git a/services/core/java/com/android/server/vibrator/TurnOffVibratorStep.java b/services/core/java/com/android/server/vibrator/TurnOffVibratorStep.java new file mode 100644 index 000000000000..297ef5614e84 --- /dev/null +++ b/services/core/java/com/android/server/vibrator/TurnOffVibratorStep.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2022 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.vibrator; + +import android.os.SystemClock; +import android.os.Trace; + +import java.util.Arrays; +import java.util.List; + +/** + * Represents a step to turn the vibrator off. + * + * <p>This runs after a timeout on the expected time the vibrator should have finished playing, + * and can be brought forward by vibrator complete callbacks. The step shouldn't be skipped, even + * if the vibrator-complete callback was received, as some implementations still rely on the + * "off" call to actually stop. + */ +final class TurnOffVibratorStep extends AbstractVibratorStep { + + TurnOffVibratorStep(VibrationStepConductor conductor, long startTime, + VibratorController controller) { + super(conductor, startTime, controller, /* effect= */ null, /* index= */ -1, startTime); + } + + @Override + public boolean isCleanUp() { + return true; + } + + @Override + public List<Step> cancel() { + return Arrays.asList( + new TurnOffVibratorStep(conductor, SystemClock.uptimeMillis(), controller)); + } + + @Override + public void cancelImmediately() { + stopVibrating(); + } + + @Override + public List<Step> play() { + Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "TurnOffVibratorStep"); + try { + stopVibrating(); + return VibrationStepConductor.EMPTY_STEP_LIST; + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); + } + } +} diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java new file mode 100644 index 000000000000..51691fbcdf5a --- /dev/null +++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java @@ -0,0 +1,355 @@ +/* + * Copyright (C) 2022 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.vibrator; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.CombinedVibration; +import android.os.VibrationEffect; +import android.os.WorkSource; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.PrimitiveSegment; +import android.os.vibrator.RampSegment; +import android.os.vibrator.VibrationEffectSegment; +import android.util.Slog; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.PriorityQueue; +import java.util.Queue; + +/** + * Creates and manages a queue of steps for performing a VibrationEffect, as well as coordinating + * dispatch of callbacks. + */ +final class VibrationStepConductor { + /** + * Extra timeout added to the end of each vibration step to ensure it finishes even when + * vibrator callbacks are lost. + */ + static final long CALLBACKS_EXTRA_TIMEOUT = 1_000; + /** Threshold to prevent the ramp off steps from trying to set extremely low amplitudes. */ + static final float RAMP_OFF_AMPLITUDE_MIN = 1e-3f; + static final List<Step> EMPTY_STEP_LIST = new ArrayList<>(); + + final Object mLock = new Object(); + + // Used within steps. + public final VibrationSettings vibrationSettings; + public final DeviceVibrationEffectAdapter deviceEffectAdapter; + public final VibrationThread.VibratorManagerHooks vibratorManagerHooks; + + private final WorkSource mWorkSource; + private final Vibration mVibration; + private final SparseArray<VibratorController> mVibrators = new SparseArray<>(); + + @GuardedBy("mLock") + private final PriorityQueue<Step> mNextSteps = new PriorityQueue<>(); + @GuardedBy("mLock") + private final Queue<Step> mPendingOnVibratorCompleteSteps = new LinkedList<>(); + @GuardedBy("mLock") + private final Queue<Integer> mCompletionNotifiedVibrators = new LinkedList<>(); + + @GuardedBy("mLock") + private int mPendingVibrateSteps; + @GuardedBy("mLock") + private int mConsumedStartVibrateSteps; + @GuardedBy("mLock") + private int mSuccessfulVibratorOnSteps; + @GuardedBy("mLock") + private boolean mWaitToProcessVibratorCompleteCallbacks; + + VibrationStepConductor(Vibration vib, VibrationSettings vibrationSettings, + DeviceVibrationEffectAdapter effectAdapter, + SparseArray<VibratorController> availableVibrators, + VibrationThread.VibratorManagerHooks vibratorManagerHooks) { + this.mVibration = vib; + this.vibrationSettings = vibrationSettings; + this.deviceEffectAdapter = effectAdapter; + this.vibratorManagerHooks = vibratorManagerHooks; + this.mWorkSource = new WorkSource(mVibration.uid); + + CombinedVibration effect = vib.getEffect(); + for (int i = 0; i < availableVibrators.size(); i++) { + if (effect.hasVibrator(availableVibrators.keyAt(i))) { + mVibrators.put(availableVibrators.keyAt(i), availableVibrators.valueAt(i)); + } + } + } + + @Nullable + AbstractVibratorStep nextVibrateStep(long startTime, VibratorController controller, + VibrationEffect.Composed effect, int segmentIndex, + long previousStepVibratorOffTimeout) { + if (segmentIndex >= effect.getSegments().size()) { + segmentIndex = effect.getRepeatIndex(); + } + if (segmentIndex < 0) { + // No more segments to play, last step is to complete the vibration on this vibrator. + return new CompleteEffectVibratorStep(this, startTime, /* cancelled= */ false, + controller, previousStepVibratorOffTimeout); + } + + VibrationEffectSegment segment = effect.getSegments().get(segmentIndex); + if (segment instanceof PrebakedSegment) { + return new PerformPrebakedVibratorStep(this, startTime, controller, effect, + segmentIndex, previousStepVibratorOffTimeout); + } + if (segment instanceof PrimitiveSegment) { + return new ComposePrimitivesVibratorStep(this, startTime, controller, effect, + segmentIndex, previousStepVibratorOffTimeout); + } + if (segment instanceof RampSegment) { + return new ComposePwleVibratorStep(this, startTime, controller, effect, segmentIndex, + previousStepVibratorOffTimeout); + } + return new SetAmplitudeVibratorStep(this, startTime, controller, effect, segmentIndex, + previousStepVibratorOffTimeout); + } + + public void initializeForEffect(@NonNull CombinedVibration.Sequential vibration) { + synchronized (mLock) { + mPendingVibrateSteps++; + mNextSteps.offer(new StartSequentialEffectStep(this, vibration)); + } + } + + public Vibration getVibration() { + return mVibration; + } + + public WorkSource getWorkSource() { + return mWorkSource; + } + + SparseArray<VibratorController> getVibrators() { + return mVibrators; + } + + public boolean isFinished() { + synchronized (mLock) { + return mPendingOnVibratorCompleteSteps.isEmpty() && mNextSteps.isEmpty(); + } + } + + /** + * Calculate the {@link Vibration.Status} based on the current queue state and the expected + * number of {@link StartSequentialEffectStep} to be played. + */ + public Vibration.Status calculateVibrationStatus(int expectedStartVibrateSteps) { + synchronized (mLock) { + if (mPendingVibrateSteps > 0 + || mConsumedStartVibrateSteps < expectedStartVibrateSteps) { + return Vibration.Status.RUNNING; + } + if (mSuccessfulVibratorOnSteps > 0) { + return Vibration.Status.FINISHED; + } + // If no step was able to turn the vibrator ON successfully. + return Vibration.Status.IGNORED_UNSUPPORTED; + } + } + + /** Returns the time in millis to wait before calling {@link #runNextStep()}. */ + @GuardedBy("mLock") + public long getWaitMillisBeforeNextStepLocked() { + if (!mPendingOnVibratorCompleteSteps.isEmpty()) { + // Steps resumed by vibrator complete callback should be played right away. + return 0; + } + Step nextStep = mNextSteps.peek(); + return nextStep == null ? 0 : nextStep.calculateWaitTime(); + } + + /** + * Play and remove the step at the top of this queue, and also adds the next steps generated + * to be played next. + */ + public void runNextStep() { + // Vibrator callbacks should wait until the polled step is played and the next steps are + // added back to the queue, so they can handle the callback. + markWaitToProcessVibratorCallbacks(); + try { + Step nextStep = pollNext(); + if (nextStep != null) { + // This might turn on the vibrator and have a HAL latency. Execute this outside + // any lock to avoid blocking other interactions with the thread. + List<Step> nextSteps = nextStep.play(); + synchronized (mLock) { + if (nextStep.getVibratorOnDuration() > 0) { + mSuccessfulVibratorOnSteps++; + } + if (nextStep instanceof StartSequentialEffectStep) { + mConsumedStartVibrateSteps++; + } + if (!nextStep.isCleanUp()) { + mPendingVibrateSteps--; + } + for (int i = 0; i < nextSteps.size(); i++) { + mPendingVibrateSteps += nextSteps.get(i).isCleanUp() ? 0 : 1; + } + mNextSteps.addAll(nextSteps); + } + } + } finally { + synchronized (mLock) { + processVibratorCompleteCallbacksLocked(); + } + } + } + + /** + * Notify the vibrator completion. + * + * <p>This is a lightweight method that do not trigger any operation from {@link + * VibratorController}, so it can be called directly from a native callback. + */ + @GuardedBy("mLock") + private void notifyVibratorCompleteLocked(int vibratorId) { + mCompletionNotifiedVibrators.offer(vibratorId); + if (!mWaitToProcessVibratorCompleteCallbacks) { + // No step is being played or cancelled now, process the callback right away. + processVibratorCompleteCallbacksLocked(); + } + } + + public void notifyVibratorComplete(int vibratorId) { + synchronized (mLock) { + if (VibrationThread.DEBUG) { + Slog.d(VibrationThread.TAG, + "Vibration complete reported by vibrator " + vibratorId); + } + notifyVibratorCompleteLocked(vibratorId); + mLock.notify(); + } + } + + public void notifySyncedVibrationComplete() { + synchronized (mLock) { + if (VibrationThread.DEBUG) { + Slog.d(VibrationThread.TAG, + "Synced vibration complete reported by vibrator manager"); + } + for (int i = 0; i < mVibrators.size(); i++) { + notifyVibratorCompleteLocked(mVibrators.keyAt(i)); + } + mLock.notify(); + } + } + + /** + * Cancel the current queue, replacing all remaining steps with respective clean-up steps. + * + * <p>This will remove all steps and replace them with respective + * {@link Step#cancel()}. + */ + public void cancel() { + // Vibrator callbacks should wait until all steps from the queue are properly cancelled + // and clean up steps are added back to the queue, so they can handle the callback. + markWaitToProcessVibratorCallbacks(); + try { + List<Step> cleanUpSteps = new ArrayList<>(); + Step step; + while ((step = pollNext()) != null) { + cleanUpSteps.addAll(step.cancel()); + } + synchronized (mLock) { + // All steps generated by Step.cancel() should be clean-up steps. + mPendingVibrateSteps = 0; + mNextSteps.addAll(cleanUpSteps); + } + } finally { + synchronized (mLock) { + processVibratorCompleteCallbacksLocked(); + } + } + } + + /** + * Cancel the current queue immediately, clearing all remaining steps and skipping clean-up. + * + * <p>This will remove and trigger {@link Step#cancelImmediately()} in all steps, in order. + */ + public void cancelImmediately() { + // Vibrator callbacks should wait until all steps from the queue are properly cancelled. + markWaitToProcessVibratorCallbacks(); + try { + Step step; + while ((step = pollNext()) != null) { + // This might turn off the vibrator and have a HAL latency. Execute this outside + // any lock to avoid blocking other interactions with the thread. + step.cancelImmediately(); + } + synchronized (mLock) { + mPendingVibrateSteps = 0; + } + } finally { + synchronized (mLock) { + processVibratorCompleteCallbacksLocked(); + } + } + } + + @Nullable + private Step pollNext() { + synchronized (mLock) { + // Prioritize the steps resumed by a vibrator complete callback. + if (!mPendingOnVibratorCompleteSteps.isEmpty()) { + return mPendingOnVibratorCompleteSteps.poll(); + } + return mNextSteps.poll(); + } + } + + private void markWaitToProcessVibratorCallbacks() { + synchronized (mLock) { + mWaitToProcessVibratorCompleteCallbacks = true; + } + } + + /** + * Notify the step in this queue that should be resumed by the vibrator completion + * callback and keep it separate to be consumed by {@link #runNextStep()}. + * + * <p>This is a lightweight method that do not trigger any operation from {@link + * VibratorController}, so it can be called directly from a native callback. + * + * <p>This assumes only one of the next steps is waiting on this given vibrator, so the + * first step found will be resumed by this method, in no particular order. + */ + @GuardedBy("mLock") + private void processVibratorCompleteCallbacksLocked() { + mWaitToProcessVibratorCompleteCallbacks = false; + while (!mCompletionNotifiedVibrators.isEmpty()) { + int vibratorId = mCompletionNotifiedVibrators.poll(); + Iterator<Step> it = mNextSteps.iterator(); + while (it.hasNext()) { + Step step = it.next(); + if (step.acceptVibratorCompleteCallback(vibratorId)) { + it.remove(); + mPendingOnVibratorCompleteSteps.offer(step); + break; + } + } + } + } +} diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java index 1f1f40b8121d..f2cd8c3ec3f8 100644 --- a/services/core/java/com/android/server/vibrator/VibrationThread.java +++ b/services/core/java/com/android/server/vibrator/VibrationThread.java @@ -16,59 +16,23 @@ package com.android.server.vibrator; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.hardware.vibrator.IVibratorManager; import android.os.CombinedVibration; import android.os.IBinder; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; -import android.os.SystemClock; import android.os.Trace; -import android.os.VibrationEffect; -import android.os.VibratorInfo; -import android.os.WorkSource; -import android.os.vibrator.PrebakedSegment; -import android.os.vibrator.PrimitiveSegment; -import android.os.vibrator.RampSegment; -import android.os.vibrator.StepSegment; -import android.os.vibrator.VibrationEffectSegment; import android.util.Slog; import android.util.SparseArray; -import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.app.IBatteryStats; -import com.android.internal.util.FrameworkStatsLog; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; import java.util.NoSuchElementException; -import java.util.PriorityQueue; -import java.util.Queue; /** Plays a {@link Vibration} in dedicated thread. */ final class VibrationThread extends Thread implements IBinder.DeathRecipient { - private static final String TAG = "VibrationThread"; - private static final boolean DEBUG = false; - - /** - * Extra timeout added to the end of each vibration step to ensure it finishes even when - * vibrator callbacks are lost. - */ - private static final long CALLBACKS_EXTRA_TIMEOUT = 1_000; - - /** Threshold to prevent the ramp off steps from trying to set extremely low amplitudes. */ - private static final float RAMP_OFF_AMPLITUDE_MIN = 1e-3f; - - /** Fixed large duration used to note repeating vibrations to {@link IBatteryStats}. */ - private static final long BATTERY_STATS_REPEATING_VIBRATION_DURATION = 5_000; - - private static final List<Step> EMPTY_STEP_LIST = new ArrayList<>(); + static final String TAG = "VibrationThread"; + static final boolean DEBUG = false; /** Calls into VibratorManager functionality needed for playing a {@link Vibration}. */ interface VibratorManagerHooks { @@ -94,6 +58,15 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { void cancelSyncedVibration(); /** + * Record that a vibrator was turned on, and may remain on for the specified duration, + * on behalf of the given uid. + */ + void noteVibratorOn(int uid, long duration); + + /** Record that a vibrator was turned off, on behalf of the given uid. */ + void noteVibratorOff(int uid); + + /** * Tell the manager that the currently active vibration has completed its vibration, from * the perspective of the Effect. However, the VibrationThread may still be continuing with * cleanup tasks, and should not be given new work until {@link #onVibrationThreadReleased} @@ -108,16 +81,10 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { void onVibrationThreadReleased(); } - private final Object mLock = new Object(); - private final WorkSource mWorkSource; private final PowerManager.WakeLock mWakeLock; - private final IBatteryStats mBatteryStatsService; - private final VibrationSettings mVibrationSettings; - private final DeviceVibrationEffectAdapter mDeviceEffectAdapter; - private final Vibration mVibration; - private final VibratorManagerHooks mVibratorManagerHooks; - private final SparseArray<VibratorController> mVibrators = new SparseArray<>(); - private final StepQueue mStepQueue = new StepQueue(); + private final VibrationThread.VibratorManagerHooks mVibratorManagerHooks; + + private final VibrationStepConductor mStepConductor; private volatile boolean mStop; private volatile boolean mForceStop; @@ -127,30 +94,20 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { VibrationThread(Vibration vib, VibrationSettings vibrationSettings, DeviceVibrationEffectAdapter effectAdapter, SparseArray<VibratorController> availableVibrators, PowerManager.WakeLock wakeLock, - IBatteryStats batteryStatsService, VibratorManagerHooks vibratorManagerHooks) { - mVibration = vib; - mVibrationSettings = vibrationSettings; - mDeviceEffectAdapter = effectAdapter; + VibratorManagerHooks vibratorManagerHooks) { mVibratorManagerHooks = vibratorManagerHooks; - mWorkSource = new WorkSource(mVibration.uid); mWakeLock = wakeLock; - mBatteryStatsService = batteryStatsService; - - CombinedVibration effect = vib.getEffect(); - for (int i = 0; i < availableVibrators.size(); i++) { - if (effect.hasVibrator(availableVibrators.keyAt(i))) { - mVibrators.put(availableVibrators.keyAt(i), availableVibrators.valueAt(i)); - } - } + mStepConductor = new VibrationStepConductor(vib, vibrationSettings, effectAdapter, + availableVibrators, vibratorManagerHooks); } Vibration getVibration() { - return mVibration; + return mStepConductor.getVibration(); } @VisibleForTesting SparseArray<VibratorController> getVibrators() { - return mVibrators; + return mStepConductor.getVibrators(); } @Override @@ -179,7 +136,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { /** Runs the VibrationThread ensuring that the wake lock is acquired and released. */ private void runWithWakeLock() { - mWakeLock.setWorkSource(mWorkSource); + mWakeLock.setWorkSource(mStepConductor.getWorkSource()); mWakeLock.acquire(); try { runWithWakeLockAndDeathLink(); @@ -193,8 +150,9 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { * Called from within runWithWakeLock. */ private void runWithWakeLockAndDeathLink() { + IBinder vibrationBinderToken = mStepConductor.getVibration().token; try { - mVibration.token.linkToDeath(this, 0); + vibrationBinderToken.linkToDeath(this, 0); } catch (RemoteException e) { Slog.e(TAG, "Error linking vibration to token death", e); clientVibrationCompleteIfNotAlready(Vibration.Status.IGNORED_ERROR_TOKEN); @@ -206,7 +164,7 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { playVibration(); } finally { try { - mVibration.token.unlinkToDeath(this, 0); + vibrationBinderToken.unlinkToDeath(this, 0); } catch (NoSuchElementException e) { Slog.wtf(TAG, "Failed to unlink token", e); } @@ -220,11 +178,11 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { return; } mStop = true; - synchronized (mLock) { + synchronized (mStepConductor.mLock) { if (DEBUG) { Slog.d(TAG, "Vibration cancelled"); } - mLock.notify(); + mStepConductor.mLock.notify(); } } @@ -235,36 +193,22 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { return; } mStop = mForceStop = true; - synchronized (mLock) { + synchronized (mStepConductor.mLock) { if (DEBUG) { Slog.d(TAG, "Vibration cancelled immediately"); } - mLock.notify(); + mStepConductor.mLock.notify(); } } /** Notify current vibration that a synced step has completed. */ public void syncedVibrationComplete() { - synchronized (mLock) { - if (DEBUG) { - Slog.d(TAG, "Synced vibration complete reported by vibrator manager"); - } - for (int i = 0; i < mVibrators.size(); i++) { - mStepQueue.notifyVibratorComplete(mVibrators.keyAt(i)); - } - mLock.notify(); - } + mStepConductor.notifySyncedVibrationComplete(); } /** Notify current vibration that a step has completed on given vibrator. */ public void vibratorComplete(int vibratorId) { - synchronized (mLock) { - if (DEBUG) { - Slog.d(TAG, "Vibration complete reported by vibrator " + vibratorId); - } - mStepQueue.notifyVibratorComplete(vibratorId); - mLock.notify(); - } + mStepConductor.notifyVibratorComplete(vibratorId); } // Indicate that the vibration is complete. This can be called multiple times only for @@ -273,52 +217,55 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { private void clientVibrationCompleteIfNotAlready(Vibration.Status completedStatus) { if (!mCalledVibrationCompleteCallback) { mCalledVibrationCompleteCallback = true; - mVibratorManagerHooks.onVibrationCompleted(mVibration.id, completedStatus); + mVibratorManagerHooks.onVibrationCompleted( + mStepConductor.getVibration().id, completedStatus); } } private void playVibration() { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "playVibration"); try { - CombinedVibration.Sequential sequentialEffect = toSequential(mVibration.getEffect()); + CombinedVibration.Sequential sequentialEffect = + toSequential(mStepConductor.getVibration().getEffect()); final int sequentialEffectSize = sequentialEffect.getEffects().size(); - mStepQueue.initializeForEffect(sequentialEffect); + mStepConductor.initializeForEffect(sequentialEffect); - while (!mStepQueue.isFinished()) { + while (!mStepConductor.isFinished()) { long waitMillisBeforeNextStep; - synchronized (mLock) { - waitMillisBeforeNextStep = mStepQueue.getWaitMillisBeforeNextStep(); + synchronized (mStepConductor.mLock) { + waitMillisBeforeNextStep = mStepConductor.getWaitMillisBeforeNextStepLocked(); if (waitMillisBeforeNextStep > 0) { try { - mLock.wait(waitMillisBeforeNextStep); + mStepConductor.mLock.wait(waitMillisBeforeNextStep); } catch (InterruptedException e) { } } } // Only run the next vibration step if we didn't have to wait in this loop. - // If we waited then the queue may have changed, so loop again to re-evaluate - // the scheduling of the queue top element. + // If we waited then the queue may have changed or the wait could have been + // interrupted by a cancel call, so loop again to re-evaluate the scheduling of + // the queue top element. if (waitMillisBeforeNextStep <= 0) { if (DEBUG) { Slog.d(TAG, "Play vibration consuming next step..."); } // Run the step without holding the main lock, to avoid HAL interactions from // blocking the thread. - mStepQueue.runNextStep(); + mStepConductor.runNextStep(); } Vibration.Status status = mStop ? Vibration.Status.CANCELLED - : mStepQueue.calculateVibrationStatus(sequentialEffectSize); + : mStepConductor.calculateVibrationStatus(sequentialEffectSize); if (status != Vibration.Status.RUNNING && !mCalledVibrationCompleteCallback) { // First time vibration stopped running, start clean-up tasks and notify // callback immediately. clientVibrationCompleteIfNotAlready(status); if (status == Vibration.Status.CANCELLED) { - mStepQueue.cancel(); + mStepConductor.cancel(); } } if (mForceStop) { // Cancel every step and stop playing them right away, even clean-up steps. - mStepQueue.cancelImmediately(); + mStepConductor.cancelImmediately(); clientVibrationCompleteIfNotAlready(Vibration.Status.CANCELLED); break; } @@ -328,61 +275,6 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { } } - private void noteVibratorOn(long duration) { - try { - if (duration <= 0) { - return; - } - if (duration == Long.MAX_VALUE) { - // Repeating duration has started. Report a fixed duration here, noteVibratorOff - // should be called when this is cancelled. - duration = BATTERY_STATS_REPEATING_VIBRATION_DURATION; - } - mBatteryStatsService.noteVibratorOn(mVibration.uid, duration); - FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED, - mVibration.uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__ON, - duration); - } catch (RemoteException e) { - } - } - - private void noteVibratorOff() { - try { - mBatteryStatsService.noteVibratorOff(mVibration.uid); - FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED, - mVibration.uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__OFF, - /* duration= */ 0); - } catch (RemoteException e) { - } - } - - @Nullable - private SingleVibratorStep nextVibrateStep(long startTime, VibratorController controller, - VibrationEffect.Composed effect, int segmentIndex, long vibratorOffTimeout) { - if (segmentIndex >= effect.getSegments().size()) { - segmentIndex = effect.getRepeatIndex(); - } - if (segmentIndex < 0) { - // No more segments to play, last step is to complete the vibration on this vibrator. - return new EffectCompleteStep(startTime, /* cancelled= */ false, controller, - vibratorOffTimeout); - } - - VibrationEffectSegment segment = effect.getSegments().get(segmentIndex); - if (segment instanceof PrebakedSegment) { - return new PerformStep(startTime, controller, effect, segmentIndex, vibratorOffTimeout); - } - if (segment instanceof PrimitiveSegment) { - return new ComposePrimitivesStep(startTime, controller, effect, segmentIndex, - vibratorOffTimeout); - } - if (segment instanceof RampSegment) { - return new ComposePwleStep(startTime, controller, effect, segmentIndex, - vibratorOffTimeout); - } - return new AmplitudeStep(startTime, controller, effect, segmentIndex, vibratorOffTimeout); - } - private static CombinedVibration.Sequential toSequential(CombinedVibration effect) { if (effect instanceof CombinedVibration.Sequential) { return (CombinedVibration.Sequential) effect; @@ -392,1268 +284,4 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient { .combine(); } - /** Queue for {@link Step Steps}, sorted by their start time. */ - private final class StepQueue { - @GuardedBy("mLock") - private final PriorityQueue<Step> mNextSteps = new PriorityQueue<>(); - @GuardedBy("mLock") - private final Queue<Step> mPendingOnVibratorCompleteSteps = new LinkedList<>(); - @GuardedBy("mLock") - private final Queue<Integer> mCompletionNotifiedVibrators = new LinkedList<>(); - - @GuardedBy("mLock") - private int mPendingVibrateSteps; - @GuardedBy("mLock") - private int mConsumedStartVibrateSteps; - @GuardedBy("mLock") - private int mSuccessfulVibratorOnSteps; - @GuardedBy("mLock") - private boolean mWaitToProcessVibratorCompleteCallbacks; - - public void initializeForEffect(@NonNull CombinedVibration.Sequential vibration) { - synchronized (mLock) { - mPendingVibrateSteps++; - mNextSteps.offer(new StartVibrateStep(vibration)); - } - } - - public boolean isFinished() { - synchronized (mLock) { - return mPendingOnVibratorCompleteSteps.isEmpty() && mNextSteps.isEmpty(); - } - } - - /** - * Calculate the {@link Vibration.Status} based on the current queue state and the expected - * number of {@link StartVibrateStep} to be played. - */ - public Vibration.Status calculateVibrationStatus(int expectedStartVibrateSteps) { - synchronized (mLock) { - if (mPendingVibrateSteps > 0 - || mConsumedStartVibrateSteps < expectedStartVibrateSteps) { - return Vibration.Status.RUNNING; - } - if (mSuccessfulVibratorOnSteps > 0) { - return Vibration.Status.FINISHED; - } - // If no step was able to turn the vibrator ON successfully. - return Vibration.Status.IGNORED_UNSUPPORTED; - } - } - - /** Returns the time in millis to wait before calling {@link #runNextStep()}. */ - @GuardedBy("VibrationThread.this.mLock") - public long getWaitMillisBeforeNextStep() { - if (!mPendingOnVibratorCompleteSteps.isEmpty()) { - // Steps resumed by vibrator complete callback should be played right away. - return 0; - } - Step nextStep = mNextSteps.peek(); - return nextStep == null ? 0 : nextStep.calculateWaitTime(); - } - - /** - * Play and remove the step at the top of this queue, and also adds the next steps generated - * to be played next. - */ - public void runNextStep() { - // Vibrator callbacks should wait until the polled step is played and the next steps are - // added back to the queue, so they can handle the callback. - markWaitToProcessVibratorCallbacks(); - try { - Step nextStep = pollNext(); - if (nextStep != null) { - // This might turn on the vibrator and have a HAL latency. Execute this outside - // any lock to avoid blocking other interactions with the thread. - List<Step> nextSteps = nextStep.play(); - synchronized (mLock) { - if (nextStep.getVibratorOnDuration() > 0) { - mSuccessfulVibratorOnSteps++; - } - if (nextStep instanceof StartVibrateStep) { - mConsumedStartVibrateSteps++; - } - if (!nextStep.isCleanUp()) { - mPendingVibrateSteps--; - } - for (int i = 0; i < nextSteps.size(); i++) { - mPendingVibrateSteps += nextSteps.get(i).isCleanUp() ? 0 : 1; - } - mNextSteps.addAll(nextSteps); - } - } - } finally { - synchronized (mLock) { - processVibratorCompleteCallbacks(); - } - } - } - - /** - * Notify the vibrator completion. - * - * <p>This is a lightweight method that do not trigger any operation from {@link - * VibratorController}, so it can be called directly from a native callback. - */ - @GuardedBy("mLock") - public void notifyVibratorComplete(int vibratorId) { - mCompletionNotifiedVibrators.offer(vibratorId); - if (!mWaitToProcessVibratorCompleteCallbacks) { - // No step is being played or cancelled now, process the callback right away. - processVibratorCompleteCallbacks(); - } - } - - /** - * Cancel the current queue, replacing all remaining steps with respective clean-up steps. - * - * <p>This will remove all steps and replace them with respective - * {@link Step#cancel()}. - */ - public void cancel() { - // Vibrator callbacks should wait until all steps from the queue are properly cancelled - // and clean up steps are added back to the queue, so they can handle the callback. - markWaitToProcessVibratorCallbacks(); - try { - List<Step> cleanUpSteps = new ArrayList<>(); - Step step; - while ((step = pollNext()) != null) { - cleanUpSteps.addAll(step.cancel()); - } - synchronized (mLock) { - // All steps generated by Step.cancel() should be clean-up steps. - mPendingVibrateSteps = 0; - mNextSteps.addAll(cleanUpSteps); - } - } finally { - synchronized (mLock) { - processVibratorCompleteCallbacks(); - } - } - } - - /** - * Cancel the current queue immediately, clearing all remaining steps and skipping clean-up. - * - * <p>This will remove and trigger {@link Step#cancelImmediately()} in all steps, in order. - */ - public void cancelImmediately() { - // Vibrator callbacks should wait until all steps from the queue are properly cancelled. - markWaitToProcessVibratorCallbacks(); - try { - Step step; - while ((step = pollNext()) != null) { - // This might turn off the vibrator and have a HAL latency. Execute this outside - // any lock to avoid blocking other interactions with the thread. - step.cancelImmediately(); - } - synchronized (mLock) { - mPendingVibrateSteps = 0; - } - } finally { - synchronized (mLock) { - processVibratorCompleteCallbacks(); - } - } - } - - @Nullable - private Step pollNext() { - synchronized (mLock) { - // Prioritize the steps resumed by a vibrator complete callback. - if (!mPendingOnVibratorCompleteSteps.isEmpty()) { - return mPendingOnVibratorCompleteSteps.poll(); - } - return mNextSteps.poll(); - } - } - - private void markWaitToProcessVibratorCallbacks() { - synchronized (mLock) { - mWaitToProcessVibratorCompleteCallbacks = true; - } - } - - /** - * Notify the step in this queue that should be resumed by the vibrator completion - * callback and keep it separate to be consumed by {@link #runNextStep()}. - * - * <p>This is a lightweight method that do not trigger any operation from {@link - * VibratorController}, so it can be called directly from a native callback. - * - * <p>This assumes only one of the next steps is waiting on this given vibrator, so the - * first step found will be resumed by this method, in no particular order. - */ - @GuardedBy("mLock") - private void processVibratorCompleteCallbacks() { - mWaitToProcessVibratorCompleteCallbacks = false; - while (!mCompletionNotifiedVibrators.isEmpty()) { - int vibratorId = mCompletionNotifiedVibrators.poll(); - Iterator<Step> it = mNextSteps.iterator(); - while (it.hasNext()) { - Step step = it.next(); - if (step.acceptVibratorCompleteCallback(vibratorId)) { - it.remove(); - mPendingOnVibratorCompleteSteps.offer(step); - break; - } - } - } - } - } - - /** - * Represent a single step for playing a vibration. - * - * <p>Every step has a start time, which can be used to apply delays between steps while - * executing them in sequence. - */ - private abstract class Step implements Comparable<Step> { - public final long startTime; - - Step(long startTime) { - this.startTime = startTime; - } - - /** - * Returns true if this step is a clean up step and not part of a {@link VibrationEffect} or - * {@link CombinedVibration}. - */ - public boolean isCleanUp() { - return false; - } - - /** Play this step, returning a (possibly empty) list of next steps. */ - @NonNull - public abstract List<Step> play(); - - /** - * Cancel this pending step and return a (possibly empty) list of clean-up steps that should - * be played to gracefully cancel this step. - */ - @NonNull - public abstract List<Step> cancel(); - - /** Cancel this pending step immediately, skipping any clean-up. */ - public abstract void cancelImmediately(); - - /** - * Return the duration the vibrator was turned on when this step was played. - * - * @return A positive duration that the vibrator was turned on for by this step; - * Zero if the segment is not supported, the step was not played yet or vibrator was never - * turned on by this step; A negative value if the vibrator call has failed. - */ - public long getVibratorOnDuration() { - return 0; - } - - /** - * Return true to run this step right after a vibrator has notified vibration completed, - * used to resume steps waiting on vibrator callbacks with a timeout. - */ - public boolean acceptVibratorCompleteCallback(int vibratorId) { - return false; - } - - /** - * Returns the time in millis to wait before playing this step. This is performed - * while holding the queue lock, so should not rely on potentially slow operations. - */ - public long calculateWaitTime() { - if (startTime == Long.MAX_VALUE) { - // This step don't have a predefined start time, it's just marked to be executed - // after all other steps have finished. - return 0; - } - return Math.max(0, startTime - SystemClock.uptimeMillis()); - } - - @Override - public int compareTo(Step o) { - return Long.compare(startTime, o.startTime); - } - } - - /** - * Starts a sync vibration. - * - * <p>If this step has successfully started playing a vibration on any vibrator, it will always - * add a {@link FinishVibrateStep} to the queue, to be played after all vibrators have finished - * all their individual steps. - * - * <p>If this step does not start any vibrator, it will add a {@link StartVibrateStep} if the - * sequential effect isn't finished yet. - */ - private final class StartVibrateStep extends Step { - public final CombinedVibration.Sequential sequentialEffect; - public final int currentIndex; - - private long mVibratorsOnMaxDuration; - - StartVibrateStep(CombinedVibration.Sequential effect) { - this(SystemClock.uptimeMillis() + effect.getDelays().get(0), effect, /* index= */ 0); - } - - StartVibrateStep(long startTime, CombinedVibration.Sequential effect, int index) { - super(startTime); - sequentialEffect = effect; - currentIndex = index; - } - - @Override - public long getVibratorOnDuration() { - return mVibratorsOnMaxDuration; - } - - @Override - public List<Step> play() { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "StartVibrateStep"); - List<Step> nextSteps = new ArrayList<>(); - mVibratorsOnMaxDuration = -1; - try { - if (DEBUG) { - Slog.d(TAG, "StartVibrateStep for effect #" + currentIndex); - } - CombinedVibration effect = sequentialEffect.getEffects().get(currentIndex); - DeviceEffectMap effectMapping = createEffectToVibratorMapping(effect); - if (effectMapping == null) { - // Unable to map effects to vibrators, ignore this step. - return nextSteps; - } - - mVibratorsOnMaxDuration = startVibrating(effectMapping, nextSteps); - noteVibratorOn(mVibratorsOnMaxDuration); - } finally { - if (mVibratorsOnMaxDuration >= 0) { - // It least one vibrator was started then add a finish step to wait for all - // active vibrators to finish their individual steps before going to the next. - // Otherwise this step was ignored so just go to the next one. - Step nextStep = - mVibratorsOnMaxDuration > 0 ? new FinishVibrateStep(this) : nextStep(); - if (nextStep != null) { - nextSteps.add(nextStep); - } - } - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - return nextSteps; - } - - @Override - public List<Step> cancel() { - return EMPTY_STEP_LIST; - } - - @Override - public void cancelImmediately() { - } - - /** - * Create the next {@link StartVibrateStep} to play this sequential effect, starting at the - * time this method is called, or null if sequence is complete. - */ - @Nullable - private Step nextStep() { - int nextIndex = currentIndex + 1; - if (nextIndex >= sequentialEffect.getEffects().size()) { - return null; - } - long nextEffectDelay = sequentialEffect.getDelays().get(nextIndex); - long nextStartTime = SystemClock.uptimeMillis() + nextEffectDelay; - return new StartVibrateStep(nextStartTime, sequentialEffect, nextIndex); - } - - /** Create a mapping of individual {@link VibrationEffect} to available vibrators. */ - @Nullable - private DeviceEffectMap createEffectToVibratorMapping( - CombinedVibration effect) { - if (effect instanceof CombinedVibration.Mono) { - return new DeviceEffectMap((CombinedVibration.Mono) effect); - } - if (effect instanceof CombinedVibration.Stereo) { - return new DeviceEffectMap((CombinedVibration.Stereo) effect); - } - return null; - } - - /** - * Starts playing effects on designated vibrators, in sync. - * - * @param effectMapping The {@link CombinedVibration} mapped to this device vibrators - * @param nextSteps An output list to accumulate the future {@link Step Steps} created - * by this method, typically one for each vibrator that has - * successfully started vibrating on this step. - * @return The duration, in millis, of the {@link CombinedVibration}. Repeating - * waveforms return {@link Long#MAX_VALUE}. Zero or negative values indicate the vibrators - * have ignored all effects. - */ - private long startVibrating(DeviceEffectMap effectMapping, List<Step> nextSteps) { - int vibratorCount = effectMapping.size(); - if (vibratorCount == 0) { - // No effect was mapped to any available vibrator. - return 0; - } - - SingleVibratorStep[] steps = new SingleVibratorStep[vibratorCount]; - long vibrationStartTime = SystemClock.uptimeMillis(); - for (int i = 0; i < vibratorCount; i++) { - steps[i] = nextVibrateStep(vibrationStartTime, - mVibrators.get(effectMapping.vibratorIdAt(i)), - effectMapping.effectAt(i), - /* segmentIndex= */ 0, /* vibratorOffTimeout= */ 0); - } - - if (steps.length == 1) { - // No need to prepare and trigger sync effects on a single vibrator. - return startVibrating(steps[0], nextSteps); - } - - // This synchronization of vibrators should be executed one at a time, even if we are - // vibrating different sets of vibrators in parallel. The manager can only prepareSynced - // one set of vibrators at a time. - synchronized (mLock) { - boolean hasPrepared = false; - boolean hasTriggered = false; - long maxDuration = 0; - try { - hasPrepared = mVibratorManagerHooks.prepareSyncedVibration( - effectMapping.getRequiredSyncCapabilities(), - effectMapping.getVibratorIds()); - - for (SingleVibratorStep step : steps) { - long duration = startVibrating(step, nextSteps); - if (duration < 0) { - // One vibrator has failed, fail this entire sync attempt. - return maxDuration = -1; - } - maxDuration = Math.max(maxDuration, duration); - } - - // Check if sync was prepared and if any step was accepted by a vibrator, - // otherwise there is nothing to trigger here. - if (hasPrepared && maxDuration > 0) { - hasTriggered = mVibratorManagerHooks.triggerSyncedVibration(mVibration.id); - } - return maxDuration; - } finally { - if (hasPrepared && !hasTriggered) { - // Trigger has failed or all steps were ignored by the vibrators. - mVibratorManagerHooks.cancelSyncedVibration(); - nextSteps.clear(); - } else if (maxDuration < 0) { - // Some vibrator failed without being prepared so other vibrators might be - // active. Cancel and remove every pending step from output list. - for (int i = nextSteps.size() - 1; i >= 0; i--) { - nextSteps.remove(i).cancelImmediately(); - } - } - } - } - } - - private long startVibrating(SingleVibratorStep step, List<Step> nextSteps) { - nextSteps.addAll(step.play()); - long stepDuration = step.getVibratorOnDuration(); - if (stepDuration < 0) { - // Step failed, so return negative duration to propagate failure. - return stepDuration; - } - // Return the longest estimation for the entire effect. - return Math.max(stepDuration, step.effect.getDuration()); - } - } - - /** - * Finish a sync vibration started by a {@link StartVibrateStep}. - * - * <p>This only plays after all active vibrators steps have finished, and adds a {@link - * StartVibrateStep} to the queue if the sequential effect isn't finished yet. - */ - private final class FinishVibrateStep extends Step { - public final StartVibrateStep startedStep; - - FinishVibrateStep(StartVibrateStep startedStep) { - super(Long.MAX_VALUE); // No predefined startTime, just wait for all steps in the queue. - this.startedStep = startedStep; - } - - @Override - public boolean isCleanUp() { - // This step only notes that all the vibrators has been turned off. - return true; - } - - @Override - public List<Step> play() { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "FinishVibrateStep"); - try { - if (DEBUG) { - Slog.d(TAG, "FinishVibrateStep for effect #" + startedStep.currentIndex); - } - noteVibratorOff(); - Step nextStep = startedStep.nextStep(); - return nextStep == null ? EMPTY_STEP_LIST : Arrays.asList(nextStep); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } - - @Override - public List<Step> cancel() { - cancelImmediately(); - return EMPTY_STEP_LIST; - } - - @Override - public void cancelImmediately() { - noteVibratorOff(); - } - } - - /** - * Represent a step on a single vibrator that plays one or more segments from a - * {@link VibrationEffect.Composed} effect. - */ - private abstract class SingleVibratorStep extends Step { - public final VibratorController controller; - public final VibrationEffect.Composed effect; - public final int segmentIndex; - public final long vibratorOffTimeout; - - long mVibratorOnResult; - boolean mVibratorCompleteCallbackReceived; - - /** - * @param startTime The time to schedule this step in the {@link StepQueue}. - * @param controller The vibrator that is playing the effect. - * @param effect The effect being played in this step. - * @param index The index of the next segment to be played by this step - * @param vibratorOffTimeout The time the vibrator is expected to complete any previous - * vibration and turn off. This is used to allow this step to be - * triggered when the completion callback is received, and can - * be used play effects back-to-back. - */ - SingleVibratorStep(long startTime, VibratorController controller, - VibrationEffect.Composed effect, int index, long vibratorOffTimeout) { - super(startTime); - this.controller = controller; - this.effect = effect; - this.segmentIndex = index; - this.vibratorOffTimeout = vibratorOffTimeout; - } - - @Override - public long getVibratorOnDuration() { - return mVibratorOnResult; - } - - @Override - public boolean acceptVibratorCompleteCallback(int vibratorId) { - boolean isSameVibrator = controller.getVibratorInfo().getId() == vibratorId; - mVibratorCompleteCallbackReceived |= isSameVibrator; - // Only activate this step if a timeout was set to wait for the vibration to complete, - // otherwise we are waiting for the correct time to play the next step. - return isSameVibrator && (vibratorOffTimeout > SystemClock.uptimeMillis()); - } - - @Override - public List<Step> cancel() { - return Arrays.asList(new EffectCompleteStep(SystemClock.uptimeMillis(), - /* cancelled= */ true, controller, vibratorOffTimeout)); - } - - @Override - public void cancelImmediately() { - if (vibratorOffTimeout > SystemClock.uptimeMillis()) { - // Vibrator might be running from previous steps, so turn it off while canceling. - stopVibrating(); - } - } - - void stopVibrating() { - if (DEBUG) { - Slog.d(TAG, "Turning off vibrator " + controller.getVibratorInfo().getId()); - } - controller.off(); - } - - void changeAmplitude(float amplitude) { - if (DEBUG) { - Slog.d(TAG, "Amplitude changed on vibrator " + controller.getVibratorInfo().getId() - + " to " + amplitude); - } - controller.setAmplitude(amplitude); - } - - /** Return the {@link #nextVibrateStep} with same timings, only jumping the segments. */ - public List<Step> skipToNextSteps(int segmentsSkipped) { - return nextSteps(startTime, vibratorOffTimeout, segmentsSkipped); - } - - /** - * Return the {@link #nextVibrateStep} with same start and off timings calculated from - * {@link #getVibratorOnDuration()}, jumping all played segments. - * - * <p>This method has same behavior as {@link #skipToNextSteps(int)} when the vibrator - * result is non-positive, meaning the vibrator has either ignored or failed to turn on. - */ - public List<Step> nextSteps(int segmentsPlayed) { - if (mVibratorOnResult <= 0) { - // Vibration was not started, so just skip the played segments and keep timings. - return skipToNextSteps(segmentsPlayed); - } - long nextStartTime = SystemClock.uptimeMillis() + mVibratorOnResult; - long nextVibratorOffTimeout = nextStartTime + CALLBACKS_EXTRA_TIMEOUT; - return nextSteps(nextStartTime, nextVibratorOffTimeout, segmentsPlayed); - } - - /** - * Return the {@link #nextVibrateStep} with given start and off timings, which might be - * calculated independently, jumping all played segments. - * - * <p>This should be used when the vibrator on/off state is not responsible for the steps - * execution timings, e.g. while playing the vibrator amplitudes. - */ - public List<Step> nextSteps(long nextStartTime, long vibratorOffTimeout, - int segmentsPlayed) { - Step nextStep = nextVibrateStep(nextStartTime, controller, effect, - segmentIndex + segmentsPlayed, vibratorOffTimeout); - return nextStep == null ? EMPTY_STEP_LIST : Arrays.asList(nextStep); - } - } - - /** - * Represent a step turn the vibrator on with a single prebaked effect. - * - * <p>This step automatically falls back by replacing the prebaked segment with - * {@link VibrationSettings#getFallbackEffect(int)}, if available. - */ - private final class PerformStep extends SingleVibratorStep { - - PerformStep(long startTime, VibratorController controller, - VibrationEffect.Composed effect, int index, long vibratorOffTimeout) { - // This step should wait for the last vibration to finish (with the timeout) and for the - // intended step start time (to respect the effect delays). - super(Math.max(startTime, vibratorOffTimeout), controller, effect, index, - vibratorOffTimeout); - } - - @Override - public List<Step> play() { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "PerformStep"); - try { - VibrationEffectSegment segment = effect.getSegments().get(segmentIndex); - if (!(segment instanceof PrebakedSegment)) { - Slog.w(TAG, "Ignoring wrong segment for a PerformStep: " + segment); - return skipToNextSteps(/* segmentsSkipped= */ 1); - } - - PrebakedSegment prebaked = (PrebakedSegment) segment; - if (DEBUG) { - Slog.d(TAG, "Perform " + VibrationEffect.effectIdToString( - prebaked.getEffectId()) + " on vibrator " - + controller.getVibratorInfo().getId()); - } - - VibrationEffect fallback = mVibration.getFallback(prebaked.getEffectId()); - mVibratorOnResult = controller.on(prebaked, mVibration.id); - - if (mVibratorOnResult == 0 && prebaked.shouldFallback() - && (fallback instanceof VibrationEffect.Composed)) { - if (DEBUG) { - Slog.d(TAG, "Playing fallback for effect " - + VibrationEffect.effectIdToString(prebaked.getEffectId())); - } - SingleVibratorStep fallbackStep = nextVibrateStep(startTime, controller, - replaceCurrentSegment((VibrationEffect.Composed) fallback), - segmentIndex, vibratorOffTimeout); - List<Step> fallbackResult = fallbackStep.play(); - // Update the result with the fallback result so this step is seamlessly - // replaced by the fallback to any outer application of this. - mVibratorOnResult = fallbackStep.getVibratorOnDuration(); - return fallbackResult; - } - - return nextSteps(/* segmentsPlayed= */ 1); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } - - /** - * Replace segment at {@link #segmentIndex} in {@link #effect} with given fallback segments. - * - * @return a copy of {@link #effect} with replaced segment. - */ - private VibrationEffect.Composed replaceCurrentSegment(VibrationEffect.Composed fallback) { - List<VibrationEffectSegment> newSegments = new ArrayList<>(effect.getSegments()); - int newRepeatIndex = effect.getRepeatIndex(); - newSegments.remove(segmentIndex); - newSegments.addAll(segmentIndex, fallback.getSegments()); - if (segmentIndex < effect.getRepeatIndex()) { - newRepeatIndex += fallback.getSegments().size() - 1; - } - return new VibrationEffect.Composed(newSegments, newRepeatIndex); - } - } - - /** - * Represent a step turn the vibrator on using a composition of primitives. - * - * <p>This step will use the maximum supported number of consecutive segments of type - * {@link PrimitiveSegment} starting at the current index. - */ - private final class ComposePrimitivesStep extends SingleVibratorStep { - - ComposePrimitivesStep(long startTime, VibratorController controller, - VibrationEffect.Composed effect, int index, long vibratorOffTimeout) { - // This step should wait for the last vibration to finish (with the timeout) and for the - // intended step start time (to respect the effect delays). - super(Math.max(startTime, vibratorOffTimeout), controller, effect, index, - vibratorOffTimeout); - } - - @Override - public List<Step> play() { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "ComposePrimitivesStep"); - try { - // Load the next PrimitiveSegments to create a single compose call to the vibrator, - // limited to the vibrator composition maximum size. - int limit = controller.getVibratorInfo().getCompositionSizeMax(); - int segmentCount = limit > 0 - ? Math.min(effect.getSegments().size(), segmentIndex + limit) - : effect.getSegments().size(); - List<PrimitiveSegment> primitives = new ArrayList<>(); - for (int i = segmentIndex; i < segmentCount; i++) { - VibrationEffectSegment segment = effect.getSegments().get(i); - if (segment instanceof PrimitiveSegment) { - primitives.add((PrimitiveSegment) segment); - } else { - break; - } - } - - if (primitives.isEmpty()) { - Slog.w(TAG, "Ignoring wrong segment for a ComposePrimitivesStep: " - + effect.getSegments().get(segmentIndex)); - return skipToNextSteps(/* segmentsSkipped= */ 1); - } - - if (DEBUG) { - Slog.d(TAG, "Compose " + primitives + " primitives on vibrator " - + controller.getVibratorInfo().getId()); - } - mVibratorOnResult = controller.on( - primitives.toArray(new PrimitiveSegment[primitives.size()]), - mVibration.id); - - return nextSteps(/* segmentsPlayed= */ primitives.size()); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } - } - - /** - * Represent a step turn the vibrator on using a composition of PWLE segments. - * - * <p>This step will use the maximum supported number of consecutive segments of type - * {@link StepSegment} or {@link RampSegment} starting at the current index. - */ - private final class ComposePwleStep extends SingleVibratorStep { - - ComposePwleStep(long startTime, VibratorController controller, - VibrationEffect.Composed effect, int index, long vibratorOffTimeout) { - // This step should wait for the last vibration to finish (with the timeout) and for the - // intended step start time (to respect the effect delays). - super(Math.max(startTime, vibratorOffTimeout), controller, effect, index, - vibratorOffTimeout); - } - - @Override - public List<Step> play() { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "ComposePwleStep"); - try { - // Load the next RampSegments to create a single composePwle call to the vibrator, - // limited to the vibrator PWLE maximum size. - int limit = controller.getVibratorInfo().getPwleSizeMax(); - int segmentCount = limit > 0 - ? Math.min(effect.getSegments().size(), segmentIndex + limit) - : effect.getSegments().size(); - List<RampSegment> pwles = new ArrayList<>(); - for (int i = segmentIndex; i < segmentCount; i++) { - VibrationEffectSegment segment = effect.getSegments().get(i); - if (segment instanceof RampSegment) { - pwles.add((RampSegment) segment); - } else { - break; - } - } - - if (pwles.isEmpty()) { - Slog.w(TAG, "Ignoring wrong segment for a ComposePwleStep: " - + effect.getSegments().get(segmentIndex)); - return skipToNextSteps(/* segmentsSkipped= */ 1); - } - - if (DEBUG) { - Slog.d(TAG, "Compose " + pwles + " PWLEs on vibrator " - + controller.getVibratorInfo().getId()); - } - mVibratorOnResult = controller.on(pwles.toArray(new RampSegment[pwles.size()]), - mVibration.id); - - return nextSteps(/* segmentsPlayed= */ pwles.size()); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } - } - - /** - * Represents a step to complete a {@link VibrationEffect}. - * - * <p>This runs right at the time the vibration is considered to end and will update the pending - * vibrators count. This can turn off the vibrator or slowly ramp it down to zero amplitude. - */ - private final class EffectCompleteStep extends SingleVibratorStep { - private final boolean mCancelled; - - EffectCompleteStep(long startTime, boolean cancelled, VibratorController controller, - long vibratorOffTimeout) { - super(startTime, controller, /* effect= */ null, /* index= */ -1, vibratorOffTimeout); - mCancelled = cancelled; - } - - @Override - public boolean isCleanUp() { - // If the vibration was cancelled then this is just a clean up to ramp off the vibrator. - // Otherwise this step is part of the vibration. - return mCancelled; - } - - @Override - public List<Step> cancel() { - if (mCancelled) { - // Double cancelling will just turn off the vibrator right away. - return Arrays.asList(new OffStep(SystemClock.uptimeMillis(), controller)); - } - return super.cancel(); - } - - @Override - public List<Step> play() { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "EffectCompleteStep"); - try { - if (DEBUG) { - Slog.d(TAG, "Running " + (mCancelled ? "cancel" : "complete") + " vibration" - + " step on vibrator " + controller.getVibratorInfo().getId()); - } - if (mVibratorCompleteCallbackReceived) { - // Vibration completion callback was received by this step, just turn if off - // and skip any clean-up. - stopVibrating(); - return EMPTY_STEP_LIST; - } - - float currentAmplitude = controller.getCurrentAmplitude(); - long remainingOnDuration = - vibratorOffTimeout - CALLBACKS_EXTRA_TIMEOUT - SystemClock.uptimeMillis(); - long rampDownDuration = - Math.min(remainingOnDuration, mVibrationSettings.getRampDownDuration()); - long stepDownDuration = mVibrationSettings.getRampStepDuration(); - if (currentAmplitude < RAMP_OFF_AMPLITUDE_MIN - || rampDownDuration <= stepDownDuration) { - // No need to ramp down the amplitude, just wait to turn it off. - if (mCancelled) { - // Vibration is completing because it was cancelled, turn off right away. - stopVibrating(); - return EMPTY_STEP_LIST; - } else { - return Arrays.asList(new OffStep(vibratorOffTimeout, controller)); - } - } - - if (DEBUG) { - Slog.d(TAG, "Ramping down vibrator " + controller.getVibratorInfo().getId() - + " from amplitude " + currentAmplitude - + " for " + rampDownDuration + "ms"); - } - float amplitudeDelta = currentAmplitude / (rampDownDuration / stepDownDuration); - float amplitudeTarget = currentAmplitude - amplitudeDelta; - long newVibratorOffTimeout = mCancelled ? rampDownDuration : vibratorOffTimeout; - return Arrays.asList(new RampOffStep(startTime, amplitudeTarget, amplitudeDelta, - controller, newVibratorOffTimeout)); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } - } - - /** Represents a step to ramp down the vibrator amplitude before turning it off. */ - private final class RampOffStep extends SingleVibratorStep { - private final float mAmplitudeTarget; - private final float mAmplitudeDelta; - - RampOffStep(long startTime, float amplitudeTarget, float amplitudeDelta, - VibratorController controller, long vibratorOffTimeout) { - super(startTime, controller, /* effect= */ null, /* index= */ -1, vibratorOffTimeout); - mAmplitudeTarget = amplitudeTarget; - mAmplitudeDelta = amplitudeDelta; - } - - @Override - public boolean isCleanUp() { - return true; - } - - @Override - public List<Step> cancel() { - return Arrays.asList(new OffStep(SystemClock.uptimeMillis(), controller)); - } - - @Override - public List<Step> play() { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "RampOffStep"); - try { - if (DEBUG) { - long latency = SystemClock.uptimeMillis() - startTime; - Slog.d(TAG, "Ramp down the vibrator amplitude, step with " - + latency + "ms latency."); - } - if (mVibratorCompleteCallbackReceived) { - // Vibration completion callback was received by this step, just turn if off - // and skip the rest of the steps to ramp down the vibrator amplitude. - stopVibrating(); - return EMPTY_STEP_LIST; - } - - changeAmplitude(mAmplitudeTarget); - - float newAmplitudeTarget = mAmplitudeTarget - mAmplitudeDelta; - if (newAmplitudeTarget < RAMP_OFF_AMPLITUDE_MIN) { - // Vibrator amplitude cannot go further down, just turn it off. - return Arrays.asList(new OffStep(vibratorOffTimeout, controller)); - } - return Arrays.asList(new RampOffStep( - startTime + mVibrationSettings.getRampStepDuration(), newAmplitudeTarget, - mAmplitudeDelta, controller, vibratorOffTimeout)); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } - } - - /** - * Represents a step to turn the vibrator off. - * - * <p>This runs after a timeout on the expected time the vibrator should have finished playing, - * and can be brought forward by vibrator complete callbacks. - */ - private final class OffStep extends SingleVibratorStep { - - OffStep(long startTime, VibratorController controller) { - super(startTime, controller, /* effect= */ null, /* index= */ -1, startTime); - } - - @Override - public boolean isCleanUp() { - return true; - } - - @Override - public List<Step> cancel() { - return Arrays.asList(new OffStep(SystemClock.uptimeMillis(), controller)); - } - - @Override - public void cancelImmediately() { - stopVibrating(); - } - - @Override - public List<Step> play() { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "OffStep"); - try { - stopVibrating(); - return EMPTY_STEP_LIST; - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } - } - - /** - * Represents a step to turn the vibrator on and change its amplitude. - * - * <p>This step ignores vibration completion callbacks and control the vibrator on/off state - * and amplitude to simulate waveforms represented by a sequence of {@link StepSegment}. - */ - private final class AmplitudeStep extends SingleVibratorStep { - private long mNextOffTime; - - AmplitudeStep(long startTime, VibratorController controller, - VibrationEffect.Composed effect, int index, long vibratorOffTimeout) { - // This step has a fixed startTime coming from the timings of the waveform it's playing. - super(startTime, controller, effect, index, vibratorOffTimeout); - mNextOffTime = vibratorOffTimeout; - } - - @Override - public boolean acceptVibratorCompleteCallback(int vibratorId) { - if (controller.getVibratorInfo().getId() == vibratorId) { - mVibratorCompleteCallbackReceived = true; - mNextOffTime = SystemClock.uptimeMillis(); - } - // Timings are tightly controlled here, so only trigger this step if the vibrator was - // supposed to be ON but has completed prematurely, to turn it back on as soon as - // possible. - return mNextOffTime < startTime && controller.getCurrentAmplitude() > 0; - } - - @Override - public List<Step> play() { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "AmplitudeStep"); - try { - long now = SystemClock.uptimeMillis(); - long latency = now - startTime; - if (DEBUG) { - Slog.d(TAG, "Running amplitude step with " + latency + "ms latency."); - } - - if (mVibratorCompleteCallbackReceived && latency < 0) { - // This step was run early because the vibrator turned off prematurely. - // Turn it back on and return this same step to run at the exact right time. - mNextOffTime = turnVibratorBackOn(/* remainingDuration= */ -latency); - return Arrays.asList(new AmplitudeStep(startTime, controller, effect, - segmentIndex, mNextOffTime)); - } - - VibrationEffectSegment segment = effect.getSegments().get(segmentIndex); - if (!(segment instanceof StepSegment)) { - Slog.w(TAG, "Ignoring wrong segment for a AmplitudeStep: " + segment); - return skipToNextSteps(/* segmentsSkipped= */ 1); - } - - StepSegment stepSegment = (StepSegment) segment; - if (stepSegment.getDuration() == 0) { - // Skip waveform entries with zero timing. - return skipToNextSteps(/* segmentsSkipped= */ 1); - } - - float amplitude = stepSegment.getAmplitude(); - if (amplitude == 0) { - if (vibratorOffTimeout > now) { - // Amplitude cannot be set to zero, so stop the vibrator. - stopVibrating(); - mNextOffTime = now; - } - } else { - if (startTime >= mNextOffTime) { - // Vibrator is OFF. Turn vibrator back on for the duration of another - // cycle before setting the amplitude. - long onDuration = getVibratorOnDuration(effect, segmentIndex); - if (onDuration > 0) { - mVibratorOnResult = startVibrating(onDuration); - mNextOffTime = now + onDuration + CALLBACKS_EXTRA_TIMEOUT; - } - } - changeAmplitude(amplitude); - } - - // Use original startTime to avoid propagating latencies to the waveform. - long nextStartTime = startTime + segment.getDuration(); - return nextSteps(nextStartTime, mNextOffTime, /* segmentsPlayed= */ 1); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } - - private long turnVibratorBackOn(long remainingDuration) { - long onDuration = getVibratorOnDuration(effect, segmentIndex); - if (onDuration <= 0) { - // Vibrator is supposed to go back off when this step starts, so just leave it off. - return vibratorOffTimeout; - } - onDuration += remainingDuration; - float expectedAmplitude = controller.getCurrentAmplitude(); - mVibratorOnResult = startVibrating(onDuration); - if (mVibratorOnResult > 0) { - // Set the amplitude back to the value it was supposed to be playing at. - changeAmplitude(expectedAmplitude); - } - return SystemClock.uptimeMillis() + onDuration + CALLBACKS_EXTRA_TIMEOUT; - } - - private long startVibrating(long duration) { - if (DEBUG) { - Slog.d(TAG, "Turning on vibrator " + controller.getVibratorInfo().getId() + " for " - + duration + "ms"); - } - return controller.on(duration, mVibration.id); - } - - /** - * Get the duration the vibrator will be on for a waveform, starting at {@code startIndex} - * until the next time it's vibrating amplitude is zero or a different type of segment is - * found. - */ - private long getVibratorOnDuration(VibrationEffect.Composed effect, int startIndex) { - List<VibrationEffectSegment> segments = effect.getSegments(); - int segmentCount = segments.size(); - int repeatIndex = effect.getRepeatIndex(); - int i = startIndex; - long timing = 0; - while (i < segmentCount) { - VibrationEffectSegment segment = segments.get(i); - if (!(segment instanceof StepSegment) - || ((StepSegment) segment).getAmplitude() == 0) { - break; - } - timing += segment.getDuration(); - i++; - if (i == segmentCount && repeatIndex >= 0) { - i = repeatIndex; - // prevent infinite loop - repeatIndex = -1; - } - if (i == startIndex) { - // The repeating waveform keeps the vibrator ON all the time. Use a minimum - // of 1s duration to prevent short patterns from turning the vibrator ON too - // frequently. - return Math.max(timing, 1000); - } - } - if (i == segmentCount && effect.getRepeatIndex() < 0) { - // Vibration ending at non-zero amplitude, add extra timings to ramp down after - // vibration is complete. - timing += mVibrationSettings.getRampDownDuration(); - } - return timing; - } - } - - /** - * Map a {@link CombinedVibration} to the vibrators available on the device. - * - * <p>This contains the logic to find the capabilities required from {@link IVibratorManager} to - * play all of the effects in sync. - */ - private final class DeviceEffectMap { - private final SparseArray<VibrationEffect.Composed> mVibratorEffects; - private final int[] mVibratorIds; - private final long mRequiredSyncCapabilities; - - DeviceEffectMap(CombinedVibration.Mono mono) { - mVibratorEffects = new SparseArray<>(mVibrators.size()); - mVibratorIds = new int[mVibrators.size()]; - for (int i = 0; i < mVibrators.size(); i++) { - int vibratorId = mVibrators.keyAt(i); - VibratorInfo vibratorInfo = mVibrators.valueAt(i).getVibratorInfo(); - VibrationEffect effect = mDeviceEffectAdapter.apply(mono.getEffect(), vibratorInfo); - if (effect instanceof VibrationEffect.Composed) { - mVibratorEffects.put(vibratorId, (VibrationEffect.Composed) effect); - mVibratorIds[i] = vibratorId; - } - } - mRequiredSyncCapabilities = calculateRequiredSyncCapabilities(mVibratorEffects); - } - - DeviceEffectMap(CombinedVibration.Stereo stereo) { - SparseArray<VibrationEffect> stereoEffects = stereo.getEffects(); - mVibratorEffects = new SparseArray<>(); - for (int i = 0; i < stereoEffects.size(); i++) { - int vibratorId = stereoEffects.keyAt(i); - if (mVibrators.contains(vibratorId)) { - VibratorInfo vibratorInfo = mVibrators.valueAt(i).getVibratorInfo(); - VibrationEffect effect = mDeviceEffectAdapter.apply( - stereoEffects.valueAt(i), vibratorInfo); - if (effect instanceof VibrationEffect.Composed) { - mVibratorEffects.put(vibratorId, (VibrationEffect.Composed) effect); - } - } - } - mVibratorIds = new int[mVibratorEffects.size()]; - for (int i = 0; i < mVibratorEffects.size(); i++) { - mVibratorIds[i] = mVibratorEffects.keyAt(i); - } - mRequiredSyncCapabilities = calculateRequiredSyncCapabilities(mVibratorEffects); - } - - /** - * Return the number of vibrators mapped to play the {@link CombinedVibration} on this - * device. - */ - public int size() { - return mVibratorIds.length; - } - - /** - * Return all capabilities required to play the {@link CombinedVibration} in - * between calls to {@link IVibratorManager#prepareSynced} and - * {@link IVibratorManager#triggerSynced}. - */ - public long getRequiredSyncCapabilities() { - return mRequiredSyncCapabilities; - } - - /** Return all vibrator ids mapped to play the {@link CombinedVibration}. */ - public int[] getVibratorIds() { - return mVibratorIds; - } - - /** Return the id of the vibrator at given index. */ - public int vibratorIdAt(int index) { - return mVibratorEffects.keyAt(index); - } - - /** Return the {@link VibrationEffect} at given index. */ - public VibrationEffect.Composed effectAt(int index) { - return mVibratorEffects.valueAt(index); - } - - /** - * Return all capabilities required from the {@link IVibratorManager} to prepare and - * trigger all given effects in sync. - * - * @return {@link IVibratorManager#CAP_SYNC} together with all required - * IVibratorManager.CAP_PREPARE_* and IVibratorManager.CAP_MIXED_TRIGGER_* capabilities. - */ - private long calculateRequiredSyncCapabilities( - SparseArray<VibrationEffect.Composed> effects) { - long prepareCap = 0; - for (int i = 0; i < effects.size(); i++) { - VibrationEffectSegment firstSegment = effects.valueAt(i).getSegments().get(0); - if (firstSegment instanceof StepSegment) { - prepareCap |= IVibratorManager.CAP_PREPARE_ON; - } else if (firstSegment instanceof PrebakedSegment) { - prepareCap |= IVibratorManager.CAP_PREPARE_PERFORM; - } else if (firstSegment instanceof PrimitiveSegment) { - prepareCap |= IVibratorManager.CAP_PREPARE_COMPOSE; - } - } - int triggerCap = 0; - if (requireMixedTriggerCapability(prepareCap, IVibratorManager.CAP_PREPARE_ON)) { - triggerCap |= IVibratorManager.CAP_MIXED_TRIGGER_ON; - } - if (requireMixedTriggerCapability(prepareCap, IVibratorManager.CAP_PREPARE_PERFORM)) { - triggerCap |= IVibratorManager.CAP_MIXED_TRIGGER_PERFORM; - } - if (requireMixedTriggerCapability(prepareCap, IVibratorManager.CAP_PREPARE_COMPOSE)) { - triggerCap |= IVibratorManager.CAP_MIXED_TRIGGER_COMPOSE; - } - return IVibratorManager.CAP_SYNC | prepareCap | triggerCap; - } - - /** - * Return true if {@code prepareCapabilities} contains this {@code capability} mixed with - * different ones, requiring a mixed trigger capability from the vibrator manager for - * syncing all effects. - */ - private boolean requireMixedTriggerCapability(long prepareCapabilities, long capability) { - return (prepareCapabilities & capability) != 0 - && (prepareCapabilities & ~capability) != 0; - } - } } diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index 63f3af3f3095..01f9d0b94879 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -42,6 +42,7 @@ import android.os.IVibratorStateListener; import android.os.Looper; import android.os.PowerManager; import android.os.Process; +import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.ShellCallback; @@ -60,6 +61,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.util.DumpUtils; +import com.android.internal.util.FrameworkStatsLog; import com.android.server.LocalServices; import com.android.server.SystemService; @@ -88,6 +90,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY | VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF; + /** Fixed large duration used to note repeating vibrations to {@link IBatteryStats}. */ + private static final long BATTERY_STATS_REPEATING_VIBRATION_DURATION = 5_000; + /** Lifecycle responsible for initializing this class at the right system server phases. */ public static class Lifecycle extends SystemService { private VibratorManagerService mService; @@ -201,8 +206,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class) .getSystemUiServiceComponent().getPackageName(); - mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService( - BatteryStats.SERVICE_NAME)); + mBatteryStatsService = injector.getBatteryStatsService(); mAppOps = mContext.getSystemService(AppOpsManager.class); @@ -634,7 +638,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } VibrationThread vibThread = new VibrationThread(vib, mVibrationSettings, - mDeviceVibrationEffectAdapter, mVibrators, mWakeLock, mBatteryStatsService, + mDeviceVibrationEffectAdapter, mVibrators, mWakeLock, mVibrationThreadCallbacks); if (mCurrentVibration == null) { @@ -1105,6 +1109,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { return new Handler(looper); } + IBatteryStats getBatteryStatsService() { + return IBatteryStats.Stub.asInterface(ServiceManager.getService( + BatteryStats.SERVICE_NAME)); + } + VibratorController createVibratorController(int vibratorId, VibratorController.OnVibrationCompleteListener listener) { return new VibratorController(vibratorId, listener); @@ -1141,6 +1150,36 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } @Override + public void noteVibratorOn(int uid, long duration) { + try { + if (duration <= 0) { + return; + } + if (duration == Long.MAX_VALUE) { + // Repeating duration has started. Report a fixed duration here, noteVibratorOff + // should be called when this is cancelled. + duration = BATTERY_STATS_REPEATING_VIBRATION_DURATION; + } + mBatteryStatsService.noteVibratorOn(uid, duration); + FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED, + uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__ON, + duration); + } catch (RemoteException e) { + } + } + + @Override + public void noteVibratorOff(int uid) { + try { + mBatteryStatsService.noteVibratorOff(uid); + FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED, + uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__OFF, + /* duration= */ 0); + } catch (RemoteException e) { + } + } + + @Override public void onVibrationCompleted(long vibrationId, Vibration.Status status) { if (DEBUG) { Slog.d(TAG, "Vibration " + vibrationId + " finished with status " + status); diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index a9add59be1f6..6a23eb588102 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -310,7 +310,7 @@ class TaskSnapshotController { } final ActivityRecord activity = result.first; final WindowState mainWindow = result.second; - final Rect contentInsets = getSystemBarInsets(task.getBounds(), + final Rect contentInsets = getSystemBarInsets(mainWindow.getFrame(), mainWindow.getInsetsStateWithVisibilityOverride()); final Rect letterboxInsets = activity.getLetterboxInsets(); InsetUtils.addInsets(contentInsets, letterboxInsets); @@ -575,7 +575,7 @@ class TaskSnapshotController { final LayoutParams attrs = mainWindow.getAttrs(); final Rect taskBounds = task.getBounds(); final InsetsState insetsState = mainWindow.getInsetsStateWithVisibilityOverride(); - final Rect systemBarInsets = getSystemBarInsets(taskBounds, insetsState); + final Rect systemBarInsets = getSystemBarInsets(mainWindow.getFrame(), insetsState); final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags, attrs.privateFlags, attrs.insetsFlags.appearance, task.getTaskDescription(), mHighResTaskSnapshotScale, insetsState); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 091d879a1876..33e97aa5eaf2 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -31,20 +31,6 @@ import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEV import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER; import static android.app.admin.DevicePolicyManager.ACTION_SYSTEM_UPDATE_POLICY_CHANGED; -import static android.app.admin.DevicePolicyManager.CODE_ACCOUNTS_NOT_EMPTY; -import static android.app.admin.DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE; -import static android.app.admin.DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED; -import static android.app.admin.DevicePolicyManager.CODE_HAS_DEVICE_OWNER; -import static android.app.admin.DevicePolicyManager.CODE_HAS_PAIRED; -import static android.app.admin.DevicePolicyManager.CODE_MANAGED_USERS_NOT_SUPPORTED; -import static android.app.admin.DevicePolicyManager.CODE_NONSYSTEM_USER_EXISTS; -import static android.app.admin.DevicePolicyManager.CODE_NOT_SYSTEM_USER; -import static android.app.admin.DevicePolicyManager.CODE_OK; -import static android.app.admin.DevicePolicyManager.CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS; -import static android.app.admin.DevicePolicyManager.CODE_SYSTEM_USER; -import static android.app.admin.DevicePolicyManager.CODE_USER_HAS_PROFILE_OWNER; -import static android.app.admin.DevicePolicyManager.CODE_USER_NOT_RUNNING; -import static android.app.admin.DevicePolicyManager.CODE_USER_SETUP_COMPLETED; import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS; import static android.app.admin.DevicePolicyManager.DELEGATION_BLOCK_UNINSTALL; import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL; @@ -101,6 +87,20 @@ import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_SET_ERROR_FAILUR import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_SET_NO_ERROR; import static android.app.admin.DevicePolicyManager.PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER; import static android.app.admin.DevicePolicyManager.STATE_USER_UNMANAGED; +import static android.app.admin.DevicePolicyManager.STATUS_ACCOUNTS_NOT_EMPTY; +import static android.app.admin.DevicePolicyManager.STATUS_CANNOT_ADD_MANAGED_PROFILE; +import static android.app.admin.DevicePolicyManager.STATUS_DEVICE_ADMIN_NOT_SUPPORTED; +import static android.app.admin.DevicePolicyManager.STATUS_HAS_DEVICE_OWNER; +import static android.app.admin.DevicePolicyManager.STATUS_HAS_PAIRED; +import static android.app.admin.DevicePolicyManager.STATUS_MANAGED_USERS_NOT_SUPPORTED; +import static android.app.admin.DevicePolicyManager.STATUS_NONSYSTEM_USER_EXISTS; +import static android.app.admin.DevicePolicyManager.STATUS_NOT_SYSTEM_USER; +import static android.app.admin.DevicePolicyManager.STATUS_OK; +import static android.app.admin.DevicePolicyManager.STATUS_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS; +import static android.app.admin.DevicePolicyManager.STATUS_SYSTEM_USER; +import static android.app.admin.DevicePolicyManager.STATUS_USER_HAS_PROFILE_OWNER; +import static android.app.admin.DevicePolicyManager.STATUS_USER_NOT_RUNNING; +import static android.app.admin.DevicePolicyManager.STATUS_USER_SETUP_COMPLETED; import static android.app.admin.DevicePolicyManager.WIPE_EUICC; import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE; import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA; @@ -9658,7 +9658,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final int code = checkDeviceOwnerProvisioningPreConditionLocked(owner, /* deviceOwnerUserId= */ deviceOwnerUserId, /* callingUserId*/ caller.getUserId(), isAdb(caller), hasIncompatibleAccountsOrNonAdb); - if (code != CODE_OK) { + if (code != STATUS_OK) { throw new IllegalStateException( computeProvisioningErrorString(code, deviceOwnerUserId)); } @@ -9666,25 +9666,25 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private static String computeProvisioningErrorString(int code, @UserIdInt int userId) { switch (code) { - case CODE_OK: + case STATUS_OK: return "OK"; - case CODE_HAS_DEVICE_OWNER: + case STATUS_HAS_DEVICE_OWNER: return "Trying to set the device owner, but device owner is already set."; - case CODE_USER_HAS_PROFILE_OWNER: + case STATUS_USER_HAS_PROFILE_OWNER: return "Trying to set the device owner, but the user already has a profile owner."; - case CODE_USER_NOT_RUNNING: + case STATUS_USER_NOT_RUNNING: return "User " + userId + " not running."; - case CODE_NOT_SYSTEM_USER: + case STATUS_NOT_SYSTEM_USER: return "User " + userId + " is not system user."; - case CODE_USER_SETUP_COMPLETED: + case STATUS_USER_SETUP_COMPLETED: return "Cannot set the device owner if the device is already set-up."; - case CODE_NONSYSTEM_USER_EXISTS: + case STATUS_NONSYSTEM_USER_EXISTS: return "Not allowed to set the device owner because there are already several" + " users on the device."; - case CODE_ACCOUNTS_NOT_EMPTY: + case STATUS_ACCOUNTS_NOT_EMPTY: return "Not allowed to set the device owner because there are already some accounts" + " on the device."; - case CODE_HAS_PAIRED: + case STATUS_HAS_PAIRED: return "Not allowed to set the device owner because this device has already " + "paired."; default: @@ -14159,30 +14159,30 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mInjector.binderRestoreCallingIdentity(ident); } - return checkProvisioningPreConditionSkipPermission(action, packageName) == CODE_OK; + return checkProvisioningPreconditionSkipPermission(action, packageName) == STATUS_OK; } @Override - public int checkProvisioningPreCondition(String action, String packageName) { + public int checkProvisioningPrecondition(String action, String packageName) { Objects.requireNonNull(packageName, "packageName is null"); Preconditions.checkCallAuthorization( hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); - return checkProvisioningPreConditionSkipPermission(action, packageName); + return checkProvisioningPreconditionSkipPermission(action, packageName); } - private int checkProvisioningPreConditionSkipPermission(String action, + private int checkProvisioningPreconditionSkipPermission(String action, String packageName) { if (!mHasFeature) { logMissingFeatureAction("Cannot check provisioning for action " + action); - return CODE_DEVICE_ADMIN_NOT_SUPPORTED; + return STATUS_DEVICE_ADMIN_NOT_SUPPORTED; } if (!isProvisioningAllowed()) { - return CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS; + return STATUS_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS; } final int code = checkProvisioningPreConditionSkipPermissionNoLog(action, packageName); - if (code != CODE_OK) { + if (code != STATUS_OK) { Slogf.d(LOG_TAG, "checkProvisioningPreCondition(" + action + ", " + packageName + ") failed: " + computeProvisioningErrorString(code, mInjector.userHandleGetCallingUserId())); @@ -14230,27 +14230,27 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @UserIdInt int deviceOwnerUserId, @UserIdInt int callingUserId, boolean isAdb, boolean hasIncompatibleAccountsOrNonAdb) { if (mOwners.hasDeviceOwner()) { - return CODE_HAS_DEVICE_OWNER; + return STATUS_HAS_DEVICE_OWNER; } if (mOwners.hasProfileOwner(deviceOwnerUserId)) { - return CODE_USER_HAS_PROFILE_OWNER; + return STATUS_USER_HAS_PROFILE_OWNER; } boolean isHeadlessSystemUserMode = mInjector.userManagerIsHeadlessSystemUserMode(); // System user is always running in headless system user mode. if (!isHeadlessSystemUserMode && !mUserManager.isUserRunning(new UserHandle(deviceOwnerUserId))) { - return CODE_USER_NOT_RUNNING; + return STATUS_USER_NOT_RUNNING; } if (mIsWatch && hasPaired(UserHandle.USER_SYSTEM)) { - return CODE_HAS_PAIRED; + return STATUS_HAS_PAIRED; } if (isHeadlessSystemUserMode) { if (deviceOwnerUserId != UserHandle.USER_SYSTEM) { Slogf.e(LOG_TAG, "In headless system user mode, " + "device owner can only be set on headless system user."); - return CODE_NOT_SYSTEM_USER; + return STATUS_NOT_SYSTEM_USER; } } @@ -14265,7 +14265,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { int maxNumberOfExistingUsers = isHeadlessSystemUserMode ? 2 : 1; if (mUserManager.getUserCount() > maxNumberOfExistingUsers) { - return CODE_NONSYSTEM_USER_EXISTS; + return STATUS_NONSYSTEM_USER_EXISTS; } int currentForegroundUser = getCurrentForegroundUserId(); @@ -14274,23 +14274,23 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { && currentForegroundUser == UserHandle.USER_SYSTEM) { Slogf.wtf(LOG_TAG, "In headless system user mode, " + "current user cannot be system user when setting device owner"); - return CODE_SYSTEM_USER; + return STATUS_SYSTEM_USER; } if (hasIncompatibleAccountsOrNonAdb) { - return CODE_ACCOUNTS_NOT_EMPTY; + return STATUS_ACCOUNTS_NOT_EMPTY; } } - return CODE_OK; + return STATUS_OK; } else { // DO has to be user 0 if (deviceOwnerUserId != UserHandle.USER_SYSTEM) { - return CODE_NOT_SYSTEM_USER; + return STATUS_NOT_SYSTEM_USER; } // Only provision DO before setup wizard completes if (hasUserSetupCompleted(UserHandle.USER_SYSTEM)) { - return CODE_USER_SETUP_COMPLETED; + return STATUS_USER_SETUP_COMPLETED; } - return CODE_OK; + return STATUS_OK; } } @@ -14311,11 +14311,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private int checkManagedProfileProvisioningPreCondition(String packageName, @UserIdInt int callingUserId) { if (!hasFeatureManagedUsers()) { - return CODE_MANAGED_USERS_NOT_SUPPORTED; + return STATUS_MANAGED_USERS_NOT_SUPPORTED; } if (getProfileOwnerAsUser(callingUserId) != null) { // Managed user cannot have a managed profile. - return CODE_USER_HAS_PROFILE_OWNER; + return STATUS_USER_HAS_PROFILE_OWNER; } final long ident = mInjector.binderClearCallingIdentity(); @@ -14334,7 +14334,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { callingUserId); // The check is called from inside a managed profile. A managed profile cannot // be provisioned from within another managed profile. - return CODE_CANNOT_ADD_MANAGED_PROFILE; + return STATUS_CANNOT_ADD_MANAGED_PROFILE; } // If there's a device owner, the restriction on adding a managed profile must be set. @@ -14346,19 +14346,19 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (addingProfileRestricted) { Slogf.i(LOG_TAG, "Adding a profile is restricted: User %s Has device owner? %b", callingUserHandle, hasDeviceOwner); - return CODE_CANNOT_ADD_MANAGED_PROFILE; + return STATUS_CANNOT_ADD_MANAGED_PROFILE; } // Bail out if we are trying to provision a work profile but one already exists. if (!mUserManager.canAddMoreManagedProfiles( callingUserId, /* allowedToRemoveOne= */ false)) { Slogf.i(LOG_TAG, "A work profile already exists."); - return CODE_CANNOT_ADD_MANAGED_PROFILE; + return STATUS_CANNOT_ADD_MANAGED_PROFILE; } } finally { mInjector.binderRestoreCallingIdentity(ident); } - return CODE_OK; + return STATUS_OK; } private void checkIsDeviceOwner(CallerIdentity caller) { @@ -17697,9 +17697,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { UserInfo userInfo = null; final long identity = Binder.clearCallingIdentity(); try { - final int result = checkProvisioningPreConditionSkipPermission( + final int result = checkProvisioningPreconditionSkipPermission( ACTION_PROVISION_MANAGED_PROFILE, admin.getPackageName()); - if (result != CODE_OK) { + if (result != STATUS_OK) { throw new ServiceSpecificException( ERROR_PRE_CONDITION_FAILED, "Provisioning preconditions failed with result: " + result); @@ -18076,9 +18076,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final long identity = Binder.clearCallingIdentity(); try { - int result = checkProvisioningPreConditionSkipPermission( + int result = checkProvisioningPreconditionSkipPermission( ACTION_PROVISION_MANAGED_DEVICE, deviceAdmin.getPackageName()); - if (result != CODE_OK) { + if (result != STATUS_OK) { throw new ServiceSpecificException( ERROR_PRE_CONDITION_FAILED, "Provisioning preconditions failed with result: " + result); diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt index 43b2e1e4b21e..dcc461be0015 100644 --- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt +++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt @@ -265,12 +265,14 @@ class PackageManagerComponentLabelIconOverrideTest { assertServiceInitialized() ?: return when (params.result) { is Result.Changed, Result.ChangedWithoutNotify -> { - assertThat(mockPendingBroadcasts.get(userId, params.pkgName) ?: emptyList<String>()) + assertThat(mockPendingBroadcasts.copiedMap()?.get(userId)?.get(params.pkgName) + ?: emptyList<String>()) .containsExactly(params.componentName!!.className) .inOrder() } is Result.NotChanged, is Result.Exception -> { - assertThat(mockPendingBroadcasts.get(userId, params.pkgName)).isNull() + assertThat(mockPendingBroadcasts.copiedMap()?.get(userId)?.get(params.pkgName)) + .isNull() } }.run { /*exhaust*/ } } diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt index 92cdb348e60d..c9601de6ff26 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt @@ -20,8 +20,6 @@ import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.content.pm.SigningDetails -import com.android.server.pm.pkg.component.ParsedActivityImpl -import com.android.server.pm.pkg.component.ParsedIntentInfoImpl import android.content.pm.verify.domain.DomainVerificationManager import android.content.pm.verify.domain.DomainVerificationState import android.os.Build @@ -30,10 +28,12 @@ import android.util.ArraySet import android.util.IndentingPrintWriter import android.util.SparseArray import androidx.test.platform.app.InstrumentationRegistry +import com.android.server.pm.Computer import com.android.server.pm.parsing.pkg.AndroidPackage import com.android.server.pm.pkg.PackageStateInternal import com.android.server.pm.pkg.PackageUserStateInternal -import com.android.server.pm.test.verify.domain.DomainVerificationTestUtils.mockPackageStates +import com.android.server.pm.pkg.component.ParsedActivityImpl +import com.android.server.pm.pkg.component.ParsedIntentInfoImpl import com.android.server.pm.verify.domain.DomainVerificationEnforcer import com.android.server.pm.verify.domain.DomainVerificationManagerInternal import com.android.server.pm.verify.domain.DomainVerificationService @@ -100,11 +100,15 @@ class DomainVerificationEnforcerTest { mockThrowOnUnmocked { whenever(callingUid) { callingUidInt.get() } whenever(callingUserId) { callingUserIdInt.get() } - mockPackageStates { - when (it) { - VISIBLE_PKG -> visiblePkgState - INVISIBLE_PKG -> invisiblePkgState - else -> null + whenever(snapshot()) { + mockThrowOnUnmocked { + whenever(getPackageStateInternal(anyString())) { + when (getArgument<String>(0)) { + VISIBLE_PKG -> visiblePkgState + INVISIBLE_PKG -> invisiblePkgState + else -> null + } + } } } whenever(schedule(anyInt(), any())) @@ -211,9 +215,8 @@ class DomainVerificationEnforcerTest { printState(mock(IndentingPrintWriter::class.java), null, null) }, service(Type.QUERENT, "printStateInternal") { - printState(mock(IndentingPrintWriter::class.java), null, null) { - mockPkgState(it, UUID.randomUUID()) - } + printState(mock(Computer::class.java), mock(IndentingPrintWriter::class.java), + null, null) }, service(Type.VERIFIER, "setStatus") { setDomainVerificationStatus( diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt index 878bee012635..7273b0b54e1e 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt @@ -19,9 +19,6 @@ package com.android.server.pm.test.verify.domain import android.content.Context import android.content.Intent import android.content.pm.PackageManager -import com.android.server.pm.pkg.component.ParsedActivityImpl -import com.android.server.pm.pkg.component.ParsedIntentInfoImpl -import com.android.server.pm.pkg.PackageUserStateInternal import android.content.pm.verify.domain.DomainOwner import android.content.pm.verify.domain.DomainVerificationInfo import android.content.pm.verify.domain.DomainVerificationManager @@ -34,7 +31,9 @@ import android.util.ArraySet import android.util.SparseArray import com.android.server.pm.parsing.pkg.AndroidPackage import com.android.server.pm.pkg.PackageStateInternal -import com.android.server.pm.test.verify.domain.DomainVerificationTestUtils.mockPackageStates +import com.android.server.pm.pkg.PackageUserStateInternal +import com.android.server.pm.pkg.component.ParsedActivityImpl +import com.android.server.pm.pkg.component.ParsedIntentInfoImpl import com.android.server.pm.verify.domain.DomainVerificationManagerStub import com.android.server.pm.verify.domain.DomainVerificationService import com.android.server.testutils.mockThrowOnUnmocked @@ -502,8 +501,12 @@ class DomainVerificationManagerApiTest { whenever(callingUid) { Process.ROOT_UID } whenever(callingUserId) { 0 } - mockPackageStates { - pkgStateFunction(it) + whenever(snapshot()) { + mockThrowOnUnmocked { + whenever(getPackageStateInternal(anyString())) { + pkgStateFunction(getArgument(0)) + } + } } }) } 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 0369bab61f0f..40f37a7ee1f7 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 @@ -20,9 +20,6 @@ import android.content.Intent import android.content.pm.PackageManager import android.content.pm.Signature import android.content.pm.SigningDetails -import com.android.server.pm.pkg.component.ParsedActivityImpl -import com.android.server.pm.pkg.component.ParsedIntentInfoImpl -import com.android.server.pm.pkg.PackageUserStateInternal import android.content.pm.verify.domain.DomainOwner import android.content.pm.verify.domain.DomainVerificationInfo.STATE_MODIFIABLE_VERIFIED import android.content.pm.verify.domain.DomainVerificationInfo.STATE_NO_RESPONSE @@ -39,9 +36,12 @@ import android.os.Process import android.util.ArraySet import android.util.SparseArray import android.util.Xml +import com.android.server.pm.Computer import com.android.server.pm.parsing.pkg.AndroidPackage import com.android.server.pm.pkg.PackageStateInternal -import com.android.server.pm.test.verify.domain.DomainVerificationTestUtils.mockPackageStates +import com.android.server.pm.pkg.PackageUserStateInternal +import com.android.server.pm.pkg.component.ParsedActivityImpl +import com.android.server.pm.pkg.component.ParsedIntentInfoImpl import com.android.server.pm.verify.domain.DomainVerificationService import com.android.server.testutils.mock import com.android.server.testutils.mockThrowOnUnmocked @@ -194,7 +194,8 @@ class DomainVerificationPackageTest { """ val service = makeService(pkg1, pkg2) - service.restoreSettings(Xml.resolvePullParser(xml.byteInputStream())) + val computer = mockComputer(pkg1, pkg2) + service.restoreSettings(computer, Xml.resolvePullParser(xml.byteInputStream())) service.addPackage(pkg1) val info = service.getInfo(pkg1.packageName) assertThat(info.packageName).isEqualTo(pkg1.packageName) @@ -243,7 +244,8 @@ class DomainVerificationPackageTest { """ val service = makeService(pkg1, pkg2) - service.restoreSettings(Xml.resolvePullParser(xml.byteInputStream())) + val computer = mockComputer(pkg1, pkg2) + service.restoreSettings(computer, Xml.resolvePullParser(xml.byteInputStream())) service.addPackage(pkg1) val info = service.getInfo(pkg1.packageName) assertThat(info.packageName).isEqualTo(pkg1.packageName) @@ -298,8 +300,9 @@ class DomainVerificationPackageTest { """.trimIndent() val service = makeService(pkg1, pkg2) + val computer = mockComputer(pkg1, pkg2) xml.byteInputStream().use { - service.readSettings(Xml.resolvePullParser(it)) + service.readSettings(computer, Xml.resolvePullParser(it)) } service.addPackage(pkg1) @@ -311,8 +314,9 @@ class DomainVerificationPackageTest { fun addPackagePendingStripInvalidDomains() { val xml = addPackagePendingOrRestoredWithInvalidDomains() val service = makeService(pkg1, pkg2) + val computer = mockComputer(pkg1, pkg2) xml.byteInputStream().use { - service.readSettings(Xml.resolvePullParser(it)) + service.readSettings(computer, Xml.resolvePullParser(it)) } service.addPackage(pkg1) @@ -334,8 +338,9 @@ class DomainVerificationPackageTest { fun addPackageRestoredStripInvalidDomains() { val xml = addPackagePendingOrRestoredWithInvalidDomains() val service = makeService(pkg1, pkg2) + val computer = mockComputer(pkg1, pkg2) xml.byteInputStream().use { - service.restoreSettings(Xml.resolvePullParser(it)) + service.restoreSettings(computer, Xml.resolvePullParser(it)) } service.addPackage(pkg1) @@ -686,6 +691,7 @@ class DomainVerificationPackageTest { val pkg2 = mockPkgState(PKG_TWO, UUID_TWO, SIGNATURE_TWO, listOf(DOMAIN_1, DOMAIN_2, DOMAIN_3)) val serviceBefore = makeService(pkg1, pkg2) + val computerBefore = mockComputer(pkg1, pkg2) serviceBefore.addPackage(pkg1) serviceBefore.addPackage(pkg2) @@ -729,16 +735,17 @@ class DomainVerificationPackageTest { assertExpectedState(serviceBefore) val backupUser0 = ByteArrayOutputStream().use { - serviceBefore.writeSettings(Xml.resolveSerializer(it), true, 0) + serviceBefore.writeSettings(computerBefore, Xml.resolveSerializer(it), true, 0) it.toByteArray() } val backupUser1 = ByteArrayOutputStream().use { - serviceBefore.writeSettings(Xml.resolveSerializer(it), true, 10) + serviceBefore.writeSettings(computerBefore, Xml.resolveSerializer(it), true, 10) it.toByteArray() } val serviceAfter = makeService(pkg1, pkg2) + val computerAfter = mockComputer(pkg1, pkg2) serviceAfter.addPackage(pkg1) serviceAfter.addPackage(pkg2) @@ -763,7 +770,7 @@ class DomainVerificationPackageTest { } ByteArrayInputStream(backupUser1).use { - serviceAfter.restoreSettings(Xml.resolvePullParser(it)) + serviceAfter.restoreSettings(computerAfter, Xml.resolvePullParser(it)) } // Assert user 1 was restored @@ -800,7 +807,7 @@ class DomainVerificationPackageTest { ) ByteArrayInputStream(backupUser0).use { - serviceAfter.restoreSettings(Xml.resolvePullParser(it)) + serviceAfter.restoreSettings(computerAfter, Xml.resolvePullParser(it)) } assertExpectedState(serviceAfter) @@ -848,12 +855,20 @@ class DomainVerificationPackageTest { whenever(callingUid) { Process.ROOT_UID } whenever(callingUserId) { 0 } - mockPackageStates { - pkgStateFunction(it) - } + whenever(snapshot()) { mockComputer(pkgStateFunction) } }) } + private fun mockComputer(vararg pkgStates: PackageStateInternal) = + mockComputer { pkgName -> pkgStates.find { pkgName == it.packageName } } + + private fun mockComputer(pkgStateFunction: (String) -> PackageStateInternal? = { null }) = + mockThrowOnUnmocked<Computer> { + whenever(getPackageStateInternal(anyString())) { + pkgStateFunction(getArgument(0)) + } + } + private fun mockPkgState( pkgName: String, domainSetId: UUID, diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt index 3a602a8b3c46..fc20c2657b97 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt @@ -29,7 +29,6 @@ import android.util.ArraySet import android.util.SparseArray import com.android.server.pm.parsing.pkg.AndroidPackage import com.android.server.pm.pkg.PackageStateInternal -import com.android.server.pm.test.verify.domain.DomainVerificationTestUtils.mockPackageStates import com.android.server.pm.verify.domain.DomainVerificationManagerInternal import com.android.server.pm.verify.domain.DomainVerificationService import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy @@ -245,10 +244,14 @@ class DomainVerificationSettingsMutationTest { mockThrowOnUnmocked { whenever(callingUid) { TEST_UID } whenever(callingUserId) { TEST_USER_ID } - mockPackageStates { - when (it) { - TEST_PKG -> mockPkgState() - else -> null + whenever(snapshot()) { + mockThrowOnUnmocked { + whenever(getPackageStateInternal(anyString())) { + when (getArgument<String>(0)) { + TEST_PKG -> mockPkgState() + else -> null + } + } } } whenever(schedule(anyInt(), any())) diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationTestUtils.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationTestUtils.kt deleted file mode 100644 index c5f0eb184386..000000000000 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationTestUtils.kt +++ /dev/null @@ -1,57 +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.server.pm.test.verify.domain - -import com.android.internal.util.FunctionalUtils -import com.android.server.pm.pkg.PackageStateInternal -import com.android.server.pm.verify.domain.DomainVerificationManagerInternal -import com.android.server.testutils.whenever -import org.mockito.ArgumentMatchers.any -import java.util.function.Consumer -import java.util.function.Function - -internal object DomainVerificationTestUtils { - - @Suppress("UNCHECKED_CAST") - fun DomainVerificationManagerInternal.Connection.mockPackageStates( - block: (String) -> PackageStateInternal? - ) { - whenever(withPackageSettingsSnapshot(any())) { - (arguments[0] as Consumer<Function<String, PackageStateInternal?>>).accept { block(it) } - } - whenever(withPackageSettingsSnapshotReturning(any())) { - (arguments[0] as FunctionalUtils.ThrowingFunction< - Function<String, PackageStateInternal?>, *>) - .apply { block(it) } - } - whenever(withPackageSettingsSnapshotThrowing<Exception>(any())) { - (arguments[0] as FunctionalUtils.ThrowingCheckedConsumer< - Function<String, PackageStateInternal?>, *>) - .accept { block(it) } - } - whenever(withPackageSettingsSnapshotThrowing2<Exception, Exception>(any())) { - (arguments[0] as FunctionalUtils.ThrowingChecked2Consumer< - Function<String, PackageStateInternal?>, *, *>) - .accept { block(it) } - } - whenever(withPackageSettingsSnapshotReturningThrowing<Any, Exception>(any())) { - (arguments[0] as FunctionalUtils.ThrowingCheckedFunction< - Function<String, PackageStateInternal?>, *, *>) - .apply { block(it) } - } - } -} diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt index ffc287736066..589633ccb900 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt @@ -31,7 +31,6 @@ import android.util.SparseArray import com.android.server.pm.parsing.pkg.AndroidPackage import com.android.server.pm.pkg.PackageStateInternal import com.android.server.pm.pkg.PackageUserStateInternal -import com.android.server.pm.test.verify.domain.DomainVerificationTestUtils.mockPackageStates import com.android.server.pm.verify.domain.DomainVerificationService import com.android.server.testutils.mockThrowOnUnmocked import com.android.server.testutils.whenever @@ -85,11 +84,15 @@ class DomainVerificationUserStateOverrideTest { // Need to provide an internal UID so some permission checks are ignored whenever(callingUid) { Process.ROOT_UID } whenever(callingUserId) { 0 } - mockPackageStates { - when (it) { - PKG_ONE -> pkg1 - PKG_TWO -> pkg2 - else -> null + whenever(snapshot()) { + mockThrowOnUnmocked { + whenever(getPackageStateInternal(anyString())) { + when (getArgument<String>(0)) { + PKG_ONE -> pkg1 + PKG_TWO -> pkg2 + else -> null + } + } } } }) diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceControllerTest.java index 1c480eef96a1..03eff2a1831b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceControllerTest.java @@ -20,13 +20,18 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSess import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.any; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import android.content.BroadcastReceiver; import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; import android.content.pm.UserInfo; +import android.net.Uri; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; @@ -35,11 +40,13 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.util.ConcurrentUtils; import com.android.server.SystemService; +import com.android.server.app.GameServiceConfiguration.GameServiceComponentConfiguration; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.Mockito; @@ -61,10 +68,14 @@ public final class GameServiceControllerTest { new ComponentName(PROVIDER_A_PACKAGE_NAME, "com.provider.a.ServiceA"); private static final ComponentName PROVIDER_A_SERVICE_B = new ComponentName(PROVIDER_A_PACKAGE_NAME, "com.provider.a.ServiceB"); + private static final ComponentName PROVIDER_A_SERVICE_C = + new ComponentName(PROVIDER_A_PACKAGE_NAME, "com.provider.a.ServiceC"); private MockitoSession mMockingSession; private GameServiceController mGameServiceManager; @Mock + private Context mMockContext; + @Mock private GameServiceProviderSelector mMockGameServiceProviderSelector; @Mock private GameServiceProviderInstanceFactory mMockGameServiceProviderInstanceFactory; @@ -77,7 +88,7 @@ public final class GameServiceControllerTest { .startMocking(); mGameServiceManager = new GameServiceController( - ConcurrentUtils.DIRECT_EXECUTOR, + mMockContext, ConcurrentUtils.DIRECT_EXECUTOR, mMockGameServiceProviderSelector, mMockGameServiceProviderInstanceFactory); } @@ -96,25 +107,30 @@ public final class GameServiceControllerTest { @Test public void notifyUserStarted_createsAndStartsNewInstance() { - GameServiceProviderConfiguration configurationA = - new GameServiceProviderConfiguration(USER_HANDLE_10, PROVIDER_A_SERVICE_A, - PROVIDER_A_SERVICE_B); + GameServiceConfiguration configurationA = + new GameServiceConfiguration(PROVIDER_A_PACKAGE_NAME, + new GameServiceComponentConfiguration(USER_HANDLE_10, + PROVIDER_A_SERVICE_A, + PROVIDER_A_SERVICE_B)); FakeGameServiceProviderInstance instanceA = seedConfigurationForUser(USER_10, configurationA); mGameServiceManager.onBootComplete(); mGameServiceManager.notifyUserStarted(USER_10); - verify(mMockGameServiceProviderInstanceFactory).create(configurationA); + verify(mMockGameServiceProviderInstanceFactory).create( + configurationA.getGameServiceComponentConfiguration()); verifyNoMoreInteractions(mMockGameServiceProviderInstanceFactory); assertThat(instanceA.getIsRunning()).isTrue(); } @Test public void notifyUserStarted_sameUser_doesNotCreateNewInstance() { - GameServiceProviderConfiguration configurationA = - new GameServiceProviderConfiguration(USER_HANDLE_10, PROVIDER_A_SERVICE_A, - PROVIDER_A_SERVICE_B); + GameServiceConfiguration configurationA = + new GameServiceConfiguration(PROVIDER_A_PACKAGE_NAME, + new GameServiceComponentConfiguration(USER_HANDLE_10, + PROVIDER_A_SERVICE_A, + PROVIDER_A_SERVICE_B)); FakeGameServiceProviderInstance instanceA = seedConfigurationForUser(USER_10, configurationA); @@ -122,16 +138,19 @@ public final class GameServiceControllerTest { mGameServiceManager.notifyUserStarted(USER_10); mGameServiceManager.notifyUserStarted(USER_10); - verify(mMockGameServiceProviderInstanceFactory).create(configurationA); + verify(mMockGameServiceProviderInstanceFactory).create( + configurationA.getGameServiceComponentConfiguration()); verifyNoMoreInteractions(mMockGameServiceProviderInstanceFactory); assertThat(instanceA.getIsRunning()).isTrue(); } @Test public void notifyUserUnlocking_noForegroundUser_ignores() { - GameServiceProviderConfiguration configurationA = - new GameServiceProviderConfiguration(USER_HANDLE_10, PROVIDER_A_SERVICE_A, - PROVIDER_A_SERVICE_B); + GameServiceConfiguration configurationA = + new GameServiceConfiguration(PROVIDER_A_PACKAGE_NAME, + new GameServiceComponentConfiguration(USER_HANDLE_10, + PROVIDER_A_SERVICE_A, + PROVIDER_A_SERVICE_B)); FakeGameServiceProviderInstance instanceA = seedConfigurationForUser(USER_10, configurationA); @@ -144,9 +163,11 @@ public final class GameServiceControllerTest { @Test public void notifyUserUnlocking_sameAsForegroundUser_evaluatesProvider() { - GameServiceProviderConfiguration configurationA = - new GameServiceProviderConfiguration(USER_HANDLE_10, PROVIDER_A_SERVICE_A, - PROVIDER_A_SERVICE_B); + GameServiceConfiguration configurationA = + new GameServiceConfiguration(PROVIDER_A_PACKAGE_NAME, + new GameServiceComponentConfiguration(USER_HANDLE_10, + PROVIDER_A_SERVICE_A, + PROVIDER_A_SERVICE_B)); seedNoConfigurationForUser(USER_10); mGameServiceManager.onBootComplete(); @@ -155,16 +176,19 @@ public final class GameServiceControllerTest { seedConfigurationForUser(USER_10, configurationA); mGameServiceManager.notifyUserUnlocking(USER_10); - verify(mMockGameServiceProviderInstanceFactory).create(configurationA); + verify(mMockGameServiceProviderInstanceFactory).create( + configurationA.getGameServiceComponentConfiguration()); verifyNoMoreInteractions(mMockGameServiceProviderInstanceFactory); assertThat(instanceA.getIsRunning()).isTrue(); } @Test public void notifyUserUnlocking_differentFromForegroundUser_ignores() { - GameServiceProviderConfiguration configurationA = - new GameServiceProviderConfiguration(USER_HANDLE_10, PROVIDER_A_SERVICE_A, - PROVIDER_A_SERVICE_B); + GameServiceConfiguration configurationA = + new GameServiceConfiguration(PROVIDER_A_PACKAGE_NAME, + new GameServiceComponentConfiguration(USER_HANDLE_10, + PROVIDER_A_SERVICE_A, + PROVIDER_A_SERVICE_B)); seedNoConfigurationForUser(USER_10); mGameServiceManager.onBootComplete(); @@ -180,14 +204,18 @@ public final class GameServiceControllerTest { @Test public void notifyNewForegroundUser_differentUser_stopsPreviousInstanceAndThenStartsNewInstance() { - GameServiceProviderConfiguration configurationA = - new GameServiceProviderConfiguration(USER_HANDLE_10, PROVIDER_A_SERVICE_A, - PROVIDER_A_SERVICE_B); + GameServiceConfiguration configurationA = + new GameServiceConfiguration(PROVIDER_A_PACKAGE_NAME, + new GameServiceComponentConfiguration(USER_HANDLE_10, + PROVIDER_A_SERVICE_A, + PROVIDER_A_SERVICE_B)); FakeGameServiceProviderInstance instanceA = seedConfigurationForUser(USER_10, configurationA); - GameServiceProviderConfiguration configurationB = - new GameServiceProviderConfiguration(USER_HANDLE_11, PROVIDER_A_SERVICE_A, - PROVIDER_A_SERVICE_B); + GameServiceConfiguration configurationB = + new GameServiceConfiguration(PROVIDER_A_PACKAGE_NAME, + new GameServiceComponentConfiguration(USER_HANDLE_11, + PROVIDER_A_SERVICE_A, + PROVIDER_A_SERVICE_B)); FakeGameServiceProviderInstance instanceB = seedConfigurationForUser(USER_11, configurationB); InOrder instancesInOrder = Mockito.inOrder(instanceA, instanceB); @@ -196,8 +224,50 @@ public final class GameServiceControllerTest { mGameServiceManager.notifyUserStarted(USER_10); mGameServiceManager.notifyNewForegroundUser(USER_11); - verify(mMockGameServiceProviderInstanceFactory).create(configurationA); - verify(mMockGameServiceProviderInstanceFactory).create(configurationB); + verify(mMockGameServiceProviderInstanceFactory).create( + configurationA.getGameServiceComponentConfiguration()); + verify(mMockGameServiceProviderInstanceFactory).create( + configurationB.getGameServiceComponentConfiguration()); + instancesInOrder.verify(instanceA).start(); + instancesInOrder.verify(instanceA).stop(); + instancesInOrder.verify(instanceB).start(); + verifyNoMoreInteractions(mMockGameServiceProviderInstanceFactory); + assertThat(instanceA.getIsRunning()).isFalse(); + assertThat(instanceB.getIsRunning()).isTrue(); + } + + @Test + public void packageChanges_reevaluatesGameServiceProvider() { + GameServiceConfiguration configurationA = + new GameServiceConfiguration(PROVIDER_A_PACKAGE_NAME, + new GameServiceComponentConfiguration(USER_HANDLE_10, + PROVIDER_A_SERVICE_A, + PROVIDER_A_SERVICE_B)); + FakeGameServiceProviderInstance instanceA = + seedConfigurationForUser(USER_10, configurationA); + + mGameServiceManager.onBootComplete(); + mGameServiceManager.notifyUserStarted(USER_10); + ArgumentCaptor<BroadcastReceiver> broadcastReceiverArgumentCaptor = + ArgumentCaptor.forClass(BroadcastReceiver.class); + verify(mMockContext).registerReceiver(broadcastReceiverArgumentCaptor.capture(), any()); + + GameServiceConfiguration configurationB = + new GameServiceConfiguration(PROVIDER_A_PACKAGE_NAME, + new GameServiceComponentConfiguration(USER_HANDLE_10, + PROVIDER_A_SERVICE_A, + PROVIDER_A_SERVICE_C)); + FakeGameServiceProviderInstance instanceB = + seedConfigurationForUser(USER_10, configurationA); + Intent intent = new Intent(); + intent.setData(Uri.parse("package:" + PROVIDER_A_PACKAGE_NAME)); + broadcastReceiverArgumentCaptor.getValue().onReceive(mMockContext, intent); + + InOrder instancesInOrder = Mockito.inOrder(instanceA, instanceB); + verify(mMockGameServiceProviderInstanceFactory).create( + configurationA.getGameServiceComponentConfiguration()); + verify(mMockGameServiceProviderInstanceFactory).create( + configurationB.getGameServiceComponentConfiguration()); instancesInOrder.verify(instanceA).start(); instancesInOrder.verify(instanceA).stop(); instancesInOrder.verify(instanceB).start(); @@ -207,15 +277,16 @@ public final class GameServiceControllerTest { } private void seedNoConfigurationForUser(SystemService.TargetUser user) { - when(mMockGameServiceProviderSelector.get(user, "")).thenReturn(null); + when(mMockGameServiceProviderSelector.get(user, null)).thenReturn(null); } private FakeGameServiceProviderInstance seedConfigurationForUser(SystemService.TargetUser user, - GameServiceProviderConfiguration configuration) { - when(mMockGameServiceProviderSelector.get(user, "")).thenReturn(configuration); + GameServiceConfiguration configuration) { + when(mMockGameServiceProviderSelector.get(user, null)).thenReturn(configuration); FakeGameServiceProviderInstance instanceForConfiguration = spy(new FakeGameServiceProviderInstance()); - when(mMockGameServiceProviderInstanceFactory.create(configuration)) + when(mMockGameServiceProviderInstanceFactory.create( + configuration.getGameServiceComponentConfiguration())) .thenReturn(instanceForConfiguration); return instanceForConfiguration; diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderSelectorImplTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderSelectorImplTest.java index 23a6a49856f7..cf9ba1e2638f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderSelectorImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderSelectorImplTest.java @@ -18,6 +18,7 @@ package com.android.server.app; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; import static com.google.common.truth.Truth.assertThat; @@ -26,7 +27,6 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.content.ComponentName; @@ -49,6 +49,7 @@ import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import com.android.server.SystemService; +import com.android.server.app.GameServiceConfiguration.GameServiceComponentConfiguration; import com.google.common.collect.ImmutableList; @@ -137,10 +138,10 @@ public final class GameServiceProviderSelectorImplTest { GAME_SERVICE_META_DATA_RES_ID, "res/xml/game_service_metadata_valid.xml"); - GameServiceProviderConfiguration gameServiceProviderConfiguration = + GameServiceConfiguration gameServiceConfiguration = mGameServiceProviderSelector.get(null, null); - assertThat(gameServiceProviderConfiguration).isNull(); + assertThat(gameServiceConfiguration).isNull(); } @Test @@ -154,15 +155,16 @@ public final class GameServiceProviderSelectorImplTest { GAME_SERVICE_META_DATA_RES_ID, "res/xml/game_service_metadata_valid.xml"); - GameServiceProviderConfiguration gameServiceProviderConfiguration = + GameServiceConfiguration gameServiceConfiguration = mGameServiceProviderSelector.get(managedTargetUser(USER_HANDLE_10), null); - assertThat(gameServiceProviderConfiguration).isNull(); + assertThat(gameServiceConfiguration).isNull(); } @Test public void get_noSystemGameService_returnsNull() throws Exception { + seedSystemGameServicePackageName(""); seedGameServiceResolveInfos(GAME_SERVICE_PACKAGE_NAME, USER_HANDLE_10, resolveInfo(GAME_SERVICE_SERVICE_INFO)); seedServiceServiceInfo(GAME_SESSION_SERVICE_COMPONENT); @@ -170,14 +172,14 @@ public final class GameServiceProviderSelectorImplTest { GAME_SERVICE_META_DATA_RES_ID, "res/xml/game_service_metadata_valid.xml"); - GameServiceProviderConfiguration gameServiceProviderConfiguration = + GameServiceConfiguration gameServiceConfiguration = mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10), null); - assertThat(gameServiceProviderConfiguration).isNull(); + assertThat(gameServiceConfiguration).isNull(); } @Test - public void get_noGameServiceProvidersAvailable_returnsNull() + public void get_noGameServiceProvidersAvailable_returnsGameServicePackageName() throws Exception { seedSystemGameServicePackageName(GAME_SERVICE_PACKAGE_NAME); seedGameServiceResolveInfos(GAME_SERVICE_PACKAGE_NAME, USER_HANDLE_10); @@ -186,28 +188,30 @@ public final class GameServiceProviderSelectorImplTest { GAME_SERVICE_META_DATA_RES_ID, "res/xml/game_service_metadata_valid.xml"); - GameServiceProviderConfiguration gameServiceProviderConfiguration = + GameServiceConfiguration gameServiceConfiguration = mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10), null); - assertThat(gameServiceProviderConfiguration).isNull(); + assertThat(gameServiceConfiguration).isEqualTo( + new GameServiceConfiguration(GAME_SERVICE_PACKAGE_NAME, null)); } @Test - public void get_gameServiceProviderHasNoMetaData_returnsNull() + public void get_gameServiceProviderHasNoMetaData_returnsGameServicePackageName() throws Exception { seedSystemGameServicePackageName(GAME_SERVICE_PACKAGE_NAME); seedGameServiceResolveInfos(GAME_SERVICE_PACKAGE_NAME, USER_HANDLE_10, resolveInfo(GAME_SERVICE_SERVICE_INFO_WITHOUT_META_DATA)); seedServiceServiceInfo(GAME_SESSION_SERVICE_COMPONENT); - GameServiceProviderConfiguration gameServiceProviderConfiguration = + GameServiceConfiguration gameServiceConfiguration = mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10), null); - assertThat(gameServiceProviderConfiguration).isNull(); + assertThat(gameServiceConfiguration).isEqualTo( + new GameServiceConfiguration(GAME_SERVICE_PACKAGE_NAME, null)); } @Test - public void get_gameSessionServiceDoesNotExist_returnsNull() + public void get_gameSessionServiceDoesNotExist_returnsGameServicePackageName() throws Exception { seedSystemGameServicePackageName(GAME_SERVICE_PACKAGE_NAME); seedGameServiceResolveInfos(GAME_SERVICE_PACKAGE_NAME, USER_HANDLE_10, @@ -217,14 +221,15 @@ public final class GameServiceProviderSelectorImplTest { GAME_SERVICE_META_DATA_RES_ID, "res/xml/game_service_metadata_valid.xml"); - GameServiceProviderConfiguration gameServiceProviderConfiguration = + GameServiceConfiguration gameServiceConfiguration = mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10), null); - assertThat(gameServiceProviderConfiguration).isNull(); + assertThat(gameServiceConfiguration).isEqualTo( + new GameServiceConfiguration(GAME_SERVICE_PACKAGE_NAME, null)); } @Test - public void get_metaDataWrongFirstTag_returnsNull() throws Exception { + public void get_metaDataWrongFirstTag_returnsGameServicePackageName() throws Exception { seedSystemGameServicePackageName(GAME_SERVICE_PACKAGE_NAME); seedGameServiceResolveInfos(GAME_SERVICE_PACKAGE_NAME, USER_HANDLE_10, resolveInfo(GAME_SERVICE_SERVICE_INFO)); @@ -233,10 +238,11 @@ public final class GameServiceProviderSelectorImplTest { GAME_SERVICE_META_DATA_RES_ID, "res/xml/game_service_metadata_wrong_first_tag.xml"); - GameServiceProviderConfiguration gameServiceProviderConfiguration = + GameServiceConfiguration gameServiceConfiguration = mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10), null); - assertThat(gameServiceProviderConfiguration).isNull(); + assertThat(gameServiceConfiguration).isEqualTo( + new GameServiceConfiguration(GAME_SERVICE_PACKAGE_NAME, null)); } @Test @@ -250,15 +256,17 @@ public final class GameServiceProviderSelectorImplTest { GAME_SERVICE_META_DATA_RES_ID, "res/xml/game_service_metadata_valid.xml"); - GameServiceProviderConfiguration gameServiceProviderConfiguration = + GameServiceConfiguration gameServiceConfiguration = mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10), null); - GameServiceProviderConfiguration expectedGameServiceProviderConfiguration = - new GameServiceProviderConfiguration(USER_HANDLE_10, - GAME_SERVICE_COMPONENT, - GAME_SESSION_SERVICE_COMPONENT); - assertThat(gameServiceProviderConfiguration).isEqualTo( - expectedGameServiceProviderConfiguration); + GameServiceConfiguration expectedGameServiceConfiguration = + new GameServiceConfiguration( + GAME_SERVICE_PACKAGE_NAME, + new GameServiceComponentConfiguration(USER_HANDLE_10, + GAME_SERVICE_COMPONENT, + GAME_SESSION_SERVICE_COMPONENT)); + assertThat(gameServiceConfiguration).isEqualTo( + expectedGameServiceConfiguration); } @Test @@ -276,15 +284,17 @@ public final class GameServiceProviderSelectorImplTest { GAME_SERVICE_META_DATA_RES_ID, "res/xml/game_service_metadata_valid.xml"); - GameServiceProviderConfiguration gameServiceProviderConfiguration = + GameServiceConfiguration gameServiceConfiguration = mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10), null); - GameServiceProviderConfiguration expectedGameServiceProviderConfiguration = - new GameServiceProviderConfiguration(USER_HANDLE_10, - GAME_SERVICE_B_COMPONENT, - GAME_SESSION_SERVICE_COMPONENT); - assertThat(gameServiceProviderConfiguration).isEqualTo( - expectedGameServiceProviderConfiguration); + GameServiceConfiguration expectedGameServiceConfiguration = + new GameServiceConfiguration( + GAME_SERVICE_PACKAGE_NAME, + new GameServiceComponentConfiguration(USER_HANDLE_10, + GAME_SERVICE_B_COMPONENT, + GAME_SESSION_SERVICE_COMPONENT)); + assertThat(gameServiceConfiguration).isEqualTo( + expectedGameServiceConfiguration); } @Test @@ -299,15 +309,17 @@ public final class GameServiceProviderSelectorImplTest { GAME_SERVICE_META_DATA_RES_ID, "res/xml/game_service_metadata_valid.xml"); - GameServiceProviderConfiguration gameServiceProviderConfiguration = + GameServiceConfiguration gameServiceConfiguration = mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10), null); - GameServiceProviderConfiguration expectedGameServiceProviderConfiguration = - new GameServiceProviderConfiguration(USER_HANDLE_10, - GAME_SERVICE_COMPONENT, - GAME_SESSION_SERVICE_COMPONENT); - assertThat(gameServiceProviderConfiguration).isEqualTo( - expectedGameServiceProviderConfiguration); + GameServiceConfiguration expectedGameServiceConfiguration = + new GameServiceConfiguration( + GAME_SERVICE_PACKAGE_NAME, + new GameServiceComponentConfiguration(USER_HANDLE_10, + GAME_SERVICE_COMPONENT, + GAME_SESSION_SERVICE_COMPONENT)); + assertThat(gameServiceConfiguration).isEqualTo( + expectedGameServiceConfiguration); } @Test @@ -322,16 +334,19 @@ public final class GameServiceProviderSelectorImplTest { GAME_SERVICE_META_DATA_RES_ID, "res/xml/game_service_metadata_valid.xml"); - GameServiceProviderConfiguration gameServiceProviderConfiguration = + GameServiceConfiguration gameServiceConfiguration = mGameServiceProviderSelector.get(eligibleTargetUser(USER_HANDLE_10), GAME_SERVICE_PACKAGE_NAME); - GameServiceProviderConfiguration expectedGameServiceProviderConfiguration = - new GameServiceProviderConfiguration(USER_HANDLE_10, - GAME_SERVICE_COMPONENT, - GAME_SESSION_SERVICE_COMPONENT); - assertThat(gameServiceProviderConfiguration).isEqualTo( - expectedGameServiceProviderConfiguration); + GameServiceConfiguration expectedGameServiceConfiguration = + new GameServiceConfiguration( + GAME_SERVICE_PACKAGE_NAME, + new GameServiceComponentConfiguration( + USER_HANDLE_10, + GAME_SERVICE_COMPONENT, + GAME_SESSION_SERVICE_COMPONENT)); + assertThat(gameServiceConfiguration).isEqualTo( + expectedGameServiceConfiguration); } private void seedSystemGameServicePackageName(String gameServicePackageName) { diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt index ca5bf2085d71..6510cd19e5fb 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt @@ -167,7 +167,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { } whenever(mocks.settings.packagesLocked).thenReturn(mSettingsMap) whenever(mocks.settings.getPackageLPr(anyString())) { mSettingsMap[getArgument<Any>(0)] } - whenever(mocks.settings.readLPw(nullable())) { + whenever(mocks.settings.readLPw(any(), nullable())) { mSettingsMap.putAll(mPreExistingSettings) !mPreExistingSettings.isEmpty() } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt index f35986b178d8..b063d22de2bb 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt @@ -120,17 +120,16 @@ class SharedLibrariesImplTest { whenever(mStorageManager.findPathForUuid(nullable())).thenReturn(mFile) doAnswer { it.arguments[0] }.`when`(mPms).resolveInternalPackageName(any(), any()) doAnswer { - it.getArgument<FunctionalUtils.ThrowingConsumer<Computer>>(0).acceptOrThrow( - mockThrowOnUnmocked { - whenever(sharedLibraries) { mSharedLibrariesImpl.sharedLibraries } - whenever(resolveInternalPackageName(anyString(), anyLong())) { - mPms.resolveInternalPackageName(getArgument(0), getArgument(1)) - } - whenever(getPackageStateInternal(anyString())) { - mPms.getPackageStateInternal(getArgument(0)) - } - }) - }.`when`(mPms).executeWithConsistentComputer(any()) + mockThrowOnUnmocked<Computer> { + whenever(sharedLibraries) { mSharedLibrariesImpl.sharedLibraries } + whenever(resolveInternalPackageName(anyString(), anyLong())) { + mPms.resolveInternalPackageName(getArgument(0), getArgument(1)) + } + whenever(getPackageStateInternal(anyString())) { + mPms.getPackageStateInternal(getArgument(0)) + } + } + }.`when`(mPms).snapshotComputer() whenever(mDeletePackageHelper.deletePackageX(any(), any(), any(), any(), any())) .thenReturn(PackageManager.DELETE_SUCCEEDED) whenever(mRule.mocks().injector.compatibility).thenReturn(mPlatformCompat) @@ -206,7 +205,8 @@ class SharedLibrariesImplTest { @Test fun pruneUnusedStaticSharedLibraries() { - mSharedLibrariesImpl.pruneUnusedStaticSharedLibraries(Long.MAX_VALUE, 0) + mSharedLibrariesImpl.pruneUnusedStaticSharedLibraries(mPms.snapshotComputer(), + Long.MAX_VALUE, 0) verify(mDeletePackageHelper) .deletePackageX(eq(STATIC_LIB_PACKAGE_NAME), any(), any(), any(), any()) diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt index ac406b588d38..5230ea7304c8 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt @@ -123,8 +123,8 @@ class SuspendPackageHelperTest { @Test fun setPackagesSuspended() { val targetPackages = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2) - val failedNames = suspendPackageHelper.setPackagesSuspended(targetPackages, - true /* suspended */, null /* appExtras */, null /* launcherExtras */, + val failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), + targetPackages, true /* suspended */, null /* appExtras */, null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) testHandler.flush() @@ -144,14 +144,15 @@ class SuspendPackageHelperTest { @Test fun setPackagesSuspended_emptyPackageName() { - var failedNames = suspendPackageHelper.setPackagesSuspended(null /* packageNames */, - true /* suspended */, null /* appExtras */, null /* launcherExtras */, - null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) + var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), + null /* packageNames */, true /* suspended */, null /* appExtras */, + null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, + deviceOwnerUid) assertThat(failedNames).isNull() - failedNames = suspendPackageHelper.setPackagesSuspended(arrayOfNulls(0), - true /* suspended */, null /* appExtras */, null /* launcherExtras */, + failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), + arrayOfNulls(0), true /* suspended */, null /* appExtras */, null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) assertThat(failedNames).isEmpty() @@ -159,9 +160,10 @@ class SuspendPackageHelperTest { @Test fun setPackagesSuspended_callerIsNotAllowed() { - val failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_2), - true /* suspended */, null /* appExtras */, null /* launcherExtras */, - null /* dialogInfo */, TEST_PACKAGE_1, TEST_USER_ID, Binder.getCallingUid()) + val failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), + arrayOf(TEST_PACKAGE_2), true /* suspended */, null /* appExtras */, + null /* launcherExtras */, null /* dialogInfo */, TEST_PACKAGE_1, TEST_USER_ID, + Binder.getCallingUid()) assertThat(failedNames).asList().hasSize(1) assertThat(failedNames).asList().contains(TEST_PACKAGE_2) @@ -169,9 +171,10 @@ class SuspendPackageHelperTest { @Test fun setPackagesSuspended_callerSuspendItself() { - val failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(DEVICE_OWNER_PACKAGE), - true /* suspended */, null /* appExtras */, null /* launcherExtras */, - null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) + val failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), + arrayOf(DEVICE_OWNER_PACKAGE), true /* suspended */, null /* appExtras */, + null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, + deviceOwnerUid) assertThat(failedNames).asList().hasSize(1) assertThat(failedNames).asList().contains(DEVICE_OWNER_PACKAGE) @@ -179,9 +182,10 @@ class SuspendPackageHelperTest { @Test fun setPackagesSuspended_nonexistentPackage() { - val failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(NONEXISTENT_PACKAGE), - true /* suspended */, null /* appExtras */, null /* launcherExtras */, - null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) + val failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), + arrayOf(NONEXISTENT_PACKAGE), true /* suspended */, null /* appExtras */, + null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, + deviceOwnerUid) assertThat(failedNames).asList().hasSize(1) assertThat(failedNames).asList().contains(NONEXISTENT_PACKAGE) @@ -191,8 +195,8 @@ class SuspendPackageHelperTest { fun setPackagesSuspended_knownPackages() { val knownPackages = arrayOf(DEVICE_ADMIN_PACKAGE, DEFAULT_HOME_PACKAGE, DIALER_PACKAGE, INSTALLER_PACKAGE, UNINSTALLER_PACKAGE, VERIFIER_PACKAGE, PERMISSION_CONTROLLER_PACKAGE) - val failedNames = suspendPackageHelper.setPackagesSuspended(knownPackages, - true /* suspended */, null /* appExtras */, null /* launcherExtras */, + val failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), + knownPackages, true /* suspended */, null /* appExtras */, null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)!! assertThat(failedNames.size).isEqualTo(knownPackages.size) @@ -204,13 +208,13 @@ class SuspendPackageHelperTest { @Test fun setPackagesUnsuspended() { val targetPackages = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2) - var failedNames = suspendPackageHelper.setPackagesSuspended(targetPackages, - true /* suspended */, null /* appExtras */, null /* launcherExtras */, + var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), + targetPackages, true /* suspended */, null /* appExtras */, null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) testHandler.flush() assertThat(failedNames).isEmpty() - failedNames = suspendPackageHelper.setPackagesSuspended(targetPackages, - false /* suspended */, null /* appExtras */, null /* launcherExtras */, + failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), + targetPackages, false /* suspended */, null /* appExtras */, null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) testHandler.flush() @@ -235,7 +239,7 @@ class SuspendPackageHelperTest { val suspendables = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2) val unsuspendables = arrayOf(DEVICE_ADMIN_PACKAGE, DEFAULT_HOME_PACKAGE, DIALER_PACKAGE, INSTALLER_PACKAGE, UNINSTALLER_PACKAGE, VERIFIER_PACKAGE, PERMISSION_CONTROLLER_PACKAGE) - val results = suspendPackageHelper.getUnsuspendablePackagesForUser( + val results = suspendPackageHelper.getUnsuspendablePackagesForUser(pms.snapshotComputer(), suspendables + unsuspendables, TEST_USER_ID, deviceOwnerUid) assertThat(results.size).isEqualTo(unsuspendables.size) @@ -247,7 +251,7 @@ class SuspendPackageHelperTest { @Test fun getUnsuspendablePackagesForUser_callerIsNotAllowed() { val suspendables = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2) - val results = suspendPackageHelper.getUnsuspendablePackagesForUser( + val results = suspendPackageHelper.getUnsuspendablePackagesForUser(pms.snapshotComputer(), suspendables, TEST_USER_ID, Binder.getCallingUid()) assertThat(results.size).isEqualTo(suspendables.size) @@ -260,8 +264,8 @@ class SuspendPackageHelperTest { fun getSuspendedPackageAppExtras() { val appExtras = PersistableBundle() appExtras.putString(TEST_PACKAGE_1, TEST_PACKAGE_1) - var failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_1), - true /* suspended */, appExtras, null /* launcherExtras */, + var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), + arrayOf(TEST_PACKAGE_1), true /* suspended */, appExtras, null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) testHandler.flush() assertThat(failedNames).isEmpty() @@ -277,8 +281,8 @@ class SuspendPackageHelperTest { val appExtras = PersistableBundle() appExtras.putString(TEST_PACKAGE_1, TEST_PACKAGE_2) val targetPackages = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2) - var failedNames = suspendPackageHelper.setPackagesSuspended(targetPackages, - true /* suspended */, appExtras, null /* launcherExtras */, + var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), + targetPackages, true /* suspended */, appExtras, null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) testHandler.flush() assertThat(failedNames).isEmpty() @@ -291,8 +295,9 @@ class SuspendPackageHelperTest { assertThat(suspendPackageHelper.getSuspendedPackageAppExtras( TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isNotNull() - suspendPackageHelper.removeSuspensionsBySuspendingPackage(targetPackages, - { suspendingPackage -> suspendingPackage == DEVICE_OWNER_PACKAGE }, TEST_USER_ID) + suspendPackageHelper.removeSuspensionsBySuspendingPackage(pms.snapshotComputer(), + targetPackages, { suspendingPackage -> suspendingPackage == DEVICE_OWNER_PACKAGE }, + TEST_USER_ID) testHandler.flush() verify(pms, times(2)).scheduleWritePackageRestrictions(eq(TEST_USER_ID)) @@ -320,8 +325,8 @@ class SuspendPackageHelperTest { fun getSuspendedPackageLauncherExtras() { val launcherExtras = PersistableBundle() launcherExtras.putString(TEST_PACKAGE_2, TEST_PACKAGE_2) - var failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_2), - true /* suspended */, null /* appExtras */, launcherExtras, + var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), + arrayOf(TEST_PACKAGE_2), true /* suspended */, null /* appExtras */, launcherExtras, null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) testHandler.flush() assertThat(failedNames).isEmpty() @@ -334,9 +339,10 @@ class SuspendPackageHelperTest { @Test fun isPackageSuspended() { - var failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_1), - true /* suspended */, null /* appExtras */, null /* launcherExtras */, - null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) + var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), + arrayOf(TEST_PACKAGE_1), true /* suspended */, null /* appExtras */, + null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, + deviceOwnerUid) testHandler.flush() assertThat(failedNames).isEmpty() @@ -348,8 +354,8 @@ class SuspendPackageHelperTest { fun getSuspendingPackage() { val launcherExtras = PersistableBundle() launcherExtras.putString(TEST_PACKAGE_2, TEST_PACKAGE_2) - var failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_2), - true /* suspended */, null /* appExtras */, launcherExtras, + var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), + arrayOf(TEST_PACKAGE_2), true /* suspended */, null /* appExtras */, launcherExtras, null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) testHandler.flush() assertThat(failedNames).isEmpty() @@ -362,9 +368,10 @@ class SuspendPackageHelperTest { fun getSuspendedDialogInfo() { val dialogInfo = SuspendDialogInfo.Builder() .setTitle(TEST_PACKAGE_1).build() - var failedNames = suspendPackageHelper.setPackagesSuspended(arrayOf(TEST_PACKAGE_1), - true /* suspended */, null /* appExtras */, null /* launcherExtras */, - dialogInfo, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid) + var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(), + arrayOf(TEST_PACKAGE_1), true /* suspended */, null /* appExtras */, + null /* launcherExtras */, dialogInfo, DEVICE_OWNER_PACKAGE, TEST_USER_ID, + deviceOwnerUid) testHandler.flush() assertThat(failedNames).isEmpty() diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java index 6751b804ad9e..41d46f223f4b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java @@ -73,6 +73,7 @@ public class AgentTest { .startMocking(); when(mIrs.getContext()).thenReturn(mContext); when(mIrs.getCompleteEconomicPolicyLocked()).thenReturn(mEconomicPolicy); + when(mIrs.getLock()).thenReturn(mIrs); when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mock(AlarmManager.class)); mScribe = new MockScribe(mIrs); } @@ -89,75 +90,75 @@ public class AgentTest { Agent agent = new Agent(mIrs, mScribe); Ledger ledger = new Ledger(); - doReturn(1_000_000L).when(mIrs).getMaxCirculationLocked(); + doReturn(1_000_000L).when(mIrs).getConsumptionLimitLocked(); doReturn(1_000_000L).when(mEconomicPolicy).getMaxSatiatedBalance(); - Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5); + Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5, 0); agent.recordTransactionLocked(0, "com.test", ledger, transaction, false); assertEquals(5, ledger.getCurrentBalance()); - transaction = new Ledger.Transaction(0, 0, 0, null, 995); + transaction = new Ledger.Transaction(0, 0, 0, null, 995, 0); agent.recordTransactionLocked(0, "com.test", ledger, transaction, false); assertEquals(1000, ledger.getCurrentBalance()); - transaction = new Ledger.Transaction(0, 0, 0, null, -500); + transaction = new Ledger.Transaction(0, 0, 0, null, -500, 250); agent.recordTransactionLocked(0, "com.test", ledger, transaction, false); assertEquals(500, ledger.getCurrentBalance()); - transaction = new Ledger.Transaction(0, 0, 0, null, 999_500L); + transaction = new Ledger.Transaction(0, 0, 0, null, 999_500L, 500); agent.recordTransactionLocked(0, "com.test", ledger, transaction, false); assertEquals(1_000_000L, ledger.getCurrentBalance()); - transaction = new Ledger.Transaction(0, 0, 0, null, -1_000_001L); + transaction = new Ledger.Transaction(0, 0, 0, null, -1_000_001L, 1000); agent.recordTransactionLocked(0, "com.test", ledger, transaction, false); assertEquals(-1, ledger.getCurrentBalance()); } @Test - public void testRecordTransaction_MaxCirculation() { + public void testRecordTransaction_MaxConsumptionLimit() { Agent agent = new Agent(mIrs, mScribe); Ledger ledger = new Ledger(); - doReturn(1000L).when(mIrs).getMaxCirculationLocked(); - doReturn(1000L).when(mEconomicPolicy).getMaxSatiatedBalance(); + doReturn(1000L).when(mIrs).getConsumptionLimitLocked(); + doReturn(1_000_000L).when(mEconomicPolicy).getMaxSatiatedBalance(); - Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5); + Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5, 0); agent.recordTransactionLocked(0, "com.test", ledger, transaction, false); assertEquals(5, ledger.getCurrentBalance()); - transaction = new Ledger.Transaction(0, 0, 0, null, 995); + transaction = new Ledger.Transaction(0, 0, 0, null, 995, 0); agent.recordTransactionLocked(0, "com.test", ledger, transaction, false); assertEquals(1000, ledger.getCurrentBalance()); - transaction = new Ledger.Transaction(0, 0, 0, null, -500); + transaction = new Ledger.Transaction(0, 0, 0, null, -500, 250); agent.recordTransactionLocked(0, "com.test", ledger, transaction, false); assertEquals(500, ledger.getCurrentBalance()); - transaction = new Ledger.Transaction(0, 0, 0, null, 2000); + transaction = new Ledger.Transaction(0, 0, 0, null, 2000, 0); agent.recordTransactionLocked(0, "com.test", ledger, transaction, false); - assertEquals(1000, ledger.getCurrentBalance()); + assertEquals(2500, ledger.getCurrentBalance()); - // MaxCirculation can change as the battery level changes. Any already allocated ARCSs - // shouldn't be removed by recordTransaction(). - doReturn(900L).when(mIrs).getMaxCirculationLocked(); + // ConsumptionLimit can change as the battery level changes. Ledger balances shouldn't be + // affected. + doReturn(900L).when(mIrs).getConsumptionLimitLocked(); - transaction = new Ledger.Transaction(0, 0, 0, null, 100); + transaction = new Ledger.Transaction(0, 0, 0, null, 100, 0); agent.recordTransactionLocked(0, "com.test", ledger, transaction, false); - assertEquals(1000, ledger.getCurrentBalance()); + assertEquals(2600, ledger.getCurrentBalance()); - transaction = new Ledger.Transaction(0, 0, 0, null, -50); + transaction = new Ledger.Transaction(0, 0, 0, null, -50, 50); agent.recordTransactionLocked(0, "com.test", ledger, transaction, false); - assertEquals(950, ledger.getCurrentBalance()); + assertEquals(2550, ledger.getCurrentBalance()); - transaction = new Ledger.Transaction(0, 0, 0, null, -200); + transaction = new Ledger.Transaction(0, 0, 0, null, -200, 100); agent.recordTransactionLocked(0, "com.test", ledger, transaction, false); - assertEquals(750, ledger.getCurrentBalance()); + assertEquals(2350, ledger.getCurrentBalance()); - doReturn(800L).when(mIrs).getMaxCirculationLocked(); + doReturn(800L).when(mIrs).getConsumptionLimitLocked(); - transaction = new Ledger.Transaction(0, 0, 0, null, 100); + transaction = new Ledger.Transaction(0, 0, 0, null, 100, 0); agent.recordTransactionLocked(0, "com.test", ledger, transaction, false); - assertEquals(800, ledger.getCurrentBalance()); + assertEquals(2450, ledger.getCurrentBalance()); } @Test @@ -165,33 +166,33 @@ public class AgentTest { Agent agent = new Agent(mIrs, mScribe); Ledger ledger = new Ledger(); - doReturn(1_000_000L).when(mIrs).getMaxCirculationLocked(); + doReturn(1_000_000L).when(mIrs).getConsumptionLimitLocked(); doReturn(1000L).when(mEconomicPolicy).getMaxSatiatedBalance(); - Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5); + Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5, 0); agent.recordTransactionLocked(0, "com.test", ledger, transaction, false); assertEquals(5, ledger.getCurrentBalance()); - transaction = new Ledger.Transaction(0, 0, 0, null, 995); + transaction = new Ledger.Transaction(0, 0, 0, null, 995, 0); agent.recordTransactionLocked(0, "com.test", ledger, transaction, false); assertEquals(1000, ledger.getCurrentBalance()); - transaction = new Ledger.Transaction(0, 0, 0, null, -500); + transaction = new Ledger.Transaction(0, 0, 0, null, -500, 250); agent.recordTransactionLocked(0, "com.test", ledger, transaction, false); assertEquals(500, ledger.getCurrentBalance()); - transaction = new Ledger.Transaction(0, 0, 0, null, 999_500L); + transaction = new Ledger.Transaction(0, 0, 0, null, 999_500L, 1000); agent.recordTransactionLocked(0, "com.test", ledger, transaction, false); assertEquals(1_000, ledger.getCurrentBalance()); // Shouldn't change in normal operation, but adding test case in case it does. doReturn(900L).when(mEconomicPolicy).getMaxSatiatedBalance(); - transaction = new Ledger.Transaction(0, 0, 0, null, 500); + transaction = new Ledger.Transaction(0, 0, 0, null, 500, 0); agent.recordTransactionLocked(0, "com.test", ledger, transaction, false); assertEquals(1_000, ledger.getCurrentBalance()); - transaction = new Ledger.Transaction(0, 0, 0, null, -1001); + transaction = new Ledger.Transaction(0, 0, 0, null, -1001, 500); agent.recordTransactionLocked(0, "com.test", ledger, transaction, false); assertEquals(-1, ledger.getCurrentBalance()); } diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java index b72fc23bc16c..ab29e5903f02 100644 --- a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java @@ -107,20 +107,26 @@ public class ScribeTest { @Test public void testWriteHighLevelStateToDisk() { long lastReclamationTime = System.currentTimeMillis(); - long narcsInCirculation = 2000L; + long remainingConsumableNarcs = 2000L; + long consumptionLimit = 500_000L; Ledger ledger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE); - ledger.recordTransaction(new Ledger.Transaction(0, 1000L, 1, null, 2000)); + ledger.recordTransaction(new Ledger.Transaction(0, 1000L, 1, null, 2000, 0)); // Negative ledger balance shouldn't affect the total circulation value. ledger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID + 1, TEST_PACKAGE); - ledger.recordTransaction(new Ledger.Transaction(0, 1000L, 1, null, -5000)); + ledger.recordTransaction(new Ledger.Transaction(0, 1000L, 1, null, -5000, 3000)); mScribeUnderTest.setLastReclamationTimeLocked(lastReclamationTime); + mScribeUnderTest.setConsumptionLimitLocked(consumptionLimit); + mScribeUnderTest.adjustRemainingConsumableNarcsLocked( + remainingConsumableNarcs - consumptionLimit); mScribeUnderTest.writeImmediatelyForTesting(); mScribeUnderTest.loadFromDiskLocked(); assertEquals(lastReclamationTime, mScribeUnderTest.getLastReclamationTimeLocked()); - assertEquals(narcsInCirculation, mScribeUnderTest.getNarcsInCirculationLocked()); + assertEquals(remainingConsumableNarcs, + mScribeUnderTest.getRemainingConsumableNarcsLocked()); + assertEquals(consumptionLimit, mScribeUnderTest.getSatiatedConsumptionLimitLocked()); } @Test @@ -135,9 +141,9 @@ public class ScribeTest { @Test public void testWritingPopulatedLedgerToDisk() { final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE); - ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51)); - ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52)); - ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3)); + ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51, 0)); + ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52, -1)); + ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3, 12)); mScribeUnderTest.writeImmediatelyForTesting(); mScribeUnderTest.loadFromDiskLocked(); @@ -156,11 +162,11 @@ public class ScribeTest { addInstalledPackage(userId, pkgName); final Ledger ledger = mScribeUnderTest.getLedgerLocked(userId, pkgName); ledger.recordTransaction(new Ledger.Transaction( - 0, 1000L * u + l, 1, null, 51L * u + l)); + 0, 1000L * u + l, 1, null, -51L * u + l, 50)); ledger.recordTransaction(new Ledger.Transaction( - 1500L * u + l, 2000L * u + l, 2 * u + l, "green" + u + l, 52L * u + l)); + 1500L * u + l, 2000L * u + l, 2 * u + l, "green" + u + l, 52L * u + l, 0)); ledger.recordTransaction(new Ledger.Transaction( - 2500L * u + l, 3000L * u + l, 3 * u + l, "blue" + u + l, 3L * u + l)); + 2500L * u + l, 3000L * u + l, 3 * u + l, "blue" + u + l, 3L * u + l, 0)); ledgers.add(userId, pkgName, ledger); } } @@ -174,9 +180,9 @@ public class ScribeTest { @Test public void testDiscardLedgerFromDisk() { final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, TEST_PACKAGE); - ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51)); - ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52)); - ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3)); + ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51, 1)); + ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52, 0)); + ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3, 1)); mScribeUnderTest.writeImmediatelyForTesting(); mScribeUnderTest.loadFromDiskLocked(); @@ -195,9 +201,9 @@ public class ScribeTest { public void testLoadingMissingPackageFromDisk() { final String pkgName = TEST_PACKAGE + ".uninstalled"; final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(TEST_USER_ID, pkgName); - ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51)); - ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52)); - ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3)); + ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51, 1)); + ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52, 2)); + ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3, 3)); mScribeUnderTest.writeImmediatelyForTesting(); // Package isn't installed, so make sure it's not saved to memory after loading. @@ -209,9 +215,9 @@ public class ScribeTest { public void testLoadingMissingUserFromDisk() { final int userId = TEST_USER_ID + 1; final Ledger ogLedger = mScribeUnderTest.getLedgerLocked(userId, TEST_PACKAGE); - ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51)); - ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52)); - ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3)); + ogLedger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 51, 0)); + ogLedger.recordTransaction(new Ledger.Transaction(1500, 2000, 2, "green", 52, 1)); + ogLedger.recordTransaction(new Ledger.Transaction(2500, 3000, 3, "blue", 3, 3)); mScribeUnderTest.writeImmediatelyForTesting(); // User doesn't show up with any packages, so make sure nothing is saved after loading. @@ -219,6 +225,37 @@ public class ScribeTest { assertLedgersEqual(new Ledger(), mScribeUnderTest.getLedgerLocked(userId, TEST_PACKAGE)); } + @Test + public void testChangingConsumable() { + assertEquals(0, mScribeUnderTest.getSatiatedConsumptionLimitLocked()); + assertEquals(0, mScribeUnderTest.getRemainingConsumableNarcsLocked()); + + // Limit increased, so remaining value should be adjusted as well + mScribeUnderTest.setConsumptionLimitLocked(1000); + assertEquals(1000, mScribeUnderTest.getSatiatedConsumptionLimitLocked()); + assertEquals(1000, mScribeUnderTest.getRemainingConsumableNarcsLocked()); + + // Limit decreased below remaining, so remaining value should be adjusted as well + mScribeUnderTest.setConsumptionLimitLocked(500); + assertEquals(500, mScribeUnderTest.getSatiatedConsumptionLimitLocked()); + assertEquals(500, mScribeUnderTest.getRemainingConsumableNarcsLocked()); + + mScribeUnderTest.adjustRemainingConsumableNarcsLocked(-100); + assertEquals(500, mScribeUnderTest.getSatiatedConsumptionLimitLocked()); + assertEquals(400, mScribeUnderTest.getRemainingConsumableNarcsLocked()); + + // Limit increased, so remaining value should be adjusted by the difference as well + mScribeUnderTest.setConsumptionLimitLocked(1000); + assertEquals(1000, mScribeUnderTest.getSatiatedConsumptionLimitLocked()); + assertEquals(900, mScribeUnderTest.getRemainingConsumableNarcsLocked()); + + + // Limit decreased, but above remaining, so remaining value should left alone + mScribeUnderTest.setConsumptionLimitLocked(950); + assertEquals(950, mScribeUnderTest.getSatiatedConsumptionLimitLocked()); + assertEquals(900, mScribeUnderTest.getRemainingConsumableNarcsLocked()); + } + private void assertLedgersEqual(Ledger expected, Ledger actual) { if (expected == null) { assertNull(actual); @@ -245,6 +282,7 @@ public class ScribeTest { assertEquals(expected.eventId, actual.eventId); assertEquals(expected.tag, actual.tag); assertEquals(expected.delta, actual.delta); + assertEquals(expected.ctp, actual.ctp); } private void addInstalledPackage(int userId, String pkgName) { diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java index a9b7cfb5e338..3ce2ed84d3e8 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java @@ -52,7 +52,6 @@ import android.view.accessibility.MagnificationAnimationCallback; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; -import com.android.server.accessibility.AccessibilityManagerService; import com.android.server.accessibility.AccessibilityTraceManager; import com.android.server.accessibility.test.MessageCapturingHandler; import com.android.server.wm.WindowManagerInternal; @@ -94,7 +93,6 @@ public class FullScreenMagnificationControllerTest { final FullScreenMagnificationController.ControllerContext mMockControllerCtx = mock(FullScreenMagnificationController.ControllerContext.class); final Context mMockContext = mock(Context.class); - final AccessibilityManagerService mMockAms = mock(AccessibilityManagerService.class); final AccessibilityTraceManager mMockTraceManager = mock(AccessibilityTraceManager.class); final WindowManagerInternal mMockWindowManager = mock(WindowManagerInternal.class); private final MagnificationAnimationCallback mAnimationCallback = mock( @@ -121,12 +119,10 @@ public class FullScreenMagnificationControllerTest { // Pretending ID of the Thread associated with looper as main thread ID in controller when(mMockContext.getMainLooper()).thenReturn(looper); when(mMockControllerCtx.getContext()).thenReturn(mMockContext); - when(mMockControllerCtx.getAms()).thenReturn(mMockAms); when(mMockControllerCtx.getTraceManager()).thenReturn(mMockTraceManager); when(mMockControllerCtx.getWindowManager()).thenReturn(mMockWindowManager); when(mMockControllerCtx.getHandler()).thenReturn(mMessageCapturingHandler); when(mMockControllerCtx.getAnimationDuration()).thenReturn(1000L); - when(mMockAms.getTraceManager()).thenReturn(mMockTraceManager); initMockWindowManager(); mFullScreenMagnificationController = new FullScreenMagnificationController( @@ -357,8 +353,8 @@ public class FullScreenMagnificationControllerTest { assertEquals(newCenter.x, mFullScreenMagnificationController.getCenterX(displayId), 0.5); assertEquals(newCenter.y, mFullScreenMagnificationController.getCenterY(displayId), 0.5); assertThat(getCurrentMagnificationSpec(displayId), closeTo(endSpec)); - verify(mMockAms).notifyMagnificationChanged(eq(displayId), eq(INITIAL_MAGNIFICATION_REGION), - mConfigCaptor.capture()); + verify(mRequestObserver).onFullScreenMagnificationChanged(eq(displayId), + eq(INITIAL_MAGNIFICATION_REGION), mConfigCaptor.capture()); assertConfigEquals(config, mConfigCaptor.getValue()); verify(mMockValueAnimator).start(); verify(mRequestObserver).onRequestMagnificationSpec(displayId, SERVICE_ID_1); @@ -501,7 +497,7 @@ public class FullScreenMagnificationControllerTest { mMessageCapturingHandler.sendAllMessages(); MagnificationConfig config = buildConfig(1.0f, OTHER_MAGNIFICATION_BOUNDS.centerX(), OTHER_MAGNIFICATION_BOUNDS.centerY()); - verify(mMockAms).notifyMagnificationChanged(eq(displayId), eq(OTHER_REGION), + verify(mRequestObserver).onFullScreenMagnificationChanged(eq(displayId), eq(OTHER_REGION), mConfigCaptor.capture()); assertConfigEquals(config, mConfigCaptor.getValue()); } @@ -655,9 +651,9 @@ public class FullScreenMagnificationControllerTest { register(displayId); zoomIn2xToMiddle(displayId); mMessageCapturingHandler.sendAllMessages(); - reset(mMockAms); + reset(mRequestObserver); assertTrue(mFullScreenMagnificationController.resetIfNeeded(displayId, false)); - verify(mMockAms).notifyMagnificationChanged(eq(displayId), + verify(mRequestObserver).onFullScreenMagnificationChanged(eq(displayId), eq(INITIAL_MAGNIFICATION_REGION), any(MagnificationConfig.class)); assertFalse(mFullScreenMagnificationController.isMagnifying(displayId)); assertFalse(mFullScreenMagnificationController.resetIfNeeded(displayId, false)); @@ -676,8 +672,8 @@ public class FullScreenMagnificationControllerTest { assertFalse(mFullScreenMagnificationController.reset(displayId, mAnimationCallback)); mMessageCapturingHandler.sendAllMessages(); - verify(mMockAms, never()).notifyMagnificationChanged(eq(displayId), any(Region.class), - any(MagnificationConfig.class)); + verify(mRequestObserver, never()).onFullScreenMagnificationChanged(eq(displayId), + any(Region.class), any(MagnificationConfig.class)); verify(mAnimationCallback).onResult(true); } @@ -1072,8 +1068,8 @@ public class FullScreenMagnificationControllerTest { when(mMockValueAnimator.getAnimatedFraction()).thenReturn(0.0f); mTargetAnimationListener.onAnimationUpdate(mMockValueAnimator); verify(mMockWindowManager).setMagnificationSpec(eq(displayId), eq(startSpec)); - verify(mMockAms).notifyMagnificationChanged(eq(displayId), eq(INITIAL_MAGNIFICATION_REGION), - mConfigCaptor.capture()); + verify(mRequestObserver).onFullScreenMagnificationChanged(eq(displayId), + eq(INITIAL_MAGNIFICATION_REGION), mConfigCaptor.capture()); assertConfigEquals(config, mConfigCaptor.getValue()); Mockito.reset(mMockWindowManager); @@ -1097,7 +1093,7 @@ public class FullScreenMagnificationControllerTest { // Animation should have been restarted verify(mMockValueAnimator, times(2)).start(); - verify(mMockAms, times(2)).notifyMagnificationChanged(eq(displayId), + verify(mRequestObserver, times(2)).onFullScreenMagnificationChanged(eq(displayId), eq(INITIAL_MAGNIFICATION_REGION), mConfigCaptor.capture()); assertConfigEquals(newConfig, mConfigCaptor.getValue()); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java index 2060223f6f98..0fed89b831d6 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java @@ -151,8 +151,6 @@ public class FullScreenMagnificationGestureHandlerTest { mock(FullScreenMagnificationController.ControllerContext.class); final WindowManagerInternal mockWindowManager = mock(WindowManagerInternal.class); when(mockController.getContext()).thenReturn(mContext); - when(mockController.getAms()).thenReturn(mMockAccessibilityManagerService); - when(mMockAccessibilityManagerService.getTraceManager()).thenReturn(mMockTraceManager); when(mockController.getTraceManager()).thenReturn(mMockTraceManager); when(mockController.getWindowManager()).thenReturn(mockWindowManager); when(mockController.getHandler()).thenReturn(new Handler(mContext.getMainLooper())); 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 3fcce92fe141..ec59090240f3 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 @@ -145,9 +145,10 @@ public class MagnificationControllerTest { mCallbackDelegate, mTraceManager, mScaleProvider)); mMockConnection = new MockWindowMagnificationConnection(true); mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); - new FullScreenMagnificationControllerStubber(mScreenMagnificationController); mMagnificationController = new MagnificationController(mService, globalLock, mContext, mScreenMagnificationController, mWindowMagnificationManager, mScaleProvider); + new FullScreenMagnificationControllerStubber(mScreenMagnificationController, + mMagnificationController); mMagnificationController.setMagnificationCapabilities( Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL); @@ -451,6 +452,29 @@ public class MagnificationControllerTest { } @Test + public void onFullScreenMagnificationChanged_fullScreenEnabled_notifyMagnificationChanged() + throws RemoteException { + setMagnificationEnabled(MODE_FULLSCREEN); + + final MagnificationConfig config = obtainMagnificationConfig(MODE_FULLSCREEN); + mScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY, + config.getScale(), config.getCenterX(), config.getCenterY(), + true, TEST_SERVICE_ID); + + // The first time is triggered when setting magnification enabled. And the second time is + // triggered when calling setScaleAndCenter. + final ArgumentCaptor<MagnificationConfig> configCaptor = ArgumentCaptor.forClass( + MagnificationConfig.class); + verify(mService, times(2)).notifyMagnificationChanged(eq(TEST_DISPLAY), + eq(FullScreenMagnificationControllerStubber.MAGNIFICATION_REGION), + configCaptor.capture()); + final MagnificationConfig actualConfig = configCaptor.getValue(); + assertEquals(config.getCenterX(), actualConfig.getCenterX(), 0); + assertEquals(config.getCenterY(), actualConfig.getCenterY(), 0); + assertEquals(config.getScale(), actualConfig.getScale(), 0); + } + + @Test public void onAccessibilityActionPerformed_magnifierEnabled_showMagnificationButton() throws RemoteException { setMagnificationEnabled(MODE_WINDOW); @@ -679,7 +703,7 @@ public class MagnificationControllerTest { throws RemoteException { setMagnificationEnabled(MODE_FULLSCREEN); mScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY, - /* scale= */1, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y, + /* scale= */ 1, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y, true, TEST_SERVICE_ID); mMagnificationController.onFullScreenMagnificationActivationState(TEST_DISPLAY, false); @@ -884,6 +908,8 @@ public class MagnificationControllerTest { private static class FullScreenMagnificationControllerStubber { private static final Region MAGNIFICATION_REGION = new Region(0, 0, 500, 600); private final FullScreenMagnificationController mScreenMagnificationController; + private final FullScreenMagnificationController.MagnificationInfoChangedCallback + mMagnificationChangedCallback; private boolean mIsMagnifying = false; private float mScale = 1.0f; private float mCenterX = MAGNIFICATION_REGION.getBounds().exactCenterX(); @@ -891,8 +917,10 @@ public class MagnificationControllerTest { private int mServiceId = -1; FullScreenMagnificationControllerStubber( - FullScreenMagnificationController screenMagnificationController) { + FullScreenMagnificationController screenMagnificationController, + FullScreenMagnificationController.MagnificationInfoChangedCallback callback) { mScreenMagnificationController = screenMagnificationController; + mMagnificationChangedCallback = callback; stubMethods(); } @@ -930,6 +958,14 @@ public class MagnificationControllerTest { } else { reset(); } + + + final MagnificationConfig config = new MagnificationConfig.Builder().setMode( + MODE_FULLSCREEN).setScale(mScale).setCenterX(mCenterX).setCenterY( + mCenterY).build(); + mMagnificationChangedCallback.onFullScreenMagnificationChanged(TEST_DISPLAY, + FullScreenMagnificationControllerStubber.MAGNIFICATION_REGION, + config); return true; }; doAnswer(setScaleAndCenterStubAnswer).when( diff --git a/services/tests/servicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java b/services/tests/servicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java new file mode 100644 index 000000000000..b154d6f6db0c --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2022 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.backup.transport; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.fail; + +import android.app.backup.BackupTransport; +import android.app.backup.RestoreDescription; +import android.app.backup.RestoreSet; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.backup.IBackupTransport; +import com.android.internal.backup.ITransportStatusCallback; +import com.android.internal.infra.AndroidFuture; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.List; +import java.util.concurrent.CancellationException; + +@Presubmit +@RunWith(AndroidJUnit4.class) +public class BackupTransportClientTest { + + private static class TestFuturesFakeTransportBinder extends FakeTransportBinderBase { + public final Object mLock = new Object(); + + public String mNameCompletedImmediately; + + @GuardedBy("mLock") + public AndroidFuture<String> mNameCompletedInFuture; + + @Override public void name(AndroidFuture<String> name) throws RemoteException { + name.complete(mNameCompletedImmediately); + } + @Override public void transportDirName(AndroidFuture<String> name) throws RemoteException { + synchronized (mLock) { + mNameCompletedInFuture = name; + mLock.notifyAll(); + } + } + } + + @Test + public void testName_completesImmediately_returnsName() throws Exception { + TestFuturesFakeTransportBinder binder = new TestFuturesFakeTransportBinder(); + binder.mNameCompletedImmediately = "fake name"; + + BackupTransportClient client = new BackupTransportClient(binder); + String name = client.name(); + + assertThat(name).isEqualTo("fake name"); + } + + @Test + public void testTransportDirName_completesLater_returnsName() throws Exception { + TestFuturesFakeTransportBinder binder = new TestFuturesFakeTransportBinder(); + BackupTransportClient client = new BackupTransportClient(binder); + + Thread thread = new Thread(() -> { + try { + String name = client.transportDirName(); + assertThat(name).isEqualTo("fake name"); + } catch (Exception ex) { + fail("unexpected Exception: " + ex.getClass().getCanonicalName()); + } + }); + + thread.start(); + + synchronized (binder.mLock) { + while (binder.mNameCompletedInFuture == null) { + binder.mLock.wait(); + } + assertThat(binder.mNameCompletedInFuture.complete("fake name")).isTrue(); + } + + thread.join(); + } + + @Test + public void testTransportDirName_canceledBeforeCompletion_throwsException() throws Exception { + TestFuturesFakeTransportBinder binder = new TestFuturesFakeTransportBinder(); + BackupTransportClient client = new BackupTransportClient(binder); + + Thread thread = new Thread(() -> { + try { + /*String name =*/ client.transportDirName(); + fail("transportDirName should be cancelled"); + } catch (CancellationException ex) { + // This is expected. + } catch (Exception ex) { + fail("unexpected Exception: " + ex.getClass().getCanonicalName()); + } + }); + + thread.start(); + + synchronized (binder.mLock) { + while (binder.mNameCompletedInFuture == null) { + binder.mLock.wait(); + } + client.onBecomingUnusable(); + } + + thread.join(); + } + + private static class TestCallbacksFakeTransportBinder extends FakeTransportBinderBase { + public final Object mLock = new Object(); + + public int mStatusCompletedImmediately; + + @GuardedBy("mLock") + public ITransportStatusCallback mStatusCompletedInFuture; + + @Override public void initializeDevice(ITransportStatusCallback c) throws RemoteException { + c.onOperationCompleteWithStatus(mStatusCompletedImmediately); + } + @Override public void finishBackup(ITransportStatusCallback c) throws RemoteException { + synchronized (mLock) { + mStatusCompletedInFuture = c; + mLock.notifyAll(); + } + } + } + + @Test + public void testInitializeDevice_completesImmediately_returnsStatus() throws Exception { + TestCallbacksFakeTransportBinder binder = new TestCallbacksFakeTransportBinder(); + binder.mStatusCompletedImmediately = 123; + + BackupTransportClient client = new BackupTransportClient(binder); + int status = client.initializeDevice(); + + assertThat(status).isEqualTo(123); + } + + + @Test + public void testFinishBackup_completesLater_returnsStatus() throws Exception { + TestCallbacksFakeTransportBinder binder = new TestCallbacksFakeTransportBinder(); + BackupTransportClient client = new BackupTransportClient(binder); + + Thread thread = new Thread(() -> { + try { + int status = client.finishBackup(); + assertThat(status).isEqualTo(456); + } catch (Exception ex) { + fail("unexpected Exception: " + ex.getClass().getCanonicalName()); + } + }); + + thread.start(); + + synchronized (binder.mLock) { + while (binder.mStatusCompletedInFuture == null) { + binder.mLock.wait(); + } + binder.mStatusCompletedInFuture.onOperationCompleteWithStatus(456); + } + + thread.join(); + } + + @Test + public void testFinishBackup_canceledBeforeCompletion_throwsException() throws Exception { + TestCallbacksFakeTransportBinder binder = new TestCallbacksFakeTransportBinder(); + BackupTransportClient client = new BackupTransportClient(binder); + + Thread thread = new Thread(() -> { + try { + int status = client.finishBackup(); + assertThat(status).isEqualTo(BackupTransport.TRANSPORT_ERROR); + } catch (Exception ex) { + fail("unexpected Exception: " + ex.getClass().getCanonicalName()); + } + }); + + thread.start(); + + synchronized (binder.mLock) { + while (binder.mStatusCompletedInFuture == null) { + binder.mLock.wait(); + } + client.onBecomingUnusable(); + } + + thread.join(); + } + + // Convenience layer so we only need to fake specific methods useful for each test case. + private static class FakeTransportBinderBase implements IBackupTransport { + @Override public void name(AndroidFuture<String> f) throws RemoteException {} + @Override public void transportDirName(AndroidFuture<String> f) throws RemoteException {} + @Override public void configurationIntent(AndroidFuture<Intent> f) throws RemoteException {} + @Override public void currentDestinationString(AndroidFuture<String> f) + throws RemoteException {} + @Override public void dataManagementIntent(AndroidFuture<Intent> f) + throws RemoteException {} + @Override public void dataManagementIntentLabel(AndroidFuture<CharSequence> f) + throws RemoteException {} + @Override public void initializeDevice(ITransportStatusCallback c) throws RemoteException {} + @Override public void clearBackupData(PackageInfo i, ITransportStatusCallback c) + throws RemoteException {} + @Override public void finishBackup(ITransportStatusCallback c) throws RemoteException {} + @Override public void requestBackupTime(AndroidFuture<Long> f) throws RemoteException {} + @Override public void performBackup(PackageInfo i, ParcelFileDescriptor fd, int f, + ITransportStatusCallback c) throws RemoteException {} + @Override public void getAvailableRestoreSets(AndroidFuture<List<RestoreSet>> f) + throws RemoteException {} + @Override public void getCurrentRestoreSet(AndroidFuture<Long> f) throws RemoteException {} + @Override public void startRestore(long t, PackageInfo[] p, ITransportStatusCallback c) + throws RemoteException {} + @Override public void nextRestorePackage(AndroidFuture<RestoreDescription> f) + throws RemoteException {} + @Override public void getRestoreData(ParcelFileDescriptor fd, ITransportStatusCallback c) + throws RemoteException {} + @Override public void finishRestore(ITransportStatusCallback c) throws RemoteException {} + @Override public void requestFullBackupTime(AndroidFuture<Long> f) throws RemoteException {} + @Override public void performFullBackup(PackageInfo i, ParcelFileDescriptor fd, int f, + ITransportStatusCallback c) throws RemoteException {} + @Override public void checkFullBackupSize(long s, ITransportStatusCallback c) + throws RemoteException {} + @Override public void sendBackupData(int n, ITransportStatusCallback c) + throws RemoteException {} + @Override public void cancelFullBackup(ITransportStatusCallback c) throws RemoteException {} + @Override public void isAppEligibleForBackup(PackageInfo p, boolean b, + AndroidFuture<Boolean> f) throws RemoteException {} + @Override public void getBackupQuota(String s, boolean b, AndroidFuture<Long> f) + throws RemoteException {} + @Override public void getNextFullRestoreDataChunk(ParcelFileDescriptor fd, + ITransportStatusCallback c) throws RemoteException {} + @Override public void abortFullRestore(ITransportStatusCallback c) throws RemoteException {} + @Override public void getTransportFlags(AndroidFuture<Integer> f) throws RemoteException {} + @Override public IBinder asBinder() { + return null; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index fe110e5398ca..2398e367ebfe 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -3575,11 +3575,11 @@ public class DevicePolicyManagerTest extends DpmTestBase { setup_DeviceAdminFeatureOff(); mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, - DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED); + DevicePolicyManager.STATUS_DEVICE_ADMIN_NOT_SUPPORTED); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, - DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED); + DevicePolicyManager.STATUS_DEVICE_ADMIN_NOT_SUPPORTED); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, - DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED); + DevicePolicyManager.STATUS_DEVICE_ADMIN_NOT_SUPPORTED); } private void setup_ManagedProfileFeatureOff() throws Exception { @@ -3611,11 +3611,11 @@ public class DevicePolicyManagerTest extends DpmTestBase { setup_ManagedProfileFeatureOff(); mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, - DevicePolicyManager.CODE_OK); + DevicePolicyManager.STATUS_OK); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, - DevicePolicyManager.CODE_OK); + DevicePolicyManager.STATUS_OK); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, - DevicePolicyManager.CODE_MANAGED_USERS_NOT_SUPPORTED); + DevicePolicyManager.STATUS_MANAGED_USERS_NOT_SUPPORTED); } private void setup_firstBoot_systemUser() throws Exception { @@ -3653,15 +3653,15 @@ public class DevicePolicyManagerTest extends DpmTestBase { when(getServices().userManagerForMock.isHeadlessSystemUserMode()).thenReturn(false); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, - DevicePolicyManager.CODE_OK); + DevicePolicyManager.STATUS_OK); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, - DevicePolicyManager.CODE_OK); + DevicePolicyManager.STATUS_OK); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, - DevicePolicyManager.CODE_OK); + DevicePolicyManager.STATUS_OK); when(getServices().userManagerForMock.isHeadlessSystemUserMode()).thenReturn(true); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, - DevicePolicyManager.CODE_OK); + DevicePolicyManager.STATUS_OK); } private void setup_systemUserSetupComplete_systemUser() throws Exception { @@ -3708,11 +3708,11 @@ public class DevicePolicyManagerTest extends DpmTestBase { setup_systemUserSetupComplete_systemUser(); mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, - DevicePolicyManager.CODE_USER_SETUP_COMPLETED); + DevicePolicyManager.STATUS_USER_SETUP_COMPLETED); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, - DevicePolicyManager.CODE_USER_SETUP_COMPLETED); + DevicePolicyManager.STATUS_USER_SETUP_COMPLETED); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, - DevicePolicyManager.CODE_OK); + DevicePolicyManager.STATUS_OK); } @Test @@ -3722,22 +3722,22 @@ public class DevicePolicyManagerTest extends DpmTestBase { mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, - DevicePolicyManager.CODE_HAS_DEVICE_OWNER); + DevicePolicyManager.STATUS_HAS_DEVICE_OWNER); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, - DevicePolicyManager.CODE_HAS_DEVICE_OWNER); + DevicePolicyManager.STATUS_HAS_DEVICE_OWNER); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, false); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, false); // COMP mode NOT is allowed. assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, - DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE); + DevicePolicyManager.STATUS_CANNOT_ADD_MANAGED_PROFILE); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); // And other DPCs can NOT provision a managed profile. assertCheckProvisioningPreCondition( DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, DpmMockContext.ANOTHER_PACKAGE_NAME, - DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE); + DevicePolicyManager.STATUS_CANNOT_ADD_MANAGED_PROFILE); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false, DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID); } @@ -3759,13 +3759,13 @@ public class DevicePolicyManagerTest extends DpmTestBase { eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid)))) .thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, - DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE); + DevicePolicyManager.STATUS_CANNOT_ADD_MANAGED_PROFILE); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); assertCheckProvisioningPreCondition( DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, DpmMockContext.ANOTHER_PACKAGE_NAME, - DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE); + DevicePolicyManager.STATUS_CANNOT_ADD_MANAGED_PROFILE); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false, DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID); } @@ -3794,12 +3794,12 @@ public class DevicePolicyManagerTest extends DpmTestBase { // We can delete the managed profile to create a new one, so provisioning is allowed. assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, - DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE); + DevicePolicyManager.STATUS_CANNOT_ADD_MANAGED_PROFILE); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); assertCheckProvisioningPreCondition( DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, DpmMockContext.ANOTHER_PACKAGE_NAME, - DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE); + DevicePolicyManager.STATUS_CANNOT_ADD_MANAGED_PROFILE); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false, DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID); } @@ -3823,13 +3823,13 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertCheckProvisioningPreCondition( DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, DpmMockContext.ANOTHER_PACKAGE_NAME, - DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE); + DevicePolicyManager.STATUS_CANNOT_ADD_MANAGED_PROFILE); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false, DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID); // But the device owner can still do it because it has set the restriction itself. assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, - DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE); + DevicePolicyManager.STATUS_CANNOT_ADD_MANAGED_PROFILE); assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false); } @@ -3901,7 +3901,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { // COMP mode is NOT allowed. assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, - DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE); + DevicePolicyManager.STATUS_CANNOT_ADD_MANAGED_PROFILE); } private void setup_provisionManagedProfileOneAlreadyExist_primaryUser() throws Exception { @@ -3935,14 +3935,14 @@ public class DevicePolicyManagerTest extends DpmTestBase { setup_provisionManagedProfileOneAlreadyExist_primaryUser(); mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, - DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE); + DevicePolicyManager.STATUS_CANNOT_ADD_MANAGED_PROFILE); } @Test public void testCheckProvisioningPreCondition_permission() { // GIVEN the permission MANAGE_PROFILE_AND_DEVICE_OWNERS is not granted assertExpectException(SecurityException.class, /* messageRegex =*/ null, - () -> dpm.checkProvisioningPreCondition( + () -> dpm.checkProvisioningPrecondition( DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, "some.package")); } @@ -8696,7 +8696,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { private void assertCheckProvisioningPreCondition( String action, String packageName, int provisioningCondition) { assertWithMessage("checkProvisioningPreCondition(%s, %s) returning unexpected result", - action, packageName).that(dpm.checkProvisioningPreCondition(action, packageName)) + action, packageName).that(dpm.checkProvisioningPrecondition(action, packageName)) .isEqualTo(provisioningCondition); } diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java index 6789af4a13d8..2b34bc2ef28d 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -35,6 +35,7 @@ import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.annotation.NonNull; @@ -109,6 +110,8 @@ public class PackageManagerSettingsTests { LegacyPermissionDataProvider mPermissionDataProvider; @Mock DomainVerificationManagerInternal mDomainVerificationManager; + @Mock + Computer computer; final ArrayMap<String, Long> mOrigFirstInstallTimes = new ArrayMap<>(); @@ -132,7 +135,7 @@ public class PackageManagerSettingsTests { /* write out files and read */ writeOldFiles(); Settings settings = makeSettings(); - assertThat(settings.readLPw(createFakeUsers()), is(true)); + assertThat(settings.readLPw(computer, createFakeUsers()), is(true)); verifyKeySetMetaData(settings); } @@ -143,11 +146,11 @@ public class PackageManagerSettingsTests { // write out files and read writeOldFiles(); Settings settings = makeSettings(); - assertThat(settings.readLPw(createFakeUsers()), is(true)); + assertThat(settings.readLPw(computer, createFakeUsers()), is(true)); // write out, read back in and verify the same - settings.writeLPr(); - assertThat(settings.readLPw(createFakeUsers()), is(true)); + settings.writeLPr(computer); + assertThat(settings.readLPw(computer, createFakeUsers()), is(true)); verifyKeySetMetaData(settings); } @@ -156,7 +159,7 @@ public class PackageManagerSettingsTests { // Write delegateshellthe package files and make sure they're parsed properly the first time writeOldFiles(); Settings settings = makeSettings(); - assertThat(settings.readLPw(createFakeUsers()), is(true)); + assertThat(settings.readLPw(computer, createFakeUsers()), is(true)); assertThat(settings.getPackageLPr(PACKAGE_NAME_3), is(notNullValue())); assertThat(settings.getPackageLPr(PACKAGE_NAME_1), is(notNullValue())); @@ -175,12 +178,12 @@ public class PackageManagerSettingsTests { // Write the package files and make sure they're parsed properly the first time writeOldFiles(); Settings settings = makeSettings(); - assertThat(settings.readLPw(createFakeUsers()), is(true)); - settings.writeLPr(); + assertThat(settings.readLPw(computer, createFakeUsers()), is(true)); + settings.writeLPr(computer); // Create Settings again to make it read from the new files settings = makeSettings(); - assertThat(settings.readLPw(createFakeUsers()), is(true)); + assertThat(settings.readLPw(computer, createFakeUsers()), is(true)); PackageSetting ps = settings.getPackageLPr(PACKAGE_NAME_2); assertThat(ps.getEnabled(0), is(COMPONENT_ENABLED_STATE_DISABLED_USER)); @@ -469,12 +472,12 @@ public class PackageManagerSettingsTests { ps2.setUsesStaticLibrariesVersions(new long[] { 34 }); settingsUnderTest.mPackages.put(PACKAGE_NAME_2, ps2); - settingsUnderTest.writeLPr(); + settingsUnderTest.writeLPr(computer); settingsUnderTest.mPackages.clear(); settingsUnderTest.mDisabledSysPackages.clear(); - assertThat(settingsUnderTest.readLPw(createFakeUsers()), is(true)); + assertThat(settingsUnderTest.readLPw(computer, createFakeUsers()), is(true)); PackageSetting readPs1 = settingsUnderTest.getPackageLPr(PACKAGE_NAME_1); PackageSetting readPs2 = settingsUnderTest.getPackageLPr(PACKAGE_NAME_2); @@ -534,12 +537,12 @@ public class PackageManagerSettingsTests { ps2.setUsesSdkLibrariesVersionsMajor(new long[] { 34 }); settingsUnderTest.mPackages.put(PACKAGE_NAME_2, ps2); - settingsUnderTest.writeLPr(); + settingsUnderTest.writeLPr(computer); settingsUnderTest.mPackages.clear(); settingsUnderTest.mDisabledSysPackages.clear(); - assertThat(settingsUnderTest.readLPw(createFakeUsers()), is(true)); + assertThat(settingsUnderTest.readLPw(computer, createFakeUsers()), is(true)); PackageSetting readPs1 = settingsUnderTest.getPackageLPr(PACKAGE_NAME_1); PackageSetting readPs2 = settingsUnderTest.getPackageLPr(PACKAGE_NAME_2); @@ -587,7 +590,7 @@ public class PackageManagerSettingsTests { Settings settings = makeSettings(); final WatchableTester watcher = new WatchableTester(settings, "testEnableDisable"); watcher.register(); - assertThat(settings.readLPw(createFakeUsers()), is(true)); + assertThat(settings.readLPw(computer, createFakeUsers()), is(true)); watcher.verifyChangeReported("readLPw"); // Enable/Disable a package diff --git a/services/tests/servicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java b/services/tests/servicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java index b1b53fc7ca4c..9e986be99e95 100644 --- a/services/tests/servicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java +++ b/services/tests/servicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java @@ -65,7 +65,12 @@ public class AgentTrendCalculatorTest { } @Override - long getMaxSatiatedCirculation() { + long getInitialSatiatedConsumptionLimit() { + return 0; + } + + @Override + long getHardSatiatedConsumptionLimit() { return 0; } @@ -102,7 +107,7 @@ public class AgentTrendCalculatorTest { TrendCalculator trendCalculator = new TrendCalculator(); mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_TIMEOUT, 20); - trendCalculator.reset(0, null); + trendCalculator.reset(0, 0, null); assertEquals("Expected not to cross lower threshold", TrendCalculator.WILL_NOT_CROSS_THRESHOLD, trendCalculator.getTimeToCrossLowerThresholdMs()); @@ -115,10 +120,10 @@ public class AgentTrendCalculatorTest { new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_TIMEOUT, 1, 0))), mock(AffordabilityChangeListener.class), mEconomicPolicy)); for (ActionAffordabilityNote note : affordabilityNotes) { - note.recalculateModifiedPrice(mEconomicPolicy, 0, "com.test.app"); + note.recalculateCosts(mEconomicPolicy, 0, "com.test.app"); } - trendCalculator.reset(1234, affordabilityNotes); + trendCalculator.reset(1234, 1234, affordabilityNotes); assertEquals("Expected not to cross lower threshold", TrendCalculator.WILL_NOT_CROSS_THRESHOLD, trendCalculator.getTimeToCrossLowerThresholdMs()); @@ -133,32 +138,28 @@ public class AgentTrendCalculatorTest { OngoingEvent[] events = new OngoingEvent[]{ new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1", - null, 1, 1), + 1, new EconomicPolicy.Cost(1, 4)), new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING, "2", - null, 2, 3), - new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "3", - null, 3, -3), + 2, new EconomicPolicy.Cost(3, 6)), + new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "3", 3, + new EconomicPolicy.Reward(EconomicPolicy.REWARD_TOP_ACTIVITY, 0, 3, 3)), }; - trendCalculator.reset(0, null); + trendCalculator.reset(0, 100, null); for (OngoingEvent event : events) { trendCalculator.accept(event); } - assertEquals("Expected not to cross lower threshold", - TrendCalculator.WILL_NOT_CROSS_THRESHOLD, - trendCalculator.getTimeToCrossLowerThresholdMs()); + assertEquals(25_000, trendCalculator.getTimeToCrossLowerThresholdMs()); assertEquals("Expected not to cross upper threshold", TrendCalculator.WILL_NOT_CROSS_THRESHOLD, trendCalculator.getTimeToCrossUpperThresholdMs()); ArraySet<ActionAffordabilityNote> affordabilityNotes = new ArraySet<>(); - trendCalculator.reset(1234, affordabilityNotes); + trendCalculator.reset(1234, 1234, affordabilityNotes); for (OngoingEvent event : events) { trendCalculator.accept(event); } - assertEquals("Expected not to cross lower threshold", - TrendCalculator.WILL_NOT_CROSS_THRESHOLD, - trendCalculator.getTimeToCrossLowerThresholdMs()); + assertEquals(308_000, trendCalculator.getTimeToCrossLowerThresholdMs()); assertEquals("Expected not to cross upper threshold", TrendCalculator.WILL_NOT_CROSS_THRESHOLD, trendCalculator.getTimeToCrossUpperThresholdMs()); @@ -174,18 +175,19 @@ public class AgentTrendCalculatorTest { new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_RUNNING, 0, 1000))), mock(AffordabilityChangeListener.class), mEconomicPolicy)); for (ActionAffordabilityNote note : affordabilityNotes) { - note.recalculateModifiedPrice(mEconomicPolicy, 0, "com.test.app"); + note.recalculateCosts(mEconomicPolicy, 0, "com.test.app"); } // Balance is already above threshold and events are all positive delta. // There should be no time to report. - trendCalculator.reset(1234, affordabilityNotes); + trendCalculator.reset(1234, 1234, affordabilityNotes); trendCalculator.accept( - new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1", - null, 1, 1)); + new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1", 1, + new EconomicPolicy.Reward(EconomicPolicy.REWARD_TOP_ACTIVITY, 1, 1, 1))); trendCalculator.accept( - new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING, "2", - null, 2, 3)); + new OngoingEvent(EconomicPolicy.REWARD_OTHER_USER_INTERACTION, "2", 2, + new EconomicPolicy.Reward(EconomicPolicy.REWARD_OTHER_USER_INTERACTION, + 3, 3, 3))); assertEquals("Expected not to cross lower threshold", TrendCalculator.WILL_NOT_CROSS_THRESHOLD, @@ -196,16 +198,16 @@ public class AgentTrendCalculatorTest { // Balance is already below threshold and events are all negative delta. // There should be no time to report. - trendCalculator.reset(1, affordabilityNotes); + trendCalculator.reset(1, 0, affordabilityNotes); trendCalculator.accept( new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1", - null, 1, -1)); + 1, new EconomicPolicy.Cost(1, 1))); trendCalculator.accept( new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING, "2", - null, 2, -3)); + 2, new EconomicPolicy.Cost(3, 3))); assertEquals("Expected not to cross lower threshold", - TrendCalculator.WILL_NOT_CROSS_THRESHOLD, + 0, trendCalculator.getTimeToCrossLowerThresholdMs()); assertEquals("Expected not to cross upper threshold", TrendCalculator.WILL_NOT_CROSS_THRESHOLD, @@ -213,7 +215,7 @@ public class AgentTrendCalculatorTest { } @Test - public void testSimpleTrendToThreshold() { + public void testSimpleTrendToThreshold_Balance() { TrendCalculator trendCalculator = new TrendCalculator(); mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 20); @@ -222,18 +224,19 @@ public class AgentTrendCalculatorTest { new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 1, 0))), mock(AffordabilityChangeListener.class), mEconomicPolicy)); for (ActionAffordabilityNote note : affordabilityNotes) { - note.recalculateModifiedPrice(mEconomicPolicy, 0, "com.test.app"); + note.recalculateCosts(mEconomicPolicy, 0, "com.test.app"); } // Balance is below threshold and events are all positive delta. // Should report the correct time to the upper threshold. - trendCalculator.reset(0, affordabilityNotes); + trendCalculator.reset(0, 1000, affordabilityNotes); trendCalculator.accept( - new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1", - null, 1, 1)); + new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1", 1, + new EconomicPolicy.Reward(EconomicPolicy.REWARD_TOP_ACTIVITY, 1, 1, 1))); trendCalculator.accept( - new OngoingEvent(EconomicPolicy.REWARD_OTHER_USER_INTERACTION, "2", - null, 2, 3)); + new OngoingEvent(EconomicPolicy.REWARD_OTHER_USER_INTERACTION, "2", 2, + new EconomicPolicy.Reward(EconomicPolicy.REWARD_OTHER_USER_INTERACTION, + 3, 3, 3))); assertEquals("Expected not to cross lower threshold", TrendCalculator.WILL_NOT_CROSS_THRESHOLD, @@ -242,13 +245,13 @@ public class AgentTrendCalculatorTest { // Balance is above the threshold and events are all negative delta. // Should report the correct time to the lower threshold. - trendCalculator.reset(40, affordabilityNotes); + trendCalculator.reset(40, 100, affordabilityNotes); trendCalculator.accept( new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1", - null, 1, -1)); + 1, new EconomicPolicy.Cost(1, 1))); trendCalculator.accept( new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING, "2", - null, 2, -3)); + 2, new EconomicPolicy.Cost(3, 3))); assertEquals(5_000, trendCalculator.getTimeToCrossLowerThresholdMs()); assertEquals("Expected not to cross upper threshold", @@ -257,7 +260,7 @@ public class AgentTrendCalculatorTest { } @Test - public void testSelectCorrectThreshold() { + public void testSelectCorrectThreshold_Balance() { TrendCalculator trendCalculator = new TrendCalculator(); mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 20); mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_START, 15); @@ -278,15 +281,15 @@ public class AgentTrendCalculatorTest { new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MIN_START, 1, 0))), mock(AffordabilityChangeListener.class), mEconomicPolicy)); for (ActionAffordabilityNote note : affordabilityNotes) { - note.recalculateModifiedPrice(mEconomicPolicy, 0, "com.test.app"); + note.recalculateCosts(mEconomicPolicy, 0, "com.test.app"); } // Balance is below threshold and events are all positive delta. // Should report the correct time to the correct upper threshold. - trendCalculator.reset(0, affordabilityNotes); + trendCalculator.reset(0, 10_000, affordabilityNotes); trendCalculator.accept( - new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1", - null, 1, 1)); + new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1", 1, + new EconomicPolicy.Reward(EconomicPolicy.REWARD_TOP_ACTIVITY, 1, 1, 1))); assertEquals("Expected not to cross lower threshold", TrendCalculator.WILL_NOT_CROSS_THRESHOLD, @@ -295,10 +298,10 @@ public class AgentTrendCalculatorTest { // Balance is above the threshold and events are all negative delta. // Should report the correct time to the correct lower threshold. - trendCalculator.reset(30, affordabilityNotes); + trendCalculator.reset(30, 500, affordabilityNotes); trendCalculator.accept( new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1", - null, 1, -1)); + 1, new EconomicPolicy.Cost(1, 1))); assertEquals(10_000, trendCalculator.getTimeToCrossLowerThresholdMs()); assertEquals("Expected not to cross upper threshold", @@ -307,7 +310,7 @@ public class AgentTrendCalculatorTest { } @Test - public void testTrendsToBothThresholds() { + public void testTrendsToBothThresholds_Balance() { TrendCalculator trendCalculator = new TrendCalculator(); mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 20); mEconomicPolicy.mEventCosts.put(AlarmManagerEconomicPolicy.ACTION_ALARM_CLOCK, 50); @@ -320,26 +323,120 @@ public class AgentTrendCalculatorTest { new AnticipatedAction(AlarmManagerEconomicPolicy.ACTION_ALARM_CLOCK, 1, 0))), mock(AffordabilityChangeListener.class), mEconomicPolicy)); for (ActionAffordabilityNote note : affordabilityNotes) { - note.recalculateModifiedPrice(mEconomicPolicy, 0, "com.test.app"); + note.recalculateCosts(mEconomicPolicy, 0, "com.test.app"); } // Balance is between both thresholds and events are mixed positive/negative delta. // Should report the correct time to each threshold. - trendCalculator.reset(35, affordabilityNotes); + trendCalculator.reset(35, 10_000, affordabilityNotes); trendCalculator.accept( - new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1", - null, 1, 3)); + new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1", 1, + new EconomicPolicy.Reward(EconomicPolicy.REWARD_TOP_ACTIVITY, 3, 3, 3))); trendCalculator.accept( - new OngoingEvent(EconomicPolicy.REWARD_OTHER_USER_INTERACTION, "2", - null, 2, 2)); + new OngoingEvent(EconomicPolicy.REWARD_OTHER_USER_INTERACTION, "2", 2, + new EconomicPolicy.Reward(EconomicPolicy.REWARD_OTHER_USER_INTERACTION, 2, + 2, 2))); trendCalculator.accept( new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_LOW_RUNNING, "3", - null, 3, -2)); + 3, new EconomicPolicy.Cost(2, 2))); trendCalculator.accept( new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "4", - null, 4, -3)); + 4, new EconomicPolicy.Cost(3, 3))); assertEquals(3_000, trendCalculator.getTimeToCrossLowerThresholdMs()); assertEquals(3_000, trendCalculator.getTimeToCrossUpperThresholdMs()); } + + @Test + public void testSimpleTrendToThreshold_ConsumptionLimit() { + TrendCalculator trendCalculator = new TrendCalculator(); + mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 20); + + ArraySet<ActionAffordabilityNote> affordabilityNotes = new ArraySet<>(); + affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of( + new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 1, 0))), + mock(AffordabilityChangeListener.class), mEconomicPolicy)); + for (ActionAffordabilityNote note : affordabilityNotes) { + note.recalculateCosts(mEconomicPolicy, 0, "com.test.app"); + } + + // Events are all negative delta. Consumable credits will run out before app's balance. + // Should report the correct time to the lower threshold. + trendCalculator.reset(10000, 40, affordabilityNotes); + trendCalculator.accept( + new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1", + 1, new EconomicPolicy.Cost(1, 10))); + trendCalculator.accept( + new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING, "2", + 2, new EconomicPolicy.Cost(3, 40))); + + assertEquals(10_000, trendCalculator.getTimeToCrossLowerThresholdMs()); + assertEquals("Expected not to cross upper threshold", + TrendCalculator.WILL_NOT_CROSS_THRESHOLD, + trendCalculator.getTimeToCrossUpperThresholdMs()); + } + + @Test + public void testSelectCorrectThreshold() { + TrendCalculator trendCalculator = new TrendCalculator(); + mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 20); + mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_START, 15); + mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_LOW_START, 10); + mEconomicPolicy.mEventCosts.put(JobSchedulerEconomicPolicy.ACTION_JOB_MIN_START, 5); + + ArraySet<ActionAffordabilityNote> affordabilityNotes = new ArraySet<>(); + affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of( + new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 1, 0))), + mock(AffordabilityChangeListener.class), mEconomicPolicy)); + affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of( + new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_START, 1, 0))), + mock(AffordabilityChangeListener.class), mEconomicPolicy)); + affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of( + new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_LOW_START, 1, 0))), + mock(AffordabilityChangeListener.class), mEconomicPolicy)); + affordabilityNotes.add(new ActionAffordabilityNote(new ActionBill(List.of( + new AnticipatedAction(JobSchedulerEconomicPolicy.ACTION_JOB_MIN_START, 1, 0))), + mock(AffordabilityChangeListener.class), mEconomicPolicy)); + for (ActionAffordabilityNote note : affordabilityNotes) { + note.recalculateCosts(mEconomicPolicy, 0, "com.test.app"); + } + + // Balance is above threshold, consumable credits is 0, and events are all positive delta. + // There should be no time to the upper threshold since consumable credits is the limiting + // factor. + trendCalculator.reset(10_000, 0, affordabilityNotes); + trendCalculator.accept( + new OngoingEvent(EconomicPolicy.REWARD_TOP_ACTIVITY, "1", 1, + new EconomicPolicy.Reward(EconomicPolicy.REWARD_TOP_ACTIVITY, 1, 1, 1))); + + assertEquals("Expected not to cross lower threshold", + TrendCalculator.WILL_NOT_CROSS_THRESHOLD, + trendCalculator.getTimeToCrossLowerThresholdMs()); + assertEquals("Expected not to cross upper threshold", + TrendCalculator.WILL_NOT_CROSS_THRESHOLD, + trendCalculator.getTimeToCrossUpperThresholdMs()); + + // Balance is above threshold, consumable credits is low, and events are all negative delta. + trendCalculator.reset(10_000, 4, affordabilityNotes); + trendCalculator.accept( + new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1", + 1, new EconomicPolicy.Cost(1, 10))); + + assertEquals(4000, trendCalculator.getTimeToCrossLowerThresholdMs()); + assertEquals("Expected not to cross upper threshold", + TrendCalculator.WILL_NOT_CROSS_THRESHOLD, + trendCalculator.getTimeToCrossUpperThresholdMs()); + + // Balance is above threshold, consumable credits is 0, and events are all negative delta. + // Time to the lower threshold should be 0 since consumable credits is already 0. + trendCalculator.reset(10_000, 0, affordabilityNotes); + trendCalculator.accept( + new OngoingEvent(JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, "1", + 1, new EconomicPolicy.Cost(1, 10))); + + assertEquals(0, trendCalculator.getTimeToCrossLowerThresholdMs()); + assertEquals("Expected not to cross upper threshold", + TrendCalculator.WILL_NOT_CROSS_THRESHOLD, + trendCalculator.getTimeToCrossUpperThresholdMs()); + } } diff --git a/services/tests/servicestests/src/com/android/server/tare/LedgerTest.java b/services/tests/servicestests/src/com/android/server/tare/LedgerTest.java index 4a253234b59e..22dcf842906c 100644 --- a/services/tests/servicestests/src/com/android/server/tare/LedgerTest.java +++ b/services/tests/servicestests/src/com/android/server/tare/LedgerTest.java @@ -54,13 +54,13 @@ public class LedgerTest { @Test public void testMultipleTransactions() { final Ledger ledger = new Ledger(); - ledger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 5)); + ledger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 5, 0)); assertEquals(5, ledger.getCurrentBalance()); assertEquals(5, ledger.get24HourSum(1, 60_000)); - ledger.recordTransaction(new Ledger.Transaction(2000, 2000, 1, null, 25)); + ledger.recordTransaction(new Ledger.Transaction(2000, 2000, 1, null, 25, 0)); assertEquals(30, ledger.getCurrentBalance()); assertEquals(30, ledger.get24HourSum(1, 60_000)); - ledger.recordTransaction(new Ledger.Transaction(5000, 5500, 1, null, -10)); + ledger.recordTransaction(new Ledger.Transaction(5000, 5500, 1, null, -10, 5)); assertEquals(20, ledger.getCurrentBalance()); assertEquals(20, ledger.get24HourSum(1, 60_000)); } @@ -68,13 +68,13 @@ public class LedgerTest { @Test public void test24HourSum() { final Ledger ledger = new Ledger(); - ledger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 500)); + ledger.recordTransaction(new Ledger.Transaction(0, 1000, 1, null, 500, 0)); assertEquals(500, ledger.get24HourSum(1, 24 * HOUR_IN_MILLIS)); ledger.recordTransaction( - new Ledger.Transaction(2 * HOUR_IN_MILLIS, 3 * HOUR_IN_MILLIS, 1, null, 2500)); + new Ledger.Transaction(2 * HOUR_IN_MILLIS, 3 * HOUR_IN_MILLIS, 1, null, 2500, 0)); assertEquals(3000, ledger.get24HourSum(1, 24 * HOUR_IN_MILLIS)); ledger.recordTransaction( - new Ledger.Transaction(4 * HOUR_IN_MILLIS, 4 * HOUR_IN_MILLIS, 1, null, 1)); + new Ledger.Transaction(4 * HOUR_IN_MILLIS, 4 * HOUR_IN_MILLIS, 1, null, 1, 0)); assertEquals(3001, ledger.get24HourSum(1, 24 * HOUR_IN_MILLIS)); assertEquals(2501, ledger.get24HourSum(1, 25 * HOUR_IN_MILLIS)); assertEquals(2501, ledger.get24HourSum(1, 26 * HOUR_IN_MILLIS)); @@ -93,17 +93,17 @@ public class LedgerTest { final long now = getCurrentTimeMillis(); Ledger.Transaction transaction1 = new Ledger.Transaction( - now - 48 * HOUR_IN_MILLIS, now - 40 * HOUR_IN_MILLIS, 1, null, 4800); + now - 48 * HOUR_IN_MILLIS, now - 40 * HOUR_IN_MILLIS, 1, null, 4800, 0); Ledger.Transaction transaction2 = new Ledger.Transaction( - now - 24 * HOUR_IN_MILLIS, now - 23 * HOUR_IN_MILLIS, 1, null, 600); + now - 24 * HOUR_IN_MILLIS, now - 23 * HOUR_IN_MILLIS, 1, null, 600, 0); Ledger.Transaction transaction3 = new Ledger.Transaction( - now - 22 * HOUR_IN_MILLIS, now - 21 * HOUR_IN_MILLIS, 1, null, 600); + now - 22 * HOUR_IN_MILLIS, now - 21 * HOUR_IN_MILLIS, 1, null, 600, 0); // Instant event Ledger.Transaction transaction4 = new Ledger.Transaction( - now - 20 * HOUR_IN_MILLIS, now - 20 * HOUR_IN_MILLIS, 1, null, 500); + now - 20 * HOUR_IN_MILLIS, now - 20 * HOUR_IN_MILLIS, 1, null, 500, 0); // Recent event Ledger.Transaction transaction5 = new Ledger.Transaction( - now - 5 * MINUTE_IN_MILLIS, now - MINUTE_IN_MILLIS, 1, null, 400); + now - 5 * MINUTE_IN_MILLIS, now - MINUTE_IN_MILLIS, 1, null, 400, 0); ledger.recordTransaction(transaction1); ledger.recordTransaction(transaction2); ledger.recordTransaction(transaction3); diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java index e1a4989e5a05..01e306e744fb 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java @@ -61,8 +61,6 @@ import android.util.SparseArray; import androidx.test.InstrumentationRegistry; -import com.android.internal.app.IBatteryStats; - import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -106,8 +104,6 @@ public class VibrationThreadTest { @Mock private IBinder mVibrationToken; @Mock - private IBatteryStats mIBatteryStatsMock; - @Mock private VibrationConfig mVibrationConfigMock; private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>(); @@ -178,8 +174,8 @@ public class VibrationThreadTest { VibrationThread thread = startThreadAndDispatcher(vibrationId, effect); waitForCompletion(thread); - verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L)); - verify(mIBatteryStatsMock).noteVibratorOff(eq(UID)); + verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L)); + verify(mManagerHooks).noteVibratorOff(eq(UID)); verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED); assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); @@ -197,8 +193,8 @@ public class VibrationThreadTest { VibrationThread thread = startThreadAndDispatcher(vibrationId, effect); waitForCompletion(thread); - verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L)); - verify(mIBatteryStatsMock).noteVibratorOff(eq(UID)); + verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L)); + verify(mManagerHooks).noteVibratorOff(eq(UID)); verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED); assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); @@ -219,8 +215,8 @@ public class VibrationThreadTest { VibrationThread thread = startThreadAndDispatcher(vibrationId, effect); waitForCompletion(thread); - verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(15L)); - verify(mIBatteryStatsMock).noteVibratorOff(eq(UID)); + verify(mManagerHooks).noteVibratorOn(eq(UID), eq(15L)); + verify(mManagerHooks).noteVibratorOff(eq(UID)); verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED); assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); @@ -252,8 +248,8 @@ public class VibrationThreadTest { thread.cancel(); waitForCompletion(thread); - verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), anyLong()); - verify(mIBatteryStatsMock).noteVibratorOff(eq(UID)); + verify(mManagerHooks).noteVibratorOn(eq(UID), anyLong()); + verify(mManagerHooks).noteVibratorOff(eq(UID)); verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED); assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); @@ -404,8 +400,8 @@ public class VibrationThreadTest { VibrationThread thread = startThreadAndDispatcher(vibrationId, effect); waitForCompletion(thread); - verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L)); - verify(mIBatteryStatsMock).noteVibratorOff(eq(UID)); + verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L)); + verify(mManagerHooks).noteVibratorOff(eq(UID)); verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED); assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); @@ -427,8 +423,8 @@ public class VibrationThreadTest { VibrationThread thread = startThreadAndDispatcher(vibration); waitForCompletion(thread); - verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L)); - verify(mIBatteryStatsMock).noteVibratorOff(eq(UID)); + verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L)); + verify(mManagerHooks).noteVibratorOff(eq(UID)); verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED); assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); @@ -446,8 +442,8 @@ public class VibrationThreadTest { VibrationThread thread = startThreadAndDispatcher(vibrationId, effect); waitForCompletion(thread); - verify(mIBatteryStatsMock, never()).noteVibratorOn(eq(UID), anyLong()); - verify(mIBatteryStatsMock, never()).noteVibratorOff(eq(UID)); + verify(mManagerHooks, never()).noteVibratorOn(eq(UID), anyLong()); + verify(mManagerHooks, never()).noteVibratorOff(eq(UID)); verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED); assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty()); @@ -466,8 +462,8 @@ public class VibrationThreadTest { VibrationThread thread = startThreadAndDispatcher(vibrationId, effect); waitForCompletion(thread); - verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(40L)); - verify(mIBatteryStatsMock).noteVibratorOff(eq(UID)); + verify(mManagerHooks).noteVibratorOn(eq(UID), eq(40L)); + verify(mManagerHooks).noteVibratorOff(eq(UID)); verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED); assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); @@ -486,8 +482,8 @@ public class VibrationThreadTest { VibrationThread thread = startThreadAndDispatcher(vibrationId, effect); waitForCompletion(thread); - verify(mIBatteryStatsMock, never()).noteVibratorOn(eq(UID), anyLong()); - verify(mIBatteryStatsMock, never()).noteVibratorOff(eq(UID)); + verify(mManagerHooks, never()).noteVibratorOn(eq(UID), anyLong()); + verify(mManagerHooks, never()).noteVibratorOff(eq(UID)); verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED); assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments().isEmpty()); @@ -536,8 +532,8 @@ public class VibrationThreadTest { waitForCompletion(thread); // Use first duration the vibrator is turned on since we cannot estimate the clicks. - verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L)); - verify(mIBatteryStatsMock).noteVibratorOff(eq(UID)); + verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L)); + verify(mManagerHooks).noteVibratorOff(eq(UID)); verify(mControllerCallbacks, times(4)).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED); assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); @@ -573,8 +569,8 @@ public class VibrationThreadTest { VibrationThread thread = startThreadAndDispatcher(vibrationId, effect); waitForCompletion(thread); - verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(100L)); - verify(mIBatteryStatsMock).noteVibratorOff(eq(UID)); + verify(mManagerHooks).noteVibratorOn(eq(UID), eq(100L)); + verify(mManagerHooks).noteVibratorOff(eq(UID)); verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED); assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating()); @@ -666,8 +662,8 @@ public class VibrationThreadTest { VibrationThread thread = startThreadAndDispatcher(vibrationId, effect); waitForCompletion(thread); - verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L)); - verify(mIBatteryStatsMock).noteVibratorOff(eq(UID)); + verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L)); + verify(mManagerHooks).noteVibratorOff(eq(UID)); verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); verify(mControllerCallbacks, never()).onComplete(eq(2), eq(vibrationId)); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED); @@ -690,8 +686,8 @@ public class VibrationThreadTest { VibrationThread thread = startThreadAndDispatcher(vibrationId, effect); waitForCompletion(thread); - verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L)); - verify(mIBatteryStatsMock).noteVibratorOff(eq(UID)); + verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L)); + verify(mManagerHooks).noteVibratorOff(eq(UID)); verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId)); verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId)); verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId)); @@ -728,8 +724,8 @@ public class VibrationThreadTest { VibrationThread thread = startThreadAndDispatcher(vibrationId, effect); waitForCompletion(thread); - verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L)); - verify(mIBatteryStatsMock).noteVibratorOff(eq(UID)); + verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L)); + verify(mManagerHooks).noteVibratorOff(eq(UID)); verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId)); verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId)); verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId)); @@ -777,13 +773,13 @@ public class VibrationThreadTest { controllerVerifier.verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId)); controllerVerifier.verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId)); - InOrder batterVerifier = inOrder(mIBatteryStatsMock); - batterVerifier.verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L)); - batterVerifier.verify(mIBatteryStatsMock).noteVibratorOff(eq(UID)); - batterVerifier.verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(10L)); - batterVerifier.verify(mIBatteryStatsMock).noteVibratorOff(eq(UID)); - batterVerifier.verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(20L)); - batterVerifier.verify(mIBatteryStatsMock).noteVibratorOff(eq(UID)); + InOrder batteryVerifier = inOrder(mManagerHooks); + batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L)); + batteryVerifier.verify(mManagerHooks).noteVibratorOff(eq(UID)); + batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L)); + batteryVerifier.verify(mManagerHooks).noteVibratorOff(eq(UID)); + batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L)); + batteryVerifier.verify(mManagerHooks).noteVibratorOff(eq(UID)); verifyCallbacksTriggered(vibrationId, Vibration.Status.FINISHED); assertFalse(thread.getVibrators().get(1).isVibrating()); @@ -952,8 +948,8 @@ public class VibrationThreadTest { waitForCompletion(thread); - verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), eq(80L)); - verify(mIBatteryStatsMock).noteVibratorOff(eq(UID)); + verify(mManagerHooks).noteVibratorOn(eq(UID), eq(80L)); + verify(mManagerHooks).noteVibratorOff(eq(UID)); verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId)); verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId)); verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId)); @@ -1300,7 +1296,7 @@ public class VibrationThreadTest { private VibrationThread startThreadAndDispatcher(Vibration vib) { VibrationThread thread = new VibrationThread(vib, mVibrationSettings, mEffectAdapter, - createVibratorControllers(), mWakeLock, mIBatteryStatsMock, mManagerHooks); + createVibratorControllers(), mWakeLock, mManagerHooks); doAnswer(answer -> { thread.vibratorComplete(answer.getArgument(0)); return null; diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java index 52975ef8bfe1..19111e5d16e9 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -71,6 +71,7 @@ import android.os.VibratorInfo; import android.os.test.TestLooper; import android.os.vibrator.PrebakedSegment; import android.os.vibrator.PrimitiveSegment; +import android.os.vibrator.VibrationConfig; import android.os.vibrator.VibrationEffectSegment; import android.platform.test.annotations.Presubmit; import android.provider.Settings; @@ -78,6 +79,7 @@ import android.view.InputDevice; import androidx.test.InstrumentationRegistry; +import com.android.internal.app.IBatteryStats; import com.android.internal.util.test.FakeSettingsProvider; import com.android.internal.util.test.FakeSettingsProviderRule; import com.android.server.LocalServices; @@ -144,6 +146,8 @@ public class VibratorManagerServiceTest { private AppOpsManager mAppOpsManagerMock; @Mock private IInputManager mIInputManagerMock; + @Mock + private IBatteryStats mBatteryStatsMock; private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>(); @@ -152,12 +156,14 @@ public class VibratorManagerServiceTest { private FakeVibrator mVibrator; private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener; private VibratorManagerService.ExternalVibratorService mExternalVibratorService; + private VibrationConfig mVibrationConfig; @Before public void setUp() throws Exception { mTestLooper = new TestLooper(); mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext())); InputManager inputManager = InputManager.resetInstance(mIInputManagerMock); + mVibrationConfig = new VibrationConfig(mContextSpy.getResources()); ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy); when(mContextSpy.getContentResolver()).thenReturn(contentResolver); @@ -222,6 +228,11 @@ public class VibratorManagerServiceTest { } @Override + IBatteryStats getBatteryStatsService() { + return mBatteryStatsMock; + } + + @Override VibratorController createVibratorController(int vibratorId, VibratorController.OnVibrationCompleteListener listener) { return mVibratorProviders.get(vibratorId) @@ -382,6 +393,11 @@ public class VibratorManagerServiceTest { inOrderVerifier.verify(listenerMock).onVibrating(eq(true)); inOrderVerifier.verify(listenerMock).onVibrating(eq(false)); inOrderVerifier.verifyNoMoreInteractions(); + + InOrder batteryVerifier = inOrder(mBatteryStatsMock); + batteryVerifier.verify(mBatteryStatsMock) + .noteVibratorOn(UID, 40 + mVibrationConfig.getRampDownDurationMs()); + batteryVerifier.verify(mBatteryStatsMock).noteVibratorOff(UID); } @Test @@ -731,6 +747,12 @@ public class VibratorManagerServiceTest { // Wait before checking it never played a second effect. assertFalse(waitUntil(s -> mVibratorProviders.get(1).getEffectSegments().size() > 1, service, /* timeout= */ 50)); + + // The time estimate is recorded when the vibration starts, repeating vibrations + // are capped at BATTERY_STATS_REPEATING_VIBRATION_DURATION (=5000). + verify(mBatteryStatsMock).noteVibratorOn(UID, 5000); + // The second vibration shouldn't have recorded that the vibrators were turned on. + verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong()); } @Test @@ -765,6 +787,9 @@ public class VibratorManagerServiceTest { when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1}); when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1)); setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1); + // Mock alarm intensity equals to default value to avoid scaling in this test. + setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY, + mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_ALARM)); VibratorManagerService service = createSystemReadyService(); CombinedVibration effect = CombinedVibration.createParallel( @@ -804,6 +829,9 @@ public class VibratorManagerServiceTest { mockVibrators(1, 2); mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); + // Mock alarm intensity equals to default value to avoid scaling in this test. + setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY, + mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_ALARM)); VibratorManagerService service = createSystemReadyService(); // The native callback will be dispatched manually in this test. mTestLooper.stopAutoDispatchAndIgnoreExceptions(); diff --git a/telephony/java/android/telephony/PhysicalChannelConfig.java b/telephony/java/android/telephony/PhysicalChannelConfig.java index 95448c7807cc..d91134e33ef3 100644 --- a/telephony/java/android/telephony/PhysicalChannelConfig.java +++ b/telephony/java/android/telephony/PhysicalChannelConfig.java @@ -23,12 +23,15 @@ import android.os.Parcel; import android.os.Parcelable; import android.telephony.Annotation.NetworkType; +import com.android.telephony.Rlog; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.Objects; public final class PhysicalChannelConfig implements Parcelable { + static final String TAG = "PhysicalChannelConfig"; // TODO(b/72993578) consolidate these enums in a central location. /** @hide */ @@ -568,19 +571,21 @@ public final class PhysicalChannelConfig implements Parcelable { public @NonNull Builder setNetworkType(@NetworkType int networkType) { if (!TelephonyManager.isNetworkTypeValid(networkType)) { - throw new IllegalArgumentException("Network type: " + networkType + " is invalid."); + Rlog.e(TAG, "Builder.setNetworkType: Network type " + networkType + " is invalid."); + } else { + mNetworkType = networkType; } - mNetworkType = networkType; return this; } public @NonNull Builder setFrequencyRange(int frequencyRange) { if (!ServiceState.isFrequencyRangeValid(frequencyRange) && frequencyRange != ServiceState.FREQUENCY_RANGE_UNKNOWN) { - throw new IllegalArgumentException("Frequency range: " + frequencyRange + - " is invalid."); + Rlog.e(TAG, "Builder.setFrequencyRange: Frequency range " + frequencyRange + + " is invalid."); + } else { + mFrequencyRange = frequencyRange; } - mFrequencyRange = frequencyRange; return this; } @@ -596,19 +601,21 @@ public final class PhysicalChannelConfig implements Parcelable { public @NonNull Builder setCellBandwidthDownlinkKhz(int cellBandwidthDownlinkKhz) { if (cellBandwidthDownlinkKhz < CELL_BANDWIDTH_UNKNOWN) { - throw new IllegalArgumentException("Cell downlink bandwidth(kHz): " + - cellBandwidthDownlinkKhz + " is invalid."); + Rlog.e(TAG, "Builder.setCellBandwidthDownlinkKhz: Cell downlink bandwidth(kHz) " + + cellBandwidthDownlinkKhz + " is invalid."); + } else { + mCellBandwidthDownlinkKhz = cellBandwidthDownlinkKhz; } - mCellBandwidthDownlinkKhz = cellBandwidthDownlinkKhz; return this; } public @NonNull Builder setCellBandwidthUplinkKhz(int cellBandwidthUplinkKhz) { if (cellBandwidthUplinkKhz < CELL_BANDWIDTH_UNKNOWN) { - throw new IllegalArgumentException("Cell uplink bandwidth(kHz): "+ - cellBandwidthUplinkKhz +" is invalid."); + Rlog.e(TAG, "Builder.setCellBandwidthUplinkKhz: Cell uplink bandwidth(kHz) " + + cellBandwidthUplinkKhz + " is invalid."); + } else { + mCellBandwidthUplinkKhz = cellBandwidthUplinkKhz; } - mCellBandwidthUplinkKhz = cellBandwidthUplinkKhz; return this; } @@ -625,19 +632,20 @@ public final class PhysicalChannelConfig implements Parcelable { public @NonNull Builder setPhysicalCellId(int physicalCellId) { if (physicalCellId > PHYSICAL_CELL_ID_MAXIMUM_VALUE) { - throw new IllegalArgumentException("Physical cell Id: " + physicalCellId + - " is over limit."); + Rlog.e(TAG, "Builder.setPhysicalCellId: Physical cell ID " + physicalCellId + + " is over limit."); + } else { + mPhysicalCellId = physicalCellId; } - mPhysicalCellId = physicalCellId; return this; } public @NonNull Builder setBand(int band) { if (band <= BAND_UNKNOWN) { - throw new IllegalArgumentException("Band: " + band + - " is invalid."); + Rlog.e(TAG, "Builder.setBand: Band " + band + " is invalid."); + } else { + mBand = band; } - mBand = band; return this; } } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index f57c32c959d9..0394a546388d 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -4189,7 +4189,8 @@ public class TelephonyManager { * {@link UiccSlotMapping} which consist of both physical slot index and port index. * Logical slot is the slot that is seen by modem. Physical slot is the actual physical slot. * Port index is the index (enumerated value) for the associated port available on the SIM. - * Each physical slot can have multiple ports if multi-enabled profile(MEP) is supported. + * Each physical slot can have multiple ports if + * {@link PackageManager#FEATURE_TELEPHONY_EUICC_MEP} is supported. * * Example: no. of logical slots 1 and physical slots 2 do not support MEP, each physical slot * has one port: @@ -4285,11 +4286,11 @@ public class TelephonyManager { /** * Get the mapping from logical slots to physical sim slots and port indexes. Initially the * logical slot index was mapped to physical slot index, but with support for multi-enabled - * profile(MEP) logical slot is now mapped to port index. + * profile(MEP){@link PackageManager#FEATURE_TELEPHONY_EUICC_MEP},logical slot is now mapped to + * port index. * * @return a collection of {@link UiccSlotMapping} which indicates the mapping from logical * slots to ports and physical slots. - * * @hide */ @SystemApi diff --git a/telephony/java/android/telephony/UiccCardInfo.java b/telephony/java/android/telephony/UiccCardInfo.java index 30ca1627953f..3843a6240b43 100644 --- a/telephony/java/android/telephony/UiccCardInfo.java +++ b/telephony/java/android/telephony/UiccCardInfo.java @@ -17,6 +17,7 @@ package android.telephony; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.pm.PackageManager; import android.os.Parcel; import android.os.Parcelable; @@ -147,9 +148,10 @@ public final class UiccCardInfo implements Parcelable { * Note that this field may be omitted if the caller does not have the correct permissions * (see {@link TelephonyManager#getUiccCardsInfo()}). * - * @deprecated with support for MEP(multiple enabled profile), a SIM card can have more than one - * ICCID active at the same time.Instead use {@link UiccPortInfo#getIccId()} to retrieve ICCID. - * To find {@link UiccPortInfo} use {@link UiccCardInfo#getPorts()} + * @deprecated with support for MEP(multiple enabled profile) + * {@link PackageManager#FEATURE_TELEPHONY_EUICC_MEP}, a SIM card can have more than one + * ICCID active at the same time. Instead use {@link UiccPortInfo#getIccId()} to retrieve ICCID. + * To find {@link UiccPortInfo} use {@link UiccCardInfo#getPorts()}. * * @throws UnsupportedOperationException if the calling app's target SDK is T and beyond. */ @@ -192,11 +194,11 @@ public final class UiccCardInfo implements Parcelable { } /* - * Whether the UICC card supports multiple enable profile(MEP) + * Whether the UICC card supports multiple enabled profile(MEP) * UICCs are generally MEP disabled, there can be only one active profile on the physical * sim card. * - * @return {@code true} if the eUICC is supporting multiple enabled profile(MEP). + * @return {@code true} if the UICC is supporting multiple enabled profile(MEP). */ public boolean isMultipleEnabledProfilesSupported() { return mIsMultipleEnabledProfilesSupported; @@ -205,6 +207,9 @@ public final class UiccCardInfo implements Parcelable { /** * Get information regarding port, ICCID and its active status. * + * For device which support {@link PackageManager#FEATURE_TELEPHONY_EUICC_MEP}, it should return + * more than one {@link UiccPortInfo} object if the card is eUICC. + * * @return Collection of {@link UiccPortInfo} */ public @NonNull Collection<UiccPortInfo> getPorts() { diff --git a/telephony/java/android/telephony/UiccPortInfo.java b/telephony/java/android/telephony/UiccPortInfo.java index d1838c0b91f4..6fb0470d6225 100644 --- a/telephony/java/android/telephony/UiccPortInfo.java +++ b/telephony/java/android/telephony/UiccPortInfo.java @@ -29,7 +29,9 @@ import java.util.Objects; * Per GSMA SGP.22 V3.0, a port is a logical entity to which an active UICC profile can be bound on * a UICC card. If UICC supports 2 ports, then the port index is numbered 0,1. * Each port index is unique within an UICC, but not necessarily unique across UICC’s. - * For UICC's does not support MEP(Multi-enabled profile), just return the default port index 0. + * For UICC's does not support MEP(Multi-enabled profile) + * {@link android.content.pm.PackageManager#FEATURE_TELEPHONY_EUICC_MEP}, just return the default + * port index 0. */ public final class UiccPortInfo implements Parcelable{ private final String mIccId; diff --git a/telephony/java/android/telephony/UiccSlotInfo.java b/telephony/java/android/telephony/UiccSlotInfo.java index 17f34db4e44a..17ce45063d41 100644 --- a/telephony/java/android/telephony/UiccSlotInfo.java +++ b/telephony/java/android/telephony/UiccSlotInfo.java @@ -19,6 +19,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.content.pm.PackageManager; import android.os.Parcel; import android.os.Parcelable; @@ -225,6 +226,9 @@ public class UiccSlotInfo implements Parcelable { /** * Get Information regarding port, iccid and its active status. * + * For device which support {@link PackageManager#FEATURE_TELEPHONY_EUICC_MEP}, it should return + * more than one {@link UiccPortInfo} object if the card is eUICC. + * * @return Collection of {@link UiccPortInfo} */ public @NonNull Collection<UiccPortInfo> getPorts() { diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java index b6ae53017f64..4820d332de0f 100644 --- a/telephony/java/android/telephony/euicc/EuiccManager.java +++ b/telephony/java/android/telephony/euicc/EuiccManager.java @@ -37,6 +37,7 @@ import android.os.RemoteException; import android.telephony.SubscriptionInfo; import android.telephony.TelephonyFrameworkInitializer; import android.telephony.TelephonyManager; +import android.telephony.UiccCardInfo; import android.telephony.euicc.EuiccCardManager.ResetOption; import android.util.Log; @@ -931,6 +932,21 @@ public class EuiccManager { * intent to prompt the user to accept the download. The caller should also be authorized to * manage the subscription to be downloaded. * + * <p>If device support {@link PackageManager#FEATURE_TELEPHONY_EUICC_MEP} and + * switchAfterDownload is {@code true}, the subscription will be enabled on an esim port based + * on the following selection rules: + * <ul> + * <li>In SS(Single SIM) mode, if the embedded slot already has an active port, then download + * and enable the subscription on this port. + * <li>In SS mode, if the embedded slot is not active, then try to download and enable the + * subscription on the default port 0 of eUICC. + * <li>In DSDS mode, find first available port to download and enable the subscription. + * (see {@link #isSimPortAvailable(int)}) + *</ul> + * If there is no available port, an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} + * will be returned in the callback intent to prompt the user to disable an already-active + * subscription. + * * @param subscription the subscription to download. * @param switchAfterDownload if true, the profile will be activated upon successful download. * @param callbackIntent a PendingIntent to launch when the operation completes. @@ -1141,14 +1157,25 @@ public class EuiccManager { * intent to prompt the user to accept the download. The caller should also be authorized to * manage the subscription to be enabled. * - * <p> From Android T, devices might support MEP(Multiple Enabled Profiles), the subscription - * can be installed on different port from the eUICC. Calling apps with carrier privilege - * (see {@link TelephonyManager#hasCarrierPrivileges}) over the currently active subscriptions - * can use {@link #switchToSubscription(int, int, PendingIntent)} to specify which port to - * enable the subscription. Otherwise, use this API to enable the subscription on the eUICC - * and the platform will internally resolve a port. If there is no available port, - * an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} might be returned in the callback - * intent to prompt the user to disable an already-active subscription. + * <p> From Android T, devices might support {@link PackageManager#FEATURE_TELEPHONY_EUICC_MEP}, + * the subscription can be installed on different port from the eUICC. Calling apps with + * carrier privilege (see {@link TelephonyManager#hasCarrierPrivileges}) over the currently + * active subscriptions can use {@link #switchToSubscription(int, int, PendingIntent)} to + * specify which port to enable the subscription. Otherwise, use this API to enable the + * subscription on the eUICC and the platform will internally resolve a port based on following + * rules: + * <ul> + * <li>always use the default port 0 is eUICC does not support MEP. + * <li>In SS(Single SIM) mode, if the embedded slot already has an active port, then enable + * the subscription on this port. + * <li>In SS mode, if the embedded slot is not active, then try to enable the subscription on + * the default port 0 of eUICC. + * <li>In DSDS mode, find first available port to enable the subscription. + * (see {@link #isSimPortAvailable(int)}) + *</ul> + * If there is no available port, an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} + * will be returned in the callback intent to prompt the user to disable an already-active + * subscription. * * @param subscriptionId the ID of the subscription to enable. May be * {@link android.telephony.SubscriptionManager#INVALID_SUBSCRIPTION_ID} to deactivate the @@ -1197,7 +1224,15 @@ public class EuiccManager { * * <p> If the caller is passing invalid port index, * an {@link #EMBEDDED_SUBSCRIPTION_RESULT_ERROR} with detailed error code - * {@link #ERROR_INVALID_PORT} will be returned. + * {@link #ERROR_INVALID_PORT} will be returned. The port index is invalid if one of the + * following requirements is met: + * <ul> + * <li>index is beyond the range of {@link UiccCardInfo#getPorts()}. + * <li>In SS(Single SIM) mode, the embedded slot already has an active port with different + * port index. + * <li>In DSDS mode, if the psim slot is active and the embedded slot already has an active + * empty port with different port index. + * </ul> * * <p> Depending on the target port and permission check, * an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} might be returned to the callback @@ -1522,8 +1557,8 @@ public class EuiccManager { /** * Returns whether the passing portIndex is available. - * A port is available if it has no profiles enabled on it or calling app has carrier privilege - * over the profile installed on the selected port. + * A port is available if it is active without enabled profile on it or + * calling app has carrier privilege over the profile installed on the selected port. * Always returns false if the cardId is a physical card. * * @param portIndex is an enumeration of the ports available on the UICC. |