diff options
445 files changed, 17398 insertions, 7614 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java index e3bd5acd7bc7..dcc6aa6a1d67 100644 --- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java @@ -29,6 +29,7 @@ import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AlarmManager; +import android.app.BroadcastOptions; import android.content.BroadcastReceiver; import android.content.Context; import android.content.IIntentReceiver; @@ -314,7 +315,9 @@ public class DeviceIdleController extends SystemService private Sensor mMotionSensor; private LocationRequest mLocationRequest; private Intent mIdleIntent; + private Bundle mIdleIntentOptions; private Intent mLightIdleIntent; + private Bundle mLightIdleIntentOptions; private AnyMotionDetector mAnyMotionDetector; private final AppStateTrackerImpl mAppStateTracker; @GuardedBy("this") @@ -1798,10 +1801,12 @@ public class DeviceIdleController extends SystemService } catch (RemoteException e) { } if (deepChanged) { - getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL); + getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL, + null /* receiverPermission */, mIdleIntentOptions); } if (lightChanged) { - getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL); + getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL, + null /* receiverPermission */, mLightIdleIntentOptions); } EventLogTags.writeDeviceIdleOnComplete(); mGoingIdleWakeLock.release(); @@ -1821,13 +1826,13 @@ public class DeviceIdleController extends SystemService incActiveIdleOps(); mLocalActivityManager.broadcastIntentWithCallback(mIdleIntent, mIdleStartedDoneReceiver, null, UserHandle.USER_ALL, - null, null, null); + null, null, mIdleIntentOptions); } if (lightChanged) { incActiveIdleOps(); mLocalActivityManager.broadcastIntentWithCallback(mLightIdleIntent, mIdleStartedDoneReceiver, null, UserHandle.USER_ALL, - null, null, null); + null, null, mLightIdleIntentOptions); } // Always start with one active op for the message being sent here. // Now we are done! @@ -1849,10 +1854,12 @@ public class DeviceIdleController extends SystemService } catch (RemoteException e) { } if (deepChanged) { - getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL); + getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL, + null /* receiverPermission */, mIdleIntentOptions); } if (lightChanged) { - getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL); + getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL, + null /* receiverPermission */, mLightIdleIntentOptions); } EventLogTags.writeDeviceIdleOffComplete(); } break; @@ -2531,6 +2538,9 @@ public class DeviceIdleController extends SystemService mLightIdleIntent = new Intent(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED); mLightIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT); + mIdleIntentOptions = mLightIdleIntentOptions = options.toBundle(); IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_BATTERY_CHANGED); 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 abc196f36d7c..b84c8a41af1c 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java @@ -286,7 +286,7 @@ class Agent { for (int i = 0; i < pkgNames.size(); ++i) { final String pkgName = pkgNames.valueAt(i); - final boolean isVip = mIrs.isVip(userId, pkgName); + final boolean isVip = mIrs.isVip(userId, pkgName, nowElapsed); SparseArrayMap<String, OngoingEvent> ongoingEvents = mCurrentOngoingEvents.get(userId, pkgName); if (ongoingEvents != null) { @@ -321,7 +321,7 @@ class Agent { final long nowElapsed = SystemClock.elapsedRealtime(); final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked(); - final boolean isVip = mIrs.isVip(userId, pkgName); + final boolean isVip = mIrs.isVip(userId, pkgName, nowElapsed); SparseArrayMap<String, OngoingEvent> ongoingEvents = mCurrentOngoingEvents.get(userId, pkgName); if (ongoingEvents != null) { @@ -397,7 +397,7 @@ class Agent { if (actionAffordabilityNotes != null) { final int size = actionAffordabilityNotes.size(); final long newBalance = getBalanceLocked(userId, pkgName); - final boolean isVip = mIrs.isVip(userId, pkgName); + final boolean isVip = mIrs.isVip(userId, pkgName, nowElapsed); for (int n = 0; n < size; ++n) { final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n); note.recalculateCosts(economicPolicy, userId, pkgName); @@ -503,7 +503,8 @@ class Agent { "Tried to adjust system balance for " + appToString(userId, pkgName)); return; } - if (mIrs.isVip(userId, pkgName)) { + final boolean isVip = mIrs.isVip(userId, pkgName); + if (isVip) { // This could happen if the app was made a VIP after it started performing actions. // Continue recording the transaction for debugging purposes, but don't let it change // any numbers. @@ -536,7 +537,6 @@ class Agent { mActionAffordabilityNotes.get(userId, pkgName); if (actionAffordabilityNotes != null) { final long newBalance = ledger.getCurrentBalance(); - final boolean isVip = mIrs.isVip(userId, pkgName); for (int i = 0; i < actionAffordabilityNotes.size(); ++i) { final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i); final boolean isAffordable = isVip @@ -830,7 +830,6 @@ class Agent { @GuardedBy("mLock") void onUserRemovedLocked(final int userId) { - mScribe.discardLedgersLocked(userId); mCurrentOngoingEvents.delete(userId); mBalanceThresholdAlarmQueue.removeAlarmsForUserId(userId); } diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java b/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java index fcb3e6759412..1ff389deedd2 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java @@ -16,14 +16,20 @@ package com.android.server.tare; +import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppGlobals; +import android.content.Context; +import android.content.PermissionChecker; import android.content.pm.ApplicationInfo; import android.content.pm.InstallSourceInfo; import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; import android.os.RemoteException; +import com.android.internal.util.ArrayUtils; + /** POJO to cache only the information about installed packages that TARE cares about. */ class InstalledPackageInfo { static final int NO_UID = -1; @@ -31,14 +37,22 @@ class InstalledPackageInfo { public final int uid; public final String packageName; public final boolean hasCode; + public final boolean isSystemInstaller; @Nullable public final String installerPackageName; - InstalledPackageInfo(@NonNull PackageInfo packageInfo) { + InstalledPackageInfo(@NonNull Context context, @NonNull PackageInfo packageInfo) { final ApplicationInfo applicationInfo = packageInfo.applicationInfo; uid = applicationInfo == null ? NO_UID : applicationInfo.uid; packageName = packageInfo.packageName; hasCode = applicationInfo != null && applicationInfo.hasCode(); + isSystemInstaller = applicationInfo != null + && ArrayUtils.indexOf( + packageInfo.requestedPermissions, Manifest.permission.INSTALL_PACKAGES) >= 0 + && PackageManager.PERMISSION_GRANTED + == PermissionChecker.checkPermissionForPreflight(context, + Manifest.permission.INSTALL_PACKAGES, PermissionChecker.PID_UNKNOWN, + applicationInfo.uid, packageName); InstallSourceInfo installSourceInfo = null; try { installSourceInfo = AppGlobals.getPackageManager().getInstallSourceInfo(packageName); 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 17b87469f7be..4001d9b06ee0 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java @@ -64,6 +64,7 @@ import android.util.IndentingPrintWriter; import android.util.Log; import android.util.Slog; import android.util.SparseArrayMap; +import android.util.SparseLongArray; import android.util.SparseSetArray; import com.android.internal.annotations.GuardedBy; @@ -108,6 +109,16 @@ public class InternalResourceService extends SystemService { /** The amount of time to delay reclamation by after boot. */ private static final long RECLAMATION_STARTUP_DELAY_MS = 30_000L; /** + * The amount of time after TARE has first been set up that a system installer will be allowed + * expanded credit privileges. + */ + static final long INSTALLER_FIRST_SETUP_GRACE_PERIOD_MS = 7 * DAY_IN_MILLIS; + /** + * The amount of time to wait after TARE has first been set up before considering adjusting the + * stock/consumption limit. + */ + private static final long STOCK_ADJUSTMENT_FIRST_SETUP_GRACE_PERIOD_MS = 5 * DAY_IN_MILLIS; + /** * The battery level above which we may consider quantitative easing (increasing the consumption * limit). */ @@ -127,7 +138,7 @@ public class InternalResourceService extends SystemService { private static final long STOCK_RECALCULATION_MIN_DATA_DURATION_MS = 8 * HOUR_IN_MILLIS; private static final int PACKAGE_QUERY_FLAGS = PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE - | PackageManager.MATCH_APEX; + | PackageManager.MATCH_APEX | PackageManager.GET_PERMISSIONS; /** Global lock for all resource economy state. */ private final Object mLock = new Object(); @@ -179,6 +190,13 @@ public class InternalResourceService extends SystemService { @GuardedBy("mLock") private final SparseArrayMap<String, Boolean> mVipOverrides = new SparseArrayMap<>(); + /** + * Set of temporary Very Important Packages and when their VIP status ends, in the elapsed + * realtime ({@link android.annotation.ElapsedRealtimeLong}) timebase. + */ + @GuardedBy("mLock") + private final SparseArrayMap<String, Long> mTemporaryVips = new SparseArrayMap<>(); + /** Set of apps each installer is responsible for installing. */ @GuardedBy("mLock") private final SparseArrayMap<String, ArraySet<String>> mInstallers = new SparseArrayMap<>(); @@ -308,6 +326,7 @@ public class InternalResourceService extends SystemService { private static final int MSG_PROCESS_USAGE_EVENT = 2; private static final int MSG_NOTIFY_STATE_CHANGE_LISTENERS = 3; private static final int MSG_NOTIFY_STATE_CHANGE_LISTENER = 4; + private static final int MSG_CLEAN_UP_TEMP_VIP_LIST = 5; private static final String ALARM_TAG_WEALTH_RECLAMATION = "*tare.reclamation*"; /** @@ -413,6 +432,13 @@ public class InternalResourceService extends SystemService { return userPkgs; } + @Nullable + InstalledPackageInfo getInstalledPackageInfo(final int userId, @NonNull final String pkgName) { + synchronized (mLock) { + return mPkgCache.get(userId, pkgName); + } + } + @GuardedBy("mLock") long getConsumptionLimitLocked() { return mCurrentBatteryLevel * mScribe.getSatiatedConsumptionLimitLocked() / 100; @@ -429,6 +455,11 @@ public class InternalResourceService extends SystemService { return mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit(); } + + long getRealtimeSinceFirstSetupMs() { + return mScribe.getRealtimeSinceFirstSetupMs(SystemClock.elapsedRealtime()); + } + int getUid(final int userId, @NonNull final String pkgName) { synchronized (mPackageToUidCache) { Integer uid = mPackageToUidCache.get(userId, pkgName); @@ -470,6 +501,10 @@ public class InternalResourceService extends SystemService { } boolean isVip(final int userId, @NonNull String pkgName) { + return isVip(userId, pkgName, SystemClock.elapsedRealtime()); + } + + boolean isVip(final int userId, @NonNull String pkgName, final long nowElapsed) { synchronized (mLock) { final Boolean override = mVipOverrides.get(userId, pkgName); if (override != null) { @@ -481,6 +516,12 @@ public class InternalResourceService extends SystemService { // operate. return true; } + synchronized (mLock) { + final Long expirationTimeElapsed = mTemporaryVips.get(userId, pkgName); + if (expirationTimeElapsed != null) { + return nowElapsed <= expirationTimeElapsed; + } + } return false; } @@ -569,7 +610,7 @@ public class InternalResourceService extends SystemService { mPackageToUidCache.add(userId, pkgName, uid); } synchronized (mLock) { - final InstalledPackageInfo ipo = new InstalledPackageInfo(packageInfo); + final InstalledPackageInfo ipo = new InstalledPackageInfo(getContext(), packageInfo); final InstalledPackageInfo oldIpo = mPkgCache.add(userId, pkgName, ipo); maybeUpdateInstallerStatusLocked(oldIpo, ipo); mUidToPackageCache.add(uid, pkgName); @@ -626,11 +667,16 @@ public class InternalResourceService extends SystemService { final List<PackageInfo> pkgs = mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId); for (int i = pkgs.size() - 1; i >= 0; --i) { - final InstalledPackageInfo ipo = new InstalledPackageInfo(pkgs.get(i)); + final InstalledPackageInfo ipo = + new InstalledPackageInfo(getContext(), pkgs.get(i)); final InstalledPackageInfo oldIpo = mPkgCache.add(userId, ipo.packageName, ipo); maybeUpdateInstallerStatusLocked(oldIpo, ipo); } mAgent.grantBirthrightsLocked(userId); + final long nowElapsed = SystemClock.elapsedRealtime(); + mScribe.setUserAddedTimeLocked(userId, nowElapsed); + grantInstallersTemporaryVipStatusLocked(userId, + nowElapsed, INSTALLER_FIRST_SETUP_GRACE_PERIOD_MS); } } @@ -647,6 +693,7 @@ public class InternalResourceService extends SystemService { mInstallers.delete(userId); mPkgCache.delete(userId); mAgent.onUserRemovedLocked(userId); + mScribe.onUserRemovedLocked(userId); } } @@ -659,6 +706,10 @@ public class InternalResourceService extends SystemService { maybeAdjustDesiredStockLevelLocked(); return; } + if (getRealtimeSinceFirstSetupMs() < STOCK_ADJUSTMENT_FIRST_SETUP_GRACE_PERIOD_MS) { + // Things can be very tumultuous soon after first setup. + return; + } // We don't need to increase the limit if the device runs out of consumable credits // when the battery is low. final long remainingConsumableCakes = mScribe.getRemainingConsumableCakesLocked(); @@ -687,6 +738,10 @@ public class InternalResourceService extends SystemService { if (!mConfigObserver.ENABLE_TIP3) { return; } + if (getRealtimeSinceFirstSetupMs() < STOCK_ADJUSTMENT_FIRST_SETUP_GRACE_PERIOD_MS) { + // Things can be very tumultuous soon after first setup. + return; + } // Don't adjust the limit too often or while the battery is low. final long now = getCurrentTimeMillis(); if ((now - mScribe.getLastStockRecalculationTimeLocked()) < STOCK_RECALCULATION_DELAY_MS @@ -776,6 +831,28 @@ public class InternalResourceService extends SystemService { } @GuardedBy("mLock") + private void grantInstallersTemporaryVipStatusLocked(int userId, long nowElapsed, + long grantDurationMs) { + final long grantEndTimeElapsed = nowElapsed + grantDurationMs; + final int uIdx = mPkgCache.indexOfKey(userId); + if (uIdx < 0) { + return; + } + for (int pIdx = mPkgCache.numElementsForKey(uIdx) - 1; pIdx >= 0; --pIdx) { + final InstalledPackageInfo ipo = mPkgCache.valueAt(uIdx, pIdx); + + if (ipo.isSystemInstaller) { + final Long currentGrantEndTimeElapsed = mTemporaryVips.get(userId, ipo.packageName); + if (currentGrantEndTimeElapsed == null + || currentGrantEndTimeElapsed < grantEndTimeElapsed) { + mTemporaryVips.add(userId, ipo.packageName, grantEndTimeElapsed); + } + } + } + mHandler.sendEmptyMessageDelayed(MSG_CLEAN_UP_TEMP_VIP_LIST, grantDurationMs); + } + + @GuardedBy("mLock") private void processUsageEventLocked(final int userId, @NonNull UsageEvents.Event event) { if (!mIsEnabled) { return; @@ -870,7 +947,8 @@ public class InternalResourceService extends SystemService { final List<PackageInfo> pkgs = mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId); for (int i = pkgs.size() - 1; i >= 0; --i) { - final InstalledPackageInfo ipo = new InstalledPackageInfo(pkgs.get(i)); + final InstalledPackageInfo ipo = + new InstalledPackageInfo(getContext(), pkgs.get(i)); final InstalledPackageInfo oldIpo = mPkgCache.add(userId, ipo.packageName, ipo); maybeUpdateInstallerStatusLocked(oldIpo, ipo); } @@ -953,11 +1031,17 @@ public class InternalResourceService extends SystemService { synchronized (mLock) { mCompleteEconomicPolicy.setup(mConfigObserver.getAllDeviceConfigProperties()); loadInstalledPackageListLocked(); + final SparseLongArray timeSinceUsersAdded; final boolean isFirstSetup = !mScribe.recordExists(); + final long nowElapsed = SystemClock.elapsedRealtime(); if (isFirstSetup) { mAgent.grantBirthrightsLocked(); mScribe.setConsumptionLimitLocked( mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit()); + // Set the last reclamation time to now so we don't start reclaiming assets + // too early. + mScribe.setLastReclamationTimeLocked(getCurrentTimeMillis()); + timeSinceUsersAdded = new SparseLongArray(); } else { mScribe.loadFromDiskLocked(); if (mScribe.getSatiatedConsumptionLimitLocked() @@ -971,6 +1055,21 @@ public class InternalResourceService extends SystemService { // Adjust the supply in case battery level changed while the device was off. adjustCreditSupplyLocked(true); } + timeSinceUsersAdded = mScribe.getRealtimeSinceUsersAddedLocked(nowElapsed); + } + + final int[] userIds = LocalServices.getService(UserManagerInternal.class).getUserIds(); + for (int userId : userIds) { + final long timeSinceUserAddedMs = timeSinceUsersAdded.get(userId, 0); + // Temporarily mark installers as VIPs so they aren't subject to credit + // limits and policies on first boot. + if (timeSinceUserAddedMs < INSTALLER_FIRST_SETUP_GRACE_PERIOD_MS) { + final long remainingGraceDurationMs = + INSTALLER_FIRST_SETUP_GRACE_PERIOD_MS - timeSinceUserAddedMs; + + grantInstallersTemporaryVipStatusLocked(userId, nowElapsed, + remainingGraceDurationMs); + } } scheduleUnusedWealthReclamationLocked(); } @@ -1079,6 +1178,36 @@ public class InternalResourceService extends SystemService { @Override public void handleMessage(Message msg) { switch (msg.what) { + case MSG_CLEAN_UP_TEMP_VIP_LIST: { + removeMessages(MSG_CLEAN_UP_TEMP_VIP_LIST); + + synchronized (mLock) { + final long nowElapsed = SystemClock.elapsedRealtime(); + + long earliestExpiration = Long.MAX_VALUE; + for (int u = 0; u < mTemporaryVips.numMaps(); ++u) { + final int userId = mTemporaryVips.keyAt(u); + + for (int p = mTemporaryVips.numElementsForKeyAt(u) - 1; p >= 0; --p) { + final String pkgName = mTemporaryVips.keyAt(u, p); + final Long expiration = mTemporaryVips.valueAt(u, p); + + if (expiration == null || expiration < nowElapsed) { + mTemporaryVips.delete(userId, pkgName); + } else { + earliestExpiration = Math.min(earliestExpiration, expiration); + } + } + } + + if (earliestExpiration < Long.MAX_VALUE) { + sendEmptyMessageDelayed(MSG_CLEAN_UP_TEMP_VIP_LIST, + earliestExpiration - nowElapsed); + } + } + } + break; + case MSG_NOTIFY_AFFORDABILITY_CHANGE_LISTENER: { final SomeArgs args = (SomeArgs) msg.obj; final int userId = args.argi1; @@ -1558,6 +1687,7 @@ public class InternalResourceService extends SystemService { boolean printedVips = false; pw.println(); pw.print("VIPs:"); + pw.increaseIndent(); for (int u = 0; u < mVipOverrides.numMaps(); ++u) { final int userId = mVipOverrides.keyAt(u); @@ -1576,6 +1706,32 @@ public class InternalResourceService extends SystemService { } else { pw.print(" None"); } + pw.decreaseIndent(); + pw.println(); + + boolean printedTempVips = false; + pw.println(); + pw.print("Temp VIPs:"); + pw.increaseIndent(); + for (int u = 0; u < mTemporaryVips.numMaps(); ++u) { + final int userId = mTemporaryVips.keyAt(u); + + for (int p = 0; p < mTemporaryVips.numElementsForKeyAt(u); ++p) { + final String pkgName = mTemporaryVips.keyAt(u, p); + + printedTempVips = true; + pw.println(); + pw.print(appToString(userId, pkgName)); + pw.print("="); + pw.print(mTemporaryVips.valueAt(u, p)); + } + } + if (printedTempVips) { + pw.println(); + } else { + pw.print(" None"); + } + pw.decreaseIndent(); pw.println(); pw.println(); diff --git a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java index 7cf459c2e494..c2a6e43ba930 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java @@ -117,6 +117,7 @@ import static com.android.server.tare.Modifier.COST_MODIFIER_CHARGING; import static com.android.server.tare.Modifier.COST_MODIFIER_DEVICE_IDLE; import static com.android.server.tare.Modifier.COST_MODIFIER_POWER_SAVE_MODE; import static com.android.server.tare.Modifier.COST_MODIFIER_PROCESS_STATE; +import static com.android.server.tare.TareUtils.appToString; import static com.android.server.tare.TareUtils.cakeToString; import android.annotation.NonNull; @@ -210,6 +211,22 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { if (mIrs.isPackageRestricted(userId, pkgName)) { return 0; } + final InstalledPackageInfo ipo = mIrs.getInstalledPackageInfo(userId, pkgName); + if (ipo == null) { + Slog.wtfStack(TAG, + "Tried to get max balance of invalid app: " + appToString(userId, pkgName)); + } else { + // A system installer's max balance is elevated for some time after first boot so + // they can use jobs to download and install apps. + if (ipo.isSystemInstaller) { + final long timeSinceFirstSetupMs = mIrs.getRealtimeSinceFirstSetupMs(); + final boolean stillExempted = timeSinceFirstSetupMs + < InternalResourceService.INSTALLER_FIRST_SETUP_GRACE_PERIOD_MS; + if (stillExempted) { + return mMaxSatiatedConsumptionLimit; + } + } + } return mMaxSatiatedBalance; } 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 ee448b5a6add..b41c0d1371c0 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java @@ -24,6 +24,7 @@ import static com.android.server.tare.TareUtils.cakeToString; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Environment; +import android.os.SystemClock; import android.os.UserHandle; import android.util.ArraySet; import android.util.AtomicFile; @@ -33,6 +34,7 @@ import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.SparseArrayMap; +import android.util.SparseLongArray; import android.util.Xml; import com.android.internal.annotations.GuardedBy; @@ -88,6 +90,7 @@ public class Scribe { "lastStockRecalculationTime"; private static final String XML_ATTR_REMAINING_CONSUMABLE_CAKES = "remainingConsumableCakes"; private static final String XML_ATTR_CONSUMPTION_LIMIT = "consumptionLimit"; + private static final String XML_ATTR_TIME_SINCE_FIRST_SETUP_MS = "timeSinceFirstSetup"; private static final String XML_ATTR_PR_DISCHARGE = "discharge"; private static final String XML_ATTR_PR_BATTERY_LEVEL = "batteryLevel"; private static final String XML_ATTR_PR_PROFIT = "profit"; @@ -112,6 +115,11 @@ public class Scribe { private final InternalResourceService mIrs; private final Analyst mAnalyst; + /** + * The value of elapsed realtime since TARE was first setup that was read from disk. + * This will only be changed when the persisted file is read. + */ + private long mLoadedTimeSinceFirstSetup; @GuardedBy("mIrs.getLock()") private long mLastReclamationTime; @GuardedBy("mIrs.getLock()") @@ -122,6 +130,9 @@ public class Scribe { private long mRemainingConsumableCakes; @GuardedBy("mIrs.getLock()") private final SparseArrayMap<String, Ledger> mLedgers = new SparseArrayMap<>(); + /** Offsets used to calculate the total realtime since each user was added. */ + @GuardedBy("mIrs.getLock()") + private final SparseLongArray mRealtimeSinceUsersAddedOffsets = new SparseLongArray(); private final Runnable mCleanRunnable = this::cleanupLedgers; private final Runnable mWriteRunnable = this::writeState; @@ -163,8 +174,9 @@ public class Scribe { } @GuardedBy("mIrs.getLock()") - void discardLedgersLocked(final int userId) { + void onUserRemovedLocked(final int userId) { mLedgers.delete(userId); + mRealtimeSinceUsersAddedOffsets.delete(userId); postWrite(); } @@ -215,6 +227,11 @@ public class Scribe { return sum; } + /** Returns the cumulative elapsed realtime since TARE was first setup. */ + long getRealtimeSinceFirstSetupMs(long nowElapsed) { + return mLoadedTimeSinceFirstSetup + nowElapsed; + } + /** Returns the total amount of cakes that remain to be consumed. */ @GuardedBy("mIrs.getLock()") long getRemainingConsumableCakesLocked() { @@ -222,6 +239,16 @@ public class Scribe { } @GuardedBy("mIrs.getLock()") + SparseLongArray getRealtimeSinceUsersAddedLocked(long nowElapsed) { + final SparseLongArray realtimes = new SparseLongArray(); + for (int i = mRealtimeSinceUsersAddedOffsets.size() - 1; i >= 0; --i) { + realtimes.put(mRealtimeSinceUsersAddedOffsets.keyAt(i), + mRealtimeSinceUsersAddedOffsets.valueAt(i) + nowElapsed); + } + return realtimes; + } + + @GuardedBy("mIrs.getLock()") void loadFromDiskLocked() { mLedgers.clear(); if (!recordExists()) { @@ -276,7 +303,8 @@ public class Scribe { } } - final long endTimeCutoff = System.currentTimeMillis() - MAX_TRANSACTION_AGE_MS; + final long now = System.currentTimeMillis(); + final long endTimeCutoff = now - MAX_TRANSACTION_AGE_MS; long earliestEndTime = Long.MAX_VALUE; for (eventType = parser.next(); eventType != XmlPullParser.END_DOCUMENT; eventType = parser.next()) { @@ -294,6 +322,12 @@ public class Scribe { parser.getAttributeLong(null, XML_ATTR_LAST_RECLAMATION_TIME); mLastStockRecalculationTime = parser.getAttributeLong(null, XML_ATTR_LAST_STOCK_RECALCULATION_TIME, 0); + mLoadedTimeSinceFirstSetup = + parser.getAttributeLong(null, XML_ATTR_TIME_SINCE_FIRST_SETUP_MS, + // If there's no recorded time since first setup, then + // offset the current elapsed time so it doesn't shift the + // timing too much. + -SystemClock.elapsedRealtime()); mSatiatedConsumptionLimit = parser.getAttributeLong(null, XML_ATTR_CONSUMPTION_LIMIT, mIrs.getInitialSatiatedConsumptionLimitLocked()); @@ -356,6 +390,13 @@ public class Scribe { } @GuardedBy("mIrs.getLock()") + void setUserAddedTimeLocked(int userId, long timeElapsed) { + // Use the current time as an offset so that when we persist the time, it correctly persists + // as "time since now". + mRealtimeSinceUsersAddedOffsets.put(userId, -timeElapsed); + } + + @GuardedBy("mIrs.getLock()") void tearDownLocked() { TareHandlerThread.getHandler().removeCallbacks(mCleanRunnable); TareHandlerThread.getHandler().removeCallbacks(mWriteRunnable); @@ -486,6 +527,14 @@ public class Scribe { // Don't return early since we need to go through all the ledger tags and get to the end // of the user tag. } + if (curUser != UserHandle.USER_NULL) { + mRealtimeSinceUsersAddedOffsets.put(curUser, + parser.getAttributeLong(null, XML_ATTR_TIME_SINCE_FIRST_SETUP_MS, + // If there's no recorded time since first setup, then + // offset the current elapsed time so it doesn't shift the + // timing too much. + -SystemClock.elapsedRealtime())); + } long earliestEndTime = Long.MAX_VALUE; for (int eventType = parser.next(); eventType != XmlPullParser.END_DOCUMENT; @@ -630,6 +679,8 @@ public class Scribe { out.attributeLong(null, XML_ATTR_LAST_RECLAMATION_TIME, mLastReclamationTime); out.attributeLong(null, XML_ATTR_LAST_STOCK_RECALCULATION_TIME, mLastStockRecalculationTime); + out.attributeLong(null, XML_ATTR_TIME_SINCE_FIRST_SETUP_MS, + mLoadedTimeSinceFirstSetup + SystemClock.elapsedRealtime()); out.attributeLong(null, XML_ATTR_CONSUMPTION_LIMIT, mSatiatedConsumptionLimit); out.attributeLong(null, XML_ATTR_REMAINING_CONSUMABLE_CAKES, mRemainingConsumableCakes); @@ -665,6 +716,9 @@ public class Scribe { out.startTag(null, XML_TAG_USER); out.attributeInt(null, XML_ATTR_USER_ID, userId); + out.attributeLong(null, XML_ATTR_TIME_SINCE_FIRST_SETUP_MS, + mRealtimeSinceUsersAddedOffsets.get(userId, + mLoadedTimeSinceFirstSetup + SystemClock.elapsedRealtime())); for (int pIdx = mLedgers.numElementsForKey(userId) - 1; pIdx >= 0; --pIdx) { final String pkgName = mLedgers.keyAt(uIdx, pIdx); final Ledger ledger = mLedgers.get(userId, pkgName); diff --git a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h index 5e189f2c1340..7b38bd11d847 100644 --- a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h +++ b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h @@ -39,7 +39,7 @@ class BinaryStreamVisitor : public Visitor { void Write8(uint8_t value); void Write16(uint16_t value); void Write32(uint32_t value); - void WriteString(const StringPiece& value); + void WriteString(StringPiece value); std::ostream& stream_; }; diff --git a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp index 4b271a1ff96f..89769246434a 100644 --- a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp +++ b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp @@ -38,7 +38,7 @@ void BinaryStreamVisitor::Write32(uint32_t value) { stream_.write(reinterpret_cast<char*>(&x), sizeof(uint32_t)); } -void BinaryStreamVisitor::WriteString(const StringPiece& value) { +void BinaryStreamVisitor::WriteString(StringPiece value) { // pad with null to nearest word boundary; size_t padding_size = CalculatePadding(value.size()); Write32(value.size()); diff --git a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp index d517e29f3369..dd5be21cd164 100644 --- a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp +++ b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp @@ -101,10 +101,10 @@ FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetResourceValue( } Result<FabricatedOverlay> FabricatedOverlay::Builder::Build() { - using ConfigMap = std::map<std::string, TargetValue>; - using EntryMap = std::map<std::string, ConfigMap>; - using TypeMap = std::map<std::string, EntryMap>; - using PackageMap = std::map<std::string, TypeMap>; + using ConfigMap = std::map<std::string, TargetValue, std::less<>>; + using EntryMap = std::map<std::string, ConfigMap, std::less<>>; + using TypeMap = std::map<std::string, EntryMap, std::less<>>; + using PackageMap = std::map<std::string, TypeMap, std::less<>>; PackageMap package_map; android::StringPool string_pool; for (const auto& res_entry : entries_) { @@ -116,8 +116,7 @@ Result<FabricatedOverlay> FabricatedOverlay::Builder::Build() { return Error("failed to parse resource name '%s'", res_entry.resource_name.c_str()); } - std::string package_name = - package_substr.empty() ? target_package_name_ : package_substr.to_string(); + std::string_view package_name = package_substr.empty() ? target_package_name_ : package_substr; if (type_name.empty()) { return Error("resource name '%s' missing type name", res_entry.resource_name.c_str()); } @@ -133,17 +132,14 @@ Result<FabricatedOverlay> FabricatedOverlay::Builder::Build() { .first; } - auto type = package->second.find(type_name.to_string()); + auto type = package->second.find(type_name); if (type == package->second.end()) { - type = - package->second - .insert(std::make_pair(type_name.to_string(), EntryMap())) - .first; + type = package->second.insert(std::make_pair(type_name, EntryMap())).first; } - auto entry = type->second.find(entry_name.to_string()); + auto entry = type->second.find(entry_name); if (entry == type->second.end()) { - entry = type->second.insert(std::make_pair(entry_name.to_string(), ConfigMap())).first; + entry = type->second.insert(std::make_pair(entry_name, ConfigMap())).first; } auto value = entry->second.find(res_entry.configuration); diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp index 813dff1c141c..7c0b937122c7 100644 --- a/cmds/idmap2/libidmap2/Idmap.cpp +++ b/cmds/idmap2/libidmap2/Idmap.cpp @@ -317,7 +317,7 @@ Result<std::unique_ptr<const IdmapData>> IdmapData::FromResourceMapping( } std::unique_ptr<IdmapData> data(new IdmapData()); - data->string_pool_data_ = resource_mapping.GetStringPoolData().to_string(); + data->string_pool_data_ = std::string(resource_mapping.GetStringPoolData()); uint32_t inline_value_count = 0; std::set<std::string> config_set; for (const auto& mapping : resource_mapping.GetTargetToOverlayMap()) { diff --git a/cmds/idmap2/libidmap2/PolicyUtils.cpp b/cmds/idmap2/libidmap2/PolicyUtils.cpp index 4e3f54d2583e..76c70cab6296 100644 --- a/cmds/idmap2/libidmap2/PolicyUtils.cpp +++ b/cmds/idmap2/libidmap2/PolicyUtils.cpp @@ -53,7 +53,7 @@ std::vector<std::string> BitmaskToPolicies(const PolicyBitmask& bitmask) { for (const auto& policy : kPolicyStringToFlag) { if ((bitmask & policy.second) != 0) { - policies.emplace_back(policy.first.to_string()); + policies.emplace_back(policy.first); } } diff --git a/core/api/current.txt b/core/api/current.txt index dab32d25deab..a03a753d7cef 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -1041,6 +1041,7 @@ package android { field public static final int max = 16843062; // 0x1010136 field public static final int maxAspectRatio = 16844128; // 0x1010560 field public static final int maxButtonHeight = 16844029; // 0x10104fd + field public static final int maxConcurrentSessionsCount; field public static final int maxDate = 16843584; // 0x1010340 field public static final int maxEms = 16843095; // 0x1010157 field public static final int maxHeight = 16843040; // 0x1010120 @@ -11663,6 +11664,7 @@ package android.content.pm { public class PackageInstaller { method public void abandonSession(int); + method public void checkInstallConstraints(@NonNull java.util.List<java.lang.String>, @NonNull android.content.pm.PackageInstaller.InstallConstraints, @NonNull java.util.function.Consumer<android.content.pm.PackageInstaller.InstallConstraintsResult>); method public int createSession(@NonNull android.content.pm.PackageInstaller.SessionParams) throws java.io.IOException; method @Deprecated @Nullable public android.content.pm.PackageInstaller.SessionInfo getActiveStagedSession(); method @NonNull public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getActiveStagedSessions(); @@ -11707,6 +11709,35 @@ package android.content.pm { field public static final int STATUS_SUCCESS = 0; // 0x0 } + public static final class PackageInstaller.InstallConstraints implements android.os.Parcelable { + method public int describeContents(); + method public boolean isRequireAppNotForeground(); + method public boolean isRequireAppNotInteracting(); + method public boolean isRequireAppNotTopVisible(); + method public boolean isRequireDeviceIdle(); + method public boolean isRequireNotInCall(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.InstallConstraints> CREATOR; + field @NonNull public static final android.content.pm.PackageInstaller.InstallConstraints GENTLE_UPDATE; + } + + public static final class PackageInstaller.InstallConstraints.Builder { + ctor public PackageInstaller.InstallConstraints.Builder(); + method @NonNull public android.content.pm.PackageInstaller.InstallConstraints build(); + method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotForeground(); + method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotInteracting(); + method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotTopVisible(); + method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireDeviceIdle(); + method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireNotInCall(); + } + + public static final class PackageInstaller.InstallConstraintsResult implements android.os.Parcelable { + method public int describeContents(); + method public boolean isAllConstraintsSatisfied(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.InstallConstraintsResult> CREATOR; + } + public static final class PackageInstaller.PreapprovalDetails implements android.os.Parcelable { method public int describeContents(); method @Nullable public android.graphics.Bitmap getIcon(); @@ -11735,6 +11766,7 @@ package android.content.pm { method @NonNull public int[] getChildSessionIds(); method @NonNull public String[] getNames() throws java.io.IOException; method public int getParentSessionId(); + method public boolean isKeepApplicationEnabledSetting(); method public boolean isMultiPackage(); method public boolean isStaged(); method @NonNull public java.io.InputStream openRead(@NonNull String) throws java.io.IOException; @@ -11786,6 +11818,7 @@ package android.content.pm { method public boolean hasParentSessionId(); method public boolean isActive(); method public boolean isCommitted(); + method public boolean isKeepApplicationEnabledSetting(); method public boolean isMultiPackage(); method public boolean isSealed(); method public boolean isStaged(); @@ -11818,6 +11851,7 @@ package android.content.pm { method public void setInstallLocation(int); method public void setInstallReason(int); method public void setInstallScenario(int); + method public void setKeepApplicationEnabledSetting(); method public void setMultiPackage(); method public void setOriginatingUid(int); method public void setOriginatingUri(@Nullable android.net.Uri); @@ -35877,6 +35911,7 @@ package android.provider { method public static boolean canDrawOverlays(android.content.Context); field public static final String ACTION_ACCESSIBILITY_SETTINGS = "android.settings.ACCESSIBILITY_SETTINGS"; field public static final String ACTION_ADD_ACCOUNT = "android.settings.ADD_ACCOUNT_SETTINGS"; + field public static final String ACTION_ADVANCED_MEMORY_PROTECTION_SETTINGS = "android.settings.ADVANCED_MEMORY_PROTECTION_SETTINGS"; field public static final String ACTION_AIRPLANE_MODE_SETTINGS = "android.settings.AIRPLANE_MODE_SETTINGS"; field public static final String ACTION_ALL_APPS_NOTIFICATION_SETTINGS = "android.settings.ALL_APPS_NOTIFICATION_SETTINGS"; field public static final String ACTION_APN_SETTINGS = "android.settings.APN_SETTINGS"; @@ -35925,7 +35960,6 @@ package android.provider { field public static final String ACTION_MANAGE_UNKNOWN_APP_SOURCES = "android.settings.MANAGE_UNKNOWN_APP_SOURCES"; field public static final String ACTION_MANAGE_WRITE_SETTINGS = "android.settings.action.MANAGE_WRITE_SETTINGS"; field public static final String ACTION_MEMORY_CARD_SETTINGS = "android.settings.MEMORY_CARD_SETTINGS"; - field public static final String ACTION_MEMTAG_SETTINGS = "android.settings.MEMTAG_SETTINGS"; field public static final String ACTION_NETWORK_OPERATOR_SETTINGS = "android.settings.NETWORK_OPERATOR_SETTINGS"; field public static final String ACTION_NFCSHARING_SETTINGS = "android.settings.NFCSHARING_SETTINGS"; field public static final String ACTION_NFC_PAYMENT_SETTINGS = "android.settings.NFC_PAYMENT_SETTINGS"; @@ -42007,6 +42041,7 @@ package android.telephony { field public static final String KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL = "ignore_sim_network_locked_events_bool"; field public static final String KEY_IMS_CONFERENCE_SIZE_LIMIT_INT = "ims_conference_size_limit_int"; field public static final String KEY_IMS_DTMF_TONE_DELAY_INT = "ims_dtmf_tone_delay_int"; + field public static final String KEY_INCLUDE_LTE_FOR_NR_ADVANCED_THRESHOLD_BANDWIDTH_BOOL = "include_lte_for_nr_advanced_threshold_bandwidth_bool"; field public static final String KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL = "is_ims_conference_size_enforced_bool"; field public static final String KEY_IS_OPPORTUNISTIC_SUBSCRIPTION_BOOL = "is_opportunistic_subscription_bool"; field public static final String KEY_LTE_ENABLED_BOOL = "lte_enabled_bool"; diff --git a/core/api/system-current.txt b/core/api/system-current.txt index af188274a3eb..c10504d9ee05 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -2964,6 +2964,7 @@ package android.companion.virtual { method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int); method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int); method public int getDeviceId(); + method @Nullable public android.companion.virtual.sensor.VirtualSensor getVirtualSensor(int, @NonNull String); method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer); method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener); method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean); @@ -2981,6 +2982,7 @@ package android.companion.virtual { method public int getLockState(); method @Nullable public String getName(); method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts(); + method @NonNull public java.util.List<android.companion.virtual.sensor.VirtualSensorConfig> getVirtualSensorConfigs(); method public void writeToParcel(@NonNull android.os.Parcel, int); field public static final int ACTIVITY_POLICY_DEFAULT_ALLOWED = 0; // 0x0 field public static final int ACTIVITY_POLICY_DEFAULT_BLOCKED = 1; // 0x1 @@ -2997,6 +2999,7 @@ package android.companion.virtual { public static final class VirtualDeviceParams.Builder { ctor public VirtualDeviceParams.Builder(); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder addDevicePolicy(int, int); + method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder addVirtualSensorConfig(@NonNull android.companion.virtual.sensor.VirtualSensorConfig); method @NonNull public android.companion.virtual.VirtualDeviceParams build(); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedActivities(@NonNull java.util.Set<android.content.ComponentName>); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>); @@ -3054,6 +3057,50 @@ package android.companion.virtual.audio { } +package android.companion.virtual.sensor { + + public class VirtualSensor { + method @NonNull public String getName(); + method public int getType(); + method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendSensorEvent(@NonNull android.companion.virtual.sensor.VirtualSensorEvent); + } + + public static interface VirtualSensor.SensorStateChangeCallback { + method public void onStateChanged(boolean, @NonNull java.time.Duration, @NonNull java.time.Duration); + } + + public final class VirtualSensorConfig implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public String getName(); + method public int getType(); + method @Nullable public String getVendor(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.sensor.VirtualSensorConfig> CREATOR; + } + + public static final class VirtualSensorConfig.Builder { + ctor public VirtualSensorConfig.Builder(int, @NonNull String); + method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig build(); + method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setStateChangeCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.sensor.VirtualSensor.SensorStateChangeCallback); + method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setVendor(@Nullable String); + } + + public final class VirtualSensorEvent implements android.os.Parcelable { + method public int describeContents(); + method public long getTimestampNanos(); + method @NonNull public float[] getValues(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.sensor.VirtualSensorEvent> CREATOR; + } + + public static final class VirtualSensorEvent.Builder { + ctor public VirtualSensorEvent.Builder(@NonNull float[]); + method @NonNull public android.companion.virtual.sensor.VirtualSensorEvent build(); + method @NonNull public android.companion.virtual.sensor.VirtualSensorEvent.Builder setTimestampNanos(long); + } + +} + package android.content { public class ApexEnvironment { @@ -5534,6 +5581,7 @@ package android.hardware.usb { method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void setCurrentFunctions(long); field @RequiresPermission(android.Manifest.permission.MANAGE_USB) public static final String ACTION_USB_ACCESSORY_HANDSHAKE = "android.hardware.usb.action.USB_ACCESSORY_HANDSHAKE"; field @RequiresPermission(android.Manifest.permission.MANAGE_USB) public static final String ACTION_USB_PORT_CHANGED = "android.hardware.usb.action.USB_PORT_CHANGED"; + field @RequiresPermission(android.Manifest.permission.MANAGE_USB) public static final String ACTION_USB_PORT_COMPLIANCE_CHANGED = "android.hardware.usb.action.USB_PORT_COMPLIANCE_CHANGED"; field public static final String ACTION_USB_STATE = "android.hardware.usb.action.USB_STATE"; field public static final String EXTRA_ACCESSORY_HANDSHAKE_END = "android.hardware.usb.extra.ACCESSORY_HANDSHAKE_END"; field public static final String EXTRA_ACCESSORY_START = "android.hardware.usb.extra.ACCESSORY_START"; @@ -5561,6 +5609,7 @@ package android.hardware.usb { method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USB) public android.hardware.usb.UsbPortStatus getStatus(); method @CheckResult @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void resetUsbPort(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void setRoles(int, int); + method @CheckResult @RequiresPermission(android.Manifest.permission.MANAGE_USB) public boolean supportsComplianceWarnings(); field public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_INTERNAL = 1; // 0x1 field public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_NOT_SUPPORTED = 2; // 0x2 field public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_OTHER = 4; // 0x4 @@ -5586,6 +5635,7 @@ package android.hardware.usb { public final class UsbPortStatus implements android.os.Parcelable { method public int describeContents(); + method @CheckResult @NonNull public int[] getComplianceWarnings(); method public int getCurrentDataRole(); method public int getCurrentMode(); method public int getCurrentPowerRole(); @@ -5596,6 +5646,10 @@ package android.hardware.usb { method public boolean isPowerTransferLimited(); method public boolean isRoleCombinationSupported(int, int); method public void writeToParcel(android.os.Parcel, int); + field public static final int COMPLIANCE_WARNING_BC_1_2 = 3; // 0x3 + field public static final int COMPLIANCE_WARNING_DEBUG_ACCESSORY = 2; // 0x2 + field public static final int COMPLIANCE_WARNING_MISSING_RP = 4; // 0x4 + field public static final int COMPLIANCE_WARNING_OTHER = 1; // 0x1 field @NonNull public static final android.os.Parcelable.Creator<android.hardware.usb.UsbPortStatus> CREATOR; field public static final int DATA_ROLE_DEVICE = 2; // 0x2 field public static final int DATA_ROLE_HOST = 1; // 0x1 @@ -6474,7 +6528,6 @@ package android.media { field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_MASTER = 1; // 0x1 field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_STREAM_MUTED = 4; // 0x4 field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_STREAM_VOLUME = 2; // 0x2 - field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_UNKNOWN = -1; // 0xffffffff field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_VOLUME_SHAPER = 32; // 0x20 field public static final int PLAYER_STATE_IDLE = 1; // 0x1 field public static final int PLAYER_STATE_PAUSED = 3; // 0x3 @@ -10043,6 +10096,7 @@ package android.os { method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.os.NewUserResponse createUser(@NonNull android.os.NewUserRequest); method @NonNull public java.util.List<android.os.UserHandle> getAllProfiles(); method @NonNull public java.util.List<android.os.UserHandle> getEnabledProfiles(); + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public android.os.UserHandle getMainUser(); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public android.os.UserHandle getProfileParent(@NonNull android.os.UserHandle); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public int getRemainingCreatableProfileCount(@NonNull String); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public int getRemainingCreatableUserCount(@NonNull String); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 121741e07d43..725cef6d1344 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -459,6 +459,8 @@ package android.app { public class WallpaperManager { method @Nullable public android.graphics.Bitmap getBitmap(); + method @Nullable public android.graphics.Rect peekBitmapDimensions(); + method @Nullable public android.graphics.Rect peekBitmapDimensions(int); method public boolean shouldEnableWideColorGamut(); method @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public boolean wallpaperSupportsWcg(int); } @@ -1249,6 +1251,7 @@ package android.hardware.display { field public static final int SWITCHING_TYPE_NONE = 0; // 0x0 field public static final int SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY = 3; // 0x3 field public static final int SWITCHING_TYPE_WITHIN_GROUPS = 1; // 0x1 + field public static final int VIRTUAL_DISPLAY_FLAG_OWN_FOCUS = 16384; // 0x4000 field public static final int VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 512; // 0x200 } @@ -1905,6 +1908,7 @@ package android.os { } public abstract class VibrationEffect implements android.os.Parcelable { + method @Nullable public abstract long[] computeCreateWaveformOffOnTimingsOrNull(); method public static android.os.VibrationEffect get(int); method public static android.os.VibrationEffect get(int, boolean); method @Nullable public static android.os.VibrationEffect get(android.net.Uri, android.content.Context); @@ -1919,6 +1923,7 @@ package android.os { } public static final class VibrationEffect.Composed extends android.os.VibrationEffect { + method @Nullable public long[] computeCreateWaveformOffOnTimingsOrNull(); method public long getDuration(); method public int getRepeatIndex(); method @NonNull public java.util.List<android.os.vibrator.VibrationEffectSegment> getSegments(); diff --git a/core/java/android/accounts/ChooseTypeAndAccountActivity.java b/core/java/android/accounts/ChooseTypeAndAccountActivity.java index 4d4a4d78ac81..e447d86f9793 100644 --- a/core/java/android/accounts/ChooseTypeAndAccountActivity.java +++ b/core/java/android/accounts/ChooseTypeAndAccountActivity.java @@ -402,7 +402,7 @@ public class ChooseTypeAndAccountActivity extends Activity mExistingAccounts = AccountManager.get(this).getAccountsForPackage(mCallingPackage, mCallingUid); intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK); - startActivityForResult(intent, REQUEST_ADD_ACCOUNT); + startActivityForResult(new Intent(intent), REQUEST_ADD_ACCOUNT); return; } } catch (OperationCanceledException e) { diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java index f25e639a9c80..9d5c01af1a02 100644 --- a/core/java/android/app/BroadcastOptions.java +++ b/core/java/android/app/BroadcastOptions.java @@ -767,11 +767,11 @@ public class BroadcastOptions extends ComponentOptions { */ @SystemApi public void setDeliveryGroupMatchingKey(@NonNull String namespace, @NonNull String key) { - Preconditions.checkArgument(!namespace.contains("/"), - "namespace should not contain '/'"); - Preconditions.checkArgument(!key.contains("/"), - "key should not contain '/'"); - mDeliveryGroupMatchingKey = namespace + "/" + key; + Preconditions.checkArgument(!namespace.contains(":"), + "namespace should not contain ':'"); + Preconditions.checkArgument(!key.contains(":"), + "key should not contain ':'"); + mDeliveryGroupMatchingKey = namespace + ":" + key; } /** @@ -779,7 +779,7 @@ public class BroadcastOptions extends ComponentOptions { * broadcast belongs to. * * @return the delivery group namespace and key that was previously set using - * {@link #setDeliveryGroupMatchingKey(String, String)}, concatenated with a {@code /}. + * {@link #setDeliveryGroupMatchingKey(String, String)}, concatenated with a {@code :}. * @hide */ @SystemApi diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java index 9bf8550df6d0..63fdc2e1b686 100644 --- a/core/java/android/app/ForegroundServiceTypePolicy.java +++ b/core/java/android/app/ForegroundServiceTypePolicy.java @@ -48,6 +48,7 @@ import android.compat.annotation.Disabled; import android.compat.annotation.EnabledAfter; import android.compat.annotation.Overridable; import android.content.Context; +import android.content.PermissionChecker; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.content.pm.ServiceInfo.ForegroundServiceType; @@ -879,7 +880,8 @@ public abstract class ForegroundServiceTypePolicy { int checkPermission(@NonNull Context context, @NonNull String name, int callerUid, int callerPid, String packageName, boolean allowWhileInUse) { // Simple case, check if it's already granted. - if (context.checkPermission(name, callerPid, callerUid) == PERMISSION_GRANTED) { + if (PermissionChecker.checkPermissionForPreflight(context, name, + callerPid, callerUid, packageName) == PERMISSION_GRANTED) { return PERMISSION_GRANTED; } if (allowWhileInUse) { diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index e54a0841dd26..5d87012ec7e7 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -58,6 +58,7 @@ import android.companion.CompanionDeviceManager; import android.companion.ICompanionDeviceManager; import android.companion.virtual.IVirtualDeviceManager; import android.companion.virtual.VirtualDeviceManager; +import android.compat.Compatibility; import android.content.ClipboardManager; import android.content.ContentCaptureOptions; import android.content.Context; @@ -1092,7 +1093,10 @@ public final class SystemServiceRegistry { new CachedServiceFetcher<OverlayManager>() { @Override public OverlayManager createService(ContextImpl ctx) throws ServiceNotFoundException { - IBinder b = ServiceManager.getServiceOrThrow(Context.OVERLAY_SERVICE); + final IBinder b = + (Compatibility.isChangeEnabled(OverlayManager.SELF_TARGETING_OVERLAY)) + ? ServiceManager.getService(Context.OVERLAY_SERVICE) + : ServiceManager.getServiceOrThrow(Context.OVERLAY_SERVICE); return new OverlayManager(ctx, IOverlayManager.Stub.asInterface(b)); }}); diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING index ef10c0b245d1..f133c8aa5899 100644 --- a/core/java/android/app/TEST_MAPPING +++ b/core/java/android/app/TEST_MAPPING @@ -201,6 +201,23 @@ "file_patterns": [ "(/|^)PropertyInvalidatedCache.java" ] + }, + { + "name": "FrameworksCoreGameManagerTests", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "include-filter": "android.app" + } + ], + "file_patterns": [ + "(/|^)GameManager[^/]*", "(/|^)GameMode[^/]*" + ] } ], "presubmit-large": [ diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 8685259217c5..f5d657c4b388 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -358,13 +358,37 @@ public class WallpaperManager { } } + /** + * Convenience class representing a cached wallpaper bitmap and associated data. + */ + private static class CachedWallpaper { + final Bitmap mCachedWallpaper; + final int mCachedWallpaperUserId; + @SetWallpaperFlags final int mWhich; + + CachedWallpaper(Bitmap cachedWallpaper, int cachedWallpaperUserId, + @SetWallpaperFlags int which) { + mCachedWallpaper = cachedWallpaper; + mCachedWallpaperUserId = cachedWallpaperUserId; + mWhich = which; + } + + /** + * Returns true if this object represents a valid cached bitmap for the given parameters, + * otherwise false. + */ + boolean isValid(int userId, @SetWallpaperFlags int which) { + return userId == mCachedWallpaperUserId && which == mWhich + && !mCachedWallpaper.isRecycled(); + } + } + private static class Globals extends IWallpaperManagerCallback.Stub { private final IWallpaperManager mService; private boolean mColorCallbackRegistered; private final ArrayList<Pair<OnColorsChangedListener, Handler>> mColorListeners = new ArrayList<>(); - private Bitmap mCachedWallpaper; - private int mCachedWallpaperUserId; + private CachedWallpaper mCachedWallpaper; private Bitmap mDefaultWallpaper; private Handler mMainLooperHandler; private ArrayMap<LocalWallpaperColorConsumer, ArraySet<RectF>> mLocalColorCallbackAreas = @@ -536,6 +560,15 @@ public class WallpaperManager { false /* hardware */, cmProxy); } + /** + * Retrieves the current wallpaper Bitmap, caching the result. If this fails and + * `returnDefault` is set, returns the Bitmap for the default wallpaper; otherwise returns + * null. + * + * More sophisticated caching might a) store and compare the wallpaper ID so that + * consecutive calls for FLAG_SYSTEM and FLAG_LOCK could return the cached wallpaper if + * no lock screen wallpaper is set, or b) separately cache home and lock screen wallpaper. + */ public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault, @SetWallpaperFlags int which, int userId, boolean hardware, ColorManagementProxy cmProxy) { @@ -549,16 +582,14 @@ public class WallpaperManager { } } synchronized (this) { - if (mCachedWallpaper != null && mCachedWallpaperUserId == userId - && !mCachedWallpaper.isRecycled()) { - return mCachedWallpaper; + if (mCachedWallpaper != null && mCachedWallpaper.isValid(userId, which)) { + return mCachedWallpaper.mCachedWallpaper; } mCachedWallpaper = null; - mCachedWallpaperUserId = 0; + Bitmap currentWallpaper = null; try { - mCachedWallpaper = getCurrentWallpaperLocked( - context, userId, hardware, cmProxy); - mCachedWallpaperUserId = userId; + currentWallpaper = getCurrentWallpaperLocked( + context, which, userId, hardware, cmProxy); } catch (OutOfMemoryError e) { Log.w(TAG, "Out of memory loading the current wallpaper: " + e); } catch (SecurityException e) { @@ -570,8 +601,9 @@ public class WallpaperManager { throw e; } } - if (mCachedWallpaper != null) { - return mCachedWallpaper; + if (currentWallpaper != null) { + mCachedWallpaper = new CachedWallpaper(currentWallpaper, userId, which); + return currentWallpaper; } } if (returnDefault) { @@ -587,7 +619,9 @@ public class WallpaperManager { return null; } - public Rect peekWallpaperDimensions(Context context, boolean returnDefault, int userId) { + @Nullable + public Rect peekWallpaperDimensions(Context context, boolean returnDefault, + @SetWallpaperFlags int which, int userId) { if (mService != null) { try { if (!mService.isWallpaperSupported(context.getOpPackageName())) { @@ -600,11 +634,10 @@ public class WallpaperManager { Rect dimensions = null; synchronized (this) { - ParcelFileDescriptor pfd = null; - try { - Bundle params = new Bundle(); - pfd = mService.getWallpaperWithFeature(context.getOpPackageName(), - context.getAttributionTag(), this, FLAG_SYSTEM, params, userId); + Bundle params = new Bundle(); + try (ParcelFileDescriptor pfd = mService.getWallpaperWithFeature( + context.getOpPackageName(), context.getAttributionTag(), this, which, + params, userId)) { // Let's peek user wallpaper first. if (pfd != null) { BitmapFactory.Options options = new BitmapFactory.Options(); @@ -614,19 +647,14 @@ public class WallpaperManager { } } catch (RemoteException ex) { Log.w(TAG, "peek wallpaper dimensions failed", ex); - } finally { - if (pfd != null) { - try { - pfd.close(); - } catch (IOException ignored) { - } - } + } catch (IOException ignored) { + // This is only thrown on close and can be safely ignored. } } // If user wallpaper is unavailable, may be the default one instead. if ((dimensions == null || dimensions.width() == 0 || dimensions.height() == 0) && returnDefault) { - InputStream is = openDefaultWallpaper(context, FLAG_SYSTEM); + InputStream is = openDefaultWallpaper(context, which); if (is != null) { try { BitmapFactory.Options options = new BitmapFactory.Options(); @@ -644,13 +672,12 @@ public class WallpaperManager { void forgetLoadedWallpaper() { synchronized (this) { mCachedWallpaper = null; - mCachedWallpaperUserId = 0; mDefaultWallpaper = null; } } - private Bitmap getCurrentWallpaperLocked(Context context, int userId, boolean hardware, - ColorManagementProxy cmProxy) { + private Bitmap getCurrentWallpaperLocked(Context context, @SetWallpaperFlags int which, + int userId, boolean hardware, ColorManagementProxy cmProxy) { if (mService == null) { Log.w(TAG, "WallpaperService not running"); return null; @@ -659,7 +686,7 @@ public class WallpaperManager { try { Bundle params = new Bundle(); ParcelFileDescriptor pfd = mService.getWallpaperWithFeature( - context.getOpPackageName(), context.getAttributionTag(), this, FLAG_SYSTEM, + context.getOpPackageName(), context.getAttributionTag(), this, which, params, userId); if (pfd != null) { @@ -1148,10 +1175,10 @@ public class WallpaperManager { * @return the dimensions of system wallpaper * @hide */ + @TestApi @Nullable public Rect peekBitmapDimensions() { - return sGlobals.peekWallpaperDimensions( - mContext, true /* returnDefault */, mContext.getUserId()); + return peekBitmapDimensions(FLAG_SYSTEM); } /** @@ -1162,9 +1189,12 @@ public class WallpaperManager { * @return the dimensions of system wallpaper * @hide */ + @TestApi @Nullable public Rect peekBitmapDimensions(@SetWallpaperFlags int which) { - return peekBitmapDimensions(); + checkExactlyOneWallpaperFlagSet(which); + return sGlobals.peekWallpaperDimensions(mContext, true /* returnDefault */, which, + mContext.getUserId()); } /** diff --git a/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java b/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java index ec10d8431e74..6b5e66758d94 100644 --- a/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java +++ b/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java @@ -19,6 +19,7 @@ package android.app.time; import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING; import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED; import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING; +import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_UNKNOWN; import static android.app.time.DetectorStatusTypes.detectionAlgorithmStatusFromString; import static android.app.time.DetectorStatusTypes.detectionAlgorithmStatusToString; import static android.app.time.DetectorStatusTypes.requireValidDetectionAlgorithmStatus; @@ -319,6 +320,40 @@ public final class LocationTimeZoneAlgorithmStatus implements Parcelable { mSecondaryProviderStatus, mSecondaryProviderReportedStatus); } + /** + * Returns {@code true} if the algorithm status could allow the time zone detector to enter + * telephony fallback mode. + */ + public boolean couldEnableTelephonyFallback() { + if (mStatus == DETECTION_ALGORITHM_STATUS_UNKNOWN + || mStatus == DETECTION_ALGORITHM_STATUS_NOT_RUNNING + || mStatus == DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED) { + // This method is not expected to be called on objects with these statuses. Fallback + // should not be enabled if it is. + return false; + } + + // mStatus == DETECTOR_STATUS_RUNNING. + + boolean primarySuggestsFallback = false; + if (mPrimaryProviderStatus == PROVIDER_STATUS_NOT_PRESENT) { + primarySuggestsFallback = true; + } else if (mPrimaryProviderStatus == PROVIDER_STATUS_IS_UNCERTAIN + && mPrimaryProviderReportedStatus != null) { + primarySuggestsFallback = mPrimaryProviderReportedStatus.couldEnableTelephonyFallback(); + } + + boolean secondarySuggestsFallback = false; + if (mSecondaryProviderStatus == PROVIDER_STATUS_NOT_PRESENT) { + secondarySuggestsFallback = true; + } else if (mSecondaryProviderStatus == PROVIDER_STATUS_IS_UNCERTAIN + && mSecondaryProviderReportedStatus != null) { + secondarySuggestsFallback = + mSecondaryProviderReportedStatus.couldEnableTelephonyFallback(); + } + return primarySuggestsFallback && secondarySuggestsFallback; + } + /** @hide */ @VisibleForTesting @NonNull diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl index 295d69d4b27d..0837d85bb91c 100644 --- a/core/java/android/companion/virtual/IVirtualDevice.aidl +++ b/core/java/android/companion/virtual/IVirtualDevice.aidl @@ -19,6 +19,9 @@ package android.companion.virtual; import android.app.PendingIntent; import android.companion.virtual.audio.IAudioConfigChangedCallback; import android.companion.virtual.audio.IAudioRoutingCallback; +import android.companion.virtual.sensor.IVirtualSensorStateChangeCallback; +import android.companion.virtual.sensor.VirtualSensorConfig; +import android.companion.virtual.sensor.VirtualSensorEvent; import android.graphics.Point; import android.graphics.PointF; import android.hardware.input.VirtualKeyEvent; @@ -97,6 +100,24 @@ interface IVirtualDevice { boolean sendTouchEvent(IBinder token, in VirtualTouchEvent event); /** + * Creates a virtual sensor, capable of injecting sensor events into the system. + */ + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)") + void createVirtualSensor(IBinder tokenm, in VirtualSensorConfig config); + + /** + * Removes the sensor corresponding to the given token from the system. + */ + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)") + void unregisterSensor(IBinder token); + + /** + * Sends an event to the virtual sensor corresponding to the given token. + */ + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)") + boolean sendSensorEvent(IBinder token, in VirtualSensorEvent event); + + /** * Launches a pending intent on the given display that is owned by this virtual device. */ void launchPendingIntent( diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index 91547016df8a..01b42bfa661c 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -22,12 +22,15 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SdkConstant; import android.annotation.SystemApi; import android.annotation.SystemService; import android.app.PendingIntent; import android.companion.AssociationInfo; import android.companion.virtual.audio.VirtualAudioDevice; import android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback; +import android.companion.virtual.sensor.VirtualSensor; +import android.companion.virtual.sensor.VirtualSensorConfig; import android.content.ComponentName; import android.content.Context; import android.graphics.Point; @@ -58,6 +61,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.concurrent.Executor; import java.util.function.IntConsumer; @@ -76,7 +80,8 @@ public final class VirtualDeviceManager { | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL | DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH - | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP; + | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP + | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS; /** * The default device ID, which is the ID of the primary (non-virtual) device. @@ -88,6 +93,26 @@ public final class VirtualDeviceManager { */ public static final int INVALID_DEVICE_ID = -1; + /** + * Broadcast Action: A Virtual Device was removed. + * + * <p class="note">This is a protected intent that can only be sent by the system.</p> + * + * @hide + */ + @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_VIRTUAL_DEVICE_REMOVED = + "android.companion.virtual.action.VIRTUAL_DEVICE_REMOVED"; + + /** + * Int intent extra to be used with {@link #ACTION_VIRTUAL_DEVICE_REMOVED}. + * Contains the identifier of the virtual device, which was removed. + * + * @hide + */ + public static final String EXTRA_VIRTUAL_DEVICE_ID = + "android.companion.virtual.extra.VIRTUAL_DEVICE_ID"; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef( @@ -250,7 +275,10 @@ public final class VirtualDeviceManager { }; @Nullable private VirtualAudioDevice mVirtualAudioDevice; + @NonNull + private List<VirtualSensor> mVirtualSensors = new ArrayList<>(); + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) private VirtualDevice( IVirtualDeviceManager service, Context context, @@ -264,6 +292,10 @@ public final class VirtualDeviceManager { associationId, params, mActivityListenerBinder); + final List<VirtualSensorConfig> virtualSensorConfigs = params.getVirtualSensorConfigs(); + for (int i = 0; i < virtualSensorConfigs.size(); ++i) { + mVirtualSensors.add(createVirtualSensor(virtualSensorConfigs.get(i))); + } } /** @@ -278,6 +310,23 @@ public final class VirtualDeviceManager { } /** + * Returns this device's sensor with the given type and name, if any. + * + * @see VirtualDeviceParams.Builder#addVirtualSensorConfig + * + * @param type The type of the sensor. + * @param name The name of the sensor. + * @return The matching sensor if found, {@code null} otherwise. + */ + @Nullable + public VirtualSensor getVirtualSensor(int type, @NonNull String name) { + return mVirtualSensors.stream() + .filter(sensor -> sensor.getType() == type && sensor.getName().equals(name)) + .findAny() + .orElse(null); + } + + /** * Launches a given pending intent on the give display ID. * * @param displayId The display to launch the pending intent on. This display must be @@ -437,6 +486,7 @@ public final class VirtualDeviceManager { @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close() { try { + // This also takes care of unregistering all virtual sensors. mVirtualDevice.close(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -622,6 +672,28 @@ public final class VirtualDeviceManager { } /** + * Creates a virtual sensor, capable of injecting sensor events into the system. Only for + * internal use, since device sensors must remain valid for the entire lifetime of the + * device. + * + * @param config The configuration of the sensor. + * @hide + */ + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + @NonNull + public VirtualSensor createVirtualSensor(@NonNull VirtualSensorConfig config) { + Objects.requireNonNull(config); + try { + final IBinder token = new Binder( + "android.hardware.sensor.VirtualSensor:" + config.getName()); + mVirtualDevice.createVirtualSensor(token, config); + return new VirtualSensor(config.getType(), config.getName(), mVirtualDevice, token); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Adds an activity listener to listen for events such as top activity change or virtual * display task stack became empty. * diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java index f8c2e34a0e2a..bad26c6ed10d 100644 --- a/core/java/android/companion/virtual/VirtualDeviceParams.java +++ b/core/java/android/companion/virtual/VirtualDeviceParams.java @@ -23,20 +23,22 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; +import android.companion.virtual.sensor.VirtualSensorConfig; import android.content.ComponentName; import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; import android.util.ArraySet; +import android.util.SparseArray; import android.util.SparseIntArray; -import com.android.internal.util.Preconditions; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; import java.util.Objects; import java.util.Set; @@ -158,6 +160,7 @@ public final class VirtualDeviceParams implements Parcelable { @Nullable private final String mName; // Mapping of @PolicyType to @DevicePolicy @NonNull private final SparseIntArray mDevicePolicies; + @NonNull private final List<VirtualSensorConfig> mVirtualSensorConfigs; private VirtualDeviceParams( @LockState int lockState, @@ -169,24 +172,22 @@ public final class VirtualDeviceParams implements Parcelable { @NonNull Set<ComponentName> blockedActivities, @ActivityPolicy int defaultActivityPolicy, @Nullable String name, - @NonNull SparseIntArray devicePolicies) { - Preconditions.checkNotNull(usersWithMatchingAccounts); - Preconditions.checkNotNull(allowedCrossTaskNavigations); - Preconditions.checkNotNull(blockedCrossTaskNavigations); - Preconditions.checkNotNull(allowedActivities); - Preconditions.checkNotNull(blockedActivities); - Preconditions.checkNotNull(devicePolicies); - + @NonNull SparseIntArray devicePolicies, + @NonNull List<VirtualSensorConfig> virtualSensorConfigs) { mLockState = lockState; - mUsersWithMatchingAccounts = new ArraySet<>(usersWithMatchingAccounts); - mAllowedCrossTaskNavigations = new ArraySet<>(allowedCrossTaskNavigations); - mBlockedCrossTaskNavigations = new ArraySet<>(blockedCrossTaskNavigations); + mUsersWithMatchingAccounts = + new ArraySet<>(Objects.requireNonNull(usersWithMatchingAccounts)); + mAllowedCrossTaskNavigations = + new ArraySet<>(Objects.requireNonNull(allowedCrossTaskNavigations)); + mBlockedCrossTaskNavigations = + new ArraySet<>(Objects.requireNonNull(blockedCrossTaskNavigations)); mDefaultNavigationPolicy = defaultNavigationPolicy; - mAllowedActivities = new ArraySet<>(allowedActivities); - mBlockedActivities = new ArraySet<>(blockedActivities); + mAllowedActivities = new ArraySet<>(Objects.requireNonNull(allowedActivities)); + mBlockedActivities = new ArraySet<>(Objects.requireNonNull(blockedActivities)); mDefaultActivityPolicy = defaultActivityPolicy; mName = name; - mDevicePolicies = devicePolicies; + mDevicePolicies = Objects.requireNonNull(devicePolicies); + mVirtualSensorConfigs = Objects.requireNonNull(virtualSensorConfigs); } @SuppressWarnings("unchecked") @@ -201,6 +202,8 @@ public final class VirtualDeviceParams implements Parcelable { mDefaultActivityPolicy = parcel.readInt(); mName = parcel.readString8(); mDevicePolicies = parcel.readSparseIntArray(); + mVirtualSensorConfigs = new ArrayList<>(); + parcel.readTypedList(mVirtualSensorConfigs, VirtualSensorConfig.CREATOR); } /** @@ -316,6 +319,15 @@ public final class VirtualDeviceParams implements Parcelable { return mDevicePolicies.get(policyType, DEVICE_POLICY_DEFAULT); } + /** + * Returns the configurations for all sensors that should be created for this device. + * + * @see Builder#addVirtualSensorConfig + */ + public @NonNull List<VirtualSensorConfig> getVirtualSensorConfigs() { + return mVirtualSensorConfigs; + } + @Override public int describeContents() { return 0; @@ -333,6 +345,7 @@ public final class VirtualDeviceParams implements Parcelable { dest.writeInt(mDefaultActivityPolicy); dest.writeString8(mName); dest.writeSparseIntArray(mDevicePolicies); + dest.writeTypedList(mVirtualSensorConfigs); } @Override @@ -428,6 +441,7 @@ public final class VirtualDeviceParams implements Parcelable { private boolean mDefaultActivityPolicyConfigured = false; @Nullable private String mName; @NonNull private SparseIntArray mDevicePolicies = new SparseIntArray(); + @NonNull private List<VirtualSensorConfig> mVirtualSensorConfigs = new ArrayList<>(); /** * Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY} @@ -467,8 +481,7 @@ public final class VirtualDeviceParams implements Parcelable { @NonNull public Builder setUsersWithMatchingAccounts( @NonNull Set<UserHandle> usersWithMatchingAccounts) { - Preconditions.checkNotNull(usersWithMatchingAccounts); - mUsersWithMatchingAccounts = usersWithMatchingAccounts; + mUsersWithMatchingAccounts = Objects.requireNonNull(usersWithMatchingAccounts); return this; } @@ -491,7 +504,6 @@ public final class VirtualDeviceParams implements Parcelable { @NonNull public Builder setAllowedCrossTaskNavigations( @NonNull Set<ComponentName> allowedCrossTaskNavigations) { - Preconditions.checkNotNull(allowedCrossTaskNavigations); if (mDefaultNavigationPolicyConfigured && mDefaultNavigationPolicy != NAVIGATION_POLICY_DEFAULT_BLOCKED) { throw new IllegalArgumentException( @@ -500,7 +512,7 @@ public final class VirtualDeviceParams implements Parcelable { } mDefaultNavigationPolicy = NAVIGATION_POLICY_DEFAULT_BLOCKED; mDefaultNavigationPolicyConfigured = true; - mAllowedCrossTaskNavigations = allowedCrossTaskNavigations; + mAllowedCrossTaskNavigations = Objects.requireNonNull(allowedCrossTaskNavigations); return this; } @@ -523,7 +535,6 @@ public final class VirtualDeviceParams implements Parcelable { @NonNull public Builder setBlockedCrossTaskNavigations( @NonNull Set<ComponentName> blockedCrossTaskNavigations) { - Preconditions.checkNotNull(blockedCrossTaskNavigations); if (mDefaultNavigationPolicyConfigured && mDefaultNavigationPolicy != NAVIGATION_POLICY_DEFAULT_ALLOWED) { throw new IllegalArgumentException( @@ -532,7 +543,7 @@ public final class VirtualDeviceParams implements Parcelable { } mDefaultNavigationPolicy = NAVIGATION_POLICY_DEFAULT_ALLOWED; mDefaultNavigationPolicyConfigured = true; - mBlockedCrossTaskNavigations = blockedCrossTaskNavigations; + mBlockedCrossTaskNavigations = Objects.requireNonNull(blockedCrossTaskNavigations); return this; } @@ -551,7 +562,6 @@ public final class VirtualDeviceParams implements Parcelable { */ @NonNull public Builder setAllowedActivities(@NonNull Set<ComponentName> allowedActivities) { - Preconditions.checkNotNull(allowedActivities); if (mDefaultActivityPolicyConfigured && mDefaultActivityPolicy != ACTIVITY_POLICY_DEFAULT_BLOCKED) { throw new IllegalArgumentException( @@ -559,7 +569,7 @@ public final class VirtualDeviceParams implements Parcelable { } mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_BLOCKED; mDefaultActivityPolicyConfigured = true; - mAllowedActivities = allowedActivities; + mAllowedActivities = Objects.requireNonNull(allowedActivities); return this; } @@ -578,7 +588,6 @@ public final class VirtualDeviceParams implements Parcelable { */ @NonNull public Builder setBlockedActivities(@NonNull Set<ComponentName> blockedActivities) { - Preconditions.checkNotNull(blockedActivities); if (mDefaultActivityPolicyConfigured && mDefaultActivityPolicy != ACTIVITY_POLICY_DEFAULT_ALLOWED) { throw new IllegalArgumentException( @@ -586,7 +595,7 @@ public final class VirtualDeviceParams implements Parcelable { } mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_ALLOWED; mDefaultActivityPolicyConfigured = true; - mBlockedActivities = blockedActivities; + mBlockedActivities = Objects.requireNonNull(blockedActivities); return this; } @@ -621,10 +630,49 @@ public final class VirtualDeviceParams implements Parcelable { } /** + * Adds a configuration for a sensor that should be created for this virtual device. + * + * Device sensors must remain valid for the entire lifetime of the device, hence they are + * created together with the device itself, and removed when the device is removed. + * + * Requires {@link #DEVICE_POLICY_CUSTOM} to be set for {@link #POLICY_TYPE_SENSORS}. + * + * @see android.companion.virtual.sensor.VirtualSensor + * @see #addDevicePolicy + */ + @NonNull + public Builder addVirtualSensorConfig(@NonNull VirtualSensorConfig virtualSensorConfig) { + mVirtualSensorConfigs.add(Objects.requireNonNull(virtualSensorConfig)); + return this; + } + + /** * Builds the {@link VirtualDeviceParams} instance. + * + * @throws IllegalArgumentException if there's mismatch between policy definition and + * the passed parameters or if there are sensor configs with the same type and name. + * */ @NonNull public VirtualDeviceParams build() { + if (!mVirtualSensorConfigs.isEmpty() + && (mDevicePolicies.get(POLICY_TYPE_SENSORS, DEVICE_POLICY_DEFAULT) + != DEVICE_POLICY_CUSTOM)) { + throw new IllegalArgumentException( + "DEVICE_POLICY_CUSTOM for POLICY_TYPE_SENSORS is required for creating " + + "virtual sensors."); + } + SparseArray<Set<String>> sensorNameByType = new SparseArray(); + for (int i = 0; i < mVirtualSensorConfigs.size(); ++i) { + VirtualSensorConfig config = mVirtualSensorConfigs.get(i); + Set<String> sensorNames = sensorNameByType.get(config.getType(), new ArraySet<>()); + if (!sensorNames.add(config.getName())) { + throw new IllegalArgumentException( + "Sensor names must be unique for a particular sensor type."); + } + sensorNameByType.put(config.getType(), sensorNames); + } + return new VirtualDeviceParams( mLockState, mUsersWithMatchingAccounts, @@ -635,7 +683,8 @@ public final class VirtualDeviceParams implements Parcelable { mBlockedActivities, mDefaultActivityPolicy, mName, - mDevicePolicies); + mDevicePolicies, + mVirtualSensorConfigs); } } } diff --git a/core/java/android/companion/virtual/sensor/IVirtualSensorStateChangeCallback.aidl b/core/java/android/companion/virtual/sensor/IVirtualSensorStateChangeCallback.aidl new file mode 100644 index 000000000000..b99cc7eb67a5 --- /dev/null +++ b/core/java/android/companion/virtual/sensor/IVirtualSensorStateChangeCallback.aidl @@ -0,0 +1,35 @@ +/* + * 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.companion.virtual.sensor; + +/** + * Interface for notification of listener registration changes for a virtual sensor. + * + * @hide + */ +oneway interface IVirtualSensorStateChangeCallback { + + /** + * Called when the registered listeners to a virtual sensor have changed. + * + * @param enabled Whether the sensor is enabled. + * @param samplingPeriodMicros The requested sensor's sampling period in microseconds. + * @param batchReportingLatencyMicros The requested maximum time interval in microseconds + * between the delivery of two batches of sensor events. + */ + void onStateChanged(boolean enabled, int samplingPeriodMicros, int batchReportLatencyMicros); +} diff --git a/core/java/android/companion/virtual/sensor/VirtualSensor.java b/core/java/android/companion/virtual/sensor/VirtualSensor.java new file mode 100644 index 000000000000..a184481bdf99 --- /dev/null +++ b/core/java/android/companion/virtual/sensor/VirtualSensor.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 android.companion.virtual.sensor; + +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.companion.virtual.IVirtualDevice; +import android.os.IBinder; +import android.os.RemoteException; + +import java.time.Duration; + +/** + * Representation of a sensor on a remote device, capable of sending events, such as an + * accelerometer or a gyroscope. + * + * This registers the sensor device with the sensor framework as a runtime sensor. + * + * @hide + */ +@SystemApi +public class VirtualSensor { + + /** + * Interface for notification of listener registration changes for a virtual sensor. + */ + public interface SensorStateChangeCallback { + /** + * Called when the registered listeners to a virtual sensor have changed. + * + * @param enabled Whether the sensor is enabled. + * @param samplingPeriod The requested sampling period of the sensor. + * @param batchReportLatency The requested maximum time interval between the delivery of two + * batches of sensor events. + */ + void onStateChanged(boolean enabled, @NonNull Duration samplingPeriod, + @NonNull Duration batchReportLatency); + } + + private final int mType; + private final String mName; + private final IVirtualDevice mVirtualDevice; + private final IBinder mToken; + + /** + * @hide + */ + public VirtualSensor(int type, String name, IVirtualDevice virtualDevice, IBinder token) { + mType = type; + mName = name; + mVirtualDevice = virtualDevice; + mToken = token; + } + + /** + * Returns the + * <a href="https://source.android.com/devices/sensors/sensor-types">type</a> of the sensor. + */ + public int getType() { + return mType; + } + + /** + * Returns the name of the sensor. + */ + @NonNull + public String getName() { + return mName; + } + + /** + * Send a sensor event to the system. + */ + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + public void sendSensorEvent(@NonNull VirtualSensorEvent event) { + try { + mVirtualDevice.sendSensorEvent(mToken, event); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.aidl b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.aidl new file mode 100644 index 000000000000..48b463ab5d50 --- /dev/null +++ b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.aidl @@ -0,0 +1,19 @@ +/* + * 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.companion.virtual.sensor; + +parcelable VirtualSensorConfig; diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java new file mode 100644 index 000000000000..7982fa59daf3 --- /dev/null +++ b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java @@ -0,0 +1,210 @@ +/* + * 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.companion.virtual.sensor; + +import static java.util.concurrent.TimeUnit.MICROSECONDS; + +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.time.Duration; +import java.util.Objects; +import java.util.concurrent.Executor; + +/** + * Configuration for creation of a virtual sensor. + * @see VirtualSensor + * @hide + */ +@SystemApi +public final class VirtualSensorConfig implements Parcelable { + + private final int mType; + @NonNull + private final String mName; + @Nullable + private final String mVendor; + @Nullable + private final IVirtualSensorStateChangeCallback mStateChangeCallback; + + private VirtualSensorConfig(int type, @NonNull String name, @Nullable String vendor, + @Nullable IVirtualSensorStateChangeCallback stateChangeCallback) { + mType = type; + mName = name; + mVendor = vendor; + mStateChangeCallback = stateChangeCallback; + } + + private VirtualSensorConfig(@NonNull Parcel parcel) { + mType = parcel.readInt(); + mName = parcel.readString8(); + mVendor = parcel.readString8(); + mStateChangeCallback = + IVirtualSensorStateChangeCallback.Stub.asInterface(parcel.readStrongBinder()); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeInt(mType); + parcel.writeString8(mName); + parcel.writeString8(mVendor); + parcel.writeStrongBinder( + mStateChangeCallback != null ? mStateChangeCallback.asBinder() : null); + } + + /** + * Returns the + * <a href="https://source.android.com/devices/sensors/sensor-types">type</a> of the sensor. + */ + public int getType() { + return mType; + } + + /** + * Returns the name of the sensor, which must be unique per sensor type for each virtual device. + */ + @NonNull + public String getName() { + return mName; + } + + /** + * Returns the vendor string of the sensor. + * @see Builder#setVendor + */ + @Nullable + public String getVendor() { + return mVendor; + } + + /** + * Returns the callback to get notified about changes in the sensor listeners. + * @hide + */ + @Nullable + public IVirtualSensorStateChangeCallback getStateChangeCallback() { + return mStateChangeCallback; + } + + /** + * Builder for {@link VirtualSensorConfig}. + */ + public static final class Builder { + + private final int mType; + @NonNull + private final String mName; + @Nullable + private String mVendor; + @Nullable + private IVirtualSensorStateChangeCallback mStateChangeCallback; + + private static class SensorStateChangeCallbackDelegate + extends IVirtualSensorStateChangeCallback.Stub { + @NonNull + private final Executor mExecutor; + @NonNull + private final VirtualSensor.SensorStateChangeCallback mCallback; + + SensorStateChangeCallbackDelegate(@NonNull @CallbackExecutor Executor executor, + @NonNull VirtualSensor.SensorStateChangeCallback callback) { + mCallback = callback; + mExecutor = executor; + } + @Override + public void onStateChanged(boolean enabled, int samplingPeriodMicros, + int batchReportLatencyMicros) { + final Duration samplingPeriod = + Duration.ofNanos(MICROSECONDS.toNanos(samplingPeriodMicros)); + final Duration batchReportingLatency = + Duration.ofNanos(MICROSECONDS.toNanos(batchReportLatencyMicros)); + mExecutor.execute(() -> mCallback.onStateChanged( + enabled, samplingPeriod, batchReportingLatency)); + } + } + + /** + * Creates a new builder. + * + * @param type The + * <a href="https://source.android.com/devices/sensors/sensor-types">type</a> of the sensor. + * @param name The name of the sensor. Must be unique among all sensors with the same type + * that belong to the same virtual device. + */ + public Builder(int type, @NonNull String name) { + mType = type; + mName = Objects.requireNonNull(name); + } + + /** + * Creates a new {@link VirtualSensorConfig}. + */ + @NonNull + public VirtualSensorConfig build() { + return new VirtualSensorConfig(mType, mName, mVendor, mStateChangeCallback); + } + + /** + * Sets the vendor string of the sensor. + */ + @NonNull + public VirtualSensorConfig.Builder setVendor(@Nullable String vendor) { + mVendor = vendor; + return this; + } + + /** + * Sets the callback to get notified about changes in the sensor listeners. + * + * @param executor The executor where the callback is executed on. + * @param callback The callback to get notified when the state of the sensor + * listeners has changed, see {@link VirtualSensor.SensorStateChangeCallback} + */ + @SuppressLint("MissingGetterMatchingBuilder") + @NonNull + public VirtualSensorConfig.Builder setStateChangeCallback( + @NonNull @CallbackExecutor Executor executor, + @NonNull VirtualSensor.SensorStateChangeCallback callback) { + mStateChangeCallback = new SensorStateChangeCallbackDelegate( + Objects.requireNonNull(executor), + Objects.requireNonNull(callback)); + return this; + } + } + + @NonNull + public static final Parcelable.Creator<VirtualSensorConfig> CREATOR = + new Parcelable.Creator<>() { + public VirtualSensorConfig createFromParcel(Parcel source) { + return new VirtualSensorConfig(source); + } + + public VirtualSensorConfig[] newArray(int size) { + return new VirtualSensorConfig[size]; + } + }; +} diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorEvent.aidl b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.aidl new file mode 100644 index 000000000000..99439465a48f --- /dev/null +++ b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.aidl @@ -0,0 +1,19 @@ +/* + * 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.companion.virtual.sensor; + +parcelable VirtualSensorEvent;
\ No newline at end of file diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java new file mode 100644 index 000000000000..8f8860ed5e6e --- /dev/null +++ b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java @@ -0,0 +1,140 @@ +/* + * 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.companion.virtual.sensor; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.SystemClock; + + +/** + * A sensor event that originated from a virtual device's sensor. + * + * @hide + */ +@SystemApi +public final class VirtualSensorEvent implements Parcelable { + + @NonNull + private float[] mValues; + private long mTimestampNanos; + + private VirtualSensorEvent(@NonNull float[] values, long timestampNanos) { + mValues = values; + mTimestampNanos = timestampNanos; + } + + private VirtualSensorEvent(@NonNull Parcel parcel) { + final int valuesLength = parcel.readInt(); + mValues = new float[valuesLength]; + parcel.readFloatArray(mValues); + mTimestampNanos = parcel.readLong(); + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int parcelableFlags) { + parcel.writeInt(mValues.length); + parcel.writeFloatArray(mValues); + parcel.writeLong(mTimestampNanos); + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Returns the values of this sensor event. The length and contents depend on the + * <a href="https://source.android.com/devices/sensors/sensor-types">sensor type</a>. + * @see android.hardware.SensorEvent#values + */ + @NonNull + public float[] getValues() { + return mValues; + } + + /** + * The time in nanoseconds at which the event happened. For a given sensor, each new sensor + * event should be monotonically increasing. + * + * @see Builder#setTimestampNanos(long) + */ + public long getTimestampNanos() { + return mTimestampNanos; + } + + /** + * Builder for {@link VirtualSensorEvent}. + */ + public static final class Builder { + + @NonNull + private float[] mValues; + private long mTimestampNanos = 0; + + /** + * Creates a new builder. + * @param values the values of the sensor event. @see android.hardware.SensorEvent#values + */ + public Builder(@NonNull float[] values) { + mValues = values; + } + + /** + * Creates a new {@link VirtualSensorEvent}. + */ + @NonNull + public VirtualSensorEvent build() { + if (mValues == null || mValues.length == 0) { + throw new IllegalArgumentException( + "Cannot build virtual sensor event with no values."); + } + if (mTimestampNanos <= 0) { + mTimestampNanos = SystemClock.elapsedRealtimeNanos(); + } + return new VirtualSensorEvent(mValues, mTimestampNanos); + } + + /** + * Sets the timestamp of this event. For a given sensor, each new sensor event should be + * monotonically increasing using the same time base as + * {@link android.os.SystemClock#elapsedRealtimeNanos()}. + * + * If not explicitly set, the current timestamp is used for the sensor event. + * + * @see android.hardware.SensorEvent#timestamp + */ + @NonNull + public Builder setTimestampNanos(long timestampNanos) { + mTimestampNanos = timestampNanos; + return this; + } + } + + public static final @NonNull Parcelable.Creator<VirtualSensorEvent> CREATOR = + new Parcelable.Creator<>() { + public VirtualSensorEvent createFromParcel(Parcel source) { + return new VirtualSensorEvent(source); + } + + public VirtualSensorEvent[] newArray(int size) { + return new VirtualSensorEvent[size]; + } + }; +} diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java index cc7977a267a5..99fc5a3ae543 100644 --- a/core/java/android/content/om/FabricatedOverlay.java +++ b/core/java/android/content/om/FabricatedOverlay.java @@ -16,15 +16,21 @@ package android.content.om; +import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.FabricatedOverlayInternal; import android.os.FabricatedOverlayInternalEntry; import android.os.ParcelFileDescriptor; import android.text.TextUtils; +import android.util.TypedValue; +import com.android.internal.content.om.OverlayManagerImpl; import com.android.internal.util.Preconditions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Objects; @@ -82,8 +88,24 @@ public class FabricatedOverlay { } /** + * Constructs a builder for building a fabricated overlay. + * + * @param name a name used to uniquely identify the fabricated overlay owned by the caller + * itself. + * @param targetPackage the name of the package to overlay + */ + public Builder(@NonNull String name, @NonNull String targetPackage) { + mName = OverlayManagerImpl.checkOverlayNameValid(name); + mTargetPackage = + Preconditions.checkStringNotEmpty( + targetPackage, "'targetPackage' must not be empty nor null"); + mOwningPackage = ""; // The package name is filled in OverlayManager.commit + } + + /** * Sets the name of the overlayable resources to overlay (can be null). */ + @NonNull public Builder setTargetOverlayable(@Nullable String targetOverlayable) { mTargetOverlayable = TextUtils.emptyIfNull(targetOverlayable); return this; @@ -111,90 +133,110 @@ public class FabricatedOverlay { } /** - * Sets the value of the fabricated overlay + * Sets the value of the fabricated overlay for the integer-like types. * * @param resourceName name of the target resource to overlay (in the form - * [package]:type/entry) + * [package]:type/entry) * @param dataType the data type of the new value * @param value the unsigned 32 bit integer representing the new value - * + * @return the builder itself + * @see #setResourceValue(String, int, int, String) * @see android.util.TypedValue#type */ - public Builder setResourceValue(@NonNull String resourceName, int dataType, int value) { - ensureValidResourceName(resourceName); - - final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry(); - entry.resourceName = resourceName; - entry.dataType = dataType; - entry.data = value; - mEntries.add(entry); - return this; + @NonNull + public Builder setResourceValue( + @NonNull String resourceName, + @IntRange(from = TypedValue.TYPE_FIRST_INT, to = TypedValue.TYPE_LAST_INT) + int dataType, + int value) { + return setResourceValue(resourceName, dataType, value, null /* configuration */); } /** - * Sets the value of the fabricated overlay + * Sets the value of the fabricated overlay for the integer-like types with the + * configuration. * * @param resourceName name of the target resource to overlay (in the form - * [package]:type/entry) + * [package]:type/entry) * @param dataType the data type of the new value * @param value the unsigned 32 bit integer representing the new value * @param configuration The string representation of the config this overlay is enabled for - * * @see android.util.TypedValue#type */ - public Builder setResourceValue(@NonNull String resourceName, int dataType, int value, - String configuration) { + @NonNull + public Builder setResourceValue( + @NonNull String resourceName, + @IntRange(from = TypedValue.TYPE_FIRST_INT, to = TypedValue.TYPE_LAST_INT) + int dataType, + int value, + @Nullable String configuration) { ensureValidResourceName(resourceName); final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry(); entry.resourceName = resourceName; - entry.dataType = dataType; + entry.dataType = + Preconditions.checkArgumentInRange( + dataType, + TypedValue.TYPE_FIRST_INT, + TypedValue.TYPE_LAST_INT, + "dataType"); entry.data = value; entry.configuration = configuration; mEntries.add(entry); return this; } + /** @hide */ + @IntDef( + prefix = {"OVERLAY_TYPE"}, + value = { + TypedValue.TYPE_STRING, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface StringTypeOverlayResource {} + /** - * Sets the value of the fabricated overlay + * Sets the value of the fabricated overlay for the string-like type. * * @param resourceName name of the target resource to overlay (in the form - * [package]:type/entry) + * [package]:type/entry) * @param dataType the data type of the new value * @param value the string representing the new value - * + * @return the builder itself * @see android.util.TypedValue#type */ - public Builder setResourceValue(@NonNull String resourceName, int dataType, String value) { - ensureValidResourceName(resourceName); - - final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry(); - entry.resourceName = resourceName; - entry.dataType = dataType; - entry.stringData = value; - mEntries.add(entry); - return this; + @NonNull + public Builder setResourceValue( + @NonNull String resourceName, + @StringTypeOverlayResource int dataType, + @NonNull String value) { + return setResourceValue(resourceName, dataType, value, null /* configuration */); } /** - * Sets the value of the fabricated overlay + * Sets the value of the fabricated overlay for the string-like type with the configuration. * * @param resourceName name of the target resource to overlay (in the form - * [package]:type/entry) + * [package]:type/entry) * @param dataType the data type of the new value * @param value the string representing the new value * @param configuration The string representation of the config this overlay is enabled for - * * @see android.util.TypedValue#type */ - public Builder setResourceValue(@NonNull String resourceName, int dataType, String value, - String configuration) { + @NonNull + public Builder setResourceValue( + @NonNull String resourceName, + @StringTypeOverlayResource int dataType, + @NonNull String value, + @Nullable String configuration) { ensureValidResourceName(resourceName); final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry(); entry.resourceName = resourceName; - entry.dataType = dataType; - entry.stringData = value; + entry.dataType = + Preconditions.checkArgumentInRange( + dataType, TypedValue.TYPE_STRING, TypedValue.TYPE_FRACTION, "dataType"); + entry.stringData = Objects.requireNonNull(value); entry.configuration = configuration; mEntries.add(entry); return this; @@ -204,23 +246,32 @@ public class FabricatedOverlay { * Sets the value of the fabricated overlay * * @param resourceName name of the target resource to overlay (in the form - * [package]:type/entry) + * [package]:type/entry) * @param value the file descriptor whose contents are the value of the frro * @param configuration The string representation of the config this overlay is enabled for + * @return the builder itself */ - public Builder setResourceValue(@NonNull String resourceName, ParcelFileDescriptor value, - String configuration) { + @NonNull + public Builder setResourceValue( + @NonNull String resourceName, + @NonNull ParcelFileDescriptor value, + @Nullable String configuration) { ensureValidResourceName(resourceName); final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry(); entry.resourceName = resourceName; - entry.binaryData = value; + entry.binaryData = Objects.requireNonNull(value); entry.configuration = configuration; mEntries.add(entry); return this; } - /** Builds an immutable fabricated overlay. */ + /** + * Builds an immutable fabricated overlay. + * + * @return the fabricated overlay + */ + @NonNull public FabricatedOverlay build() { final FabricatedOverlayInternal overlay = new FabricatedOverlayInternal(); overlay.packageName = mOwningPackage; diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java index 94275aea0c4d..812f6b0fc34b 100644 --- a/core/java/android/content/om/OverlayManager.java +++ b/core/java/android/content/om/OverlayManager.java @@ -92,6 +92,21 @@ public class OverlayManager { private static final long THROW_SECURITY_EXCEPTIONS = 147340954; /** + * Applications can use OverlayManager to create overlays to overlay on itself resources. The + * overlay target is itself and the work range is only in caller application. + * + * <p>In {@link android.content.Context#getSystemService(String)}, it crashes because of {@link + * java.lang.NullPointerException} if the parameter is OverlayManager. if the self-targeting is + * enabled, the caller application can get the OverlayManager instance to use self-targeting + * functionality. + * + * @hide + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public static final long SELF_TARGETING_OVERLAY = 205919743; + + /** * Creates a new instance. * * @param context The current context in which to operate. diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl index 12911d6e1232..1e928bd6c9be 100644 --- a/core/java/android/content/pm/IPackageInstaller.aidl +++ b/core/java/android/content/pm/IPackageInstaller.aidl @@ -23,6 +23,7 @@ import android.content.pm.PackageInstaller; import android.content.pm.ParceledListSlice; import android.content.pm.VersionedPackage; import android.content.IntentSender; +import android.os.RemoteCallback; import android.graphics.Bitmap; @@ -66,4 +67,6 @@ interface IPackageInstaller { void setAllowUnlimitedSilentUpdates(String installerPackageName); void setSilentUpdatesThrottleTime(long throttleTimeInSeconds); + void checkInstallConstraints(String installerPackageName, in List<String> packageNames, + in PackageInstaller.InstallConstraints constraints, in RemoteCallback callback); } diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl index 1fc6bdaa963a..7d9c64add492 100644 --- a/core/java/android/content/pm/IPackageInstallerSession.aidl +++ b/core/java/android/content/pm/IPackageInstallerSession.aidl @@ -61,4 +61,6 @@ interface IPackageInstallerSession { int getInstallFlags(); void requestUserPreapproval(in PackageInstaller.PreapprovalDetails details, in IntentSender statusReceiver); + + boolean isKeepApplicationEnabledSetting(); } diff --git a/core/java/android/content/pm/PackageInstaller.aidl b/core/java/android/content/pm/PackageInstaller.aidl index 833919e16855..ab9d4f3194ca 100644 --- a/core/java/android/content/pm/PackageInstaller.aidl +++ b/core/java/android/content/pm/PackageInstaller.aidl @@ -16,6 +16,7 @@ package android.content.pm; +parcelable PackageInstaller.InstallConstraints; parcelable PackageInstaller.SessionParams; parcelable PackageInstaller.SessionInfo; parcelable PackageInstaller.PreapprovalDetails; diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index d7686e22756e..c79f99d9d8c9 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -36,6 +36,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.TestApi; import android.app.ActivityManager; @@ -57,6 +58,7 @@ import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; import android.os.ParcelableException; +import android.os.RemoteCallback; import android.os.RemoteException; import android.os.SystemProperties; import android.os.UserHandle; @@ -89,6 +91,7 @@ import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; +import java.util.function.Consumer; /** * Offers the ability to install, upgrade, and remove applications on the @@ -854,6 +857,29 @@ public class PackageInstaller { } /** + * Check if install constraints are satisfied for the given packages. + * + * Note this query result is just a hint and subject to race because system states could + * change anytime in-between this query and committing the session. + * + * The result is returned by a callback because some constraints might take a long time + * to evaluate. + */ + public void checkInstallConstraints(@NonNull List<String> packageNames, + @NonNull InstallConstraints constraints, + @NonNull Consumer<InstallConstraintsResult> callback) { + try { + var remoteCallback = new RemoteCallback(b -> { + callback.accept(b.getParcelable("result", InstallConstraintsResult.class)); + }); + mInstaller.checkInstallConstraints( + mInstallerPackageName, packageNames, constraints, remoteCallback); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Events for observing session lifecycle. * <p> * A typical session lifecycle looks like this: @@ -1717,6 +1743,18 @@ public class PackageInstaller { e.rethrowFromSystemServer(); } } + + /** + * @return {@code true} if this session will keep the existing application enabled setting + * after installation. + */ + public boolean isKeepApplicationEnabledSetting() { + try { + return mSession.isKeepApplicationEnabledSetting(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } /** @@ -1855,6 +1893,8 @@ public class PackageInstaller { public boolean forceQueryableOverride; /** {@hide} */ public int requireUserAction = USER_ACTION_UNSPECIFIED; + /** {@hide} */ + public boolean keepApplicationEnabledSetting = false; /** * Construct parameters for a new package install session. @@ -1899,6 +1939,7 @@ public class PackageInstaller { rollbackDataPolicy = source.readInt(); requireUserAction = source.readInt(); packageSource = source.readInt(); + keepApplicationEnabledSetting = source.readBoolean(); } /** {@hide} */ @@ -1929,6 +1970,7 @@ public class PackageInstaller { ret.rollbackDataPolicy = rollbackDataPolicy; ret.requireUserAction = requireUserAction; ret.packageSource = packageSource; + ret.keepApplicationEnabledSetting = keepApplicationEnabledSetting; return ret; } @@ -2415,6 +2457,14 @@ public class PackageInstaller { this.installScenario = installScenario; } + /** + * Request to keep the original application enabled setting. This will prevent the + * application from being enabled if it was previously in a disabled state. + */ + public void setKeepApplicationEnabledSetting() { + this.keepApplicationEnabledSetting = true; + } + /** {@hide} */ public void dump(IndentingPrintWriter pw) { pw.printPair("mode", mode); @@ -2443,6 +2493,7 @@ public class PackageInstaller { pw.printPair("requiredInstalledVersionCode", requiredInstalledVersionCode); pw.printPair("dataLoaderParams", dataLoaderParams); pw.printPair("rollbackDataPolicy", rollbackDataPolicy); + pw.printPair("keepApplicationEnabledSetting", keepApplicationEnabledSetting); pw.println(); } @@ -2483,6 +2534,7 @@ public class PackageInstaller { dest.writeInt(rollbackDataPolicy); dest.writeInt(requireUserAction); dest.writeInt(packageSource); + dest.writeBoolean(keepApplicationEnabledSetting); } public static final Parcelable.Creator<SessionParams> @@ -2684,6 +2736,9 @@ public class PackageInstaller { /** @hide */ public boolean isPreapprovalRequested; + /** @hide */ + public boolean keepApplicationEnabledSetting; + /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public SessionInfo() { @@ -2737,6 +2792,7 @@ public class PackageInstaller { requireUserAction = source.readInt(); installerUid = source.readInt(); packageSource = source.readInt(); + keepApplicationEnabledSetting = source.readBoolean(); } /** @@ -3268,6 +3324,14 @@ public class PackageInstaller { return installerUid; } + /** + * Returns {@code true} if this session will keep the existing application enabled setting + * after installation. + */ + public boolean isKeepApplicationEnabledSetting() { + return keepApplicationEnabledSetting; + } + @Override public int describeContents() { return 0; @@ -3317,6 +3381,7 @@ public class PackageInstaller { dest.writeInt(requireUserAction); dest.writeInt(installerUid); dest.writeInt(packageSource); + dest.writeBoolean(keepApplicationEnabledSetting); } public static final Parcelable.Creator<SessionInfo> @@ -3608,4 +3673,362 @@ public class PackageInstaller { // End of generated code } + + /** + * The callback result of {@link #checkInstallConstraints(List, InstallConstraints, Consumer)}. + */ + @DataClass(genParcelable = true, genHiddenConstructor = true) + public static final class InstallConstraintsResult implements Parcelable { + /** + * True if all constraints are satisfied. + */ + private boolean mAllConstraintsSatisfied; + + + + // Code below generated by codegen v1.0.23. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/PackageInstaller.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + /** + * Creates a new InstallConstraintsResult. + * + * @param allConstraintsSatisfied + * True if all constraints are satisfied. + * @hide + */ + @DataClass.Generated.Member + public InstallConstraintsResult( + boolean allConstraintsSatisfied) { + this.mAllConstraintsSatisfied = allConstraintsSatisfied; + + // onConstructed(); // You can define this method to get a callback + } + + /** + * True if all constraints are satisfied. + */ + @DataClass.Generated.Member + public boolean isAllConstraintsSatisfied() { + return mAllConstraintsSatisfied; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + byte flg = 0; + if (mAllConstraintsSatisfied) flg |= 0x1; + dest.writeByte(flg); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ InstallConstraintsResult(@NonNull Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + byte flg = in.readByte(); + boolean allConstraintsSatisfied = (flg & 0x1) != 0; + + this.mAllConstraintsSatisfied = allConstraintsSatisfied; + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<InstallConstraintsResult> CREATOR + = new Parcelable.Creator<InstallConstraintsResult>() { + @Override + public InstallConstraintsResult[] newArray(int size) { + return new InstallConstraintsResult[size]; + } + + @Override + public InstallConstraintsResult createFromParcel(@NonNull Parcel in) { + return new InstallConstraintsResult(in); + } + }; + + @DataClass.Generated( + time = 1668650523745L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java", + inputSignatures = "private boolean mAllConstraintsSatisfied\nclass InstallConstraintsResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + + } + + /** + * A class to encapsulate constraints for installation. + * + * When used with {@link #checkInstallConstraints(List, InstallConstraints, Consumer)}, it + * specifies the conditions to check against for the packages in question. This can be used + * by app stores to deliver auto updates without disrupting the user experience (referred as + * gentle update) - for example, an app store might hold off updates when it find out the + * app to update is interacting with the user. + * + * Use {@link Builder} to create a new instance and call mutator methods to add constraints. + * If no mutators were called, default constraints will be generated which implies no + * constraints. It is recommended to use preset constraints which are useful in most + * cases. + * + * For the purpose of gentle update, it is recommended to always use {@link #GENTLE_UPDATE} + * for the system knows best how to do it. It will also benefits the installer as the + * platform evolves and add more constraints to improve the accuracy and efficiency of + * gentle update. + * + * Note the constraints are applied transitively. If app Foo is used by app Bar (via shared + * library or bounded service), the constraints will also be applied to Bar. + */ + @DataClass(genParcelable = true, genHiddenConstructor = true) + public static final class InstallConstraints implements Parcelable { + /** + * Preset constraints suitable for gentle update. + */ + @NonNull + public static final InstallConstraints GENTLE_UPDATE = + new Builder().requireAppNotInteracting().build(); + + private final boolean mRequireDeviceIdle; + private final boolean mRequireAppNotForeground; + private final boolean mRequireAppNotInteracting; + private final boolean mRequireAppNotTopVisible; + private final boolean mRequireNotInCall; + + /** + * Builder class for constructing {@link InstallConstraints}. + */ + public static final class Builder { + private boolean mRequireDeviceIdle; + private boolean mRequireAppNotForeground; + private boolean mRequireAppNotInteracting; + private boolean mRequireAppNotTopVisible; + private boolean mRequireNotInCall; + + /** + * This constraint requires the device is idle. + */ + @SuppressLint("BuilderSetStyle") + @NonNull + public Builder requireDeviceIdle() { + mRequireDeviceIdle = true; + return this; + } + + /** + * This constraint requires the app in question is not in the foreground. + */ + @SuppressLint("BuilderSetStyle") + @NonNull + public Builder requireAppNotForeground() { + mRequireAppNotForeground = true; + return this; + } + + /** + * This constraint requires the app in question is not interacting with the user. + * User interaction includes: + * <ul> + * <li>playing or recording audio/video</li> + * <li>sending or receiving network data</li> + * <li>being visible to the user</li> + * </ul> + */ + @SuppressLint("BuilderSetStyle") + @NonNull + public Builder requireAppNotInteracting() { + mRequireAppNotInteracting = true; + return this; + } + + /** + * This constraint requires the app in question is not top-visible to the user. + * A top-visible app is showing UI at the top of the screen that the user is + * interacting with. + * + * Note this constraint is a subset of {@link #requireAppNotForeground()} + * because a top-visible app is also a foreground app. This is also a subset + * of {@link #requireAppNotInteracting()} because a top-visible app is interacting + * with the user. + */ + @SuppressLint("BuilderSetStyle") + @NonNull + public Builder requireAppNotTopVisible() { + mRequireAppNotTopVisible = true; + return this; + } + + /** + * This constraint requires there is no ongoing call in the device. + */ + @SuppressLint("BuilderSetStyle") + @NonNull + public Builder requireNotInCall() { + mRequireNotInCall = true; + return this; + } + + /** + * Builds a new {@link InstallConstraints} instance. + */ + @NonNull + public InstallConstraints build() { + return new InstallConstraints(mRequireDeviceIdle, mRequireAppNotForeground, + mRequireAppNotInteracting, mRequireAppNotTopVisible, mRequireNotInCall); + } + } + + + + // Code below generated by codegen v1.0.23. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/PackageInstaller.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + /** + * Creates a new InstallConstraints. + * + * @hide + */ + @DataClass.Generated.Member + public InstallConstraints( + boolean requireDeviceIdle, + boolean requireAppNotForeground, + boolean requireAppNotInteracting, + boolean requireAppNotTopVisible, + boolean requireNotInCall) { + this.mRequireDeviceIdle = requireDeviceIdle; + this.mRequireAppNotForeground = requireAppNotForeground; + this.mRequireAppNotInteracting = requireAppNotInteracting; + this.mRequireAppNotTopVisible = requireAppNotTopVisible; + this.mRequireNotInCall = requireNotInCall; + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public boolean isRequireDeviceIdle() { + return mRequireDeviceIdle; + } + + @DataClass.Generated.Member + public boolean isRequireAppNotForeground() { + return mRequireAppNotForeground; + } + + @DataClass.Generated.Member + public boolean isRequireAppNotInteracting() { + return mRequireAppNotInteracting; + } + + @DataClass.Generated.Member + public boolean isRequireAppNotTopVisible() { + return mRequireAppNotTopVisible; + } + + @DataClass.Generated.Member + public boolean isRequireNotInCall() { + return mRequireNotInCall; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + byte flg = 0; + if (mRequireDeviceIdle) flg |= 0x1; + if (mRequireAppNotForeground) flg |= 0x2; + if (mRequireAppNotInteracting) flg |= 0x4; + if (mRequireAppNotTopVisible) flg |= 0x8; + if (mRequireNotInCall) flg |= 0x10; + dest.writeByte(flg); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ InstallConstraints(@NonNull Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + byte flg = in.readByte(); + boolean requireDeviceIdle = (flg & 0x1) != 0; + boolean requireAppNotForeground = (flg & 0x2) != 0; + boolean requireAppNotInteracting = (flg & 0x4) != 0; + boolean requireAppNotTopVisible = (flg & 0x8) != 0; + boolean requireNotInCall = (flg & 0x10) != 0; + + this.mRequireDeviceIdle = requireDeviceIdle; + this.mRequireAppNotForeground = requireAppNotForeground; + this.mRequireAppNotInteracting = requireAppNotInteracting; + this.mRequireAppNotTopVisible = requireAppNotTopVisible; + this.mRequireNotInCall = requireNotInCall; + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<InstallConstraints> CREATOR + = new Parcelable.Creator<InstallConstraints>() { + @Override + public InstallConstraints[] newArray(int size) { + return new InstallConstraints[size]; + } + + @Override + public InstallConstraints createFromParcel(@NonNull Parcel in) { + return new InstallConstraints(in); + } + }; + + @DataClass.Generated( + time = 1668650523752L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java", + inputSignatures = "public static final @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints GENTLE_UPDATE\nprivate final boolean mRequireDeviceIdle\nprivate final boolean mRequireAppNotForeground\nprivate final boolean mRequireAppNotInteracting\nprivate final boolean mRequireAppNotTopVisible\nprivate final boolean mRequireNotInCall\nclass InstallConstraints extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mRequireDeviceIdle\nprivate boolean mRequireAppNotForeground\nprivate boolean mRequireAppNotInteracting\nprivate boolean mRequireAppNotTopVisible\nprivate boolean mRequireNotInCall\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireDeviceIdle()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotForeground()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotInteracting()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotTopVisible()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireNotInCall()\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints build()\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + + } + } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 485d04db82d5..88b5e021882a 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1959,6 +1959,14 @@ public abstract class PackageManager { public static final int INSTALL_FAILED_MISSING_SPLIT = -28; /** + * Installation return code: this is passed in the {@link PackageInstaller#EXTRA_LEGACY_STATUS} + * if the new package targets a deprecated SDK version. + * + * @hide + */ + public static final int INSTALL_FAILED_DEPRECATED_SDK_VERSION = -29; + + /** * Installation parse return code: this is passed in the * {@link PackageInstaller#EXTRA_LEGACY_STATUS} if the parser was given a path that is not a * file, or does not end with the expected '.apk' extension. @@ -9618,6 +9626,7 @@ public abstract class PackageManager { case INSTALL_FAILED_ABORTED: return "INSTALL_FAILED_ABORTED"; case INSTALL_FAILED_BAD_DEX_METADATA: return "INSTALL_FAILED_BAD_DEX_METADATA"; case INSTALL_FAILED_MISSING_SPLIT: return "INSTALL_FAILED_MISSING_SPLIT"; + case INSTALL_FAILED_DEPRECATED_SDK_VERSION: return "INSTALL_FAILED_DEPRECATED_SDK_VERSION"; case INSTALL_FAILED_BAD_SIGNATURE: return "INSTALL_FAILED_BAD_SIGNATURE"; case INSTALL_FAILED_WRONG_INSTALLED_VERSION: return "INSTALL_FAILED_WRONG_INSTALLED_VERSION"; case INSTALL_FAILED_PROCESS_NOT_DEFINED: return "INSTALL_FAILED_PROCESS_NOT_DEFINED"; @@ -9675,6 +9684,7 @@ public abstract class PackageManager { case INSTALL_FAILED_ABORTED: return PackageInstaller.STATUS_FAILURE_ABORTED; case INSTALL_FAILED_MISSING_SPLIT: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE; case INSTALL_FAILED_PRE_APPROVAL_NOT_AVAILABLE: return PackageInstaller.STATUS_FAILURE_BLOCKED; + case INSTALL_FAILED_DEPRECATED_SDK_VERSION: return PackageInstaller.STATUS_FAILURE_INCOMPATIBLE; default: return PackageInstaller.STATUS_FAILURE; } } diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java index 7a5ac8ede4a4..143c00dd4d81 100644 --- a/core/java/android/content/res/ApkAssets.java +++ b/core/java/android/content/res/ApkAssets.java @@ -26,6 +26,8 @@ import android.text.TextUtils; import com.android.internal.annotations.GuardedBy; +import dalvik.annotation.optimization.CriticalNative; + import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; @@ -459,7 +461,7 @@ public final class ApkAssets { private static native @NonNull String nativeGetAssetPath(long ptr); private static native @NonNull String nativeGetDebugName(long ptr); private static native long nativeGetStringBlock(long ptr); - private static native boolean nativeIsUpToDate(long ptr); + @CriticalNative private static native boolean nativeIsUpToDate(long ptr); private static native long nativeOpenXml(long ptr, @NonNull String fileName) throws IOException; private static native @Nullable OverlayableInfo nativeGetOverlayableInfo(long ptr, String overlayableName) throws IOException; diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java index 6ce22422643e..ce6e1c7c676f 100644 --- a/core/java/android/content/res/CompatibilityInfo.java +++ b/core/java/android/content/res/CompatibilityInfo.java @@ -563,6 +563,9 @@ public class CompatibilityInfo implements Parcelable { if (applyToSize) { inoutDm.widthPixels = (int) (inoutDm.widthPixels * invertedRatio + 0.5f); inoutDm.heightPixels = (int) (inoutDm.heightPixels * invertedRatio + 0.5f); + + float fontScale = inoutDm.scaledDensity / inoutDm.density; + inoutDm.fontScaleConverter = FontScaleConverterFactory.forScale(fontScale); } } diff --git a/core/java/android/content/res/FontScaleConverter.java b/core/java/android/content/res/FontScaleConverter.java new file mode 100644 index 000000000000..c7fdb16682e3 --- /dev/null +++ b/core/java/android/content/res/FontScaleConverter.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 android.content.res; + +import android.annotation.NonNull; +import android.util.MathUtils; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Arrays; + +/** + * A lookup table for non-linear font scaling. Converts font sizes given in "sp" dimensions to a + * "dp" dimension according to a non-linear curve. + * + * <p>This is meant to improve readability at larger font scales: larger fonts will scale up more + * slowly than smaller fonts, so we don't get ridiculously huge fonts that don't fit on the screen. + * + * <p>The thinking here is that large fonts are already big enough to read, but we still want to + * scale them slightly to preserve the visual hierarchy when compared to smaller fonts. + * + * @hide + */ +public class FontScaleConverter { + /** + * How close the given SP should be to a canonical SP in the array before they are considered + * the same for lookup purposes. + */ + private static final float THRESHOLD_FOR_MATCHING_SP = 0.02f; + + @VisibleForTesting + final float[] mFromSpValues; + @VisibleForTesting + final float[] mToDpValues; + + /** + * Creates a lookup table for the given conversions. + * + * <p>Any "sp" value not in the lookup table will be derived via linear interpolation. + * + * <p>The arrays must be sorted ascending and monotonically increasing. + * + * @param fromSp array of dimensions in SP + * @param toDp array of dimensions in DP that correspond to an SP value in fromSp + * + * @throws IllegalArgumentException if the array lengths don't match or are empty + * @hide + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public FontScaleConverter(@NonNull float[] fromSp, @NonNull float[] toDp) { + if (fromSp.length != toDp.length || fromSp.length == 0) { + throw new IllegalArgumentException("Array lengths must match and be nonzero"); + } + + mFromSpValues = fromSp; + mToDpValues = toDp; + } + + /** + * Convert a dimension in "sp" to "dp" using the lookup table. + * + * @hide + */ + public float convertSpToDp(float sp) { + final float spPositive = Math.abs(sp); + // TODO(b/247861374): find a match at a higher index? + final int spRounded = Math.round(spPositive); + final float sign = Math.signum(sp); + final int index = Arrays.binarySearch(mFromSpValues, spRounded); + if (index >= 0 && Math.abs(spRounded - spPositive) < THRESHOLD_FOR_MATCHING_SP) { + // exact match, return the matching dp + return sign * mToDpValues[index]; + } else { + // must be a value in between index and index + 1: interpolate. + final int lowerIndex = -(index + 1) - 1; + + final float startSp; + final float endSp; + final float startDp; + final float endDp; + + if (lowerIndex >= mFromSpValues.length - 1) { + // It's past our lookup table. Determine the last elements' scaling factor and use. + startSp = mFromSpValues[mFromSpValues.length - 1]; + startDp = mToDpValues[mFromSpValues.length - 1]; + + if (startSp == 0) return 0; + + final float scalingFactor = startDp / startSp; + return sp * scalingFactor; + } else if (lowerIndex == -1) { + // It's smaller than the smallest value in our table. Interpolate from 0. + startSp = 0; + startDp = 0; + endSp = mFromSpValues[0]; + endDp = mToDpValues[0]; + } else { + startSp = mFromSpValues[lowerIndex]; + endSp = mFromSpValues[lowerIndex + 1]; + startDp = mToDpValues[lowerIndex]; + endDp = mToDpValues[lowerIndex + 1]; + } + + return sign * MathUtils.constrainedMap(startDp, endDp, startSp, endSp, spPositive); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + if (!(o instanceof FontScaleConverter)) return false; + FontScaleConverter that = (FontScaleConverter) o; + return Arrays.equals(mFromSpValues, that.mFromSpValues) + && Arrays.equals(mToDpValues, that.mToDpValues); + } + + @Override + public int hashCode() { + int result = Arrays.hashCode(mFromSpValues); + result = 31 * result + Arrays.hashCode(mToDpValues); + return result; + } + + @Override + public String toString() { + return "FontScaleConverter{" + + "fromSpValues=" + + Arrays.toString(mFromSpValues) + + ", toDpValues=" + + Arrays.toString(mToDpValues) + + '}'; + } +} diff --git a/core/java/android/content/res/FontScaleConverterFactory.java b/core/java/android/content/res/FontScaleConverterFactory.java new file mode 100644 index 000000000000..c77a372f1939 --- /dev/null +++ b/core/java/android/content/res/FontScaleConverterFactory.java @@ -0,0 +1,119 @@ +/* + * 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.content.res; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.SparseArray; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * Stores lookup tables for creating {@link FontScaleConverter}s at various scales. + * + * @hide + */ +public class FontScaleConverterFactory { + private static final float SCALE_KEY_MULTIPLIER = 100f; + + @VisibleForTesting + static final SparseArray<FontScaleConverter> LOOKUP_TABLES = new SparseArray<>(); + + static { + // These were generated by frameworks/base/tools/fonts/font-scaling-array-generator.js and + // manually tweaked for optimum readability. + put( + /* scaleKey= */ 1.15f, + new FontScaleConverter( + /* fromSp= */ + new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100}, + /* toDp= */ + new float[] { 9.2f, 11.5f, 13.8f, 16.1f, 20.7f, 23f, 27.6f, 34.5f, 115}) + ); + + put( + /* scaleKey= */ 1.3f, + new FontScaleConverter( + /* fromSp= */ + new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100}, + /* toDp= */ + new float[] {10.4f, 13f, 15.6f, 18.2f, 23.4f, 26f, 31.2f, 39f, 130}) + ); + + put( + /* scaleKey= */ 1.5f, + new FontScaleConverter( + /* fromSp= */ + new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100}, + /* toDp= */ + new float[] { 12f, 15f, 18f, 21f, 27f, 30f, 36f, 45f, 150}) + ); + + put( + /* scaleKey= */ 1.8f, + new FontScaleConverter( + /* fromSp= */ + new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100}, + /* toDp= */ + new float[] {14.4f, 18f, 21.6f, 25.2f, 32.4f, 36f, 43.2f, 54f, 180}) + ); + + put( + /* scaleKey= */ 2f, + new FontScaleConverter( + /* fromSp= */ + new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100}, + /* toDp= */ + new float[] { 16f, 20f, 24f, 28f, 36f, 40f, 48f, 60f, 200}) + ); + + } + + private FontScaleConverterFactory() {} + + /** + * Finds a matching FontScaleConverter for the given fontScale factor. + * + * @param fontScale the scale factor, usually from {@link Configuration#fontScale}. + * + * @return a converter for the given scale, or null if non-linear scaling should not be used. + * + * @hide + */ + @Nullable + public static FontScaleConverter forScale(float fontScale) { + if (fontScale <= 1) { + // We don't need non-linear curves for shrinking text or for 100%. + // Also, fontScale==0 should not have a curve either + return null; + } + + FontScaleConverter lookupTable = get(fontScale); + // TODO(b/247861716): interpolate between two tables when null + + return lookupTable; + } + + private static void put(float scaleKey, @NonNull FontScaleConverter fontScaleConverter) { + LOOKUP_TABLES.put((int) (scaleKey * SCALE_KEY_MULTIPLIER), fontScaleConverter); + } + + @Nullable + private static FontScaleConverter get(float scaleKey) { + return LOOKUP_TABLES.get((int) (scaleKey * SCALE_KEY_MULTIPLIER)); + } +} diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java index 09d24d47cc7a..c2b37694c0c3 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -434,6 +434,8 @@ public class ResourcesImpl { // Protect against an unset fontScale. mMetrics.scaledDensity = mMetrics.density * (mConfiguration.fontScale != 0 ? mConfiguration.fontScale : 1.0f); + mMetrics.fontScaleConverter = + FontScaleConverterFactory.forScale(mConfiguration.fontScale); final int width, height; if (mMetrics.widthPixels >= mMetrics.heightPixels) { diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java index 1c4898a4c8d0..18118f5bfe29 100644 --- a/core/java/android/hardware/SystemSensorManager.java +++ b/core/java/android/hardware/SystemSensorManager.java @@ -16,8 +16,14 @@ package android.hardware; +import static android.companion.virtual.VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED; +import static android.companion.virtual.VirtualDeviceManager.DEFAULT_DEVICE_ID; +import static android.companion.virtual.VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID; +import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT; +import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import android.companion.virtual.VirtualDeviceManager; import android.compat.Compatibility; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledAfter; @@ -45,6 +51,7 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -80,6 +87,8 @@ public class SystemSensorManager extends SensorManager { private static native boolean nativeGetSensorAtIndex(long nativeInstance, Sensor sensor, int index); private static native void nativeGetDynamicSensors(long nativeInstance, List<Sensor> list); + private static native void nativeGetRuntimeSensors( + long nativeInstance, int deviceId, List<Sensor> list); private static native boolean nativeIsDataInjectionEnabled(long nativeInstance); private static native int nativeCreateDirectChannel( @@ -100,6 +109,10 @@ public class SystemSensorManager extends SensorManager { private final ArrayList<Sensor> mFullSensorsList = new ArrayList<>(); private List<Sensor> mFullDynamicSensorsList = new ArrayList<>(); + private final SparseArray<List<Sensor>> mFullRuntimeSensorListByDevice = new SparseArray<>(); + private final SparseArray<SparseArray<List<Sensor>>> mRuntimeSensorListByDeviceByType = + new SparseArray<>(); + private boolean mDynamicSensorListDirty = true; private final HashMap<Integer, Sensor> mHandleToSensor = new HashMap<>(); @@ -114,6 +127,7 @@ public class SystemSensorManager extends SensorManager { private HashMap<DynamicSensorCallback, Handler> mDynamicSensorCallbacks = new HashMap<>(); private BroadcastReceiver mDynamicSensorBroadcastReceiver; + private BroadcastReceiver mRuntimeSensorBroadcastReceiver; // Looper associated with the context in which this instance was created. private final Looper mMainLooper; @@ -121,6 +135,7 @@ public class SystemSensorManager extends SensorManager { private final boolean mIsPackageDebuggable; private final Context mContext; private final long mNativeInstance; + private final VirtualDeviceManager mVdm; private Optional<Boolean> mHasHighSamplingRateSensorsPermission = Optional.empty(); @@ -139,6 +154,7 @@ public class SystemSensorManager extends SensorManager { mContext = context; mNativeInstance = nativeCreate(context.getOpPackageName()); mIsPackageDebuggable = (0 != (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE)); + mVdm = mContext.getSystemService(VirtualDeviceManager.class); // initialize the sensor list for (int index = 0;; ++index) { @@ -147,12 +163,63 @@ public class SystemSensorManager extends SensorManager { mFullSensorsList.add(sensor); mHandleToSensor.put(sensor.getHandle(), sensor); } + + } + + /** @hide */ + @Override + public List<Sensor> getSensorList(int type) { + final int deviceId = mContext.getDeviceId(); + if (deviceId == DEFAULT_DEVICE_ID || mVdm == null + || mVdm.getDevicePolicy(deviceId, POLICY_TYPE_SENSORS) == DEVICE_POLICY_DEFAULT) { + return super.getSensorList(type); + } + + // Cache the per-device lists on demand. + List<Sensor> list; + synchronized (mFullRuntimeSensorListByDevice) { + List<Sensor> fullList = mFullRuntimeSensorListByDevice.get(deviceId); + if (fullList == null) { + fullList = createRuntimeSensorListLocked(deviceId); + } + SparseArray<List<Sensor>> deviceSensorListByType = + mRuntimeSensorListByDeviceByType.get(deviceId); + list = deviceSensorListByType.get(type); + if (list == null) { + if (type == Sensor.TYPE_ALL) { + list = fullList; + } else { + list = new ArrayList<>(); + for (Sensor i : fullList) { + if (i.getType() == type) { + list.add(i); + } + } + } + list = Collections.unmodifiableList(list); + deviceSensorListByType.append(type, list); + } + } + return list; } /** @hide */ @Override protected List<Sensor> getFullSensorList() { - return mFullSensorsList; + final int deviceId = mContext.getDeviceId(); + if (deviceId == DEFAULT_DEVICE_ID || mVdm == null + || mVdm.getDevicePolicy(deviceId, POLICY_TYPE_SENSORS) == DEVICE_POLICY_DEFAULT) { + return mFullSensorsList; + } + + List<Sensor> fullList; + synchronized (mFullRuntimeSensorListByDevice) { + fullList = mFullRuntimeSensorListByDevice.get(deviceId); + if (fullList == null) { + fullList = createRuntimeSensorListLocked(deviceId); + } + } + return fullList; } /** @hide */ @@ -446,12 +513,53 @@ public class SystemSensorManager extends SensorManager { } } + private List<Sensor> createRuntimeSensorListLocked(int deviceId) { + setupRuntimeSensorBroadcastReceiver(); + List<Sensor> list = new ArrayList<>(); + nativeGetRuntimeSensors(mNativeInstance, deviceId, list); + mFullRuntimeSensorListByDevice.put(deviceId, list); + mRuntimeSensorListByDeviceByType.put(deviceId, new SparseArray<>()); + for (Sensor s : list) { + mHandleToSensor.put(s.getHandle(), s); + } + return list; + } + + private void setupRuntimeSensorBroadcastReceiver() { + if (mRuntimeSensorBroadcastReceiver == null) { + mRuntimeSensorBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(ACTION_VIRTUAL_DEVICE_REMOVED)) { + synchronized (mFullRuntimeSensorListByDevice) { + final int deviceId = intent.getIntExtra( + EXTRA_VIRTUAL_DEVICE_ID, DEFAULT_DEVICE_ID); + List<Sensor> removedSensors = + mFullRuntimeSensorListByDevice.removeReturnOld(deviceId); + if (removedSensors != null) { + for (Sensor s : removedSensors) { + cleanupSensorConnection(s); + } + } + mRuntimeSensorListByDeviceByType.remove(deviceId); + } + } + } + }; + + IntentFilter filter = new IntentFilter("virtual_device_removed"); + filter.addAction(ACTION_VIRTUAL_DEVICE_REMOVED); + mContext.registerReceiver(mRuntimeSensorBroadcastReceiver, filter, + Context.RECEIVER_NOT_EXPORTED); + } + } + private void setupDynamicSensorBroadcastReceiver() { if (mDynamicSensorBroadcastReceiver == null) { mDynamicSensorBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (intent.getAction() == Intent.ACTION_DYNAMIC_SENSOR_CHANGED) { + if (intent.getAction().equals(Intent.ACTION_DYNAMIC_SENSOR_CHANGED)) { if (DEBUG_DYNAMIC_SENSOR) { Log.i(TAG, "DYNS received DYNAMIC_SENSOR_CHANED broadcast"); } diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 9b07d3a9f8fb..441fd88f15ee 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -137,7 +137,8 @@ public final class DisplayManager { VIRTUAL_DISPLAY_FLAG_TRUSTED, VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP, VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED, - VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED + VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED, + VIRTUAL_DISPLAY_FLAG_OWN_FOCUS, }) @Retention(RetentionPolicy.SOURCE) public @interface VirtualDisplayFlag {} @@ -403,6 +404,22 @@ public final class DisplayManager { */ public static final int VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED = 1 << 13; + /** + * Virtual display flags: Indicates that the display maintains its own focus and touch mode. + * + * This flag is similar to {@link com.android.internal.R.bool.config_perDisplayFocusEnabled} in + * behavior, but only applies to the specific display instead of system-wide to all displays. + * + * Note: The display must be trusted in order to have its own focus. + * + * @see #createVirtualDisplay + * @see #VIRTUAL_DISPLAY_FLAG_TRUSTED + * @hide + */ + @TestApi + public static final int VIRTUAL_DISPLAY_FLAG_OWN_FOCUS = 1 << 14; + + /** @hide */ @IntDef(prefix = {"MATCH_CONTENT_FRAMERATE_"}, value = { MATCH_CONTENT_FRAMERATE_UNKNOWN, diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java index ade9fd68ade8..b2dfd8501772 100644 --- a/core/java/android/hardware/radio/ProgramList.java +++ b/core/java/android/hardware/radio/ProgramList.java @@ -24,6 +24,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.util.ArrayMap; +import com.android.internal.annotations.GuardedBy; + import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; @@ -41,14 +43,25 @@ import java.util.stream.Collectors; public final class ProgramList implements AutoCloseable { private final Object mLock = new Object(); + + @GuardedBy("mLock") private final Map<ProgramSelector.Identifier, RadioManager.ProgramInfo> mPrograms = new ArrayMap<>(); + @GuardedBy("mLock") private final List<ListCallback> mListCallbacks = new ArrayList<>(); + + @GuardedBy("mLock") private final List<OnCompleteListener> mOnCompleteListeners = new ArrayList<>(); + + @GuardedBy("mLock") private OnCloseListener mOnCloseListener; - private boolean mIsClosed = false; - private boolean mIsComplete = false; + + @GuardedBy("mLock") + private boolean mIsClosed; + + @GuardedBy("mLock") + private boolean mIsComplete; ProgramList() {} @@ -227,6 +240,7 @@ public final class ProgramList implements AutoCloseable { } } + @GuardedBy("mLock") private void putLocked(RadioManager.ProgramInfo value, List<ProgramSelector.Identifier> changedIdentifierList) { ProgramSelector.Identifier key = value.getSelector().getPrimaryId(); @@ -235,6 +249,7 @@ public final class ProgramList implements AutoCloseable { changedIdentifierList.add(sel); } + @GuardedBy("mLock") private void removeLocked(ProgramSelector.Identifier key, List<ProgramSelector.Identifier> removedIdentifierList) { RadioManager.ProgramInfo removed = mPrograms.remove(Objects.requireNonNull(key)); diff --git a/core/java/android/hardware/usb/ParcelableUsbPort.java b/core/java/android/hardware/usb/ParcelableUsbPort.java index 19655edf9fb2..7fc282c141ba 100644 --- a/core/java/android/hardware/usb/ParcelableUsbPort.java +++ b/core/java/android/hardware/usb/ParcelableUsbPort.java @@ -34,11 +34,13 @@ public final class ParcelableUsbPort implements Parcelable { private final int mSupportedContaminantProtectionModes; private final boolean mSupportsEnableContaminantPresenceProtection; private final boolean mSupportsEnableContaminantPresenceDetection; + private final boolean mSupportsComplianceWarnings; private ParcelableUsbPort(@NonNull String id, int supportedModes, int supportedContaminantProtectionModes, boolean supportsEnableContaminantPresenceProtection, - boolean supportsEnableContaminantPresenceDetection) { + boolean supportsEnableContaminantPresenceDetection, + boolean supportsComplianceWarnings) { mId = id; mSupportedModes = supportedModes; mSupportedContaminantProtectionModes = supportedContaminantProtectionModes; @@ -46,6 +48,8 @@ public final class ParcelableUsbPort implements Parcelable { supportsEnableContaminantPresenceProtection; mSupportsEnableContaminantPresenceDetection = supportsEnableContaminantPresenceDetection; + mSupportsComplianceWarnings = + supportsComplianceWarnings; } /** @@ -59,7 +63,8 @@ public final class ParcelableUsbPort implements Parcelable { return new ParcelableUsbPort(port.getId(), port.getSupportedModes(), port.getSupportedContaminantProtectionModes(), port.supportsEnableContaminantPresenceProtection(), - port.supportsEnableContaminantPresenceDetection()); + port.supportsEnableContaminantPresenceDetection(), + port.supportsComplianceWarnings()); } /** @@ -72,7 +77,8 @@ public final class ParcelableUsbPort implements Parcelable { public @NonNull UsbPort getUsbPort(@NonNull UsbManager usbManager) { return new UsbPort(usbManager, mId, mSupportedModes, mSupportedContaminantProtectionModes, mSupportsEnableContaminantPresenceProtection, - mSupportsEnableContaminantPresenceDetection); + mSupportsEnableContaminantPresenceDetection, + mSupportsComplianceWarnings); } @Override @@ -87,6 +93,7 @@ public final class ParcelableUsbPort implements Parcelable { dest.writeInt(mSupportedContaminantProtectionModes); dest.writeBoolean(mSupportsEnableContaminantPresenceProtection); dest.writeBoolean(mSupportsEnableContaminantPresenceDetection); + dest.writeBoolean(mSupportsComplianceWarnings); } public static final @android.annotation.NonNull Creator<ParcelableUsbPort> CREATOR = @@ -98,11 +105,13 @@ public final class ParcelableUsbPort implements Parcelable { int supportedContaminantProtectionModes = in.readInt(); boolean supportsEnableContaminantPresenceProtection = in.readBoolean(); boolean supportsEnableContaminantPresenceDetection = in.readBoolean(); + boolean supportsComplianceWarnings = in.readBoolean(); return new ParcelableUsbPort(id, supportedModes, supportedContaminantProtectionModes, supportsEnableContaminantPresenceProtection, - supportsEnableContaminantPresenceDetection); + supportsEnableContaminantPresenceDetection, + supportsComplianceWarnings); } @Override diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java index 50dd0064a5cb..342c33665b02 100644 --- a/core/java/android/hardware/usb/UsbManager.java +++ b/core/java/android/hardware/usb/UsbManager.java @@ -113,6 +113,19 @@ public class UsbManager { public static final String ACTION_USB_PORT_CHANGED = "android.hardware.usb.action.USB_PORT_CHANGED"; + /** + * Broadcast Action: A broadcast for USB compliance warning changes. + * + * This intent is sent when a port partner's + * (USB power source/cable/accessory) compliance warnings change state. + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.MANAGE_USB) + public static final String ACTION_USB_PORT_COMPLIANCE_CHANGED = + "android.hardware.usb.action.USB_PORT_COMPLIANCE_CHANGED"; + /** * Activity intent sent when user attaches a USB device. * diff --git a/core/java/android/hardware/usb/UsbPort.java b/core/java/android/hardware/usb/UsbPort.java index 7c5a4c6d2b87..e0f9cad0835a 100644 --- a/core/java/android/hardware/usb/UsbPort.java +++ b/core/java/android/hardware/usb/UsbPort.java @@ -46,6 +46,10 @@ import static android.hardware.usb.UsbPortStatus.DATA_STATUS_DISABLED_CONTAMINAN import static android.hardware.usb.UsbPortStatus.DATA_STATUS_DISABLED_DOCK; import static android.hardware.usb.UsbPortStatus.DATA_STATUS_DISABLED_FORCE; import static android.hardware.usb.UsbPortStatus.DATA_STATUS_DISABLED_DEBUG; +import static android.hardware.usb.UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY; +import static android.hardware.usb.UsbPortStatus.COMPLIANCE_WARNING_BC_1_2; +import static android.hardware.usb.UsbPortStatus.COMPLIANCE_WARNING_MISSING_RP; +import static android.hardware.usb.UsbPortStatus.COMPLIANCE_WARNING_OTHER; import android.Manifest; import android.annotation.CallbackExecutor; @@ -83,6 +87,7 @@ public final class UsbPort { private final int mSupportedContaminantProtectionModes; private final boolean mSupportsEnableContaminantPresenceProtection; private final boolean mSupportsEnableContaminantPresenceDetection; + private final boolean mSupportsComplianceWarnings; private static final int NUM_DATA_ROLES = Constants.PortDataRole.NUM_DATA_ROLES; /** @@ -250,6 +255,18 @@ public final class UsbPort { int supportedContaminantProtectionModes, boolean supportsEnableContaminantPresenceProtection, boolean supportsEnableContaminantPresenceDetection) { + this(usbManager, id, supportedModes, supportedContaminantProtectionModes, + supportsEnableContaminantPresenceProtection, + supportsEnableContaminantPresenceDetection, + false); + } + + /** @hide */ + public UsbPort(@NonNull UsbManager usbManager, @NonNull String id, int supportedModes, + int supportedContaminantProtectionModes, + boolean supportsEnableContaminantPresenceProtection, + boolean supportsEnableContaminantPresenceDetection, + boolean supportsComplianceWarnings) { Objects.requireNonNull(id); Preconditions.checkFlagsArgument(supportedModes, MODE_DFP | MODE_UFP | MODE_AUDIO_ACCESSORY | MODE_DEBUG_ACCESSORY); @@ -262,6 +279,7 @@ public final class UsbPort { supportsEnableContaminantPresenceProtection; mSupportsEnableContaminantPresenceDetection = supportsEnableContaminantPresenceDetection; + mSupportsComplianceWarnings = supportsComplianceWarnings; } /** @@ -331,6 +349,21 @@ public final class UsbPort { } /** + * Queries USB Port to see if the port is capable of identifying + * non compliant USB power source/cable/accessory. + * + * @return true when the UsbPort is capable of identifying + * non compliant USB power + * source/cable/accessory. + * @return false otherwise. + */ + @CheckResult + @RequiresPermission(Manifest.permission.MANAGE_USB) + public boolean supportsComplianceWarnings() { + return mSupportsComplianceWarnings; + } + + /** * Sets the desired role combination of the port. * <p> * The supported role combinations depend on what is connected to the port and may be @@ -686,6 +719,37 @@ public final class UsbPort { } /** @hide */ + public static String complianceWarningsToString(@NonNull int[] complianceWarnings) { + StringBuilder complianceWarningString = new StringBuilder(); + complianceWarningString.append("["); + + if (complianceWarnings != null) { + for (int warning : complianceWarnings) { + switch (warning) { + case UsbPortStatus.COMPLIANCE_WARNING_OTHER: + complianceWarningString.append("other, "); + break; + case UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY: + complianceWarningString.append("debug accessory, "); + break; + case UsbPortStatus.COMPLIANCE_WARNING_BC_1_2: + complianceWarningString.append("bc12, "); + break; + case UsbPortStatus.COMPLIANCE_WARNING_MISSING_RP: + complianceWarningString.append("missing rp, "); + break; + default: + complianceWarningString.append(String.format("Unknown(%d), ", warning)); + break; + } + } + } + + complianceWarningString.append("]"); + return complianceWarningString.toString().replaceAll(", ]$", "]"); + } + + /** @hide */ public static void checkMode(int powerRole) { Preconditions.checkArgumentInRange(powerRole, Constants.PortMode.NONE, Constants.PortMode.NUM_MODES - 1, "portMode"); @@ -720,10 +784,12 @@ public final class UsbPort { @Override public String toString() { return "UsbPort{id=" + mId + ", supportedModes=" + modeToString(mSupportedModes) - + "supportedContaminantProtectionModes=" + mSupportedContaminantProtectionModes - + "supportsEnableContaminantPresenceProtection=" + + ", supportedContaminantProtectionModes=" + mSupportedContaminantProtectionModes + + ", supportsEnableContaminantPresenceProtection=" + mSupportsEnableContaminantPresenceProtection - + "supportsEnableContaminantPresenceDetection=" - + mSupportsEnableContaminantPresenceDetection; + + ", supportsEnableContaminantPresenceDetection=" + + mSupportsEnableContaminantPresenceDetection + + ", supportsComplianceWarnings=" + + mSupportsComplianceWarnings; } } diff --git a/core/java/android/hardware/usb/UsbPortStatus.java b/core/java/android/hardware/usb/UsbPortStatus.java index 3221ec8577ac..ed3e40d6b04c 100644 --- a/core/java/android/hardware/usb/UsbPortStatus.java +++ b/core/java/android/hardware/usb/UsbPortStatus.java @@ -16,9 +16,11 @@ package android.hardware.usb; +import android.Manifest; +import android.annotation.CheckResult; import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -46,6 +48,7 @@ public final class UsbPortStatus implements Parcelable { private final boolean mPowerTransferLimited; private final @UsbDataStatus int mUsbDataStatus; private final @PowerBrickConnectionStatus int mPowerBrickConnectionStatus; + private final @NonNull @ComplianceWarning int[] mComplianceWarnings; /** * Power role: This USB port does not have a power role. @@ -246,6 +249,41 @@ public final class UsbPortStatus implements Parcelable { */ public static final int POWER_BRICK_STATUS_DISCONNECTED = 2; + /** + * Used to indicate attached sources/cables/accessories/ports + * that do not match the other warnings below and do not meet the + * requirements of specifications including but not limited to + * USB Type-C Cable and Connector, Universal Serial Bus + * Power Delivery, and Universal Serial Bus 1.x/2.0/3.x/4.0. + * In addition, constants introduced after the target sdk will be + * remapped into COMPLIANCE_WARNING_OTHER. + */ + public static final int COMPLIANCE_WARNING_OTHER = 1; + + /** + * Used to indicate Type-C port partner + * (cable/accessory/source) that identifies itself as debug + * accessory source as defined in USB Type-C Cable and + * Connector Specification. However, the specification states + * that this is meant for debug only and shall not be used for + * with commercial products. + */ + public static final int COMPLIANCE_WARNING_DEBUG_ACCESSORY = 2; + + /** + * Used to indicate USB ports that does not + * identify itself as one of the charging port types (SDP/CDP + * DCP etc) as defined by Battery Charging v1.2 Specification. + */ + public static final int COMPLIANCE_WARNING_BC_1_2 = 3; + + /** + * Used to indicate Type-C sources/cables that are missing pull + * up resistors on the CC pins as required by USB Type-C Cable + * and Connector Specification. + */ + public static final int COMPLIANCE_WARNING_MISSING_RP = 4; + @IntDef(prefix = { "CONTAMINANT_DETECTION_" }, value = { CONTAMINANT_DETECTION_NOT_SUPPORTED, CONTAMINANT_DETECTION_DISABLED, @@ -275,6 +313,15 @@ public final class UsbPortStatus implements Parcelable { @Retention(RetentionPolicy.SOURCE) @interface UsbPortMode{} + @IntDef(prefix = { "COMPLIANCE_WARNING_" }, value = { + COMPLIANCE_WARNING_OTHER, + COMPLIANCE_WARNING_DEBUG_ACCESSORY, + COMPLIANCE_WARNING_BC_1_2, + COMPLIANCE_WARNING_MISSING_RP, + }) + @Retention(RetentionPolicy.SOURCE) + @interface ComplianceWarning{} + /** @hide */ @IntDef(prefix = { "DATA_STATUS_" }, flag = true, value = { DATA_STATUS_UNKNOWN, @@ -302,7 +349,8 @@ public final class UsbPortStatus implements Parcelable { int supportedRoleCombinations, int contaminantProtectionStatus, int contaminantDetectionStatus, @UsbDataStatus int usbDataStatus, boolean powerTransferLimited, - @PowerBrickConnectionStatus int powerBrickConnectionStatus) { + @PowerBrickConnectionStatus int powerBrickConnectionStatus, + @NonNull @ComplianceWarning int[] complianceWarnings) { mCurrentMode = currentMode; mCurrentPowerRole = currentPowerRole; mCurrentDataRole = currentDataRole; @@ -312,21 +360,29 @@ public final class UsbPortStatus implements Parcelable { mUsbDataStatus = usbDataStatus; mPowerTransferLimited = powerTransferLimited; mPowerBrickConnectionStatus = powerBrickConnectionStatus; + mComplianceWarnings = complianceWarnings; + } + + /** @hide */ + public UsbPortStatus(int currentMode, int currentPowerRole, int currentDataRole, + int supportedRoleCombinations, int contaminantProtectionStatus, + int contaminantDetectionStatus, @UsbDataStatus int usbDataStatus, + boolean powerTransferLimited, + @PowerBrickConnectionStatus int powerBrickConnectionStatus) { + this(currentMode, currentPowerRole, currentDataRole, supportedRoleCombinations, + contaminantProtectionStatus, contaminantDetectionStatus, + usbDataStatus, powerTransferLimited, powerBrickConnectionStatus, + new int[] {}); } /** @hide */ public UsbPortStatus(int currentMode, int currentPowerRole, int currentDataRole, int supportedRoleCombinations, int contaminantProtectionStatus, int contaminantDetectionStatus) { - mCurrentMode = currentMode; - mCurrentPowerRole = currentPowerRole; - mCurrentDataRole = currentDataRole; - mSupportedRoleCombinations = supportedRoleCombinations; - mContaminantProtectionStatus = contaminantProtectionStatus; - mContaminantDetectionStatus = contaminantDetectionStatus; - mUsbDataStatus = DATA_STATUS_UNKNOWN; - mPowerBrickConnectionStatus = POWER_BRICK_STATUS_UNKNOWN; - mPowerTransferLimited = false; + this(currentMode, currentPowerRole, currentDataRole, supportedRoleCombinations, + contaminantProtectionStatus, contaminantDetectionStatus, + DATA_STATUS_UNKNOWN, false, POWER_BRICK_STATUS_UNKNOWN, + new int[] {}); } /** @@ -443,6 +499,21 @@ public final class UsbPortStatus implements Parcelable { return mPowerBrickConnectionStatus; } + /** + * Returns non compliant reasons, if any, for the connected + * charger/cable/accessory/USB port. + * + * @return array including {@link #NON_COMPLIANT_REASON_DEBUG_ACCESSORY}, + * {@link #NON_COMPLIANT_REASON_BC12}, + * {@link #NON_COMPLIANT_REASON_MISSING_RP}, + * or {@link #NON_COMPLIANT_REASON_TYPEC} + */ + @CheckResult + @NonNull + public @ComplianceWarning int[] getComplianceWarnings() { + return mComplianceWarnings; + } + @NonNull @Override public String toString() { @@ -460,9 +531,11 @@ public final class UsbPortStatus implements Parcelable { + UsbPort.usbDataStatusToString(getUsbDataStatus()) + ", isPowerTransferLimited=" + isPowerTransferLimited() - +", powerBrickConnectionStatus=" + + ", powerBrickConnectionStatus=" + UsbPort .powerBrickConnectionStatusToString(getPowerBrickConnectionStatus()) + + ", complianceWarnings=" + + UsbPort.complianceWarningsToString(getComplianceWarnings()) + "}"; } @@ -482,6 +555,7 @@ public final class UsbPortStatus implements Parcelable { dest.writeInt(mUsbDataStatus); dest.writeBoolean(mPowerTransferLimited); dest.writeInt(mPowerBrickConnectionStatus); + dest.writeIntArray(mComplianceWarnings); } public static final @NonNull Parcelable.Creator<UsbPortStatus> CREATOR = @@ -497,10 +571,12 @@ public final class UsbPortStatus implements Parcelable { int usbDataStatus = in.readInt(); boolean powerTransferLimited = in.readBoolean(); int powerBrickConnectionStatus = in.readInt(); + @ComplianceWarning int[] complianceWarnings = in.createIntArray(); return new UsbPortStatus(currentMode, currentPowerRole, currentDataRole, supportedRoleCombinations, contaminantProtectionStatus, contaminantDetectionStatus, usbDataStatus, powerTransferLimited, - powerBrickConnectionStatus); + powerBrickConnectionStatus, + complianceWarnings); } @Override @@ -524,6 +600,7 @@ public final class UsbPortStatus implements Parcelable { private boolean mPowerTransferLimited; private @UsbDataStatus int mUsbDataStatus; private @PowerBrickConnectionStatus int mPowerBrickConnectionStatus; + private @ComplianceWarning int[] mComplianceWarnings; public Builder() { mCurrentMode = MODE_NONE; @@ -533,6 +610,7 @@ public final class UsbPortStatus implements Parcelable { mContaminantDetectionStatus = CONTAMINANT_DETECTION_NOT_SUPPORTED; mUsbDataStatus = DATA_STATUS_UNKNOWN; mPowerBrickConnectionStatus = POWER_BRICK_STATUS_UNKNOWN; + mComplianceWarnings = new int[] {}; } /** @@ -619,6 +697,20 @@ public final class UsbPortStatus implements Parcelable { } /** + * Sets the non-compliant charger reasons of {@link UsbPortStatus} + * + * @return Instance of {@link Builder} + */ + @NonNull + public Builder setComplianceWarnings( + @NonNull int[] complianceWarnings) { + mComplianceWarnings = complianceWarnings == null ? new int[] {} : + complianceWarnings; + return this; + } + + + /** * Creates the {@link UsbPortStatus} object. */ @NonNull @@ -626,7 +718,7 @@ public final class UsbPortStatus implements Parcelable { UsbPortStatus status = new UsbPortStatus(mCurrentMode, mCurrentPowerRole, mCurrentDataRole, mSupportedRoleCombinations, mContaminantProtectionStatus, mContaminantDetectionStatus, mUsbDataStatus, mPowerTransferLimited, - mPowerBrickConnectionStatus); + mPowerBrickConnectionStatus, mComplianceWarnings); return status; } }; diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index a887f2a6ef29..eae7ce06008c 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -58,6 +58,7 @@ interface IUserManager { void setUserIcon(int userId, in Bitmap icon); ParcelFileDescriptor getUserIcon(int userId); UserInfo getPrimaryUser(); + int getMainUserId(); List<UserInfo> getUsers(boolean excludePartial, boolean excludeDying, boolean excludePreCreated); List<UserInfo> getProfiles(int userId, boolean enabledOnly); int[] getProfileIds(int userId, boolean enabledOnly); diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java index 9ea42780981d..394927e17167 100644 --- a/core/java/android/os/ServiceManager.java +++ b/core/java/android/os/ServiceManager.java @@ -252,10 +252,12 @@ public final class ServiceManager { } /** - * Returns the list of declared instances for an interface. + * Returns an array of all declared instances for a particular interface. + * + * For instance, if 'android.foo.IFoo/foo' is declared (e.g. in VINTF + * manifest), and 'android.foo.IFoo' is passed here, then ["foo"] would be + * returned. * - * @return true if the service is declared somewhere (eg. VINTF manifest) and - * waitForService should always be able to return the service. * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 1f21bfe6cd72..954d1fca4ce0 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -2377,14 +2377,16 @@ public class UserManager { } /** - * Returns true if the context user is the designated "main user" of the device. This user may - * have access to certain features which are limited to at most one user. + * Returns {@code true} if the context user is the designated "main user" of the device. This + * user may have access to certain features which are limited to at most one user. There will + * never be more than one main user on a device. * - * <p>Currently, the first human user on the device will be the main user; in the future, the - * concept may be transferable, so a different user (or even no user at all) may be designated - * the main user instead. + * <p>Currently, on most form factors the first human user on the device will be the main user; + * in the future, the concept may be transferable, so a different user (or even no user at all) + * may be designated the main user instead. On other form factors there might not be a main + * user. * - * <p>Note that this will be the not be the system user on devices for which + * <p>Note that this will not be the system user on devices for which * {@link #isHeadlessSystemUserMode()} returns true. * @hide */ @@ -2400,6 +2402,29 @@ public class UserManager { } /** + * Returns the designated "main user" of the device, or {@code null} if there is no main user. + * + * @see #isMainUser() + * @hide + */ + @SystemApi + @RequiresPermission(anyOf = { + Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS, + Manifest.permission.QUERY_USERS}) + public @Nullable UserHandle getMainUser() { + try { + final int mainUserId = mService.getMainUserId(); + if (mainUserId == UserHandle.USER_NULL) { + return null; + } + return UserHandle.of(mainUserId); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Used to check if the context user is an admin user. An admin user is allowed to * modify or configure certain settings that aren't available to non-admin users, * create and delete additional users, etc. There can be more than one admin users. diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java index 71bc4b36f4e0..3448a9eed80c 100644 --- a/core/java/android/os/VibrationEffect.java +++ b/core/java/android/os/VibrationEffect.java @@ -227,6 +227,31 @@ public abstract class VibrationEffect implements Parcelable { } /** + * Computes a legacy vibration pattern (i.e. a pattern with duration values for "off/on" + * vibration components) that is equivalent to this VibrationEffect. + * + * <p>All non-repeating effects created with {@link #createWaveform(int[], int)} are convertible + * into an equivalent vibration pattern with this method. It is not guaranteed that an effect + * created with other means becomes converted into an equivalent legacy vibration pattern, even + * if it has an equivalent vibration pattern. If this method is unable to create an equivalent + * vibration pattern for such effects, it will return {@code null}. + * + * <p>Note that a valid equivalent long[] pattern cannot be created for an effect that has any + * form of repeating behavior, regardless of how the effect was created. For repeating effects, + * the method will always return {@code null}. + * + * @return a long array representing a vibration pattern equivalent to the VibrationEffect, if + * the method successfully derived a vibration pattern equivalent to the effect + * (this will always be the case if the effect was created via + * {@link #createWaveform(int[], int)} and is non-repeating). Otherwise, returns + * {@code null}. + * @hide + */ + @TestApi + @Nullable + public abstract long[] computeCreateWaveformOffOnTimingsOrNull(); + + /** * Create a waveform vibration. * * <p>Waveform vibrations are a potentially repeating series of timing and amplitude pairs, @@ -641,6 +666,51 @@ public abstract class VibrationEffect implements Parcelable { return mRepeatIndex; } + /** @hide */ + @Override + @Nullable + public long[] computeCreateWaveformOffOnTimingsOrNull() { + if (getRepeatIndex() >= 0) { + // Repeating effects cannot be fully represented as a long[] legacy pattern. + return null; + } + + List<VibrationEffectSegment> segments = getSegments(); + + // The maximum possible size of the final pattern is 1 plus the number of segments in + // the original effect. This is because we will add an empty "off" segment at the + // start of the pattern if the first segment of the original effect is an "on" segment. + // (because the legacy patterns start with an "off" pattern). Other than this one case, + // we will add the durations of back-to-back segments of similar amplitudes (amplitudes + // that are all "on" or "off") and create a pattern entry for the total duration, which + // will not take more number pattern entries than the number of segments processed. + long[] patternBuffer = new long[segments.size() + 1]; + int patternIndex = 0; + + for (int i = 0; i < segments.size(); i++) { + StepSegment stepSegment = + castToValidStepSegmentForOffOnTimingsOrNull(segments.get(i)); + if (stepSegment == null) { + // This means that there is 1 or more segments of this effect that is/are not a + // possible component of a legacy vibration pattern. Thus, the VibrationEffect + // does not have any equivalent legacy vibration pattern. + return null; + } + + boolean isSegmentOff = stepSegment.getAmplitude() == 0; + // Even pattern indices are "off", and odd pattern indices are "on" + boolean isCurrentPatternIndexOff = (patternIndex % 2) == 0; + if (isSegmentOff != isCurrentPatternIndexOff) { + // Move the pattern index one step ahead, so that the current segment's + // "off"/"on" property matches that of the index's + ++patternIndex; + } + patternBuffer[patternIndex] += stepSegment.getDuration(); + } + + return Arrays.copyOf(patternBuffer, patternIndex + 1); + } + /** @hide */ @Override public void validate() { @@ -806,6 +876,31 @@ public abstract class VibrationEffect implements Parcelable { return new Composed[size]; } }; + + /** + * Casts a provided {@link VibrationEffectSegment} to a {@link StepSegment} and returns it, + * only if it can possibly be a segment for an effect created via + * {@link #createWaveform(int[], int)}. Otherwise, returns {@code null}. + */ + @Nullable + private static StepSegment castToValidStepSegmentForOffOnTimingsOrNull( + VibrationEffectSegment segment) { + if (!(segment instanceof StepSegment)) { + return null; + } + + StepSegment stepSegment = (StepSegment) segment; + if (stepSegment.getFrequencyHz() != 0) { + return null; + } + + float amplitude = stepSegment.getAmplitude(); + if (amplitude != 0 && amplitude != DEFAULT_AMPLITUDE) { + return null; + } + + return stepSegment; + } } /** diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 630ad6c31560..2adbbcd454e5 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -708,7 +708,7 @@ public final class Settings { "android.settings.WIFI_SETTINGS"; /** - * Activity Action: Show settings to allow configuration of MTE. + * Activity Action: Show settings to allow configuration of Advanced memory protection. * <p> * Memory Tagging Extension (MTE) is a CPU extension that allows to protect against certain * classes of security problems at a small runtime performance cost overhead. @@ -720,8 +720,8 @@ public final class Settings { * Output: Nothing. */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) - public static final String ACTION_MEMTAG_SETTINGS = - "android.settings.MEMTAG_SETTINGS"; + public static final String ACTION_ADVANCED_MEMORY_PROTECTION_SETTINGS = + "android.settings.ADVANCED_MEMORY_PROTECTION_SETTINGS"; /** * Activity Action: Show settings to allow configuration of a static IP diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index aebd91a0be2f..84a233ffd2ad 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -2183,6 +2183,17 @@ public abstract class WallpaperService extends Service { } } + /** + * Returns a Looper which messages such as {@link WallpaperService#DO_ATTACH}, + * {@link WallpaperService#DO_DETACH} etc. are sent to. + * By default, returns the process's main looper. + * @hide + */ + @NonNull + public Looper onProvideEngineLooper() { + return super.getMainLooper(); + } + private boolean isValid(RectF area) { if (area == null) return false; boolean valid = area.bottom > area.top && area.left < area.right @@ -2215,12 +2226,12 @@ public abstract class WallpaperService extends Service { Engine mEngine; @SetWallpaperFlags int mWhich; - IWallpaperEngineWrapper(WallpaperService context, + IWallpaperEngineWrapper(WallpaperService service, IWallpaperConnection conn, IBinder windowToken, int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding, int displayId, @SetWallpaperFlags int which) { mWallpaperManager = getSystemService(WallpaperManager.class); - mCaller = new HandlerCaller(context, context.getMainLooper(), this, true); + mCaller = new HandlerCaller(service, service.onProvideEngineLooper(), this, true); mConnection = conn; mWindowToken = windowToken; mWindowType = windowType; diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java index 517d98222093..959295b883d5 100755 --- a/core/java/android/util/DisplayMetrics.java +++ b/core/java/android/util/DisplayMetrics.java @@ -18,6 +18,7 @@ package android.util; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; +import android.content.res.FontScaleConverter; import android.os.SystemProperties; /** @@ -273,6 +274,15 @@ public class DisplayMetrics { * increments at runtime based on a user preference for the font size. */ public float scaledDensity; + + /** + * If non-null, this will be used to calculate font sizes instead of {@link #scaledDensity}. + * + * @hide + */ + @Nullable + public FontScaleConverter fontScaleConverter; + /** * The exact physical pixels per inch of the screen in the X dimension. */ @@ -350,6 +360,7 @@ public class DisplayMetrics { noncompatScaledDensity = o.noncompatScaledDensity; noncompatXdpi = o.noncompatXdpi; noncompatYdpi = o.noncompatYdpi; + fontScaleConverter = o.fontScaleConverter; } public void setToDefaults() { @@ -367,6 +378,7 @@ public class DisplayMetrics { noncompatScaledDensity = scaledDensity; noncompatXdpi = xdpi; noncompatYdpi = ydpi; + fontScaleConverter = null; } @Override diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 4afd26879218..608cbda28370 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -122,6 +122,13 @@ public class FeatureFlagUtils { public static final String SETTINGS_NEW_KEYBOARD_TRACKPAD = "settings_new_keyboard_trackpad"; /** + * Enable trackpad gesture settings UI + * @hide + */ + public static final String SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE = + "settings_new_keyboard_trackpad_gesture"; + + /** * Enable the new pages which is implemented with SPA. * @hide */ @@ -171,6 +178,7 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_SHORTCUT, "false"); DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY, "false"); DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "false"); + DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE, "false"); DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "false"); DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false"); DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false"); @@ -190,6 +198,7 @@ public class FeatureFlagUtils { PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_SHORTCUT); PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY); PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_TRACKPAD); + PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE); } /** diff --git a/core/java/android/util/IntArray.java b/core/java/android/util/IntArray.java index 7b28b8a607de..bc0e35da37c3 100644 --- a/core/java/android/util/IntArray.java +++ b/core/java/android/util/IntArray.java @@ -234,4 +234,23 @@ public class IntArray implements Cloneable { public int[] toArray() { return Arrays.copyOf(mValues, mSize); } + + @Override + public String toString() { + // Code below is copied from Arrays.toString(), but uses mSize in the lopp (it cannot call + // Arrays.toString() directly as it would return the unused elements as well) + int iMax = mSize - 1; + if (iMax == -1) { + return "[]"; + } + StringBuilder b = new StringBuilder(); + b.append('['); + for (int i = 0;; i++) { + b.append(mValues[i]); + if (i == iMax) { + return b.append(']').toString(); + } + b.append(", "); + } + } } diff --git a/core/java/android/util/TypedValue.java b/core/java/android/util/TypedValue.java index 44318bbc5468..7e054fc3f952 100644 --- a/core/java/android/util/TypedValue.java +++ b/core/java/android/util/TypedValue.java @@ -408,7 +408,14 @@ public class TypedValue { case COMPLEX_UNIT_DIP: return value * metrics.density; case COMPLEX_UNIT_SP: - return value * metrics.scaledDensity; + if (metrics.fontScaleConverter != null) { + return applyDimension( + COMPLEX_UNIT_DIP, + metrics.fontScaleConverter.convertSpToDp(value), + metrics); + } else { + return value * metrics.scaledDensity; + } case COMPLEX_UNIT_PT: return value * metrics.xdpi * (1.0f/72); case COMPLEX_UNIT_IN: diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 5933ae4f8ca4..fbca37359779 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -319,6 +319,19 @@ public final class Display { public static final int FLAG_TOUCH_FEEDBACK_DISABLED = 1 << 10; /** + * Flag: Indicates that the display maintains its own focus and touch mode. + * + * This flag is similar to {@link com.android.internal.R.bool.config_perDisplayFocusEnabled} in + * behavior, but only applies to the specific display instead of system-wide to all displays. + * + * Note: The display must be trusted in order to have its own focus. + * + * @see #FLAG_TRUSTED + * @hide + */ + public static final int FLAG_OWN_FOCUS = 1 << 11; + + /** * Display flag: Indicates that the contents of the display should not be scaled * to fit the physical screen dimensions. Used for development only to emulate * devices with smaller physicals screens while preserving density. diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index e2bc5668d058..0743ccb37f51 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -738,9 +738,8 @@ interface IWindowManager * If invoked through a package other than a launcher app, returns an empty list. * * @param displayId the id of the logical display - * @param packageName the name of the calling package */ - List<DisplayInfo> getPossibleDisplayInfo(int displayId, String packageName); + List<DisplayInfo> getPossibleDisplayInfo(int displayId); /** * Called to show global actions. diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index ef18458d3244..33ea92de68b4 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -728,8 +728,8 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall private void releaseSurfaces(boolean releaseSurfacePackage) { mAlpha = 1f; + mSurface.destroy(); synchronized (mSurfaceControlLock) { - mSurface.destroy(); if (mBlastBufferQueue != null) { mBlastBufferQueue.destroy(); mBlastBufferQueue = null; @@ -947,108 +947,112 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall + " left=" + (mWindowSpaceLeft != mLocation[0]) + " top=" + (mWindowSpaceTop != mLocation[1])); - mVisible = mRequestedVisible; - mWindowSpaceLeft = mLocation[0]; - mWindowSpaceTop = mLocation[1]; - mSurfaceWidth = myWidth; - mSurfaceHeight = myHeight; - mFormat = mRequestedFormat; - mAlpha = alpha; - mLastWindowVisibility = mWindowVisibility; - mTransformHint = viewRoot.getBufferTransformHint(); - mSubLayer = mRequestedSubLayer; - - mScreenRect.left = mWindowSpaceLeft; - mScreenRect.top = mWindowSpaceTop; - mScreenRect.right = mWindowSpaceLeft + getWidth(); - mScreenRect.bottom = mWindowSpaceTop + getHeight(); - if (translator != null) { - translator.translateRectInAppWindowToScreen(mScreenRect); - } - - final Rect surfaceInsets = viewRoot.mWindowAttributes.surfaceInsets; - mScreenRect.offset(surfaceInsets.left, surfaceInsets.top); - // Collect all geometry changes and apply these changes on the RenderThread worker - // via the RenderNode.PositionUpdateListener. - final Transaction surfaceUpdateTransaction = new Transaction(); - if (creating) { - updateOpaqueFlag(); - final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]"; - createBlastSurfaceControls(viewRoot, name, surfaceUpdateTransaction); - } else if (mSurfaceControl == null) { - return; - } + try { + mVisible = mRequestedVisible; + mWindowSpaceLeft = mLocation[0]; + mWindowSpaceTop = mLocation[1]; + mSurfaceWidth = myWidth; + mSurfaceHeight = myHeight; + mFormat = mRequestedFormat; + mAlpha = alpha; + mLastWindowVisibility = mWindowVisibility; + mTransformHint = viewRoot.getBufferTransformHint(); + mSubLayer = mRequestedSubLayer; + + mScreenRect.left = mWindowSpaceLeft; + mScreenRect.top = mWindowSpaceTop; + mScreenRect.right = mWindowSpaceLeft + getWidth(); + mScreenRect.bottom = mWindowSpaceTop + getHeight(); + if (translator != null) { + translator.translateRectInAppWindowToScreen(mScreenRect); + } - final boolean redrawNeeded = sizeChanged || creating || hintChanged - || (mVisible && !mDrawFinished) || alphaChanged || relativeZChanged; - boolean shouldSyncBuffer = - redrawNeeded && viewRoot.wasRelayoutRequested() && viewRoot.isInLocalSync(); - SyncBufferTransactionCallback syncBufferTransactionCallback = null; - if (shouldSyncBuffer) { - syncBufferTransactionCallback = new SyncBufferTransactionCallback(); - mBlastBufferQueue.syncNextTransaction( - false /* acquireSingleBuffer */, - syncBufferTransactionCallback::onTransactionReady); - } + final Rect surfaceInsets = viewRoot.mWindowAttributes.surfaceInsets; + mScreenRect.offset(surfaceInsets.left, surfaceInsets.top); + // Collect all geometry changes and apply these changes on the RenderThread worker + // via the RenderNode.PositionUpdateListener. + final Transaction surfaceUpdateTransaction = new Transaction(); + if (creating) { + updateOpaqueFlag(); + final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]"; + createBlastSurfaceControls(viewRoot, name, surfaceUpdateTransaction); + } else if (mSurfaceControl == null) { + return; + } - final boolean realSizeChanged = performSurfaceTransaction(viewRoot, translator, - creating, sizeChanged, hintChanged, relativeZChanged, - surfaceUpdateTransaction); + final boolean redrawNeeded = sizeChanged || creating || hintChanged + || (mVisible && !mDrawFinished) || alphaChanged || relativeZChanged; + boolean shouldSyncBuffer = + redrawNeeded && viewRoot.wasRelayoutRequested() && viewRoot.isInLocalSync(); + SyncBufferTransactionCallback syncBufferTransactionCallback = null; + if (shouldSyncBuffer) { + syncBufferTransactionCallback = new SyncBufferTransactionCallback(); + mBlastBufferQueue.syncNextTransaction( + false /* acquireSingleBuffer */, + syncBufferTransactionCallback::onTransactionReady); + } - try { - SurfaceHolder.Callback[] callbacks = null; + final boolean realSizeChanged = performSurfaceTransaction(viewRoot, translator, + creating, sizeChanged, hintChanged, relativeZChanged, + surfaceUpdateTransaction); - final boolean surfaceChanged = creating; - if (mSurfaceCreated && (surfaceChanged || (!mVisible && visibleChanged))) { - mSurfaceCreated = false; - notifySurfaceDestroyed(); - } + try { + SurfaceHolder.Callback[] callbacks = null; - copySurface(creating /* surfaceControlCreated */, sizeChanged); - - if (mVisible && mSurface.isValid()) { - if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) { - mSurfaceCreated = true; - mIsCreating = true; - if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " - + "visibleChanged -- surfaceCreated"); - callbacks = getSurfaceCallbacks(); - for (SurfaceHolder.Callback c : callbacks) { - c.surfaceCreated(mSurfaceHolder); - } + final boolean surfaceChanged = creating; + if (mSurfaceCreated && (surfaceChanged || (!mVisible && visibleChanged))) { + mSurfaceCreated = false; + notifySurfaceDestroyed(); } - if (creating || formatChanged || sizeChanged || hintChanged - || visibleChanged || realSizeChanged) { - if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " - + "surfaceChanged -- format=" + mFormat - + " w=" + myWidth + " h=" + myHeight); - if (callbacks == null) { + + copySurface(creating /* surfaceControlCreated */, sizeChanged); + + if (mVisible && mSurface.isValid()) { + if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) { + mSurfaceCreated = true; + mIsCreating = true; + if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + + "visibleChanged -- surfaceCreated"); callbacks = getSurfaceCallbacks(); + for (SurfaceHolder.Callback c : callbacks) { + c.surfaceCreated(mSurfaceHolder); + } } - for (SurfaceHolder.Callback c : callbacks) { - c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight); - } - } - if (redrawNeeded) { - if (DEBUG) { - Log.i(TAG, System.identityHashCode(this) + " surfaceRedrawNeeded"); - } - if (callbacks == null) { - callbacks = getSurfaceCallbacks(); + if (creating || formatChanged || sizeChanged || hintChanged + || visibleChanged || realSizeChanged) { + if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + + "surfaceChanged -- format=" + mFormat + + " w=" + myWidth + " h=" + myHeight); + if (callbacks == null) { + callbacks = getSurfaceCallbacks(); + } + for (SurfaceHolder.Callback c : callbacks) { + c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight); + } } - - if (shouldSyncBuffer) { - handleSyncBufferCallback(callbacks, syncBufferTransactionCallback); - } else { - handleSyncNoBuffer(callbacks); + if (redrawNeeded) { + if (DEBUG) { + Log.i(TAG, System.identityHashCode(this) + " surfaceRedrawNeeded"); + } + if (callbacks == null) { + callbacks = getSurfaceCallbacks(); + } + + if (shouldSyncBuffer) { + handleSyncBufferCallback(callbacks, syncBufferTransactionCallback); + } else { + handleSyncNoBuffer(callbacks); + } } } + } finally { + mIsCreating = false; + if (mSurfaceControl != null && !mSurfaceCreated) { + releaseSurfaces(false /* releaseSurfacePackage*/); + } } - } finally { - mIsCreating = false; - if (mSurfaceControl != null && !mSurfaceCreated) { - releaseSurfaces(false /* releaseSurfacePackage*/); - } + } catch (Exception ex) { + Log.e(TAG, "Exception configuring surface", ex); } if (DEBUG) Log.v( TAG, "Layout: x=" + mScreenRect.left + " y=" + mScreenRect.top diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index 6dc90117bf55..5c4305cc1647 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -366,7 +366,7 @@ public final class WindowManagerImpl implements WindowManager { List<DisplayInfo> possibleDisplayInfos; try { possibleDisplayInfos = WindowManagerGlobal.getWindowManagerService() - .getPossibleDisplayInfo(displayId, mContext.getPackageName()); + .getPossibleDisplayInfo(displayId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index c2da638aca8d..a35e13e3ac3b 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -421,6 +421,53 @@ public final class TransitionInfo implements Parcelable { return false; } + /** + * Releases temporary-for-animation surfaces referenced by this to potentially free up memory. + * This includes root-leash and snapshots. + */ + public void releaseAnimSurfaces() { + for (int i = mChanges.size() - 1; i >= 0; --i) { + final Change c = mChanges.get(i); + if (c.mSnapshot != null) { + c.mSnapshot.release(); + c.mSnapshot = null; + } + } + if (mRootLeash != null) { + mRootLeash.release(); + } + } + + /** + * Releases ALL the surfaces referenced by this to potentially free up memory. Do NOT use this + * if the surface-controls get stored and used elsewhere in the process. To just release + * temporary-for-animation surfaces, use {@link #releaseAnimSurfaces}. + */ + public void releaseAllSurfaces() { + releaseAnimSurfaces(); + for (int i = mChanges.size() - 1; i >= 0; --i) { + mChanges.get(i).getLeash().release(); + } + } + + /** + * Makes a copy of this as if it were parcel'd and unparcel'd. This implies that surfacecontrol + * refcounts are incremented which allows the "remote" receiver to release them without breaking + * the caller's references. Use this only if you need to "send" this to a local function which + * assumes it is being called from a remote caller. + */ + public TransitionInfo localRemoteCopy() { + final TransitionInfo out = new TransitionInfo(mType, mFlags); + for (int i = 0; i < mChanges.size(); ++i) { + out.mChanges.add(mChanges.get(i).localRemoteCopy()); + } + out.mRootLeash = mRootLeash != null ? new SurfaceControl(mRootLeash, "localRemote") : null; + // Doesn't have any native stuff, so no need for actual copy + out.mOptions = mOptions; + out.mRootOffset.set(mRootOffset); + return out; + } + /** Represents the change a WindowContainer undergoes during a transition */ public static final class Change implements Parcelable { private final WindowContainerToken mContainer; @@ -473,6 +520,27 @@ public final class TransitionInfo implements Parcelable { mSnapshotLuma = in.readFloat(); } + private Change localRemoteCopy() { + final Change out = new Change(mContainer, new SurfaceControl(mLeash, "localRemote")); + out.mParent = mParent; + out.mLastParent = mLastParent; + out.mMode = mMode; + out.mFlags = mFlags; + out.mStartAbsBounds.set(mStartAbsBounds); + out.mEndAbsBounds.set(mEndAbsBounds); + out.mEndRelOffset.set(mEndRelOffset); + out.mTaskInfo = mTaskInfo; + out.mAllowEnterPip = mAllowEnterPip; + out.mStartRotation = mStartRotation; + out.mEndRotation = mEndRotation; + out.mEndFixedRotation = mEndFixedRotation; + out.mRotationAnimation = mRotationAnimation; + out.mBackgroundColor = mBackgroundColor; + out.mSnapshot = mSnapshot != null ? new SurfaceControl(mSnapshot, "localRemote") : null; + out.mSnapshotLuma = mSnapshotLuma; + return out; + } + /** Sets the parent of this change's container. The parent must be a participant or null. */ public void setParent(@Nullable WindowContainerToken parent) { mParent = parent; diff --git a/core/java/com/android/internal/content/om/OverlayManagerImpl.java b/core/java/com/android/internal/content/om/OverlayManagerImpl.java index 6ceccd1b544b..260d1a2c88e5 100644 --- a/core/java/com/android/internal/content/om/OverlayManagerImpl.java +++ b/core/java/com/android/internal/content/om/OverlayManagerImpl.java @@ -192,7 +192,7 @@ public class OverlayManagerImpl { * @param name the non-check overlay name * @return the valid overlay name */ - private static String checkOverlayNameValid(@NonNull String name) { + public static String checkOverlayNameValid(@NonNull String name) { final String overlayName = Preconditions.checkStringNotEmpty( name, "overlayName should be neither empty nor null string"); diff --git a/core/java/com/android/internal/usb/DumpUtils.java b/core/java/com/android/internal/usb/DumpUtils.java index 1eb446eff171..f974d9d10efd 100644 --- a/core/java/com/android/internal/usb/DumpUtils.java +++ b/core/java/com/android/internal/usb/DumpUtils.java @@ -174,6 +174,9 @@ public class DumpUtils { } else { dump.write("supported_modes", UsbPortProto.SUPPORTED_MODES, UsbPort.modeToString(mode)); } + dump.write("supports_compliance_warnings", + UsbPortProto.SUPPORTS_COMPLIANCE_WARNINGS, + port.supportsComplianceWarnings()); dump.end(token); } @@ -250,6 +253,8 @@ public class DumpUtils { status.isPowerTransferLimited()); dump.write("usb_power_brick_status", UsbPortStatusProto.USB_POWER_BRICK_STATUS, UsbPort.powerBrickConnectionStatusToString(status.getPowerBrickConnectionStatus())); + dump.write("compliance_warning_status", UsbPortStatusProto.COMPLIANCE_WARNINGS_STRING, + UsbPort.complianceWarningsToString(status.getComplianceWarnings())); dump.end(token); } } diff --git a/core/jni/Android.bp b/core/jni/Android.bp index f140e7980f52..1bc903a191ad 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -40,6 +40,8 @@ cc_library_shared { cppflags: ["-Wno-conversion-null"], + cpp_std: "gnu++20", + srcs: [ "android_animation_PropertyValuesHolder.cpp", "android_os_SystemClock.cpp", diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp index a8d72316036e..29560dce1cd8 100644 --- a/core/jni/android_content_res_ApkAssets.cpp +++ b/core/jni/android_content_res_ApkAssets.cpp @@ -96,7 +96,7 @@ class LoaderAssetsProvider : public AssetsProvider { } bool ForEachFile(const std::string& /* root_path */, - const std::function<void(const StringPiece&, FileType)>& /* f */) const { + const std::function<void(StringPiece, FileType)>& /* f */) const { return true; } @@ -402,7 +402,7 @@ static jlong NativeGetStringBlock(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) return reinterpret_cast<jlong>(apk_assets->GetLoadedArsc()->GetStringPool()); } -static jboolean NativeIsUpToDate(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) { +static jboolean NativeIsUpToDate(jlong ptr) { auto scoped_apk_assets = ScopedLock(ApkAssetsFromLong(ptr)); auto apk_assets = scoped_apk_assets->get(); return apk_assets->IsUpToDate() ? JNI_TRUE : JNI_FALSE; @@ -500,24 +500,28 @@ static jboolean NativeDefinesOverlayable(JNIEnv* env, jclass /*clazz*/, jlong pt // JNI registration. static const JNINativeMethod gApkAssetsMethods[] = { - {"nativeLoad", "(ILjava/lang/String;ILandroid/content/res/loader/AssetsProvider;)J", - (void*)NativeLoad}, - {"nativeLoadEmpty", "(ILandroid/content/res/loader/AssetsProvider;)J", (void*)NativeLoadEmpty}, - {"nativeLoadFd", - "(ILjava/io/FileDescriptor;Ljava/lang/String;ILandroid/content/res/loader/AssetsProvider;)J", - (void*)NativeLoadFromFd}, - {"nativeLoadFdOffsets", - "(ILjava/io/FileDescriptor;Ljava/lang/String;JJILandroid/content/res/loader/AssetsProvider;)J", - (void*)NativeLoadFromFdOffset}, - {"nativeDestroy", "(J)V", (void*)NativeDestroy}, - {"nativeGetAssetPath", "(J)Ljava/lang/String;", (void*)NativeGetAssetPath}, - {"nativeGetDebugName", "(J)Ljava/lang/String;", (void*)NativeGetDebugName}, - {"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock}, - {"nativeIsUpToDate", "(J)Z", (void*)NativeIsUpToDate}, - {"nativeOpenXml", "(JLjava/lang/String;)J", (void*)NativeOpenXml}, - {"nativeGetOverlayableInfo", "(JLjava/lang/String;)Landroid/content/om/OverlayableInfo;", - (void*)NativeGetOverlayableInfo}, - {"nativeDefinesOverlayable", "(J)Z", (void*)NativeDefinesOverlayable}, + {"nativeLoad", "(ILjava/lang/String;ILandroid/content/res/loader/AssetsProvider;)J", + (void*)NativeLoad}, + {"nativeLoadEmpty", "(ILandroid/content/res/loader/AssetsProvider;)J", + (void*)NativeLoadEmpty}, + {"nativeLoadFd", + "(ILjava/io/FileDescriptor;Ljava/lang/String;ILandroid/content/res/loader/" + "AssetsProvider;)J", + (void*)NativeLoadFromFd}, + {"nativeLoadFdOffsets", + "(ILjava/io/FileDescriptor;Ljava/lang/String;JJILandroid/content/res/loader/" + "AssetsProvider;)J", + (void*)NativeLoadFromFdOffset}, + {"nativeDestroy", "(J)V", (void*)NativeDestroy}, + {"nativeGetAssetPath", "(J)Ljava/lang/String;", (void*)NativeGetAssetPath}, + {"nativeGetDebugName", "(J)Ljava/lang/String;", (void*)NativeGetDebugName}, + {"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock}, + // @CriticalNative + {"nativeIsUpToDate", "(J)Z", (void*)NativeIsUpToDate}, + {"nativeOpenXml", "(JLjava/lang/String;)J", (void*)NativeOpenXml}, + {"nativeGetOverlayableInfo", "(JLjava/lang/String;)Landroid/content/om/OverlayableInfo;", + (void*)NativeGetOverlayableInfo}, + {"nativeDefinesOverlayable", "(J)Z", (void*)NativeDefinesOverlayable}, }; int register_android_content_res_ApkAssets(JNIEnv* env) { diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp index cb97698fefea..939a0e411913 100644 --- a/core/jni/android_hardware_SensorManager.cpp +++ b/core/jni/android_hardware_SensorManager.cpp @@ -243,6 +243,23 @@ nativeGetDynamicSensors(JNIEnv *env, jclass clazz, jlong sensorManager, jobject } } +static void nativeGetRuntimeSensors(JNIEnv *env, jclass clazz, jlong sensorManager, jint deviceId, + jobject sensorList) { + SensorManager *mgr = reinterpret_cast<SensorManager *>(sensorManager); + const ListOffsets &listOffsets(gListOffsets); + + Vector<Sensor> nativeList; + + mgr->getRuntimeSensorList(deviceId, nativeList); + + ALOGI("DYNS native SensorManager.getRuntimeSensorList return %zu sensors", nativeList.size()); + for (size_t i = 0; i < nativeList.size(); ++i) { + jobject sensor = translateNativeSensorToJavaSensor(env, NULL, nativeList[i]); + // add to list + env->CallBooleanMethod(sensorList, listOffsets.add, sensor); + } +} + static jboolean nativeIsDataInjectionEnabled(JNIEnv *_env, jclass _this, jlong sensorManager) { SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager); return mgr->isDataInjectionEnabled(); @@ -503,40 +520,26 @@ static jint nativeInjectSensorData(JNIEnv *env, jclass clazz, jlong eventQ, jint //---------------------------------------------------------------------------- static const JNINativeMethod gSystemSensorManagerMethods[] = { - {"nativeClassInit", - "()V", - (void*)nativeClassInit }, - {"nativeCreate", - "(Ljava/lang/String;)J", - (void*)nativeCreate }, - - {"nativeGetSensorAtIndex", - "(JLandroid/hardware/Sensor;I)Z", - (void*)nativeGetSensorAtIndex }, - - {"nativeGetDynamicSensors", - "(JLjava/util/List;)V", - (void*)nativeGetDynamicSensors }, - - {"nativeIsDataInjectionEnabled", - "(J)Z", - (void*)nativeIsDataInjectionEnabled }, - - {"nativeCreateDirectChannel", - "(JJIILandroid/hardware/HardwareBuffer;)I", - (void*)nativeCreateDirectChannel }, - - {"nativeDestroyDirectChannel", - "(JI)V", - (void*)nativeDestroyDirectChannel }, - - {"nativeConfigDirectChannel", - "(JIII)I", - (void*)nativeConfigDirectChannel }, - - {"nativeSetOperationParameter", - "(JII[F[I)I", - (void*)nativeSetOperationParameter }, + {"nativeClassInit", "()V", (void *)nativeClassInit}, + {"nativeCreate", "(Ljava/lang/String;)J", (void *)nativeCreate}, + + {"nativeGetSensorAtIndex", "(JLandroid/hardware/Sensor;I)Z", + (void *)nativeGetSensorAtIndex}, + + {"nativeGetDynamicSensors", "(JLjava/util/List;)V", (void *)nativeGetDynamicSensors}, + + {"nativeGetRuntimeSensors", "(JILjava/util/List;)V", (void *)nativeGetRuntimeSensors}, + + {"nativeIsDataInjectionEnabled", "(J)Z", (void *)nativeIsDataInjectionEnabled}, + + {"nativeCreateDirectChannel", "(JJIILandroid/hardware/HardwareBuffer;)I", + (void *)nativeCreateDirectChannel}, + + {"nativeDestroyDirectChannel", "(JI)V", (void *)nativeDestroyDirectChannel}, + + {"nativeConfigDirectChannel", "(JIII)I", (void *)nativeConfigDirectChannel}, + + {"nativeSetOperationParameter", "(JII[F[I)I", (void *)nativeSetOperationParameter}, }; static const JNINativeMethod gBaseEventQueueMethods[] = { diff --git a/core/proto/android/service/usb.proto b/core/proto/android/service/usb.proto index df5e0a942ca7..607fd108c999 100644 --- a/core/proto/android/service/usb.proto +++ b/core/proto/android/service/usb.proto @@ -240,6 +240,7 @@ message UsbPortProto { // ID of the port. A device (eg: Chromebooks) might have multiple ports. optional string id = 1; repeated Mode supported_modes = 2; + optional bool supports_compliance_warnings = 3; } message UsbPortStatusProto { @@ -268,6 +269,7 @@ message UsbPortStatusProto { optional string usb_data_status = 7; optional bool is_power_transfer_limited = 8; optional string usb_power_brick_status = 9; + optional string compliance_warnings_string = 10; } message UsbPortStatusRoleCombinationProto { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 5a7abcce6b75..ad8f7fb2d425 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -293,6 +293,7 @@ <protected-broadcast android:name="android.hardware.usb.action.USB_STATE" /> <protected-broadcast android:name="android.hardware.usb.action.USB_PORT_CHANGED" /> + <protected-broadcast android:name="android.hardware.usb.action.USB_PORT_COMPLIANCE_CHANGED" /> <protected-broadcast android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" /> <protected-broadcast android:name="android.hardware.usb.action.USB_ACCESSORY_DETACHED" /> <protected-broadcast android:name="android.hardware.usb.action.USB_ACCESSORY_HANDSHAKE" /> @@ -6286,12 +6287,12 @@ <!-- Allows a regular application to use {@link android.app.Service#startForeground Service.startForeground} with the type "specialUse". - <p>Protection level: signature|appop|instant + <p>Protection level: normal|appop|instant --> <permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" android:description="@string/permdesc_foregroundServiceSpecialUse" android:label="@string/permlab_foregroundServiceSpecialUse" - android:protectionLevel="signature|appop|instant" /> + android:protectionLevel="normal|appop|instant" /> <!-- @SystemApi Allows to access all app shortcuts. @hide --> @@ -7290,6 +7291,10 @@ android:permission="android.permission.BIND_JOB_SERVICE" > </service> + <service android:name="com.android.server.pm.GentleUpdateHelper$Service" + android:permission="android.permission.BIND_JOB_SERVICE" > + </service> + <service android:name="com.android.server.autofill.AutofillCompatAccessibilityService" android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 2d832bc71684..79464937ff30 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -8955,6 +8955,9 @@ <!-- Flag indicating whether a recognition service can be selected as default. The default value of this flag is true. --> <attr name="selectableAsDefault" format="boolean" /> + <!-- The maximal number of recognition sessions ongoing at the same time. + The default value is 1, meaning no concurrency. --> + <attr name="maxConcurrentSessionsCount" format="integer" /> </declare-styleable> <!-- Use <code>voice-interaction-service</code> as the root tag of the XML resource that diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index ccce9ba6a709..9c2643b164ef 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1828,6 +1828,14 @@ config_enableFusedLocationOverlay is false. --> <string name="config_fusedLocationProviderPackageName" translatable="false">com.android.location.fused</string> + <!-- If true will use the GNSS hardware implementation to service the GPS_PROVIDER. If false + will allow the GPS_PROVIDER to be replaced by an app at run-time (restricted to the package + specified by config_gnssLocationProviderPackageName). --> + <bool name="config_useGnssHardwareProvider" translatable="false">true</bool> + <!-- Package name providing GNSS location support. Used only when + config_useGnssHardwareProvider is false. --> + <string name="config_gnssLocationProviderPackageName" translatable="false">@null</string> + <!-- Default value for the ADAS GNSS Location Enabled setting if this setting has never been set before. --> <bool name="config_defaultAdasGnssLocationEnabled" translatable="false">false</bool> diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml index 61229cb79154..bc5878a0b2ed 100644 --- a/core/res/res/values/public-staging.xml +++ b/core/res/res/values/public-staging.xml @@ -117,6 +117,7 @@ <public name="accessibilityDataPrivate" /> <public name="enableTextStylingShortcuts" /> <public name="targetDisplayCategory"/> + <public name="maxConcurrentSessionsCount" /> </staging-public-group> <staging-public-group type="id" first-id="0x01cd0000"> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index ae033cab0807..cd93932065fa 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1963,6 +1963,7 @@ <java-symbol type="bool" name="config_enableActivityRecognitionHardwareOverlay" /> <java-symbol type="bool" name="config_defaultAdasGnssLocationEnabled" /> <java-symbol type="bool" name="config_enableFusedLocationOverlay" /> + <java-symbol type="bool" name="config_useGnssHardwareProvider" /> <java-symbol type="bool" name="config_enableGeocoderOverlay" /> <java-symbol type="bool" name="config_enableGeofenceOverlay" /> <java-symbol type="bool" name="config_enableNetworkLocationOverlay" /> @@ -2125,6 +2126,7 @@ <java-symbol type="string" name="config_datause_iface" /> <java-symbol type="string" name="config_activityRecognitionHardwarePackageName" /> <java-symbol type="string" name="config_fusedLocationProviderPackageName" /> + <java-symbol type="string" name="config_gnssLocationProviderPackageName" /> <java-symbol type="string" name="config_geocoderProviderPackageName" /> <java-symbol type="string" name="config_geofenceProviderPackageName" /> <java-symbol type="string" name="config_networkLocationProviderPackageName" /> diff --git a/core/tests/GameManagerTests/AndroidManifest.xml b/core/tests/GameManagerTests/AndroidManifest.xml index 6a01abee3e1c..f1ab6969e99f 100644 --- a/core/tests/GameManagerTests/AndroidManifest.xml +++ b/core/tests/GameManagerTests/AndroidManifest.xml @@ -17,7 +17,9 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.app.gamemanagertests" - android:sharedUserId="android.uid.system" > + android:sharedUserId="com.android.uid.test" > + + <uses-permission android:name="android.permission.MANAGE_GAME_MODE" /> <application android:appCategory="game"> <uses-library android:name="android.test.runner" /> diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index 48cfc87d7d55..e811bb67fb99 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -43,10 +43,12 @@ android_test { "mockwebserver", "guava", "androidx.core_core", + "androidx.core_core-ktx", "androidx.test.espresso.core", "androidx.test.ext.junit", "androidx.test.runner", "androidx.test.rules", + "junit-params", "kotlin-test", "mockito-target-minus-junit4", "ub-uiautomator", diff --git a/core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java b/core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java index a648a885aea2..fc69f6914658 100644 --- a/core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java +++ b/core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java @@ -17,19 +17,26 @@ package android.app.time; import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING; +import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED; import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING; +import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_UNKNOWN; import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN; import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_UNCERTAIN; import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT; import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY; import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode; import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable; +import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT; +import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_NOT_APPLICABLE; import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK; +import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_NOT_APPLICABLE; import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; import android.app.time.LocationTimeZoneAlgorithmStatus.ProviderStatus; import android.service.timezone.TimeZoneProviderStatus; @@ -207,4 +214,114 @@ public class LocationTimeZoneAlgorithmStatusTest { assertEquals(status, LocationTimeZoneAlgorithmStatus.parseCommandlineArg(status.toString())); } + + @Test + public void testCouldEnableTelephonyFallback_notRunning() { + LocationTimeZoneAlgorithmStatus notRunning = + new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_RUNNING, + PROVIDER_STATUS_NOT_READY, null, PROVIDER_STATUS_NOT_READY, null); + assertFalse(notRunning.couldEnableTelephonyFallback()); + } + + @Test + public void testCouldEnableTelephonyFallback_unknown() { + // DETECTION_ALGORITHM_STATUS_UNKNOWN must never allow fallback + LocationTimeZoneAlgorithmStatus unknown = + new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_UNKNOWN, + PROVIDER_STATUS_NOT_READY, null, PROVIDER_STATUS_NOT_READY, null); + assertFalse(unknown.couldEnableTelephonyFallback()); + } + + @Test + public void testCouldEnableTelephonyFallback_notSupported() { + // DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED must never allow fallback + LocationTimeZoneAlgorithmStatus notSupported = + new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED, + PROVIDER_STATUS_NOT_READY, null, PROVIDER_STATUS_NOT_READY, null); + assertFalse(notSupported.couldEnableTelephonyFallback()); + } + + @Test + public void testCouldEnableTelephonyFallback_running() { + // DETECTION_ALGORITHM_STATUS_RUNNING may allow fallback + + // Sample provider-reported statuses that do / do not enable fallback. + TimeZoneProviderStatus enableTelephonyFallbackProviderStatus = + new TimeZoneProviderStatus.Builder() + .setLocationDetectionDependencyStatus( + DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT) + .setConnectivityDependencyStatus(DEPENDENCY_STATUS_NOT_APPLICABLE) + .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_NOT_APPLICABLE) + .build(); + assertTrue(enableTelephonyFallbackProviderStatus.couldEnableTelephonyFallback()); + + TimeZoneProviderStatus notEnableTelephonyFallbackProviderStatus = + new TimeZoneProviderStatus.Builder() + .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_NOT_APPLICABLE) + .setConnectivityDependencyStatus(DEPENDENCY_STATUS_NOT_APPLICABLE) + .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_NOT_APPLICABLE) + .build(); + assertFalse(notEnableTelephonyFallbackProviderStatus.couldEnableTelephonyFallback()); + + // Provider not ready: Never enable fallback + { + LocationTimeZoneAlgorithmStatus status = + new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_NOT_READY, null, PROVIDER_STATUS_NOT_READY, null); + assertFalse(status.couldEnableTelephonyFallback()); + } + + // Provider uncertain without reported status: Never enable fallback + { + LocationTimeZoneAlgorithmStatus status = + new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_IS_UNCERTAIN, null, PROVIDER_STATUS_NOT_READY, null); + assertFalse(status.couldEnableTelephonyFallback()); + } + { + LocationTimeZoneAlgorithmStatus status = + new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_IS_UNCERTAIN, null, PROVIDER_STATUS_NOT_PRESENT, null); + assertFalse(status.couldEnableTelephonyFallback()); + } + + // Provider uncertain with reported status: Fallback is based on the status for present + // providers that report their status. All present providers must have reported status and + // agree that fallback is a good idea. + { + LocationTimeZoneAlgorithmStatus status = + new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_IS_UNCERTAIN, enableTelephonyFallbackProviderStatus, + PROVIDER_STATUS_NOT_READY, null); + assertFalse(status.couldEnableTelephonyFallback()); + } + { + LocationTimeZoneAlgorithmStatus status = + new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_IS_UNCERTAIN, enableTelephonyFallbackProviderStatus, + PROVIDER_STATUS_NOT_PRESENT, null); + assertTrue(status.couldEnableTelephonyFallback()); + } + { + LocationTimeZoneAlgorithmStatus status = + new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_IS_UNCERTAIN, enableTelephonyFallbackProviderStatus, + PROVIDER_STATUS_IS_UNCERTAIN, enableTelephonyFallbackProviderStatus); + assertTrue(status.couldEnableTelephonyFallback()); + } + { + LocationTimeZoneAlgorithmStatus status = + new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_IS_UNCERTAIN, enableTelephonyFallbackProviderStatus, + PROVIDER_STATUS_IS_UNCERTAIN, notEnableTelephonyFallbackProviderStatus); + assertFalse(status.couldEnableTelephonyFallback()); + } + { + LocationTimeZoneAlgorithmStatus status = + new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_NOT_PRESENT, null, + PROVIDER_STATUS_IS_UNCERTAIN, enableTelephonyFallbackProviderStatus); + assertTrue(status.couldEnableTelephonyFallback()); + } + } } diff --git a/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java new file mode 100644 index 000000000000..11afd045f364 --- /dev/null +++ b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java @@ -0,0 +1,104 @@ +/* + * 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.companion.virtual.sensor; + +import static android.hardware.Sensor.TYPE_ACCELEROMETER; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import android.os.Parcel; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.os.BackgroundThread; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.time.Duration; + +@RunWith(AndroidJUnit4.class) +public class VirtualSensorConfigTest { + + private static final String SENSOR_NAME = "VirtualSensorName"; + private static final String SENSOR_VENDOR = "VirtualSensorVendor"; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private VirtualSensor.SensorStateChangeCallback mSensorCallback; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void parcelAndUnparcel_matches() { + final VirtualSensorConfig originalConfig = + new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME) + .setVendor(SENSOR_VENDOR) + .setStateChangeCallback(BackgroundThread.getExecutor(), mSensorCallback) + .build(); + final Parcel parcel = Parcel.obtain(); + originalConfig.writeToParcel(parcel, /* flags= */ 0); + parcel.setDataPosition(0); + final VirtualSensorConfig recreatedConfig = + VirtualSensorConfig.CREATOR.createFromParcel(parcel); + assertThat(recreatedConfig.getType()).isEqualTo(originalConfig.getType()); + assertThat(recreatedConfig.getName()).isEqualTo(originalConfig.getName()); + assertThat(recreatedConfig.getVendor()).isEqualTo(originalConfig.getVendor()); + assertThat(recreatedConfig.getStateChangeCallback()).isNotNull(); + } + + @Test + public void sensorConfig_onlyRequiredFields() { + final VirtualSensorConfig config = + new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME).build(); + assertThat(config.getVendor()).isNull(); + assertThat(config.getStateChangeCallback()).isNull(); + } + + @Test + public void sensorConfig_sensorCallbackInvocation() throws Exception { + final VirtualSensorConfig config = + new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME) + .setStateChangeCallback(BackgroundThread.getExecutor(), mSensorCallback) + .build(); + + final Duration samplingPeriod = Duration.ofMillis(123); + final Duration batchLatency = Duration.ofMillis(456); + + config.getStateChangeCallback().onStateChanged(true, + (int) MILLISECONDS.toMicros(samplingPeriod.toMillis()), + (int) MILLISECONDS.toMicros(batchLatency.toMillis())); + + verify(mSensorCallback, timeout(1000)).onStateChanged(true, samplingPeriod, batchLatency); + } +} diff --git a/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorEventTest.java b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorEventTest.java new file mode 100644 index 000000000000..a9583fdc2e2d --- /dev/null +++ b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorEventTest.java @@ -0,0 +1,80 @@ +/* + * 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.companion.virtual.sensor; + +import static com.google.common.truth.Truth.assertThat; + +import static org.testng.Assert.assertThrows; + +import android.os.Parcel; +import android.os.SystemClock; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class VirtualSensorEventTest { + + private static final long TIMESTAMP_NANOS = SystemClock.elapsedRealtimeNanos(); + private static final float[] SENSOR_VALUES = new float[] {1.2f, 3.4f, 5.6f}; + + @Test + public void parcelAndUnparcel_matches() { + final VirtualSensorEvent originalEvent = new VirtualSensorEvent.Builder(SENSOR_VALUES) + .setTimestampNanos(TIMESTAMP_NANOS) + .build(); + final Parcel parcel = Parcel.obtain(); + originalEvent.writeToParcel(parcel, /* flags= */ 0); + parcel.setDataPosition(0); + final VirtualSensorEvent recreatedEvent = + VirtualSensorEvent.CREATOR.createFromParcel(parcel); + assertThat(recreatedEvent.getValues()).isEqualTo(originalEvent.getValues()); + assertThat(recreatedEvent.getTimestampNanos()).isEqualTo(originalEvent.getTimestampNanos()); + } + + @Test + public void sensorEvent_nullValues() { + assertThrows( + IllegalArgumentException.class, () -> new VirtualSensorEvent.Builder(null).build()); + } + + @Test + public void sensorEvent_noValues() { + assertThrows( + IllegalArgumentException.class, + () -> new VirtualSensorEvent.Builder(new float[0]).build()); + } + + @Test + public void sensorEvent_noTimestamp_usesCurrentTime() { + final VirtualSensorEvent event = new VirtualSensorEvent.Builder(SENSOR_VALUES).build(); + assertThat(event.getValues()).isEqualTo(SENSOR_VALUES); + assertThat(TIMESTAMP_NANOS).isLessThan(event.getTimestampNanos()); + assertThat(event.getTimestampNanos()).isLessThan(SystemClock.elapsedRealtimeNanos()); + } + + @Test + public void sensorEvent_created() { + final VirtualSensorEvent event = new VirtualSensorEvent.Builder(SENSOR_VALUES) + .setTimestampNanos(TIMESTAMP_NANOS) + .build(); + assertThat(event.getTimestampNanos()).isEqualTo(TIMESTAMP_NANOS); + assertThat(event.getValues()).isEqualTo(SENSOR_VALUES); + } +} diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt new file mode 100644 index 000000000000..cfca0375bb96 --- /dev/null +++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt @@ -0,0 +1,78 @@ +/* + * 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.content.res + +import androidx.core.util.forEach +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class FontScaleConverterFactoryTest { + + @Test + fun scale200IsTwiceAtSmallSizes() { + val table = FontScaleConverterFactory.forScale(2F)!! + assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(2f) + assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(16f) + assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(20f) + assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(10f) + assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f) + } + + @SmallTest + fun missingLookupTableReturnsNull() { + assertThat(FontScaleConverterFactory.forScale(3F)).isNull() + } + + @SmallTest + fun missingLookupTable105ReturnsNull() { + assertThat(FontScaleConverterFactory.forScale(1.05F)).isNull() + } + + @SmallTest + fun missingLookupTableNegativeReturnsNull() { + assertThat(FontScaleConverterFactory.forScale(-1F)).isNull() + } + + @SmallTest + fun unnecessaryFontScalesReturnsNull() { + assertThat(FontScaleConverterFactory.forScale(0F)).isNull() + assertThat(FontScaleConverterFactory.forScale(1F)).isNull() + assertThat(FontScaleConverterFactory.forScale(0.85F)).isNull() + } + + @SmallTest + fun tablesMatchAndAreMonotonicallyIncreasing() { + FontScaleConverterFactory.LOOKUP_TABLES.forEach { _, lookupTable -> + assertThat(lookupTable.mToDpValues).hasLength(lookupTable.mFromSpValues.size) + assertThat(lookupTable.mToDpValues).isNotEmpty() + + assertThat(lookupTable.mFromSpValues.asList()).isInStrictOrder() + assertThat(lookupTable.mToDpValues.asList()).isInStrictOrder() + + assertThat(lookupTable.mFromSpValues.asList()).containsNoDuplicates() + assertThat(lookupTable.mToDpValues.asList()).containsNoDuplicates() + } + } + + companion object { + private const val CONVERSION_TOLERANCE = 0.05f + } +} diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterTest.kt new file mode 100644 index 000000000000..e405c55a53e3 --- /dev/null +++ b/core/tests/coretests/src/android/content/res/FontScaleConverterTest.kt @@ -0,0 +1,95 @@ +/* + * 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.content.res + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class FontScaleConverterTest { + + @Test + fun straightInterpolation() { + val table = createTable(8f to 8f, 10f to 10f, 20f to 20f) + assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(1f) + assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(8f) + assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(10f) + assertThat(table.convertSpToDp(30F)).isWithin(CONVERSION_TOLERANCE).of(30f) + assertThat(table.convertSpToDp(20F)).isWithin(CONVERSION_TOLERANCE).of(20f) + assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(5f) + assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f) + } + + @Test + fun interpolate200Percent() { + val table = createTable(8f to 16f, 10f to 20f, 30f to 60f) + assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(2f) + assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(16f) + assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(20f) + assertThat(table.convertSpToDp(30F)).isWithin(CONVERSION_TOLERANCE).of(60f) + assertThat(table.convertSpToDp(20F)).isWithin(CONVERSION_TOLERANCE).of(40f) + assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(10f) + assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f) + } + + @Test + fun interpolate150Percent() { + val table = createTable(2f to 3f, 10f to 15f, 20f to 30f, 100f to 150f) + assertThat(table.convertSpToDp(2F)).isWithin(CONVERSION_TOLERANCE).of(3f) + assertThat(table.convertSpToDp(1F)).isWithin(CONVERSION_TOLERANCE).of(1.5f) + assertThat(table.convertSpToDp(8F)).isWithin(CONVERSION_TOLERANCE).of(12f) + assertThat(table.convertSpToDp(10F)).isWithin(CONVERSION_TOLERANCE).of(15f) + assertThat(table.convertSpToDp(20F)).isWithin(CONVERSION_TOLERANCE).of(30f) + assertThat(table.convertSpToDp(50F)).isWithin(CONVERSION_TOLERANCE).of(75f) + assertThat(table.convertSpToDp(5F)).isWithin(CONVERSION_TOLERANCE).of(7.5f) + assertThat(table.convertSpToDp(0F)).isWithin(CONVERSION_TOLERANCE).of(0f) + } + + @Test + fun pastEndsUsesLastScalingFactor() { + val table = createTable(8f to 16f, 10f to 20f, 30f to 60f) + assertThat(table.convertSpToDp(100F)).isWithin(CONVERSION_TOLERANCE).of(200f) + assertThat(table.convertSpToDp(31F)).isWithin(CONVERSION_TOLERANCE).of(62f) + assertThat(table.convertSpToDp(1000F)).isWithin(CONVERSION_TOLERANCE).of(2000f) + assertThat(table.convertSpToDp(2000F)).isWithin(CONVERSION_TOLERANCE).of(4000f) + assertThat(table.convertSpToDp(10000F)).isWithin(CONVERSION_TOLERANCE).of(20000f) + } + + @Test + fun negativeSpIsNegativeDp() { + val table = createTable(8f to 16f, 10f to 20f, 30f to 60f) + assertThat(table.convertSpToDp(-1F)).isWithin(CONVERSION_TOLERANCE).of(-2f) + assertThat(table.convertSpToDp(-8F)).isWithin(CONVERSION_TOLERANCE).of(-16f) + assertThat(table.convertSpToDp(-10F)).isWithin(CONVERSION_TOLERANCE).of(-20f) + assertThat(table.convertSpToDp(-30F)).isWithin(CONVERSION_TOLERANCE).of(-60f) + assertThat(table.convertSpToDp(-20F)).isWithin(CONVERSION_TOLERANCE).of(-40f) + assertThat(table.convertSpToDp(-5F)).isWithin(CONVERSION_TOLERANCE).of(-10f) + assertThat(table.convertSpToDp(-0F)).isWithin(CONVERSION_TOLERANCE).of(0f) + } + + private fun createTable(vararg pairs: Pair<Float, Float>) = + FontScaleConverter( + pairs.map { it.first }.toFloatArray(), + pairs.map { it.second }.toFloatArray() + ) + + companion object { + private const val CONVERSION_TOLERANCE = 0.05f + } +} diff --git a/core/tests/coretests/src/android/os/EnvironmentTest.java b/core/tests/coretests/src/android/os/EnvironmentTest.java index c0325caf1425..8e63a0fe3364 100644 --- a/core/tests/coretests/src/android/os/EnvironmentTest.java +++ b/core/tests/coretests/src/android/os/EnvironmentTest.java @@ -47,29 +47,6 @@ public class EnvironmentTest { return InstrumentationRegistry.getContext(); } - /** - * Sets {@code mode} for the given {@code ops} and the given {@code uid}. - * - * <p>This method drops shell permission identity. - */ - private static void setAppOpsModeForUid(int uid, int mode, String... ops) { - if (ops == null) { - return; - } - InstrumentationRegistry.getInstrumentation() - .getUiAutomation() - .adoptShellPermissionIdentity(); - try { - for (String op : ops) { - getContext().getSystemService(AppOpsManager.class).setUidMode(op, uid, mode); - } - } finally { - InstrumentationRegistry.getInstrumentation() - .getUiAutomation() - .dropShellPermissionIdentity(); - } - } - @Before public void setUp() throws Exception { dir = getContext().getDir("testing", Context.MODE_PRIVATE); @@ -127,17 +104,4 @@ public class EnvironmentTest { Environment.buildPath(dir, "Taxes.pdf").createNewFile(); assertEquals(HAS_OTHER, classifyExternalStorageDirectory(dir)); } - - @Test - public void testIsExternalStorageManager() throws Exception { - assertFalse(Environment.isExternalStorageManager()); - try { - setAppOpsModeForUid(Process.myUid(), AppOpsManager.MODE_ALLOWED, - AppOpsManager.OPSTR_MANAGE_EXTERNAL_STORAGE); - assertTrue(Environment.isExternalStorageManager()); - } finally { - setAppOpsModeForUid(Process.myUid(), AppOpsManager.MODE_DEFAULT, - AppOpsManager.OPSTR_MANAGE_EXTERNAL_STORAGE); - } - } } diff --git a/core/tests/coretests/src/android/os/VibrationEffectTest.java b/core/tests/coretests/src/android/os/VibrationEffectTest.java index f7ca822c36e2..0c7ff4a762b5 100644 --- a/core/tests/coretests/src/android/os/VibrationEffectTest.java +++ b/core/tests/coretests/src/android/os/VibrationEffectTest.java @@ -16,6 +16,7 @@ package android.os; +import static android.os.VibrationEffect.DEFAULT_AMPLITUDE; import static android.os.VibrationEffect.VibrationParameter.targetAmplitude; import static android.os.VibrationEffect.VibrationParameter.targetFrequency; @@ -48,6 +49,7 @@ import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import java.time.Duration; +import java.util.Arrays; @Presubmit @RunWith(MockitoJUnitRunner.class) @@ -62,16 +64,363 @@ public class VibrationEffectTest { private static final int TEST_AMPLITUDE = 100; private static final long[] TEST_TIMINGS = new long[] { 100, 100, 200 }; private static final int[] TEST_AMPLITUDES = - new int[] { 255, 0, VibrationEffect.DEFAULT_AMPLITUDE }; + new int[] { 255, 0, DEFAULT_AMPLITUDE }; private static final VibrationEffect TEST_ONE_SHOT = VibrationEffect.createOneShot(TEST_TIMING, TEST_AMPLITUDE); private static final VibrationEffect DEFAULT_ONE_SHOT = - VibrationEffect.createOneShot(TEST_TIMING, VibrationEffect.DEFAULT_AMPLITUDE); + VibrationEffect.createOneShot(TEST_TIMING, DEFAULT_AMPLITUDE); private static final VibrationEffect TEST_WAVEFORM = VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1); @Test + public void computeLegacyPattern_timingsAndAmplitudes_zeroAmplitudesOnEvenIndices() { + VibrationEffect effect = VibrationEffect.createWaveform( + /* timings= */ new long[] {1, 2, 3, 4, 5}, + /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, 0}, + /* repeatIndex= */ -1); + long[] expectedPattern = new long[] {1, 2, 3, 4, 5}; + + assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); + } + + @Test + public void computeLegacyPattern_timingsAndAmplitudes_zeroAmplitudesOnOddIndices() { + VibrationEffect effect = VibrationEffect.createWaveform( + /* timings= */ new long[] {1, 2, 3, 4, 5}, + /* amplitudes= */ new int[] { + DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE}, + /* repeatIndex= */ -1); + long[] expectedPattern = new long[] {0, 1, 2, 3, 4, 5}; + + assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); + } + + @Test + public void computeLegacyPattern_timingsAndAmplitudes_zeroAmplitudesAtTheStart() { + VibrationEffect effect = VibrationEffect.createWaveform( + /* timings= */ new long[] {1, 2, 3}, + /* amplitudes= */ new int[] {0, 0, DEFAULT_AMPLITUDE}, + /* repeatIndex= */ -1); + long[] expectedPattern = new long[] {3, 3}; + + assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); + } + + @Test + public void computeLegacyPattern_timingsAndAmplitudes_zeroAmplitudesAtTheEnd() { + VibrationEffect effect = VibrationEffect.createWaveform( + /* timings= */ new long[] {1, 2, 3}, + /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE, 0, 0}, + /* repeatIndex= */ -1); + long[] expectedPattern = new long[] {0, 1, 5}; + + assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); + } + + @Test + public void computeLegacyPattern_timingsAndAmplitudes_allDefaultAmplitudes() { + VibrationEffect effect = VibrationEffect.createWaveform( + /* timings= */ new long[] {1, 2, 3}, + /* amplitudes= */ new int[] { + DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE}, + /* repeatIndex= */ -1); + long[] expectedPattern = new long[] {0, 6}; + + assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); + } + + @Test + public void computeLegacyPattern_timingsAndAmplitudes_allZeroAmplitudes() { + VibrationEffect effect = VibrationEffect.createWaveform( + /* timings= */ new long[] {1, 2, 3}, + /* amplitudes= */ new int[] {0, 0, 0}, + /* repeatIndex= */ -1); + long[] expectedPattern = new long[] {6}; + + assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); + } + + @Test + public void computeLegacyPattern_timingsAndAmplitudes_sparsedZeroAmplitudes() { + VibrationEffect effect = VibrationEffect.createWaveform( + /* timings= */ new long[] {1, 2, 3, 4, 5, 6, 7}, + /* amplitudes= */ new int[] { + 0, 0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE, 0}, + /* repeatIndex= */ -1); + long[] expectedPattern = new long[] {3, 3, 4, 11, 7}; + + assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); + } + + @Test + public void computeLegacyPattern_timingsAndAmplitudes_oneTimingWithDefaultAmplitude() { + VibrationEffect effect = VibrationEffect.createWaveform( + /* timings= */ new long[] {1}, + /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE}, + /* repeatIndex= */ -1); + long[] expectedPattern = new long[] {0, 1}; + + assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); + } + + @Test + public void computeLegacyPattern_timingsAndAmplitudes_oneTimingWithZeroAmplitude() { + VibrationEffect effect = VibrationEffect.createWaveform( + /* timings= */ new long[] {1}, + /* amplitudes= */ new int[] {0}, + /* repeatIndex= */ -1); + long[] expectedPattern = new long[] {1}; + + assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); + } + + @Test + public void computeLegacyPattern_timingsAndAmplitudes_repeating() { + VibrationEffect effect = VibrationEffect.createWaveform( + /* timings= */ new long[] {1, 2, 3, 4, 5}, + /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, 0}, + /* repeatIndex= */ 0); + + assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); + + effect = VibrationEffect.createWaveform( + /* timings= */ new long[] {1, 2, 3, 4, 5}, + /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, 0}, + /* repeatIndex= */ 3); + + assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); + + effect = VibrationEffect.createWaveform( + /* timings= */ new long[] {1, 2}, + /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE}, + /* repeatIndex= */ 1); + + assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); + } + + @Test + public void computeLegacyPattern_timingsAndAmplitudes_badAmplitude() { + VibrationEffect effect = VibrationEffect.createWaveform( + /* timings= */ new long[] {1}, + /* amplitudes= */ new int[] {200}, + /* repeatIndex= */ -1); + + assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); + } + + @Test + public void computeLegacyPattern_timingsOnly_nonZeroTimings() { + VibrationEffect effect = VibrationEffect.createWaveform( + /* timings= */ new long[] {1, 2, 3}, + /* repeatIndex= */ -1); + long[] expectedPattern = new long[] {1, 2, 3}; + + assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); + } + + @Test + public void computeLegacyPattern_timingsOnly_oneValue() { + VibrationEffect effect = VibrationEffect.createWaveform( + /* timings= */ new long[] {5}, + /* repeatIndex= */ -1); + long[] expectedPattern = new long[] {5}; + + assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); + } + + @Test + public void computeLegacyPattern_timingsOnly_zeroesAtTheEnd() { + VibrationEffect effect = VibrationEffect.createWaveform( + /* timings= */ new long[] {1, 2, 3, 0, 0}, + /* repeatIndex= */ -1); + long[] expectedPattern = new long[] {1, 2, 3, 0, 0}; + + assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); + } + + @Test + public void computeLegacyPattern_timingsOnly_zeroesAtTheStart() { + VibrationEffect effect = VibrationEffect.createWaveform( + /* timings= */ new long[] {0, 0, 1, 2, 3}, + /* repeatIndex= */ -1); + long[] expectedPattern = new long[] {0, 0, 1, 2, 3}; + + assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); + } + + @Test + public void computeLegacyPattern_timingsOnly_zeroesAtTheMiddle() { + VibrationEffect effect = VibrationEffect.createWaveform( + /* timings= */ new long[] {1, 2, 0, 0, 3, 4, 5}, + /* repeatIndex= */ -1); + long[] expectedPattern = new long[] {1, 2, 0, 0, 3, 4, 5}; + + assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); + } + + @Test + public void computeLegacyPattern_timingsOnly_sparsedZeroes() { + VibrationEffect effect = VibrationEffect.createWaveform( + /* timings= */ new long[] {0, 1, 2, 0, 0, 3, 4, 5, 0}, + /* repeatIndex= */ -1); + long[] expectedPattern = new long[] {0, 1, 2, 0, 0, 3, 4, 5, 0}; + + assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); + } + + @Test + public void computeLegacyPattern_timingsOnly_repeating() { + VibrationEffect effect = VibrationEffect.createWaveform( + /* timings= */ new long[] {0, 1, 2, 0, 0, 3, 4, 5, 0}, + /* repeatIndex= */ 0); + + assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); + + effect = VibrationEffect.createWaveform( + /* timings= */ new long[] {1, 2, 3, 4}, + /* repeatIndex= */ 2); + + assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); + } + + @Test + public void computeLegacyPattern_notPatternPased() { + VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK); + + assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); + } + + @Test + public void computeLegacyPattern_oneShot_defaultAmplitude() { + VibrationEffect effect = VibrationEffect.createOneShot( + /* milliseconds= */ 5, /* ampliutde= */ DEFAULT_AMPLITUDE); + long[] expectedPattern = new long[] {0, 5}; + + assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); + } + + @Test + public void computeLegacyPattern_oneShot_badAmplitude() { + VibrationEffect effect = VibrationEffect.createOneShot( + /* milliseconds= */ 5, /* ampliutde= */ 50); + + assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); + } + + @Test + public void computeLegacyPattern_composition_noOffDuration() { + VibrationEffect effect = VibrationEffect.startComposition() + .addEffect( + VibrationEffect.createWaveform( + /* timings= */ new long[] {5}, + /* repeatIndex= */ -1)) + .addEffect( + VibrationEffect.createWaveform( + /* timings= */ new long[] {2, 3}, + /* repeatIndex= */ -1)) + .addEffect( + VibrationEffect.createWaveform( + /* timings= */ new long[] {10, 20}, + /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE}, + /* repeatIndex= */ -1)) + .addEffect( + VibrationEffect.createWaveform( + /* timings= */ new long[] {4, 5}, + /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE}, + /* repeatIndex= */ -1)) + .compose(); + long[] expectedPattern = new long[] {7, 33, 4, 5}; + + assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); + } + + @Test + public void computeLegacyPattern_composition_withOffDuration() { + VibrationEffect effect = VibrationEffect.startComposition() + .addOffDuration(Duration.ofMillis(20)) + .addEffect( + VibrationEffect.createWaveform( + /* timings= */ new long[] {10, 20}, + /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE}, + /* repeatIndex= */ -1)) + .addEffect( + VibrationEffect.createWaveform( + /* timings= */ new long[] {30, 40}, + /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE}, + /* repeatIndex= */ -1)) + .addOffDuration(Duration.ofMillis(10)) + .addEffect( + VibrationEffect.createWaveform( + /* timings= */ new long[] {4, 5}, + /* repeatIndex= */ -1)) + .addOffDuration(Duration.ofMillis(5)) + .compose(); + long[] expectedPattern = new long[] {30, 90, 14, 5, 5}; + + assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull()); + } + + @Test + public void computeLegacyPattern_composition_withPrimitives() { + VibrationEffect effect = VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK) + .addOffDuration(Duration.ofMillis(20)) + .addEffect( + VibrationEffect.createWaveform( + /* timings= */ new long[] {5}, + /* repeatIndex= */ -1)) + .compose(); + + assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); + } + + @Test + public void computeLegacyPattern_composition_repeating() { + VibrationEffect effect = VibrationEffect.startComposition() + .addEffect( + VibrationEffect.createWaveform( + /* timings= */ new long[] {5}, + /* repeatIndex= */ -1)) + .repeatEffectIndefinitely( + VibrationEffect.createWaveform( + /* timings= */ new long[] {2, 3}, + /* repeatIndex= */ -1)) + .compose(); + + assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); + } + + @Test + public void computeLegacyPattern_effectsViaStartWaveform() { + // Effects created via startWaveform are not expected to be converted to long[] patterns, as + // they are not configured to always play with the default amplitude. + VibrationEffect effect = VibrationEffect.startWaveform(targetFrequency(60)) + .addTransition(Duration.ofMillis(100), targetAmplitude(1), targetFrequency(120)) + .addSustain(Duration.ofMillis(200)) + .addTransition(Duration.ofMillis(100), targetAmplitude(0), targetFrequency(60)) + .build(); + + assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); + + effect = VibrationEffect.startWaveform(targetFrequency(60)) + .addTransition(Duration.ofMillis(80), targetAmplitude(1)) + .addSustain(Duration.ofMillis(200)) + .addTransition(Duration.ofMillis(100), targetAmplitude(0)) + .build(); + + assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); + + effect = VibrationEffect.startWaveform(targetFrequency(60)) + .addTransition(Duration.ofMillis(100), targetFrequency(50)) + .addSustain(Duration.ofMillis(50)) + .addTransition(Duration.ofMillis(20), targetFrequency(75)) + .build(); + + assertNull(effect.computeCreateWaveformOffOnTimingsOrNull()); + } + + @Test public void getRingtones_noPrebakedRingtones() { Resources r = mockRingtoneResources(new String[0]); Context context = mockContext(r); @@ -100,7 +449,7 @@ public class VibrationEffectTest { @Test public void testValidateOneShot() { VibrationEffect.createOneShot(1, 255).validate(); - VibrationEffect.createOneShot(1, VibrationEffect.DEFAULT_AMPLITUDE).validate(); + VibrationEffect.createOneShot(1, DEFAULT_AMPLITUDE).validate(); assertThrows(IllegalArgumentException.class, () -> VibrationEffect.createOneShot(-1, 255).validate()); @@ -501,6 +850,13 @@ public class VibrationEffectTest { assertTrue(VibrationEffect.get(VibrationEffect.EFFECT_TICK).isHapticFeedbackCandidate()); } + private void assertArrayEq(long[] expected, long[] actual) { + assertTrue( + String.format("Expected pattern %s, but was %s", + Arrays.toString(expected), Arrays.toString(actual)), + Arrays.equals(expected, actual)); + } + private Resources mockRingtoneResources() { return mockRingtoneResources(new String[]{ RINGTONE_URI_1, diff --git a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java index 9006cd91d616..0c1630e8fefd 100644 --- a/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java +++ b/core/tests/coretests/src/android/service/timezone/TimeZoneProviderStatusTest.java @@ -16,15 +16,31 @@ package android.service.timezone; +import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT; import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS; import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK; +import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_UNKNOWN; +import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_FAILED; import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK; +import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_UNKNOWN; import static org.junit.Assert.assertEquals; +import android.service.timezone.TimeZoneProviderStatus.DependencyStatus; +import android.service.timezone.TimeZoneProviderStatus.OperationStatus; + import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.IntStream; + +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; /** Non-SDK tests. See CTS for SDK API tests. */ +@RunWith(JUnitParamsRunner.class) public class TimeZoneProviderStatusTest { @Test @@ -37,4 +53,51 @@ public class TimeZoneProviderStatusTest { assertEquals(status, TimeZoneProviderStatus.parseProviderStatus(status.toString())); } + + @Test + @Parameters(method = "couldEnableTelephonyFallbackParams") + public void couldEnableTelephonyFallback(@DependencyStatus int locationDetectionStatus, + @DependencyStatus int connectivityStatus, @OperationStatus int tzResolutionStatus) { + TimeZoneProviderStatus providerStatus = + new TimeZoneProviderStatus.Builder() + .setLocationDetectionDependencyStatus(locationDetectionStatus) + .setConnectivityDependencyStatus(connectivityStatus) + .setTimeZoneResolutionOperationStatus(tzResolutionStatus) + .build(); + boolean locationDetectionStatusCouldEnableFallback = + (locationDetectionStatus == DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT + || locationDetectionStatus == DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS); + boolean connectivityStatusCouldEnableFallback = + (connectivityStatus == DEPENDENCY_STATUS_BLOCKED_BY_ENVIRONMENT + || connectivityStatus == DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS); + boolean tzResolutionStatusCouldEnableFallback = false; + + assertEquals(locationDetectionStatusCouldEnableFallback + || connectivityStatusCouldEnableFallback + || tzResolutionStatusCouldEnableFallback, + providerStatus.couldEnableTelephonyFallback()); + } + + public static Integer[][] couldEnableTelephonyFallbackParams() { + List<Integer[]> params = new ArrayList<>(); + @DependencyStatus int[] dependencyStatuses = + IntStream.rangeClosed( + DEPENDENCY_STATUS_UNKNOWN, DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS).toArray(); + @OperationStatus int[] operationStatuses = + IntStream.rangeClosed(OPERATION_STATUS_UNKNOWN, OPERATION_STATUS_FAILED).toArray(); + + // Cartesian product: dependencyStatus x dependencyStatus x operationStatus + for (@DependencyStatus int locationDetectionStatus : dependencyStatuses) { + for (@DependencyStatus int connectivityStatus : dependencyStatuses) { + for (@OperationStatus int tzResolutionStatus : operationStatuses) { + params.add(new Integer[] { + locationDetectionStatus, + connectivityStatus, + tzResolutionStatus + }); + } + } + } + return params.toArray(new Integer[0][0]); + } } diff --git a/core/tests/coretests/src/android/util/TypedValueTest.kt b/core/tests/coretests/src/android/util/TypedValueTest.kt index 7a05d970de33..7d98a7d1faa1 100644 --- a/core/tests/coretests/src/android/util/TypedValueTest.kt +++ b/core/tests/coretests/src/android/util/TypedValueTest.kt @@ -16,16 +16,17 @@ package android.util +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest import androidx.test.filters.SmallTest -import androidx.test.runner.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import kotlin.math.abs +import kotlin.math.min +import kotlin.math.roundToInt import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.mock -import kotlin.math.abs -import kotlin.math.min -import kotlin.math.roundToInt @RunWith(AndroidJUnit4::class) class TypedValueTest { @@ -152,4 +153,19 @@ class TypedValueTest { val widthPx = TypedValue.complexToDimensionPixelSize(widthDimen, metrics) assertEquals(widthFloat.roundToInt(), widthPx) } -}
\ No newline at end of file + + @SmallTest + @Test + fun testNonLinearFontScaling_nullLookupFallsBackToScaledDensity() { + val metrics: DisplayMetrics = mock(DisplayMetrics::class.java) + val fontScale = 2f + metrics.density = 1f + metrics.scaledDensity = fontScale * metrics.density + metrics.fontScaleConverter = null + + assertThat(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10f, metrics)) + .isEqualTo(20f) + assertThat(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 50f, metrics)) + .isEqualTo(100f) + } +} diff --git a/core/tests/utiltests/src/android/util/IntArrayTest.java b/core/tests/utiltests/src/android/util/IntArrayTest.java index a76c640db74a..caa7312a4475 100644 --- a/core/tests/utiltests/src/android/util/IntArrayTest.java +++ b/core/tests/utiltests/src/android/util/IntArrayTest.java @@ -16,8 +16,8 @@ package android.util; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -25,6 +25,8 @@ import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.Arrays; + @RunWith(AndroidJUnit4.class) @SmallTest public class IntArrayTest { @@ -35,51 +37,65 @@ public class IntArrayTest { a.add(1); a.add(2); a.add(3); - verify(new int[]{1, 2, 3}, a); + verify(a, 1, 2, 3); IntArray b = IntArray.fromArray(new int[]{4, 5, 6, 7, 8}, 3); a.addAll(b); - verify(new int[]{1, 2, 3, 4, 5, 6}, a); + verify(a, 1, 2, 3, 4, 5, 6); a.resize(2); - verify(new int[]{1, 2}, a); + verify(a, 1, 2); a.resize(8); - verify(new int[]{1, 2, 0, 0, 0, 0, 0, 0}, a); + verify(a, 1, 2, 0, 0, 0, 0, 0, 0); a.set(5, 10); - verify(new int[]{1, 2, 0, 0, 0, 10, 0, 0}, a); + verify(a, 1, 2, 0, 0, 0, 10, 0, 0); a.add(5, 20); - assertEquals(20, a.get(5)); - assertEquals(5, a.indexOf(20)); - verify(new int[]{1, 2, 0, 0, 0, 20, 10, 0, 0}, a); + assertThat(a.get(5)).isEqualTo(20); + assertThat(a.indexOf(20)).isEqualTo(5); + verify(a, 1, 2, 0, 0, 0, 20, 10, 0, 0); - assertEquals(-1, a.indexOf(99)); + assertThat(a.indexOf(99)).isEqualTo(-1); a.resize(15); a.set(14, 30); - verify(new int[]{1, 2, 0, 0, 0, 20, 10, 0, 0, 0, 0, 0, 0, 0, 30}, a); + verify(a, 1, 2, 0, 0, 0, 20, 10, 0, 0, 0, 0, 0, 0, 0, 30); int[] backingArray = new int[]{1, 2, 3, 4}; a = IntArray.wrap(backingArray); a.set(0, 10); - assertEquals(10, backingArray[0]); + assertThat(backingArray[0]).isEqualTo(10); backingArray[1] = 20; backingArray[2] = 30; - verify(backingArray, a); - assertEquals(2, a.indexOf(30)); + verify(a, backingArray); + assertThat(a.indexOf(30)).isEqualTo(2); a.resize(2); - assertEquals(0, backingArray[2]); - assertEquals(0, backingArray[3]); + assertThat(backingArray[2]).isEqualTo(0); + assertThat(backingArray[3]).isEqualTo(0); a.add(50); - verify(new int[]{10, 20, 50}, a); + verify(a, 10, 20, 50); + } + + @Test + public void testToString() { + IntArray a = new IntArray(10); + a.add(4); + a.add(8); + a.add(15); + a.add(16); + a.add(23); + a.add(42); + + assertWithMessage("toString()").that(a.toString()).contains("4, 8, 15, 16, 23, 42"); + assertWithMessage("toString()").that(a.toString()).doesNotContain("0"); } - public void verify(int[] expected, IntArray intArray) { - assertEquals(expected.length, intArray.size()); - assertArrayEquals(expected, intArray.toArray()); + public void verify(IntArray intArray, int... expected) { + assertWithMessage("contents of %s", intArray).that(intArray.toArray()).asList() + .containsExactlyElementsIn(Arrays.stream(expected).boxed().toList()); } } diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java new file mode 100644 index 000000000000..f0a0cf40ffa9 --- /dev/null +++ b/graphics/java/android/graphics/Mesh.java @@ -0,0 +1,317 @@ +/* + * 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.graphics; + +import libcore.util.NativeAllocationRegistry; + +import java.nio.Buffer; +import java.nio.ShortBuffer; + +/** + * Class representing a mesh object. + * + * This class generates Mesh objects via the + * {@link #make(MeshSpecification, Mode, Buffer, int, Rect)} and + * {@link #makeIndexed(MeshSpecification, Mode, Buffer, int, ShortBuffer, Rect)} methods, + * where a {@link MeshSpecification} is required along with various attributes for + * detailing the mesh object, including a mode, vertex buffer, optional index buffer, and bounds + * for the mesh. + * + * @hide + */ +public class Mesh { + private long mNativeMeshWrapper; + private boolean mIsIndexed; + + /** + * Enum to determine how the mesh is represented. + */ + public enum Mode {Triangles, TriangleStrip} + + private static class MeshHolder { + public static final NativeAllocationRegistry MESH_SPECIFICATION_REGISTRY = + NativeAllocationRegistry.createMalloced( + MeshSpecification.class.getClassLoader(), nativeGetFinalizer()); + } + + /** + * Generates a {@link Mesh} object. + * + * @param meshSpec {@link MeshSpecification} used when generating the mesh. + * @param mode {@link Mode} enum + * @param vertexBuffer vertex buffer representing through {@link Buffer}. + * @param vertexCount the number of vertices represented in the vertexBuffer. + * @param bounds bounds of the mesh object. + * @return a new Mesh object. + */ + public static Mesh make(MeshSpecification meshSpec, Mode mode, Buffer vertexBuffer, + int vertexCount, Rect bounds) { + long nativeMesh = nativeMake(meshSpec.mNativeMeshSpec, mode.ordinal(), vertexBuffer, + vertexBuffer.isDirect(), vertexCount, vertexBuffer.position(), bounds.left, + bounds.top, bounds.right, bounds.bottom); + if (nativeMesh == 0) { + throw new IllegalArgumentException("Mesh construction failed."); + } + return new Mesh(nativeMesh, false); + } + + /** + * Generates an indexed {@link Mesh} object. + * + * @param meshSpec {@link MeshSpecification} used when generating the mesh. + * @param mode {@link Mode} enum + * @param vertexBuffer vertex buffer representing through {@link Buffer}. + * @param vertexCount the number of vertices represented in the vertexBuffer. + * @param indexBuffer index buffer representing through {@link ShortBuffer}. + * @param bounds bounds of the mesh object. + * @return a new Mesh object. + */ + public static Mesh makeIndexed(MeshSpecification meshSpec, Mode mode, Buffer vertexBuffer, + int vertexCount, ShortBuffer indexBuffer, Rect bounds) { + long nativeMesh = nativeMakeIndexed(meshSpec.mNativeMeshSpec, mode.ordinal(), vertexBuffer, + vertexBuffer.isDirect(), vertexCount, vertexBuffer.position(), indexBuffer, + indexBuffer.isDirect(), indexBuffer.capacity(), indexBuffer.position(), bounds.left, + bounds.top, bounds.right, bounds.bottom); + if (nativeMesh == 0) { + throw new IllegalArgumentException("Mesh construction failed."); + } + return new Mesh(nativeMesh, true); + } + + /** + * Sets the uniform color value corresponding to the shader assigned to the mesh. + * + * @param uniformName name matching the color uniform declared in the shader program. + * @param color the provided sRGB color will be converted into the shader program's output + * colorspace and be available as a vec4 uniform in the program. + */ + public void setColorUniform(String uniformName, int color) { + setUniform(uniformName, Color.valueOf(color).getComponents(), true); + } + + /** + * Sets the uniform color value corresponding to the shader assigned to the mesh. + * + * @param uniformName name matching the color uniform declared in the shader program. + * @param color the provided sRGB color will be converted into the shader program's output + * colorspace and be available as a vec4 uniform in the program. + */ + public void setColorUniform(String uniformName, long color) { + Color exSRGB = Color.valueOf(color).convert(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB)); + setUniform(uniformName, exSRGB.getComponents(), true); + } + + /** + * Sets the uniform color value corresponding to the shader assigned to the mesh. + * + * @param uniformName name matching the color uniform declared in the shader program. + * @param color the provided sRGB color will be converted into the shader program's output + * colorspace and will be made available as a vec4 uniform in the program. + */ + public void setColorUniform(String uniformName, Color color) { + if (color == null) { + throw new NullPointerException("The color parameter must not be null"); + } + + Color exSRGB = color.convert(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB)); + setUniform(uniformName, exSRGB.getComponents(), true); + } + + /** + * Sets the uniform color value corresponding to the shader assigned to the mesh. + * + * @param uniformName name matching the float uniform declared in the shader program. + * @param value float value corresponding to the float uniform with the given name. + */ + public void setFloatUniform(String uniformName, float value) { + setFloatUniform(uniformName, value, 0.0f, 0.0f, 0.0f, 1); + } + + /** + * Sets the uniform color value corresponding to the shader assigned to the mesh. + * + * @param uniformName name matching the float uniform declared in the shader program. + * @param value1 first float value corresponding to the float uniform with the given name. + * @param value2 second float value corresponding to the float uniform with the given name. + */ + public void setFloatUniform(String uniformName, float value1, float value2) { + setFloatUniform(uniformName, value1, value2, 0.0f, 0.0f, 2); + } + + /** + * Sets the uniform color value corresponding to the shader assigned to the mesh. + * + * @param uniformName name matching the float uniform declared in the shader program. + * @param value1 first float value corresponding to the float uniform with the given name. + * @param value2 second float value corresponding to the float uniform with the given name. + * @param value3 third float value corresponding to the float unifiform with the given + * name. + */ + public void setFloatUniform(String uniformName, float value1, float value2, float value3) { + setFloatUniform(uniformName, value1, value2, value3, 0.0f, 3); + } + + /** + * Sets the uniform color value corresponding to the shader assigned to the mesh. + * + * @param uniformName name matching the float uniform declared in the shader program. + * @param value1 first float value corresponding to the float uniform with the given name. + * @param value2 second float value corresponding to the float uniform with the given name. + * @param value3 third float value corresponding to the float uniform with the given name. + * @param value4 fourth float value corresponding to the float uniform with the given name. + */ + public void setFloatUniform( + String uniformName, float value1, float value2, float value3, float value4) { + setFloatUniform(uniformName, value1, value2, value3, value4, 4); + } + + /** + * Sets the uniform color value corresponding to the shader assigned to the mesh. + * + * @param uniformName name matching the float uniform declared in the shader program. + * @param values float value corresponding to the vec4 float uniform with the given name. + */ + public void setFloatUniform(String uniformName, float[] values) { + setUniform(uniformName, values, false); + } + + private void setFloatUniform( + String uniformName, float value1, float value2, float value3, float value4, int count) { + if (uniformName == null) { + throw new NullPointerException("The uniformName parameter must not be null"); + } + nativeUpdateUniforms( + mNativeMeshWrapper, uniformName, value1, value2, value3, value4, count); + nativeUpdateMesh(mNativeMeshWrapper); + } + + private void setUniform(String uniformName, float[] values, boolean isColor) { + if (uniformName == null) { + throw new NullPointerException("The uniformName parameter must not be null"); + } + if (values == null) { + throw new NullPointerException("The uniform values parameter must not be null"); + } + + nativeUpdateUniforms(mNativeMeshWrapper, uniformName, values, isColor); + nativeUpdateMesh(mNativeMeshWrapper); + } + + /** + * Sets the uniform color value corresponding to the shader assigned to the mesh. + * + * @param uniformName name matching the int uniform delcared in the shader program. + * @param value value corresponding to the int uniform with the given name. + */ + public void setIntUniform(String uniformName, int value) { + setIntUniform(uniformName, value, 0, 0, 0, 1); + } + + /** + * Sets the uniform color value corresponding to the shader assigned to the mesh. + * + * @param uniformName name matching the int uniform delcared in the shader program. + * @param value1 first value corresponding to the int uniform with the given name. + * @param value2 second value corresponding to the int uniform with the given name. + */ + public void setIntUniform(String uniformName, int value1, int value2) { + setIntUniform(uniformName, value1, value2, 0, 0, 2); + } + + /** + * Sets the uniform color value corresponding to the shader assigned to the mesh. + * + * @param uniformName name matching the int uniform delcared in the shader program. + * @param value1 first value corresponding to the int uniform with the given name. + * @param value2 second value corresponding to the int uniform with the given name. + * @param value3 third value corresponding to the int uniform with the given name. + */ + public void setIntUniform(String uniformName, int value1, int value2, int value3) { + setIntUniform(uniformName, value1, value2, value3, 0, 3); + } + + /** + * Sets the uniform color value corresponding to the shader assigned to the mesh. + * + * @param uniformName name matching the int uniform delcared in the shader program. + * @param value1 first value corresponding to the int uniform with the given name. + * @param value2 second value corresponding to the int uniform with the given name. + * @param value3 third value corresponding to the int uniform with the given name. + * @param value4 fourth value corresponding to the int uniform with the given name. + */ + public void setIntUniform(String uniformName, int value1, int value2, int value3, int value4) { + setIntUniform(uniformName, value1, value2, value3, value4, 4); + } + + /** + * Sets the uniform color value corresponding to the shader assigned to the mesh. + * + * @param uniformName name matching the int uniform delcared in the shader program. + * @param values int values corresponding to the vec4 int uniform with the given name. + */ + public void setIntUniform(String uniformName, int[] values) { + if (uniformName == null) { + throw new NullPointerException("The uniformName parameter must not be null"); + } + if (values == null) { + throw new NullPointerException("The uniform values parameter must not be null"); + } + nativeUpdateUniforms(mNativeMeshWrapper, uniformName, values); + nativeUpdateMesh(mNativeMeshWrapper); + } + + private void setIntUniform( + String uniformName, int value1, int value2, int value3, int value4, int count) { + if (uniformName == null) { + throw new NullPointerException("The uniformName parameter must not be null"); + } + + nativeUpdateUniforms( + mNativeMeshWrapper, uniformName, value1, value2, value3, value4, count); + nativeUpdateMesh(mNativeMeshWrapper); + } + + private Mesh(long nativeMeshWrapper, boolean isIndexed) { + mNativeMeshWrapper = nativeMeshWrapper; + this.mIsIndexed = isIndexed; + MeshHolder.MESH_SPECIFICATION_REGISTRY.registerNativeAllocation(this, mNativeMeshWrapper); + } + + private static native long nativeGetFinalizer(); + + private static native long nativeMake(long meshSpec, int mode, Buffer vertexBuffer, + boolean isDirect, int vertexCount, int vertexOffset, int left, int top, int right, + int bottom); + + private static native long nativeMakeIndexed(long meshSpec, int mode, Buffer vertexBuffer, + boolean isVertexDirect, int vertexCount, int vertexOffset, ShortBuffer indexBuffer, + boolean isIndexDirect, int indexCount, int indexOffset, int left, int top, int right, + int bottom); + + private static native void nativeUpdateUniforms(long builder, String uniformName, float value1, + float value2, float value3, float value4, int count); + + private static native void nativeUpdateUniforms( + long builder, String uniformName, float[] values, boolean isColor); + + private static native void nativeUpdateUniforms(long builder, String uniformName, int value1, + int value2, int value3, int value4, int count); + + private static native void nativeUpdateUniforms(long builder, String uniformName, int[] values); + + private static native void nativeUpdateMesh(long nativeMeshWrapper); +} diff --git a/graphics/java/android/graphics/MeshSpecification.java b/graphics/java/android/graphics/MeshSpecification.java index b27c5e0ab728..45c13affac14 100644 --- a/graphics/java/android/graphics/MeshSpecification.java +++ b/graphics/java/android/graphics/MeshSpecification.java @@ -39,7 +39,7 @@ import libcore.util.NativeAllocationRegistry; * @hide */ public class MeshSpecification { - private long mNativeMeshSpec; + long mNativeMeshSpec; /** * Constants for {@link #make(Attribute[], int, Varying[], String, String, ColorSpace, int)} diff --git a/graphics/java/android/view/PixelCopy.java b/graphics/java/android/view/PixelCopy.java index 82ced438f0a5..0e198d5c56ec 100644 --- a/graphics/java/android/view/PixelCopy.java +++ b/graphics/java/android/view/PixelCopy.java @@ -382,9 +382,9 @@ public final class PixelCopy { } /** - * Creates a PixelCopy request for the given {@link Window} + * Creates a PixelCopy Builder for the given {@link Window} * @param source The Window to copy from - * @return A {@link Builder} builder to set the optional params & execute the request + * @return A {@link Builder} builder to set the optional params & build the request */ @SuppressLint("BuilderSetStyle") public static @NonNull Builder ofWindow(@NonNull Window source) { @@ -394,7 +394,7 @@ public final class PixelCopy { } /** - * Creates a PixelCopy request for the {@link Window} that the given {@link View} is + * Creates a PixelCopy Builder for the {@link Window} that the given {@link View} is * attached to. * * Note that this copy request is not cropped to the area the View occupies by default. @@ -404,7 +404,7 @@ public final class PixelCopy { * * @param source A View that {@link View#isAttachedToWindow() is attached} to a window * that will be used to retrieve the window to copy from. - * @return A {@link Builder} builder to set the optional params & execute the request + * @return A {@link Builder} builder to set the optional params & build the request */ @SuppressLint("BuilderSetStyle") public static @NonNull Builder ofWindow(@NonNull View source) { @@ -427,10 +427,10 @@ public final class PixelCopy { } /** - * Creates a PixelCopy request for the given {@link Surface} + * Creates a PixelCopy Builder for the given {@link Surface} * * @param source The Surface to copy from. Must be {@link Surface#isValid() valid}. - * @return A {@link Builder} builder to set the optional params & execute the request + * @return A {@link Builder} builder to set the optional params & build the request */ @SuppressLint("BuilderSetStyle") public static @NonNull Builder ofSurface(@NonNull Surface source) { @@ -441,12 +441,12 @@ public final class PixelCopy { } /** - * Creates a PixelCopy request for the {@link Surface} belonging to the + * Creates a PixelCopy Builder for the {@link Surface} belonging to the * given {@link SurfaceView} * * @param source The SurfaceView to copy from. The backing surface must be * {@link Surface#isValid() valid} - * @return A {@link Builder} builder to set the optional params & execute the request + * @return A {@link Builder} builder to set the optional params & build the request */ @SuppressLint("BuilderSetStyle") public static @NonNull Builder ofSurface(@NonNull SurfaceView source) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java index a0dde6ad168d..2ec9e8b12fc6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java @@ -16,6 +16,7 @@ package com.android.wm.shell.animation; +import android.graphics.Path; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import android.view.animation.PathInterpolator; @@ -53,6 +54,11 @@ public class Interpolators { public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f); /** + * The default emphasized interpolator. Used for hero / emphasized movement of content. + */ + public static final Interpolator EMPHASIZED = createEmphasizedInterpolator(); + + /** * The accelerated emphasized interpolator. Used for hero / emphasized movement of content that * is disappearing e.g. when moving off screen. */ @@ -81,4 +87,14 @@ public class Interpolators { public static final PathInterpolator DIM_INTERPOLATOR = new PathInterpolator(.23f, .87f, .52f, -0.11f); + + // Create the default emphasized interpolator + private static PathInterpolator createEmphasizedInterpolator() { + Path path = new Path(); + // Doing the same as fast_out_extra_slow_in + path.moveTo(0f, 0f); + path.cubicTo(0.05f, 0f, 0.133333f, 0.06f, 0.166666f, 0.4f); + path.cubicTo(0.208333f, 0.82f, 0.25f, 1f, 1f, 1f); + return new PathInterpolator(path); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java new file mode 100644 index 000000000000..36cf29a4c4f3 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java @@ -0,0 +1,69 @@ +/* + * 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.wm.shell.back; + +import static android.view.Display.DEFAULT_DISPLAY; + +import android.annotation.NonNull; +import android.graphics.Color; +import android.view.SurfaceControl; + +import com.android.wm.shell.RootTaskDisplayAreaOrganizer; + +/** + * Controls background surface for the back animations + */ +public class BackAnimationBackground { + private static final int BACKGROUND_LAYER = -1; + private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; + private SurfaceControl mBackgroundSurface; + + public BackAnimationBackground(RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) { + mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer; + } + + void ensureBackground(int color, @NonNull SurfaceControl.Transaction transaction) { + if (mBackgroundSurface != null) { + return; + } + + final float[] colorComponents = new float[] { Color.red(color) / 255.f, + Color.green(color) / 255.f, Color.blue(color) / 255.f }; + + final SurfaceControl.Builder colorLayerBuilder = new SurfaceControl.Builder() + .setName("back-animation-background") + .setCallsite("BackAnimationBackground") + .setColorLayer(); + + mRootTaskDisplayAreaOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, colorLayerBuilder); + mBackgroundSurface = colorLayerBuilder.build(); + transaction.setColor(mBackgroundSurface, colorComponents) + .setLayer(mBackgroundSurface, BACKGROUND_LAYER) + .show(mBackgroundSurface); + } + + void removeBackground(@NonNull SurfaceControl.Transaction transaction) { + if (mBackgroundSurface == null) { + return; + } + + if (mBackgroundSurface.isValid()) { + transaction.remove(mBackgroundSurface); + } + mBackgroundSurface = null; + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index f811940fd304..0133f6b44d2b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -138,14 +138,18 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } }; + private final BackAnimationBackground mAnimationBackground; + public BackAnimationController( @NonNull ShellInit shellInit, @NonNull ShellController shellController, @NonNull @ShellMainThread ShellExecutor shellExecutor, @NonNull @ShellBackgroundThread Handler backgroundHandler, - Context context) { + Context context, + @NonNull BackAnimationBackground backAnimationBackground) { this(shellInit, shellController, shellExecutor, backgroundHandler, - ActivityTaskManager.getService(), context, context.getContentResolver()); + ActivityTaskManager.getService(), context, context.getContentResolver(), + backAnimationBackground); } @VisibleForTesting @@ -155,7 +159,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont @NonNull @ShellMainThread ShellExecutor shellExecutor, @NonNull @ShellBackgroundThread Handler bgHandler, @NonNull IActivityTaskManager activityTaskManager, - Context context, ContentResolver contentResolver) { + Context context, ContentResolver contentResolver, + @NonNull BackAnimationBackground backAnimationBackground) { mShellController = shellController; mShellExecutor = shellExecutor; mActivityTaskManager = activityTaskManager; @@ -163,6 +168,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mContentResolver = contentResolver; mBgHandler = bgHandler; shellInit.addInitCallback(this::onInit, this); + mAnimationBackground = backAnimationBackground; } @VisibleForTesting @@ -184,10 +190,14 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont return; } - final CrossTaskBackAnimation crossTaskAnimation = new CrossTaskBackAnimation(mContext); + final CrossTaskBackAnimation crossTaskAnimation = + new CrossTaskBackAnimation(mContext, mAnimationBackground); mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_TASK, - new BackAnimationRunner(crossTaskAnimation.mCallback, crossTaskAnimation.mRunner)); - // TODO (238474994): register cross activity animation when it's completed. + crossTaskAnimation.mBackAnimationRunner); + final CrossActivityAnimation crossActivityAnimation = + new CrossActivityAnimation(mContext, mAnimationBackground); + mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY, + crossActivityAnimation.mBackAnimationRunner); // TODO (236760237): register dialog close animation when it's completed. } @@ -275,7 +285,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont @Override public void clearBackToLauncherCallback() { executeRemoteCallWithTaskPermission(mController, "clearBackToLauncherCallback", - (controller) -> controller.clearBackToLauncherCallback()); + (controller) -> controller.unregisterAnimation( + BackNavigationInfo.TYPE_RETURN_TO_HOME)); } @Override @@ -289,8 +300,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mAnimationDefinition.set(type, runner); } - private void clearBackToLauncherCallback() { - mAnimationDefinition.remove(BackNavigationInfo.TYPE_RETURN_TO_HOME); + void unregisterAnimation(@BackNavigationInfo.BackTargetType int type) { + mAnimationDefinition.remove(type); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java new file mode 100644 index 000000000000..9f6bc5d89e10 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java @@ -0,0 +1,373 @@ +/* + * 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.wm.shell.back; + +import static android.view.RemoteAnimationTarget.MODE_CLOSING; +import static android.view.RemoteAnimationTarget.MODE_OPENING; + +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.annotation.NonNull; +import android.content.Context; +import android.graphics.Matrix; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.RectF; +import android.os.RemoteException; +import android.view.IRemoteAnimationFinishedCallback; +import android.view.IRemoteAnimationRunner; +import android.view.RemoteAnimationTarget; +import android.view.SurfaceControl; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; +import android.window.BackEvent; +import android.window.BackProgressAnimator; +import android.window.IOnBackInvokedCallback; + +import com.android.internal.policy.ScreenDecorationsUtils; +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.animation.Interpolators; +import com.android.wm.shell.common.annotations.ShellMainThread; + +/** Class that defines cross-activity animation. */ +@ShellMainThread +class CrossActivityAnimation { + /** + * Minimum scale of the entering/closing window. + */ + private static final float MIN_WINDOW_SCALE = 0.9f; + + /** + * Minimum alpha of the closing/entering window. + */ + private static final float CLOSING_MIN_WINDOW_ALPHA = 0.5f; + + /** + * Progress value to fly out closing window and fly in entering window. + */ + private static final float SWITCH_ENTERING_WINDOW_PROGRESS = 0.5f; + + /** Max window translation in the Y axis. */ + private static final int WINDOW_MAX_DELTA_Y = 160; + + /** Duration of fade in/out entering window. */ + private static final int FADE_IN_DURATION = 100; + /** Duration of post animation after gesture committed. */ + private static final int POST_ANIMATION_DURATION = 350; + private static final Interpolator INTERPOLATOR = Interpolators.EMPHASIZED; + + private final Rect mStartTaskRect = new Rect(); + private final float mCornerRadius; + + // The closing window properties. + private final RectF mClosingRect = new RectF(); + + // The entering window properties. + private final Rect mEnteringStartRect = new Rect(); + private final RectF mEnteringRect = new RectF(); + + private float mCurrentAlpha = 1.0f; + + private float mEnteringMargin = 0; + private ValueAnimator mEnteringAnimator; + private boolean mEnteringWindowShow = false; + + private final PointF mInitialTouchPos = new PointF(); + + private final Matrix mTransformMatrix = new Matrix(); + + private final float[] mTmpFloat9 = new float[9]; + + private RemoteAnimationTarget mEnteringTarget; + private RemoteAnimationTarget mClosingTarget; + private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); + + private boolean mBackInProgress = false; + + private PointF mTouchPos = new PointF(); + private IRemoteAnimationFinishedCallback mFinishCallback; + + private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator(); + final BackAnimationRunner mBackAnimationRunner; + + private final BackAnimationBackground mBackground; + + CrossActivityAnimation(Context context, BackAnimationBackground background) { + mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context); + mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner()); + mBackground = background; + } + + private static float mapRange(float value, float min, float max) { + return min + (value * (max - min)); + } + + private float getInterpolatedProgress(float backProgress) { + return INTERPOLATOR.getInterpolation(backProgress); + } + + private void startBackAnimation() { + if (mEnteringTarget == null || mClosingTarget == null) { + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Entering target or closing target is null."); + return; + } + mTransaction.setAnimationTransaction(); + + // Offset start rectangle to align task bounds. + mStartTaskRect.set(mClosingTarget.windowConfiguration.getBounds()); + mStartTaskRect.offsetTo(0, 0); + + // Draw background with task background color. + mBackground.ensureBackground( + mEnteringTarget.taskInfo.taskDescription.getBackgroundColor(), mTransaction); + } + + private void applyTransform(SurfaceControl leash, RectF targetRect, float targetAlpha) { + final float scale = targetRect.width() / mStartTaskRect.width(); + mTransformMatrix.reset(); + mTransformMatrix.setScale(scale, scale); + mTransformMatrix.postTranslate(targetRect.left, targetRect.top); + mTransaction.setAlpha(leash, targetAlpha) + .setMatrix(leash, mTransformMatrix, mTmpFloat9) + .setWindowCrop(leash, mStartTaskRect) + .setCornerRadius(leash, mCornerRadius); + } + + private void finishAnimation() { + if (mEnteringTarget != null) { + mEnteringTarget.leash.release(); + mEnteringTarget = null; + } + if (mClosingTarget != null) { + mClosingTarget.leash.release(); + mClosingTarget = null; + } + if (mBackground != null) { + mBackground.removeBackground(mTransaction); + } + + mTransaction.apply(); + mBackInProgress = false; + mTransformMatrix.reset(); + mInitialTouchPos.set(0, 0); + mEnteringWindowShow = false; + mEnteringMargin = 0; + + if (mFinishCallback != null) { + try { + mFinishCallback.onAnimationFinished(); + } catch (RemoteException e) { + e.printStackTrace(); + } + mFinishCallback = null; + } + } + + private void onGestureProgress(@NonNull BackEvent backEvent) { + if (!mBackInProgress) { + mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY()); + mBackInProgress = true; + } + mTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY()); + + if (mEnteringTarget == null || mClosingTarget == null) { + return; + } + + final float progress = getInterpolatedProgress(backEvent.getProgress()); + final float touchY = mTouchPos.y; + + final int width = mStartTaskRect.width(); + final int height = mStartTaskRect.height(); + + final float closingScale = mapRange(progress, 1, MIN_WINDOW_SCALE); + + final float closingWidth = closingScale * width; + final float closingHeight = (float) height / width * closingWidth; + + // Move the window along the X axis. + final float closingLeft = mStartTaskRect.left + (width - closingWidth) / 2; + + // Move the window along the Y axis. + final float deltaYRatio = (touchY - mInitialTouchPos.y) / height; + final float deltaY = (float) Math.sin(deltaYRatio * Math.PI * 0.5f) * WINDOW_MAX_DELTA_Y; + final float closingTop = (height - closingHeight) * 0.5f + deltaY; + mClosingRect.set( + closingLeft, closingTop, closingLeft + closingWidth, closingTop + closingHeight); + mEnteringRect.set(mClosingRect); + + // Switch closing/entering targets while reach to the threshold progress. + if (showEnteringWindow(progress > SWITCH_ENTERING_WINDOW_PROGRESS)) { + return; + } + + // Present windows and update the alpha. + mCurrentAlpha = Math.max(mapRange(progress, 1.0f, 0), CLOSING_MIN_WINDOW_ALPHA); + mClosingRect.offset(mEnteringMargin, 0); + mEnteringRect.offset(mEnteringMargin - width, 0); + + applyTransform( + mClosingTarget.leash, mClosingRect, mEnteringWindowShow ? 0.01f : mCurrentAlpha); + applyTransform( + mEnteringTarget.leash, mEnteringRect, mEnteringWindowShow ? mCurrentAlpha : 0.01f); + mTransaction.apply(); + } + + private boolean showEnteringWindow(boolean show) { + if (mEnteringAnimator == null) { + mEnteringAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(FADE_IN_DURATION); + mEnteringAnimator.setInterpolator(new AccelerateInterpolator()); + mEnteringAnimator.addUpdateListener(animation -> { + float progress = animation.getAnimatedFraction(); + final int width = mStartTaskRect.width(); + mEnteringMargin = width * progress; + // We don't animate to 0 or the surface would become invisible and lose focus. + final float alpha = progress >= 0.5f ? 0.01f + : mapRange(progress * 2, mCurrentAlpha, 0.01f); + mClosingRect.offset(mEnteringMargin, 0); + mEnteringRect.offset(mEnteringMargin - width, 0); + + applyTransform(mClosingTarget.leash, mClosingRect, alpha); + applyTransform(mEnteringTarget.leash, mEnteringRect, mCurrentAlpha); + mTransaction.apply(); + }); + } + + if (mEnteringAnimator.isRunning()) { + return true; + } + + if (mEnteringWindowShow == show) { + return false; + } + + mEnteringWindowShow = show; + if (show) { + mEnteringAnimator.start(); + } else { + mEnteringAnimator.reverse(); + } + return true; + } + + private void onGestureCommitted() { + if (mEnteringTarget == null || mClosingTarget == null) { + finishAnimation(); + return; + } + + // End the fade in animation. + if (mEnteringAnimator.isRunning()) { + mEnteringAnimator.cancel(); + } + + // We enter phase 2 of the animation, the starting coordinates for phase 2 are the current + // coordinate of the gesture driven phase. + mEnteringRect.round(mEnteringStartRect); + mTransaction.hide(mClosingTarget.leash); + + ValueAnimator valueAnimator = + ValueAnimator.ofFloat(1f, 0f).setDuration(POST_ANIMATION_DURATION); + valueAnimator.setInterpolator(new DecelerateInterpolator()); + valueAnimator.addUpdateListener(animation -> { + float progress = animation.getAnimatedFraction(); + updatePostCommitEnteringAnimation(progress); + mTransaction.apply(); + }); + + valueAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + finishAnimation(); + } + }); + valueAnimator.start(); + } + + private void updatePostCommitEnteringAnimation(float progress) { + float left = mapRange(progress, mEnteringStartRect.left, mStartTaskRect.left); + float top = mapRange(progress, mEnteringStartRect.top, mStartTaskRect.top); + float width = mapRange(progress, mEnteringStartRect.width(), mStartTaskRect.width()); + float height = mapRange(progress, mEnteringStartRect.height(), mStartTaskRect.height()); + float alpha = mapRange(progress, mCurrentAlpha, 1.0f); + + mEnteringRect.set(left, top, left + width, top + height); + applyTransform(mEnteringTarget.leash, mEnteringRect, alpha); + } + + private final class Callback extends IOnBackInvokedCallback.Default { + @Override + public void onBackStarted(BackEvent backEvent) { + mProgressAnimator.onBackStarted(backEvent, + CrossActivityAnimation.this::onGestureProgress); + } + + @Override + public void onBackProgressed(@NonNull BackEvent backEvent) { + mProgressAnimator.onBackProgressed(backEvent); + } + + @Override + public void onBackCancelled() { + // End the fade in animation. + if (mEnteringAnimator.isRunning()) { + mEnteringAnimator.cancel(); + } + // TODO (b259608500): Let BackProgressAnimator could play cancel animation. + mProgressAnimator.reset(); + finishAnimation(); + } + + @Override + public void onBackInvoked() { + mProgressAnimator.reset(); + onGestureCommitted(); + } + } + + private final class Runner extends IRemoteAnimationRunner.Default { + @Override + public void onAnimationStart( + int transit, + RemoteAnimationTarget[] apps, + RemoteAnimationTarget[] wallpapers, + RemoteAnimationTarget[] nonApps, + IRemoteAnimationFinishedCallback finishedCallback) { + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to activity animation."); + for (RemoteAnimationTarget a : apps) { + if (a.mode == MODE_CLOSING) { + mClosingTarget = a; + } + if (a.mode == MODE_OPENING) { + mEnteringTarget = a; + } + } + + startBackAnimation(); + mFinishCallback = finishedCallback; + } + + @Override + public void onAnimationCancelled(boolean isKeyguardOccluded) { + finishAnimation(); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java index 2074b6ab44f2..a9a7b7742d2f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java @@ -61,7 +61,7 @@ import com.android.wm.shell.common.annotations.ShellMainThread; */ @ShellMainThread class CrossTaskBackAnimation { - private static final float[] BACKGROUNDCOLOR = {0.263f, 0.263f, 0.227f}; + private static final int BACKGROUNDCOLOR = 0x43433A; /** * Minimum scale of the entering window. @@ -106,7 +106,6 @@ class CrossTaskBackAnimation { private RemoteAnimationTarget mEnteringTarget; private RemoteAnimationTarget mClosingTarget; - private SurfaceControl mBackgroundSurface; private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); private boolean mBackInProgress = false; @@ -115,56 +114,15 @@ class CrossTaskBackAnimation { private float mProgress = 0; private PointF mTouchPos = new PointF(); private IRemoteAnimationFinishedCallback mFinishCallback; - private BackProgressAnimator mProgressAnimator = new BackProgressAnimator(); + final BackAnimationRunner mBackAnimationRunner; - final IOnBackInvokedCallback mCallback = new IOnBackInvokedCallback.Default() { - @Override - public void onBackStarted(BackEvent backEvent) { - mProgressAnimator.onBackStarted(backEvent, - CrossTaskBackAnimation.this::onGestureProgress); - } - - @Override - public void onBackProgressed(@NonNull BackEvent backEvent) { - mProgressAnimator.onBackProgressed(backEvent); - } - - @Override - public void onBackCancelled() { - mProgressAnimator.reset(); - finishAnimation(); - } - - @Override - public void onBackInvoked() { - mProgressAnimator.reset(); - onGestureCommitted(); - } - }; - - final IRemoteAnimationRunner mRunner = new IRemoteAnimationRunner.Default() { - @Override - public void onAnimationStart(int transit, RemoteAnimationTarget[] apps, - RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, - IRemoteAnimationFinishedCallback finishedCallback) { - ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to task animation."); - for (RemoteAnimationTarget a : apps) { - if (a.mode == MODE_CLOSING) { - mClosingTarget = a; - } - if (a.mode == MODE_OPENING) { - mEnteringTarget = a; - } - } - - startBackAnimation(); - mFinishCallback = finishedCallback; - } - }; + private final BackAnimationBackground mBackground; - CrossTaskBackAnimation(Context context) { + CrossTaskBackAnimation(Context context, BackAnimationBackground background) { mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context); + mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner()); + mBackground = background; } private float getInterpolatedProgress(float backProgress) { @@ -182,14 +140,7 @@ class CrossTaskBackAnimation { mStartTaskRect.offsetTo(0, 0); // Draw background. - mBackgroundSurface = new SurfaceControl.Builder() - .setName("Background of Back Navigation") - .setColorLayer() - .setHidden(false) - .build(); - mTransaction.setColor(mBackgroundSurface, BACKGROUNDCOLOR) - .setLayer(mBackgroundSurface, -1); - mTransaction.apply(); + mBackground.ensureBackground(BACKGROUNDCOLOR, mTransaction); } private void updateGestureBackProgress(float progress, BackEvent event) { @@ -300,11 +251,11 @@ class CrossTaskBackAnimation { mClosingTarget = null; } - if (mBackgroundSurface != null) { - mBackgroundSurface.release(); - mBackgroundSurface = null; + if (mBackground != null) { + mBackground.removeBackground(mTransaction); } + mTransaction.apply(); mBackInProgress = false; mTransformMatrix.reset(); mClosingCurrentRect.setEmpty(); @@ -362,4 +313,49 @@ class CrossTaskBackAnimation { private static float mapRange(float value, float min, float max) { return min + (value * (max - min)); } + + private final class Callback extends IOnBackInvokedCallback.Default { + @Override + public void onBackStarted(BackEvent backEvent) { + mProgressAnimator.onBackStarted(backEvent, + CrossTaskBackAnimation.this::onGestureProgress); + } + + @Override + public void onBackProgressed(@NonNull BackEvent backEvent) { + mProgressAnimator.onBackProgressed(backEvent); + } + + @Override + public void onBackCancelled() { + mProgressAnimator.reset(); + finishAnimation(); + } + + @Override + public void onBackInvoked() { + mProgressAnimator.reset(); + onGestureCommitted(); + } + }; + + private final class Runner extends IRemoteAnimationRunner.Default { + @Override + public void onAnimationStart(int transit, RemoteAnimationTarget[] apps, + RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, + IRemoteAnimationFinishedCallback finishedCallback) { + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to task animation."); + for (RemoteAnimationTarget a : apps) { + if (a.mode == MODE_CLOSING) { + mClosingTarget = a; + } + if (a.mode == MODE_OPENING) { + mEnteringTarget = a; + } + } + + startBackAnimation(); + mFinishCallback = finishedCallback; + } + }; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 962be9da2111..4ea8a5dbc5a9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -38,6 +38,7 @@ import com.android.wm.shell.TaskViewTransitions; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.activityembedding.ActivityEmbeddingController; import com.android.wm.shell.back.BackAnimation; +import com.android.wm.shell.back.BackAnimationBackground; import com.android.wm.shell.back.BackAnimationController; import com.android.wm.shell.bubbles.BubbleController; import com.android.wm.shell.bubbles.Bubbles; @@ -93,13 +94,13 @@ import com.android.wm.shell.unfold.UnfoldAnimationController; import com.android.wm.shell.unfold.UnfoldTransitionHandler; import com.android.wm.shell.windowdecor.WindowDecorViewModel; -import java.util.Optional; - import dagger.BindsOptionalOf; import dagger.Lazy; import dagger.Module; import dagger.Provides; +import java.util.Optional; + /** * Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only * accessible from components within the WM subcomponent (can be explicitly exposed to the @@ -255,21 +256,30 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides + static BackAnimationBackground provideBackAnimationBackground( + RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) { + return new BackAnimationBackground(rootTaskDisplayAreaOrganizer); + } + + @WMSingleton + @Provides static Optional<BackAnimationController> provideBackAnimationController( Context context, ShellInit shellInit, ShellController shellController, @ShellMainThread ShellExecutor shellExecutor, - @ShellBackgroundThread Handler backgroundHandler + @ShellBackgroundThread Handler backgroundHandler, + BackAnimationBackground backAnimationBackground ) { if (BackAnimationController.IS_ENABLED) { return Optional.of( new BackAnimationController(shellInit, shellController, shellExecutor, - backgroundHandler, context)); + backgroundHandler, context, backAnimationBackground)); } return Optional.empty(); } + // // Bubbles (optional feature) // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index f170e774739f..8ba2583757cd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -63,6 +63,7 @@ import android.graphics.Rect; import android.os.RemoteException; import android.os.SystemClock; import android.util.Log; +import android.view.Choreographer; import android.view.Display; import android.view.Surface; import android.view.SurfaceControl; @@ -179,8 +180,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, // This is necessary in case there was a resize animation ongoing when exit PIP // started, in which case the first resize will be skipped to let the exit // operation handle the final resize out of PIP mode. See b/185306679. - finishResize(tx, destinationBounds, direction, animationType); - sendOnPipTransitionFinished(direction); + finishResizeDelayedIfNeeded(() -> { + finishResize(tx, destinationBounds, direction, animationType); + sendOnPipTransitionFinished(direction); + }); } } @@ -196,6 +199,39 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } }; + /** + * Finishes resizing the PiP, delaying the operation if it has to be synced with the PiP menu. + * + * This is done to avoid a race condition between the last transaction applied in + * onPipAnimationUpdate and the finishResize in onPipAnimationEnd. The transaction in + * onPipAnimationUpdate is applied directly from WmShell, while onPipAnimationEnd creates a + * WindowContainerTransaction in finishResize, which is to be applied by WmCore later. Normally, + * the WCT should be the last transaction to finish the animation. However, it may happen that + * it gets applied *before* the transaction created by the last onPipAnimationUpdate. This + * happens only when the PiP surface transaction has to be synced with the PiP menu due to the + * necessity for a delay when syncing the PiP surface animation with the PiP menu surface + * animation and redrawing the PiP menu contents. As a result, the PiP surface gets scaled after + * the new bounds are applied by WmCore, which makes the PiP surface have unexpected bounds. + * + * To avoid this, we delay the finishResize operation until + * the next frame. This aligns the last onAnimationUpdate transaction with the WCT application. + */ + private void finishResizeDelayedIfNeeded(Runnable finishResizeRunnable) { + if (!shouldSyncPipTransactionWithMenu()) { + finishResizeRunnable.run(); + return; + } + + // Delay the finishResize to the next frame + Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT, () -> { + mMainExecutor.execute(finishResizeRunnable); + }, null); + } + + private boolean shouldSyncPipTransactionWithMenu() { + return mPipMenuController.isMenuVisible(); + } + @VisibleForTesting final PipTransitionController.PipTransitionCallback mPipTransitionCallback = new PipTransitionController.PipTransitionCallback() { @@ -221,7 +257,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, @Override public boolean handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, Rect destinationBounds) { - if (mPipMenuController.isMenuVisible()) { + if (shouldSyncPipTransactionWithMenu()) { mPipMenuController.movePipMenu(leash, tx, destinationBounds); return true; } @@ -1223,7 +1259,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mSurfaceTransactionHelper .crop(tx, mLeash, toBounds) .round(tx, mLeash, mPipTransitionState.isInPip()); - if (mPipMenuController.isMenuVisible()) { + if (shouldSyncPipTransactionWithMenu()) { mPipMenuController.resizePipMenu(mLeash, tx, toBounds); } else { tx.apply(); @@ -1265,7 +1301,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mSurfaceTransactionHelper .scale(tx, mLeash, startBounds, toBounds, degrees) .round(tx, mLeash, startBounds, toBounds); - if (mPipMenuController.isMenuVisible()) { + if (shouldSyncPipTransactionWithMenu()) { mPipMenuController.movePipMenu(mLeash, tx, toBounds); } else { tx.apply(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java index 4e1fa290270d..485b400f458d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java @@ -77,10 +77,10 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler { if (mRemote.asBinder() != null) { mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */); } + if (sct != null) { + finishTransaction.merge(sct); + } mMainExecutor.execute(() -> { - if (sct != null) { - finishTransaction.merge(sct); - } finishCallback.onTransitionFinished(wct, null /* wctCB */); }); } @@ -90,7 +90,13 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler { if (mRemote.asBinder() != null) { mRemote.asBinder().linkToDeath(remoteDied, 0 /* flags */); } - mRemote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb); + // If the remote is actually in the same process, then make a copy of parameters since + // remote impls assume that they have to clean-up native references. + final SurfaceControl.Transaction remoteStartT = RemoteTransitionHandler.copyIfLocal( + startTransaction, mRemote.getRemoteTransition()); + final TransitionInfo remoteInfo = + remoteStartT == startTransaction ? info : info.localRemoteCopy(); + mRemote.getRemoteTransition().startAnimation(transition, remoteInfo, remoteStartT, cb); // assume that remote will apply the start transaction. startTransaction.clear(); } catch (RemoteException e) { @@ -124,7 +130,13 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler { } }; try { - mRemote.getRemoteTransition().mergeAnimation(transition, info, t, mergeTarget, cb); + // If the remote is actually in the same process, then make a copy of parameters since + // remote impls assume that they have to clean-up native references. + final SurfaceControl.Transaction remoteT = + RemoteTransitionHandler.copyIfLocal(t, mRemote.getRemoteTransition()); + final TransitionInfo remoteInfo = remoteT == t ? info : info.localRemoteCopy(); + mRemote.getRemoteTransition().mergeAnimation( + transition, remoteInfo, remoteT, mergeTarget, cb); } catch (RemoteException e) { Log.e(Transitions.TAG, "Error merging remote transition.", e); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java index 9469529de8f1..b4e05848882c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java @@ -19,6 +19,7 @@ package com.android.wm.shell.transition; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.IBinder; +import android.os.Parcel; import android.os.RemoteException; import android.util.ArrayMap; import android.util.Log; @@ -120,10 +121,10 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { public void onTransitionFinished(WindowContainerTransaction wct, SurfaceControl.Transaction sct) { unhandleDeath(remote.asBinder(), finishCallback); + if (sct != null) { + finishTransaction.merge(sct); + } mMainExecutor.execute(() -> { - if (sct != null) { - finishTransaction.merge(sct); - } mRequestedRemotes.remove(transition); finishCallback.onTransitionFinished(wct, null /* wctCB */); }); @@ -131,8 +132,14 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { }; Transitions.setRunningRemoteTransitionDelegate(remote.getAppThread()); try { + // If the remote is actually in the same process, then make a copy of parameters since + // remote impls assume that they have to clean-up native references. + final SurfaceControl.Transaction remoteStartT = + copyIfLocal(startTransaction, remote.getRemoteTransition()); + final TransitionInfo remoteInfo = + remoteStartT == startTransaction ? info : info.localRemoteCopy(); handleDeath(remote.asBinder(), finishCallback); - remote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb); + remote.getRemoteTransition().startAnimation(transition, remoteInfo, remoteStartT, cb); // assume that remote will apply the start transaction. startTransaction.clear(); } catch (RemoteException e) { @@ -145,6 +152,28 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { return true; } + static SurfaceControl.Transaction copyIfLocal(SurfaceControl.Transaction t, + IRemoteTransition remote) { + // We care more about parceling than local (though they should be the same); so, use + // queryLocalInterface since that's what Binder uses to decide if it needs to parcel. + if (remote.asBinder().queryLocalInterface(IRemoteTransition.DESCRIPTOR) == null) { + // No local interface, so binder itself will parcel and thus we don't need to. + return t; + } + // Binder won't be parceling; however, the remotes assume they have their own native + // objects (and don't know if caller is local or not), so we need to make a COPY here so + // that the remote can clean it up without clearing the original transaction. + // Since there's no direct `copy` for Transaction, we have to parcel/unparcel instead. + final Parcel p = Parcel.obtain(); + try { + t.writeToParcel(p, 0); + p.setDataPosition(0); + return SurfaceControl.Transaction.CREATOR.createFromParcel(p); + } finally { + p.recycle(); + } + } + @Override public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @@ -175,7 +204,11 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { } }; try { - remote.mergeAnimation(transition, info, t, mergeTarget, cb); + // If the remote is actually in the same process, then make a copy of parameters since + // remote impls assume that they have to clean-up native references. + final SurfaceControl.Transaction remoteT = copyIfLocal(t, remote); + final TransitionInfo remoteInfo = remoteT == t ? info : info.localRemoteCopy(); + remote.mergeAnimation(transition, remoteInfo, remoteT, mergeTarget, cb); } catch (RemoteException e) { Log.e(Transitions.TAG, "Error attempting to merge remote transition.", e); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 56d51bda762f..c6935c054422 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -503,6 +503,7 @@ public class Transitions implements RemoteCallable<Transitions> { // Treat this as an abort since we are bypassing any merge logic and effectively // finishing immediately. onAbort(transitionToken); + releaseSurfaces(info); return; } @@ -607,6 +608,15 @@ public class Transitions implements RemoteCallable<Transitions> { onFinish(transition, wct, wctCB, false /* abort */); } + /** + * Releases an info's animation-surfaces. These don't need to persist and we need to release + * them asap so that SF can free memory sooner. + */ + private void releaseSurfaces(@Nullable TransitionInfo info) { + if (info == null) return; + info.releaseAnimSurfaces(); + } + private void onFinish(IBinder transition, @Nullable WindowContainerTransaction wct, @Nullable WindowContainerTransactionCallback wctCB, @@ -645,6 +655,11 @@ public class Transitions implements RemoteCallable<Transitions> { } ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition animation finished (abort=%b), notifying core %s", abort, transition); + if (active.mStartT != null) { + // Applied by now, so close immediately. Do not set to null yet, though, since nullness + // is used later to disambiguate malformed transitions. + active.mStartT.close(); + } // Merge all relevant transactions together SurfaceControl.Transaction fullFinish = active.mFinishT; for (int iA = activeIdx + 1; iA < mActiveTransitions.size(); ++iA) { @@ -664,12 +679,14 @@ public class Transitions implements RemoteCallable<Transitions> { fullFinish.apply(); } // Now perform all the finishes. + releaseSurfaces(active.mInfo); mActiveTransitions.remove(activeIdx); mOrganizer.finishTransition(transition, wct, wctCB); while (activeIdx < mActiveTransitions.size()) { if (!mActiveTransitions.get(activeIdx).mMerged) break; ActiveTransition merged = mActiveTransitions.remove(activeIdx); mOrganizer.finishTransition(merged.mToken, null /* wct */, null /* wctCB */); + releaseSurfaces(merged.mInfo); } // sift through aborted transitions while (mActiveTransitions.size() > activeIdx @@ -682,8 +699,9 @@ public class Transitions implements RemoteCallable<Transitions> { } mOrganizer.finishTransition(aborted.mToken, null /* wct */, null /* wctCB */); for (int i = 0; i < mObservers.size(); ++i) { - mObservers.get(i).onTransitionFinished(active.mToken, true); + mObservers.get(i).onTransitionFinished(aborted.mToken, true); } + releaseSurfaces(aborted.mInfo); } if (mActiveTransitions.size() <= activeIdx) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition animations " diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index d75c36c99a4c..bee9a90ec6b8 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -110,6 +110,9 @@ public class BackAnimationControllerTest extends ShellTestCase { @Mock private ShellController mShellController; + @Mock + private BackAnimationBackground mAnimationBackground; + private BackAnimationController mController; private TestableContentResolver mContentResolver; private TestableLooper mTestableLooper; @@ -127,7 +130,7 @@ public class BackAnimationControllerTest extends ShellTestCase { mController = new BackAnimationController(mShellInit, mShellController, mShellExecutor, new Handler(mTestableLooper.getLooper()), mActivityTaskManager, mContext, - mContentResolver); + mContentResolver, mAnimationBackground); mController.setEnableUAnimation(true); mShellInit.init(); mShellExecutor.flushAll(); @@ -239,7 +242,7 @@ public class BackAnimationControllerTest extends ShellTestCase { mController = new BackAnimationController(shellInit, mShellController, mShellExecutor, new Handler(mTestableLooper.getLooper()), mActivityTaskManager, mContext, - mContentResolver); + mContentResolver, mAnimationBackground); shellInit.init(); registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); @@ -354,6 +357,10 @@ public class BackAnimationControllerTest extends ShellTestCase { BackNavigationInfo.TYPE_DIALOG_CLOSE}; for (int type: testTypes) { + unregisterAnimation(type); + } + + for (int type: testTypes) { final ResultListener result = new ResultListener(); createNavigationInfo(new BackNavigationInfo.Builder() .setType(type) @@ -431,6 +438,10 @@ public class BackAnimationControllerTest extends ShellTestCase { new BackAnimationRunner(mAnimatorCallback, mBackAnimationRunner)); } + private void unregisterAnimation(int type) { + mController.unregisterAnimation(type); + } + private static class ResultListener implements RemoteCallback.OnResultListener { boolean mBackNavigationDone = false; boolean mTriggerBack = false; diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp index 9aa37872b8de..c0fa63a08fac 100755 --- a/libs/androidfw/ApkAssets.cpp +++ b/libs/androidfw/ApkAssets.cpp @@ -83,15 +83,16 @@ std::unique_ptr<ApkAssets> ApkAssets::LoadOverlay(const std::string& idmap_path, return {}; } + std::string overlay_path(loaded_idmap->OverlayApkPath()); + auto fd = unique_fd(::open(overlay_path.c_str(), O_RDONLY|O_CLOEXEC)); std::unique_ptr<AssetsProvider> overlay_assets; - const std::string overlay_path(loaded_idmap->OverlayApkPath()); - if (IsFabricatedOverlay(overlay_path)) { + if (IsFabricatedOverlay(fd)) { // Fabricated overlays do not contain resource definitions. All of the overlay resource values // are defined inline in the idmap. - overlay_assets = EmptyAssetsProvider::Create(overlay_path); + overlay_assets = EmptyAssetsProvider::Create(std::move(overlay_path)); } else { // The overlay should be an APK. - overlay_assets = ZipAssetsProvider::Create(overlay_path, flags); + overlay_assets = ZipAssetsProvider::Create(std::move(fd), std::move(overlay_path), flags); } if (overlay_assets == nullptr) { return {}; diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index 3fa369d7ff4a..c3d153d56b26 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -22,6 +22,7 @@ #include <iterator> #include <map> #include <set> +#include <span> #include "android-base/logging.h" #include "android-base/stringprintf.h" @@ -111,7 +112,7 @@ void AssetManager2::BuildDynamicRefTable() { // A mapping from path of apk assets that could be target packages of overlays to the runtime // package id of its first loaded package. Overlays currently can only override resources in the // first package in the target resource table. - std::unordered_map<std::string, uint8_t> target_assets_package_ids; + std::unordered_map<std::string_view, uint8_t> target_assets_package_ids; // Overlay resources are not directly referenced by an application so their resource ids // can change throughout the application's lifetime. Assign overlay package ids last. @@ -134,7 +135,7 @@ void AssetManager2::BuildDynamicRefTable() { if (auto loaded_idmap = apk_assets->GetLoadedIdmap(); loaded_idmap != nullptr) { // The target package must precede the overlay package in the apk assets paths in order // to take effect. - auto iter = target_assets_package_ids.find(std::string(loaded_idmap->TargetApkPath())); + auto iter = target_assets_package_ids.find(loaded_idmap->TargetApkPath()); if (iter == target_assets_package_ids.end()) { LOG(INFO) << "failed to find target package for overlay " << loaded_idmap->OverlayApkPath(); @@ -179,7 +180,7 @@ void AssetManager2::BuildDynamicRefTable() { if (overlay_ref_table != nullptr) { // If this package is from an overlay, use a dynamic reference table that can rewrite // overlay resource ids to their corresponding target resource ids. - new_group.dynamic_ref_table = overlay_ref_table; + new_group.dynamic_ref_table = std::move(overlay_ref_table); } DynamicRefTable* ref_table = new_group.dynamic_ref_table.get(); @@ -187,9 +188,9 @@ void AssetManager2::BuildDynamicRefTable() { ref_table->mAppAsLib = package->IsDynamic() && package->GetPackageId() == 0x7f; } - // Add the package and to the set of packages with the same ID. + // Add the package to the set of packages with the same ID. PackageGroup* package_group = &package_groups_[idx]; - package_group->packages_.push_back(ConfiguredPackage{package.get(), {}}); + package_group->packages_.emplace_back().loaded_package_ = package.get(); package_group->cookies_.push_back(apk_assets_cookies[apk_assets]); // Add the package name -> build time ID mappings. @@ -201,29 +202,38 @@ void AssetManager2::BuildDynamicRefTable() { if (auto apk_assets_path = apk_assets->GetPath()) { // Overlay target ApkAssets must have been created using path based load apis. - target_assets_package_ids.insert(std::make_pair(std::string(*apk_assets_path), package_id)); + target_assets_package_ids.emplace(*apk_assets_path, package_id); } } } // Now assign the runtime IDs so that we have a build-time to runtime ID map. - const auto package_groups_end = package_groups_.end(); - for (auto iter = package_groups_.begin(); iter != package_groups_end; ++iter) { - const std::string& package_name = iter->packages_[0].loaded_package_->GetPackageName(); - for (auto iter2 = package_groups_.begin(); iter2 != package_groups_end; ++iter2) { - iter2->dynamic_ref_table->addMapping(String16(package_name.c_str(), package_name.size()), - iter->dynamic_ref_table->mAssignedPackageId); - - // Add the alias resources to the dynamic reference table of every package group. Since - // staging aliases can only be defined by the framework package (which is not a shared - // library), the compile-time package id of the framework is the same across all packages - // that compile against the framework. - for (const auto& package : iter->packages_) { - for (const auto& entry : package.loaded_package_->GetAliasResourceIdMap()) { - iter2->dynamic_ref_table->addAlias(entry.first, entry.second); - } - } + DynamicRefTable::AliasMap aliases; + for (const auto& group : package_groups_) { + const std::string& package_name = group.packages_[0].loaded_package_->GetPackageName(); + const auto name_16 = String16(package_name.c_str(), package_name.size()); + for (auto&& inner_group : package_groups_) { + inner_group.dynamic_ref_table->addMapping(name_16, + group.dynamic_ref_table->mAssignedPackageId); + } + + for (const auto& package : group.packages_) { + const auto& package_aliases = package.loaded_package_->GetAliasResourceIdMap(); + aliases.insert(aliases.end(), package_aliases.begin(), package_aliases.end()); + } + } + + if (!aliases.empty()) { + std::sort(aliases.begin(), aliases.end(), [](auto&& l, auto&& r) { return l.first < r.first; }); + + // Add the alias resources to the dynamic reference table of every package group. Since + // staging aliases can only be defined by the framework package (which is not a shared + // library), the compile-time package id of the framework is the same across all packages + // that compile against the framework. + for (auto& group : std::span(package_groups_.data(), package_groups_.size() - 1)) { + group.dynamic_ref_table->setAliases(aliases); } + package_groups_.back().dynamic_ref_table->setAliases(std::move(aliases)); } } @@ -317,7 +327,7 @@ const std::unordered_map<std::string, std::string>* return &loaded_package->GetOverlayableMap(); } -bool AssetManager2::GetOverlayablesToString(const android::StringPiece& package_name, +bool AssetManager2::GetOverlayablesToString(android::StringPiece package_name, std::string* out) const { uint8_t package_id = 0U; for (const auto& apk_assets : apk_assets_) { @@ -492,7 +502,7 @@ std::unique_ptr<AssetDir> AssetManager2::OpenDir(const std::string& dirname) con continue; } - auto func = [&](const StringPiece& name, FileType type) { + auto func = [&](StringPiece name, FileType type) { AssetDir::FileInfo info; info.setFileName(String8(name.data(), name.size())); info.setFileType(type); @@ -1271,7 +1281,7 @@ base::expected<const ResolvedBag*, NullOrIOError> AssetManager2::GetBag( return result; } -static bool Utf8ToUtf16(const StringPiece& str, std::u16string* out) { +static bool Utf8ToUtf16(StringPiece str, std::u16string* out) { ssize_t len = utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(str.data()), str.size(), false); if (len < 0) { @@ -1347,18 +1357,17 @@ base::expected<uint32_t, NullOrIOError> AssetManager2::GetResourceId( void AssetManager2::RebuildFilterList() { for (PackageGroup& group : package_groups_) { for (ConfiguredPackage& impl : group.packages_) { - // Destroy it. - impl.filtered_configs_.~ByteBucketArray(); - - // Re-create it. - new (&impl.filtered_configs_) ByteBucketArray<FilteredConfigGroup>(); + impl.filtered_configs_.clear(); // Create the filters here. impl.loaded_package_->ForEachTypeSpec([&](const TypeSpec& type_spec, uint8_t type_id) { - FilteredConfigGroup& group = impl.filtered_configs_.editItemAt(type_id - 1); + FilteredConfigGroup* group = nullptr; for (const auto& type_entry : type_spec.type_entries) { if (type_entry.config.match(configuration_)) { - group.type_entries.push_back(&type_entry); + if (!group) { + group = &impl.filtered_configs_.editItemAt(type_id - 1); + } + group->type_entries.push_back(&type_entry); } } }); diff --git a/libs/androidfw/AssetsProvider.cpp b/libs/androidfw/AssetsProvider.cpp index bce34d37c90b..80e560747a3e 100644 --- a/libs/androidfw/AssetsProvider.cpp +++ b/libs/androidfw/AssetsProvider.cpp @@ -211,8 +211,7 @@ std::unique_ptr<Asset> ZipAssetsProvider::OpenInternal(const std::string& path, } bool ZipAssetsProvider::ForEachFile(const std::string& root_path, - const std::function<void(const StringPiece&, FileType)>& f) - const { + const std::function<void(StringPiece, FileType)>& f) const { std::string root_path_full = root_path; if (root_path_full.back() != '/') { root_path_full += '/'; @@ -238,8 +237,7 @@ bool ZipAssetsProvider::ForEachFile(const std::string& root_path, if (!leaf_file_path.empty()) { auto iter = std::find(leaf_file_path.begin(), leaf_file_path.end(), '/'); if (iter != leaf_file_path.end()) { - std::string dir = - leaf_file_path.substr(0, std::distance(leaf_file_path.begin(), iter)).to_string(); + std::string dir(leaf_file_path.substr(0, std::distance(leaf_file_path.begin(), iter))); dirs.insert(std::move(dir)); } else { f(leaf_file_path, kFileTypeRegular); @@ -324,8 +322,7 @@ std::unique_ptr<Asset> DirectoryAssetsProvider::OpenInternal(const std::string& bool DirectoryAssetsProvider::ForEachFile( const std::string& /* root_path */, - const std::function<void(const StringPiece&, FileType)>& /* f */) - const { + const std::function<void(StringPiece, FileType)>& /* f */) const { return true; } @@ -373,8 +370,7 @@ std::unique_ptr<Asset> MultiAssetsProvider::OpenInternal(const std::string& path } bool MultiAssetsProvider::ForEachFile(const std::string& root_path, - const std::function<void(const StringPiece&, FileType)>& f) - const { + const std::function<void(StringPiece, FileType)>& f) const { return primary_->ForEachFile(root_path, f) && secondary_->ForEachFile(root_path, f); } @@ -397,8 +393,8 @@ std::unique_ptr<AssetsProvider> EmptyAssetsProvider::Create() { return std::unique_ptr<EmptyAssetsProvider>(new EmptyAssetsProvider({})); } -std::unique_ptr<AssetsProvider> EmptyAssetsProvider::Create(const std::string& path) { - return std::unique_ptr<EmptyAssetsProvider>(new EmptyAssetsProvider(path)); +std::unique_ptr<AssetsProvider> EmptyAssetsProvider::Create(std::string path) { + return std::unique_ptr<EmptyAssetsProvider>(new EmptyAssetsProvider(std::move(path))); } std::unique_ptr<Asset> EmptyAssetsProvider::OpenInternal(const std::string& /* path */, @@ -412,7 +408,7 @@ std::unique_ptr<Asset> EmptyAssetsProvider::OpenInternal(const std::string& /* p bool EmptyAssetsProvider::ForEachFile( const std::string& /* root_path */, - const std::function<void(const StringPiece&, FileType)>& /* f */) const { + const std::function<void(StringPiece, FileType)>& /* f */) const { return true; } diff --git a/libs/androidfw/ConfigDescription.cpp b/libs/androidfw/ConfigDescription.cpp index 19ead9583eb2..93a7d173cb97 100644 --- a/libs/androidfw/ConfigDescription.cpp +++ b/libs/androidfw/ConfigDescription.cpp @@ -637,7 +637,7 @@ static bool parseVersion(const char* name, ResTable_config* out) { return true; } -bool ConfigDescription::Parse(const StringPiece& str, ConfigDescription* out) { +bool ConfigDescription::Parse(StringPiece str, ConfigDescription* out) { std::vector<std::string> parts = util::SplitAndLowercase(str, '-'); ConfigDescription config; diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp index e122d4844ee9..f3d244342b55 100644 --- a/libs/androidfw/Idmap.cpp +++ b/libs/androidfw/Idmap.cpp @@ -274,8 +274,7 @@ LoadedIdmap::LoadedIdmap(std::string&& idmap_path, target_apk_path_(target_apk_path), idmap_last_mod_time_(getFileModDate(idmap_path_.data())) {} -std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_path, - const StringPiece& idmap_data) { +std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPiece idmap_data) { ATRACE_CALL(); size_t data_size = idmap_data.size(); auto data_ptr = reinterpret_cast<const uint8_t*>(idmap_data.data()); @@ -365,7 +364,7 @@ std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_path, // Can't use make_unique because LoadedIdmap constructor is private. return std::unique_ptr<LoadedIdmap>( - new LoadedIdmap(idmap_path.to_string(), header, data_header, target_entries, + new LoadedIdmap(std::string(idmap_path), header, data_header, target_entries, target_inline_entries, target_inline_entry_values, configurations, overlay_entries, std::move(idmap_string_pool), *target_path, *overlay_path)); } diff --git a/libs/androidfw/Locale.cpp b/libs/androidfw/Locale.cpp index d87a3ce72177..272a988ec55a 100644 --- a/libs/androidfw/Locale.cpp +++ b/libs/androidfw/Locale.cpp @@ -66,7 +66,7 @@ static inline bool is_number(const std::string& str) { return std::all_of(std::begin(str), std::end(str), ::isdigit); } -bool LocaleValue::InitFromFilterString(const StringPiece& str) { +bool LocaleValue::InitFromFilterString(StringPiece str) { // A locale (as specified in the filter) is an underscore separated name such // as "en_US", "en_Latn_US", or "en_US_POSIX". std::vector<std::string> parts = util::SplitAndLowercase(str, '_'); @@ -132,11 +132,11 @@ bool LocaleValue::InitFromFilterString(const StringPiece& str) { return true; } -bool LocaleValue::InitFromBcp47Tag(const StringPiece& bcp47tag) { +bool LocaleValue::InitFromBcp47Tag(StringPiece bcp47tag) { return InitFromBcp47TagImpl(bcp47tag, '-'); } -bool LocaleValue::InitFromBcp47TagImpl(const StringPiece& bcp47tag, const char separator) { +bool LocaleValue::InitFromBcp47TagImpl(StringPiece bcp47tag, const char separator) { std::vector<std::string> subtags = util::SplitAndLowercase(bcp47tag, separator); if (subtags.size() == 1) { set_language(subtags[0].c_str()); diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index 267190a54195..31516dc58035 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -33,7 +33,9 @@ #include <type_traits> #include <vector> +#include <android-base/file.h> #include <android-base/macros.h> +#include <android-base/utf8.h> #include <androidfw/ByteBucketArray.h> #include <androidfw/ResourceTypes.h> #include <androidfw/TypeWrappers.h> @@ -236,12 +238,23 @@ void Res_png_9patch::serialize(const Res_png_9patch& patch, const int32_t* xDivs } bool IsFabricatedOverlay(const std::string& path) { - std::ifstream fin(path); + return IsFabricatedOverlay(path.c_str()); +} + +bool IsFabricatedOverlay(const char* path) { + auto fd = base::unique_fd(base::utf8::open(path, O_RDONLY|O_CLOEXEC)); + if (fd < 0) { + return false; + } + return IsFabricatedOverlay(fd); +} + +bool IsFabricatedOverlay(base::borrowed_fd fd) { uint32_t magic; - if (fin.read(reinterpret_cast<char*>(&magic), sizeof(uint32_t))) { - return magic == kFabricatedOverlayMagic; + if (!base::ReadFullyAtOffset(fd, &magic, sizeof(magic), 0)) { + return false; } - return false; + return magic == kFabricatedOverlayMagic; } static bool assertIdmapHeader(const void* idmap, size_t size) { @@ -6988,11 +7001,10 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, DynamicRefTable::DynamicRefTable() : DynamicRefTable(0, false) {} DynamicRefTable::DynamicRefTable(uint8_t packageId, bool appAsLib) - : mAssignedPackageId(packageId) + : mLookupTable() + , mAssignedPackageId(packageId) , mAppAsLib(appAsLib) { - memset(mLookupTable, 0, sizeof(mLookupTable)); - // Reserved package ids mLookupTable[APP_PACKAGE_ID] = APP_PACKAGE_ID; mLookupTable[SYS_PACKAGE_ID] = SYS_PACKAGE_ID; @@ -7073,10 +7085,6 @@ void DynamicRefTable::addMapping(uint8_t buildPackageId, uint8_t runtimePackageI mLookupTable[buildPackageId] = runtimePackageId; } -void DynamicRefTable::addAlias(uint32_t stagedId, uint32_t finalizedId) { - mAliasId[stagedId] = finalizedId; -} - status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const { uint32_t res = *resId; size_t packageId = Res_GETPACKAGE(res) + 1; @@ -7086,11 +7094,12 @@ status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const { return NO_ERROR; } - auto alias_id = mAliasId.find(res); - if (alias_id != mAliasId.end()) { + const auto alias_it = std::lower_bound(mAliasId.begin(), mAliasId.end(), res, + [](const AliasMap::value_type& pair, uint32_t val) { return pair.first < val; }); + if (alias_it != mAliasId.end() && alias_it->first == res) { // Rewrite the resource id to its alias resource id. Since the alias resource id is a // compile-time id, it still needs to be resolved further. - res = alias_id->second; + res = alias_it->second; } if (packageId == SYS_PACKAGE_ID || (packageId == APP_PACKAGE_ID && !mAppAsLib)) { diff --git a/libs/androidfw/ResourceUtils.cpp b/libs/androidfw/ResourceUtils.cpp index 87fb2c038c9f..ccb61561578f 100644 --- a/libs/androidfw/ResourceUtils.cpp +++ b/libs/androidfw/ResourceUtils.cpp @@ -18,7 +18,7 @@ namespace android { -bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, StringPiece* out_type, +bool ExtractResourceName(StringPiece str, StringPiece* out_package, StringPiece* out_type, StringPiece* out_entry) { *out_package = ""; *out_type = ""; @@ -33,16 +33,16 @@ bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, Strin while (current != end) { if (out_type->size() == 0 && *current == '/') { has_type_separator = true; - out_type->assign(start, current - start); + *out_type = StringPiece(start, current - start); start = current + 1; } else if (out_package->size() == 0 && *current == ':') { has_package_separator = true; - out_package->assign(start, current - start); + *out_package = StringPiece(start, current - start); start = current + 1; } current++; } - out_entry->assign(start, end - start); + *out_entry = StringPiece(start, end - start); return !(has_package_separator && out_package->empty()) && !(has_type_separator && out_type->empty()); @@ -50,7 +50,7 @@ bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, Strin base::expected<AssetManager2::ResourceName, NullOrIOError> ToResourceName( const StringPoolRef& type_string_ref, const StringPoolRef& entry_string_ref, - const StringPiece& package_name) { + StringPiece package_name) { AssetManager2::ResourceName name{ .package = package_name.data(), .package_len = package_name.size(), diff --git a/libs/androidfw/StringPool.cpp b/libs/androidfw/StringPool.cpp index b59e906f6281..1cb8df311c89 100644 --- a/libs/androidfw/StringPool.cpp +++ b/libs/androidfw/StringPool.cpp @@ -161,16 +161,15 @@ const StringPool::Context& StringPool::StyleRef::GetContext() const { return entry_->context; } -StringPool::Ref StringPool::MakeRef(const StringPiece& str) { +StringPool::Ref StringPool::MakeRef(StringPiece str) { return MakeRefImpl(str, Context{}, true); } -StringPool::Ref StringPool::MakeRef(const StringPiece& str, const Context& context) { +StringPool::Ref StringPool::MakeRef(StringPiece str, const Context& context) { return MakeRefImpl(str, context, true); } -StringPool::Ref StringPool::MakeRefImpl(const StringPiece& str, const Context& context, - bool unique) { +StringPool::Ref StringPool::MakeRefImpl(StringPiece str, const Context& context, bool unique) { if (unique) { auto range = indexed_strings_.equal_range(str); for (auto iter = range.first; iter != range.second; ++iter) { @@ -181,7 +180,7 @@ StringPool::Ref StringPool::MakeRefImpl(const StringPiece& str, const Context& c } std::unique_ptr<Entry> entry(new Entry()); - entry->value = str.to_string(); + entry->value = std::string(str); entry->context = context; entry->index_ = strings_.size(); entry->ref_ = 0; diff --git a/libs/androidfw/Util.cpp b/libs/androidfw/Util.cpp index 52ad0dce8187..be55fe8b4bb6 100644 --- a/libs/androidfw/Util.cpp +++ b/libs/androidfw/Util.cpp @@ -42,7 +42,7 @@ void ReadUtf16StringFromDevice(const uint16_t* src, size_t len, std::string* out } } -std::u16string Utf8ToUtf16(const StringPiece& utf8) { +std::u16string Utf8ToUtf16(StringPiece utf8) { ssize_t utf16_length = utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length()); if (utf16_length <= 0) { @@ -56,7 +56,7 @@ std::u16string Utf8ToUtf16(const StringPiece& utf8) { return utf16; } -std::string Utf16ToUtf8(const StringPiece16& utf16) { +std::string Utf16ToUtf8(StringPiece16 utf16) { ssize_t utf8_length = utf16_to_utf8_length(utf16.data(), utf16.length()); if (utf8_length <= 0) { return {}; @@ -68,7 +68,7 @@ std::string Utf16ToUtf8(const StringPiece16& utf16) { return utf8; } -std::string Utf8ToModifiedUtf8(const std::string& utf8) { +std::string Utf8ToModifiedUtf8(std::string_view utf8) { // Java uses Modified UTF-8 which only supports the 1, 2, and 3 byte formats of UTF-8. To encode // 4 byte UTF-8 codepoints, Modified UTF-8 allows the use of surrogate pairs in the same format // of CESU-8 surrogate pairs. Calculate the size of the utf8 string with all 4 byte UTF-8 @@ -86,7 +86,7 @@ std::string Utf8ToModifiedUtf8(const std::string& utf8) { // Early out if no 4 byte codepoints are found if (size == modified_size) { - return utf8; + return std::string(utf8); } std::string output; @@ -115,7 +115,7 @@ std::string Utf8ToModifiedUtf8(const std::string& utf8) { return output; } -std::string ModifiedUtf8ToUtf8(const std::string& modified_utf8) { +std::string ModifiedUtf8ToUtf8(std::string_view modified_utf8) { // The UTF-8 representation will have a byte length less than or equal to the Modified UTF-8 // representation. std::string output; @@ -170,30 +170,28 @@ std::string ModifiedUtf8ToUtf8(const std::string& modified_utf8) { return output; } -static std::vector<std::string> SplitAndTransform( - const StringPiece& str, char sep, const std::function<char(char)>& f) { +template <class Func> +static std::vector<std::string> SplitAndTransform(StringPiece str, char sep, Func&& f) { std::vector<std::string> parts; const StringPiece::const_iterator end = std::end(str); StringPiece::const_iterator start = std::begin(str); StringPiece::const_iterator current; do { current = std::find(start, end, sep); - parts.emplace_back(str.substr(start, current).to_string()); - if (f) { - std::string& part = parts.back(); - std::transform(part.begin(), part.end(), part.begin(), f); - } + parts.emplace_back(StringPiece(start, current - start)); + std::string& part = parts.back(); + std::transform(part.begin(), part.end(), part.begin(), f); start = current + 1; } while (current != end); return parts; } -std::vector<std::string> SplitAndLowercase(const StringPiece& str, char sep) { - return SplitAndTransform(str, sep, ::tolower); +std::vector<std::string> SplitAndLowercase(StringPiece str, char sep) { + return SplitAndTransform(str, sep, [](char c) { return ::tolower(c); }); } std::unique_ptr<uint8_t[]> Copy(const BigBuffer& buffer) { - std::unique_ptr<uint8_t[]> data = std::unique_ptr<uint8_t[]>(new uint8_t[buffer.size()]); + auto data = std::unique_ptr<uint8_t[]>(new uint8_t[buffer.size()]); uint8_t* p = data.get(); for (const auto& block : buffer) { memcpy(p, block.buffer.get(), block.size); @@ -211,7 +209,7 @@ StringPiece16 GetString16(const android::ResStringPool& pool, size_t idx) { std::string GetString(const android::ResStringPool& pool, size_t idx) { if (auto str = pool.string8At(idx); str.ok()) { - return ModifiedUtf8ToUtf8(str->to_string()); + return ModifiedUtf8ToUtf8(*str); } return Utf16ToUtf8(GetString16(pool, idx)); } diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h index 1bde792da2ba..e9aaedc229d6 100644 --- a/libs/androidfw/include/androidfw/AssetManager2.h +++ b/libs/androidfw/include/androidfw/AssetManager2.h @@ -124,8 +124,7 @@ class AssetManager2 { uint8_t GetAssignedPackageId(const LoadedPackage* package) const; // Returns a string representation of the overlayable API of a package. - bool GetOverlayablesToString(const android::StringPiece& package_name, - std::string* out) const; + bool GetOverlayablesToString(android::StringPiece package_name, std::string* out) const; const std::unordered_map<std::string, std::string>* GetOverlayableMapForPackage( uint32_t package_id) const; diff --git a/libs/androidfw/include/androidfw/AssetsProvider.h b/libs/androidfw/include/androidfw/AssetsProvider.h index 966ec74c1786..af6e7f41e6bb 100644 --- a/libs/androidfw/include/androidfw/AssetsProvider.h +++ b/libs/androidfw/include/androidfw/AssetsProvider.h @@ -46,7 +46,7 @@ struct AssetsProvider { // Iterate over all files and directories provided by the interface. The order of iteration is // stable. virtual bool ForEachFile(const std::string& path, - const std::function<void(const StringPiece&, FileType)>& f) const = 0; + const std::function<void(StringPiece, FileType)>& f) const = 0; // Retrieves the path to the contents of the AssetsProvider on disk. The path could represent an // APk, a directory, or some other file type. @@ -90,7 +90,7 @@ struct ZipAssetsProvider : public AssetsProvider { off64_t len = kUnknownLength); bool ForEachFile(const std::string& root_path, - const std::function<void(const StringPiece&, FileType)>& f) const override; + const std::function<void(StringPiece, FileType)>& f) const override; WARN_UNUSED std::optional<std::string_view> GetPath() const override; WARN_UNUSED const std::string& GetDebugName() const override; @@ -132,7 +132,7 @@ struct DirectoryAssetsProvider : public AssetsProvider { static std::unique_ptr<DirectoryAssetsProvider> Create(std::string root_dir); bool ForEachFile(const std::string& path, - const std::function<void(const StringPiece&, FileType)>& f) const override; + const std::function<void(StringPiece, FileType)>& f) const override; WARN_UNUSED std::optional<std::string_view> GetPath() const override; WARN_UNUSED const std::string& GetDebugName() const override; @@ -157,7 +157,7 @@ struct MultiAssetsProvider : public AssetsProvider { std::unique_ptr<AssetsProvider>&& secondary); bool ForEachFile(const std::string& root_path, - const std::function<void(const StringPiece&, FileType)>& f) const override; + const std::function<void(StringPiece, FileType)>& f) const override; WARN_UNUSED std::optional<std::string_view> GetPath() const override; WARN_UNUSED const std::string& GetDebugName() const override; @@ -181,10 +181,10 @@ struct MultiAssetsProvider : public AssetsProvider { // Does not provide any assets. struct EmptyAssetsProvider : public AssetsProvider { static std::unique_ptr<AssetsProvider> Create(); - static std::unique_ptr<AssetsProvider> Create(const std::string& path); + static std::unique_ptr<AssetsProvider> Create(std::string path); bool ForEachFile(const std::string& path, - const std::function<void(const StringPiece&, FileType)>& f) const override; + const std::function<void(StringPiece, FileType)>& f) const override; WARN_UNUSED std::optional<std::string_view> GetPath() const override; WARN_UNUSED const std::string& GetDebugName() const override; diff --git a/libs/androidfw/include/androidfw/ByteBucketArray.h b/libs/androidfw/include/androidfw/ByteBucketArray.h index 949c9445b3e8..05a2c4db8fe9 100644 --- a/libs/androidfw/include/androidfw/ByteBucketArray.h +++ b/libs/androidfw/include/androidfw/ByteBucketArray.h @@ -31,9 +31,15 @@ namespace android { template <typename T> class ByteBucketArray { public: - ByteBucketArray() : default_() { memset(buckets_, 0, sizeof(buckets_)); } + ByteBucketArray() { + memset(buckets_, 0, sizeof(buckets_)); + } ~ByteBucketArray() { + clear(); + } + + void clear() { for (size_t i = 0; i < kNumBuckets; i++) { if (buckets_[i] != NULL) { delete[] buckets_[i]; @@ -84,7 +90,7 @@ class ByteBucketArray { enum { kNumBuckets = 16, kBucketSize = 16 }; T* buckets_[kNumBuckets]; - T default_; + static inline const T default_ = {}; }; } // namespace android diff --git a/libs/androidfw/include/androidfw/ConfigDescription.h b/libs/androidfw/include/androidfw/ConfigDescription.h index 61d10cd4e55b..71087cdfb6fa 100644 --- a/libs/androidfw/include/androidfw/ConfigDescription.h +++ b/libs/androidfw/include/androidfw/ConfigDescription.h @@ -72,7 +72,7 @@ struct ConfigDescription : public ResTable_config { * The resulting configuration has the appropriate sdkVersion defined * for backwards compatibility. */ - static bool Parse(const android::StringPiece& str, ConfigDescription* out = nullptr); + static bool Parse(android::StringPiece str, ConfigDescription* out = nullptr); /** * If the configuration uses an axis that was added after diff --git a/libs/androidfw/include/androidfw/IDiagnostics.h b/libs/androidfw/include/androidfw/IDiagnostics.h index 273b05ac7689..4d5844eaa069 100644 --- a/libs/androidfw/include/androidfw/IDiagnostics.h +++ b/libs/androidfw/include/androidfw/IDiagnostics.h @@ -35,7 +35,7 @@ struct DiagMessage { public: DiagMessage() = default; - explicit DiagMessage(const android::StringPiece& src) : source_(src) { + explicit DiagMessage(android::StringPiece src) : source_(src) { } explicit DiagMessage(const Source& src) : source_(src) { @@ -61,7 +61,7 @@ struct DiagMessage { template <> inline DiagMessage& DiagMessage::operator<<(const ::std::u16string& value) { - message_ << android::StringPiece16(value); + message_ << value; return *this; } diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h index a1cbbbf2271b..f173e6ef9c16 100644 --- a/libs/androidfw/include/androidfw/Idmap.h +++ b/libs/androidfw/include/androidfw/Idmap.h @@ -157,8 +157,7 @@ class IdmapResMap { class LoadedIdmap { public: // Loads an IDMAP from a chunk of memory. Returns nullptr if the IDMAP data was malformed. - static std::unique_ptr<LoadedIdmap> Load(const StringPiece& idmap_path, - const StringPiece& idmap_data); + static std::unique_ptr<LoadedIdmap> Load(StringPiece idmap_path, StringPiece idmap_data); // Returns the path to the IDMAP. std::string_view IdmapPath() const { diff --git a/libs/androidfw/include/androidfw/Locale.h b/libs/androidfw/include/androidfw/Locale.h index 484ed79a8efd..8934bed098fe 100644 --- a/libs/androidfw/include/androidfw/Locale.h +++ b/libs/androidfw/include/androidfw/Locale.h @@ -39,10 +39,10 @@ struct LocaleValue { /** * Initialize this LocaleValue from a config string. */ - bool InitFromFilterString(const android::StringPiece& config); + bool InitFromFilterString(android::StringPiece config); // Initializes this LocaleValue from a BCP-47 locale tag. - bool InitFromBcp47Tag(const android::StringPiece& bcp47tag); + bool InitFromBcp47Tag(android::StringPiece bcp47tag); /** * Initialize this LocaleValue from parts of a vector. @@ -70,7 +70,7 @@ struct LocaleValue { inline bool operator>(const LocaleValue& o) const; private: - bool InitFromBcp47TagImpl(const android::StringPiece& bcp47tag, const char separator); + bool InitFromBcp47TagImpl(android::StringPiece bcp47tag, const char separator); void set_language(const char* language); void set_region(const char* language); diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h index b2b95b72e0e0..52321dad8a5a 100644 --- a/libs/androidfw/include/androidfw/ResourceTypes.h +++ b/libs/androidfw/include/androidfw/ResourceTypes.h @@ -21,6 +21,7 @@ #define _LIBS_UTILS_RESOURCE_TYPES_H #include <android-base/expected.h> +#include <android-base/unique_fd.h> #include <androidfw/Asset.h> #include <androidfw/Errors.h> @@ -58,6 +59,8 @@ constexpr const uint32_t kFabricatedOverlayCurrentVersion = 3; // Returns whether or not the path represents a fabricated overlay. bool IsFabricatedOverlay(const std::string& path); +bool IsFabricatedOverlay(const char* path); +bool IsFabricatedOverlay(android::base::borrowed_fd fd); /** * In C++11, char16_t is defined as *at least* 16 bits. We do a lot of @@ -1882,7 +1885,10 @@ public: void addMapping(uint8_t buildPackageId, uint8_t runtimePackageId); - void addAlias(uint32_t stagedId, uint32_t finalizedId); + using AliasMap = std::vector<std::pair<uint32_t, uint32_t>>; + void setAliases(AliasMap aliases) { + mAliasId = std::move(aliases); + } // Returns whether or not the value must be looked up. bool requiresLookup(const Res_value* value) const; @@ -1896,12 +1902,12 @@ public: return mEntries; } -private: - uint8_t mAssignedPackageId; - uint8_t mLookupTable[256]; - KeyedVector<String16, uint8_t> mEntries; - bool mAppAsLib; - std::map<uint32_t, uint32_t> mAliasId; + private: + uint8_t mLookupTable[256]; + uint8_t mAssignedPackageId; + bool mAppAsLib; + KeyedVector<String16, uint8_t> mEntries; + AliasMap mAliasId; }; bool U16StringToInt(const char16_t* s, size_t len, Res_value* outValue); diff --git a/libs/androidfw/include/androidfw/ResourceUtils.h b/libs/androidfw/include/androidfw/ResourceUtils.h index bd1c44033b88..2d90a526dfbe 100644 --- a/libs/androidfw/include/androidfw/ResourceUtils.h +++ b/libs/androidfw/include/androidfw/ResourceUtils.h @@ -25,14 +25,14 @@ namespace android { // Extracts the package, type, and name from a string of the format: [[package:]type/]name // Validation must be performed on each extracted piece. // Returns false if there was a syntax error. -bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, StringPiece* out_type, +bool ExtractResourceName(StringPiece str, StringPiece* out_package, StringPiece* out_type, StringPiece* out_entry); // Convert a type_string_ref, entry_string_ref, and package to AssetManager2::ResourceName. // Useful for getting resource name without re-running AssetManager2::FindEntry searches. base::expected<AssetManager2::ResourceName, NullOrIOError> ToResourceName( const StringPoolRef& type_string_ref, const StringPoolRef& entry_string_ref, - const StringPiece& package_name); + StringPiece package_name); // Formats a ResourceName to "package:type/entry_name". std::string ToFormattedResourceString(const AssetManager2::ResourceName& resource_name); diff --git a/libs/androidfw/include/androidfw/Source.h b/libs/androidfw/include/androidfw/Source.h index 0421a91d3eba..ddc9ba421101 100644 --- a/libs/androidfw/include/androidfw/Source.h +++ b/libs/androidfw/include/androidfw/Source.h @@ -34,15 +34,14 @@ struct Source { Source() = default; - inline Source(const android::StringPiece& path) : path(path.to_string()) { // NOLINT(implicit) + inline Source(android::StringPiece path) : path(path) { // NOLINT(implicit) } - inline Source(const android::StringPiece& path, const android::StringPiece& archive) - : path(path.to_string()), archive(archive.to_string()) { + inline Source(android::StringPiece path, android::StringPiece archive) + : path(path), archive(archive) { } - inline Source(const android::StringPiece& path, size_t line) - : path(path.to_string()), line(line) { + inline Source(android::StringPiece path, size_t line) : path(path), line(line) { } inline Source WithLine(size_t line) const { diff --git a/libs/androidfw/include/androidfw/StringPiece.h b/libs/androidfw/include/androidfw/StringPiece.h index fac2fa4fa575..f6cc64edfb5a 100644 --- a/libs/androidfw/include/androidfw/StringPiece.h +++ b/libs/androidfw/include/androidfw/StringPiece.h @@ -19,307 +19,37 @@ #include <ostream> #include <string> +#include <string_view> -#include "utils/JenkinsHash.h" #include "utils/Unicode.h" namespace android { -// Read only wrapper around basic C strings. Prevents excessive copying. -// StringPiece does not own the data it is wrapping. The lifetime of the underlying -// data must outlive this StringPiece. -// -// WARNING: When creating from std::basic_string<>, moving the original -// std::basic_string<> will invalidate the data held in a BasicStringPiece<>. -// BasicStringPiece<> should only be used transitively. -// -// NOTE: When creating an std::pair<StringPiece, T> using std::make_pair(), -// passing an std::string will first copy the string, then create a StringPiece -// on the copy, which is then immediately destroyed. -// Instead, create a StringPiece explicitly: -// -// std::string my_string = "foo"; -// std::make_pair<StringPiece, T>(StringPiece(my_string), ...); -template <typename TChar> -class BasicStringPiece { - public: - using const_iterator = const TChar*; - using difference_type = size_t; - using size_type = size_t; - - // End of string marker. - constexpr static const size_t npos = static_cast<size_t>(-1); - - BasicStringPiece(); - BasicStringPiece(const BasicStringPiece<TChar>& str); - BasicStringPiece(const std::basic_string<TChar>& str); // NOLINT(google-explicit-constructor) - BasicStringPiece(const TChar* str); // NOLINT(google-explicit-constructor) - BasicStringPiece(const TChar* str, size_t len); - - BasicStringPiece<TChar>& operator=(const BasicStringPiece<TChar>& rhs); - BasicStringPiece<TChar>& assign(const TChar* str, size_t len); - - BasicStringPiece<TChar> substr(size_t start, size_t len = npos) const; - BasicStringPiece<TChar> substr(BasicStringPiece<TChar>::const_iterator begin, - BasicStringPiece<TChar>::const_iterator end) const; - - const TChar* data() const; - size_t length() const; - size_t size() const; - bool empty() const; - std::basic_string<TChar> to_string() const; - - bool contains(const BasicStringPiece<TChar>& rhs) const; - int compare(const BasicStringPiece<TChar>& rhs) const; - bool operator<(const BasicStringPiece<TChar>& rhs) const; - bool operator>(const BasicStringPiece<TChar>& rhs) const; - bool operator==(const BasicStringPiece<TChar>& rhs) const; - bool operator!=(const BasicStringPiece<TChar>& rhs) const; - - const_iterator begin() const; - const_iterator end() const; - - private: - const TChar* data_; - size_t length_; -}; +template <class T> +using BasicStringPiece = std::basic_string_view<T>; using StringPiece = BasicStringPiece<char>; using StringPiece16 = BasicStringPiece<char16_t>; -// -// BasicStringPiece implementation. -// - -template <typename TChar> -constexpr const size_t BasicStringPiece<TChar>::npos; - -template <typename TChar> -inline BasicStringPiece<TChar>::BasicStringPiece() : data_(nullptr), length_(0) {} - -template <typename TChar> -inline BasicStringPiece<TChar>::BasicStringPiece(const BasicStringPiece<TChar>& str) - : data_(str.data_), length_(str.length_) {} - -template <typename TChar> -inline BasicStringPiece<TChar>::BasicStringPiece(const std::basic_string<TChar>& str) - : data_(str.data()), length_(str.length()) {} - -template <> -inline BasicStringPiece<char>::BasicStringPiece(const char* str) - : data_(str), length_(str != nullptr ? strlen(str) : 0) {} - -template <> -inline BasicStringPiece<char16_t>::BasicStringPiece(const char16_t* str) - : data_(str), length_(str != nullptr ? strlen16(str) : 0) {} - -template <typename TChar> -inline BasicStringPiece<TChar>::BasicStringPiece(const TChar* str, size_t len) - : data_(str), length_(len) {} - -template <typename TChar> -inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::operator=( - const BasicStringPiece<TChar>& rhs) { - data_ = rhs.data_; - length_ = rhs.length_; - return *this; -} - -template <typename TChar> -inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::assign(const TChar* str, size_t len) { - data_ = str; - length_ = len; - return *this; -} - -template <typename TChar> -inline BasicStringPiece<TChar> BasicStringPiece<TChar>::substr(size_t start, size_t len) const { - if (len == npos) { - len = length_ - start; - } - - if (start > length_ || start + len > length_) { - return BasicStringPiece<TChar>(); - } - return BasicStringPiece<TChar>(data_ + start, len); -} - -template <typename TChar> -inline BasicStringPiece<TChar> BasicStringPiece<TChar>::substr( - BasicStringPiece<TChar>::const_iterator begin, - BasicStringPiece<TChar>::const_iterator end) const { - return BasicStringPiece<TChar>(begin, end - begin); -} - -template <typename TChar> -inline const TChar* BasicStringPiece<TChar>::data() const { - return data_; -} - -template <typename TChar> -inline size_t BasicStringPiece<TChar>::length() const { - return length_; -} - -template <typename TChar> -inline size_t BasicStringPiece<TChar>::size() const { - return length_; -} - -template <typename TChar> -inline bool BasicStringPiece<TChar>::empty() const { - return length_ == 0; -} - -template <typename TChar> -inline std::basic_string<TChar> BasicStringPiece<TChar>::to_string() const { - return std::basic_string<TChar>(data_, length_); -} - -template <> -inline bool BasicStringPiece<char>::contains(const BasicStringPiece<char>& rhs) const { - if (!data_ || !rhs.data_) { - return false; - } - if (rhs.length_ > length_) { - return false; - } - return strstr(data_, rhs.data_) != nullptr; -} - -template <> -inline int BasicStringPiece<char>::compare(const BasicStringPiece<char>& rhs) const { - const char nullStr = '\0'; - const char* b1 = data_ != nullptr ? data_ : &nullStr; - const char* e1 = b1 + length_; - const char* b2 = rhs.data_ != nullptr ? rhs.data_ : &nullStr; - const char* e2 = b2 + rhs.length_; - - while (b1 < e1 && b2 < e2) { - const int d = static_cast<int>(*b1++) - static_cast<int>(*b2++); - if (d) { - return d; - } - } - return static_cast<int>(length_ - rhs.length_); -} - -inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<char16_t>& str) { - const ssize_t result_len = utf16_to_utf8_length(str.data(), str.size()); - if (result_len < 0) { - // Empty string. - return out; - } - - std::string result; - result.resize(static_cast<size_t>(result_len)); - utf16_to_utf8(str.data(), str.length(), &*result.begin(), static_cast<size_t>(result_len) + 1); - return out << result; -} - -template <> -inline bool BasicStringPiece<char16_t>::contains(const BasicStringPiece<char16_t>& rhs) const { - if (!data_ || !rhs.data_) { - return false; - } - if (rhs.length_ > length_) { - return false; - } - return strstr16(data_, rhs.data_) != nullptr; -} - -template <> -inline int BasicStringPiece<char16_t>::compare(const BasicStringPiece<char16_t>& rhs) const { - const char16_t nullStr = u'\0'; - const char16_t* b1 = data_ != nullptr ? data_ : &nullStr; - const char16_t* b2 = rhs.data_ != nullptr ? rhs.data_ : &nullStr; - return strzcmp16(b1, length_, b2, rhs.length_); -} - -template <typename TChar> -inline bool BasicStringPiece<TChar>::operator<(const BasicStringPiece<TChar>& rhs) const { - return compare(rhs) < 0; -} - -template <typename TChar> -inline bool BasicStringPiece<TChar>::operator>(const BasicStringPiece<TChar>& rhs) const { - return compare(rhs) > 0; -} - -template <typename TChar> -inline bool BasicStringPiece<TChar>::operator==(const BasicStringPiece<TChar>& rhs) const { - return compare(rhs) == 0; -} - -template <typename TChar> -inline bool BasicStringPiece<TChar>::operator!=(const BasicStringPiece<TChar>& rhs) const { - return compare(rhs) != 0; -} - -template <typename TChar> -inline typename BasicStringPiece<TChar>::const_iterator BasicStringPiece<TChar>::begin() const { - return data_; -} - -template <typename TChar> -inline typename BasicStringPiece<TChar>::const_iterator BasicStringPiece<TChar>::end() const { - return data_ + length_; -} - -template <typename TChar> -inline bool operator==(const TChar* lhs, const BasicStringPiece<TChar>& rhs) { - return BasicStringPiece<TChar>(lhs) == rhs; -} - -template <typename TChar> -inline bool operator!=(const TChar* lhs, const BasicStringPiece<TChar>& rhs) { - return BasicStringPiece<TChar>(lhs) != rhs; -} - -inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<char>& str) { - return out.write(str.data(), str.size()); -} - -template <typename TChar> -inline ::std::basic_string<TChar>& operator+=(::std::basic_string<TChar>& lhs, - const BasicStringPiece<TChar>& rhs) { - return lhs.append(rhs.data(), rhs.size()); -} - -template <typename TChar> -inline bool operator==(const ::std::basic_string<TChar>& lhs, const BasicStringPiece<TChar>& rhs) { - return BasicStringPiece<TChar>(lhs) == rhs; -} - -template <typename TChar> -inline bool operator!=(const ::std::basic_string<TChar>& lhs, const BasicStringPiece<TChar>& rhs) { - return BasicStringPiece<TChar>(lhs) != rhs; -} - } // namespace android -inline ::std::ostream& operator<<(::std::ostream& out, const std::u16string& str) { +namespace std { + +inline ::std::ostream& operator<<(::std::ostream& out, ::std::u16string_view str) { ssize_t utf8_len = utf16_to_utf8_length(str.data(), str.size()); if (utf8_len < 0) { - return out << "???"; + return out; // empty } std::string utf8; utf8.resize(static_cast<size_t>(utf8_len)); - utf16_to_utf8(str.data(), str.size(), &*utf8.begin(), utf8_len + 1); + utf16_to_utf8(str.data(), str.size(), utf8.data(), utf8_len + 1); return out << utf8; } -namespace std { - -template <typename TChar> -struct hash<android::BasicStringPiece<TChar>> { - size_t operator()(const android::BasicStringPiece<TChar>& str) const { - uint32_t hashCode = android::JenkinsHashMixBytes( - 0, reinterpret_cast<const uint8_t*>(str.data()), sizeof(TChar) * str.size()); - return static_cast<size_t>(hashCode); - } -}; +inline ::std::ostream& operator<<(::std::ostream& out, const ::std::u16string& str) { + return out << std::u16string_view(str); +} } // namespace std diff --git a/libs/androidfw/include/androidfw/StringPool.h b/libs/androidfw/include/androidfw/StringPool.h index 25174d8fe3c8..0190ab57bf23 100644 --- a/libs/androidfw/include/androidfw/StringPool.h +++ b/libs/androidfw/include/androidfw/StringPool.h @@ -167,11 +167,11 @@ class StringPool { // Adds a string to the pool, unless it already exists. Returns a reference to the string in the // pool. - Ref MakeRef(const android::StringPiece& str); + Ref MakeRef(android::StringPiece str); // Adds a string to the pool, unless it already exists, with a context object that can be used // when sorting the string pool. Returns a reference to the string in the pool. - Ref MakeRef(const android::StringPiece& str, const Context& context); + Ref MakeRef(android::StringPiece str, const Context& context); // Adds a string from another string pool. Returns a reference to the string in the string pool. Ref MakeRef(const Ref& ref); @@ -215,7 +215,7 @@ class StringPool { static bool Flatten(BigBuffer* out, const StringPool& pool, bool utf8, IDiagnostics* diag); - Ref MakeRefImpl(const android::StringPiece& str, const Context& context, bool unique); + Ref MakeRefImpl(android::StringPiece str, const Context& context, bool unique); void ReAssignIndices(); std::vector<std::unique_ptr<Entry>> strings_; diff --git a/libs/androidfw/include/androidfw/Util.h b/libs/androidfw/include/androidfw/Util.h index 1bbc7f5716bc..ae7c65f2d4e1 100644 --- a/libs/androidfw/include/androidfw/Util.h +++ b/libs/androidfw/include/androidfw/Util.h @@ -123,16 +123,16 @@ class unique_cptr { void ReadUtf16StringFromDevice(const uint16_t* src, size_t len, std::string* out); // Converts a UTF-8 string to a UTF-16 string. -std::u16string Utf8ToUtf16(const StringPiece& utf8); +std::u16string Utf8ToUtf16(StringPiece utf8); // Converts a UTF-16 string to a UTF-8 string. -std::string Utf16ToUtf8(const StringPiece16& utf16); +std::string Utf16ToUtf8(StringPiece16 utf16); // Converts a UTF8 string into Modified UTF8 -std::string Utf8ToModifiedUtf8(const std::string& utf8); +std::string Utf8ToModifiedUtf8(std::string_view utf8); // Converts a Modified UTF8 string into a UTF8 string -std::string ModifiedUtf8ToUtf8(const std::string& modified_utf8); +std::string ModifiedUtf8ToUtf8(std::string_view modified_utf8); inline uint16_t HostToDevice16(uint16_t value) { return htods(value); @@ -150,7 +150,7 @@ inline uint32_t DeviceToHost32(uint32_t value) { return dtohl(value); } -std::vector<std::string> SplitAndLowercase(const android::StringPiece& str, char sep); +std::vector<std::string> SplitAndLowercase(android::StringPiece str, char sep); template <typename T> inline bool IsFourByteAligned(const incfs::map_ptr<T>& data) { diff --git a/libs/androidfw/tests/AttributeResolution_bench.cpp b/libs/androidfw/tests/AttributeResolution_bench.cpp index ddd8ab820cb1..1c89c61c8f78 100644 --- a/libs/androidfw/tests/AttributeResolution_bench.cpp +++ b/libs/androidfw/tests/AttributeResolution_bench.cpp @@ -120,8 +120,8 @@ static void BM_ApplyStyleFramework(benchmark::State& state) { return; } - std::unique_ptr<Asset> asset = assetmanager.OpenNonAsset(layout_path->to_string(), value->cookie, - Asset::ACCESS_BUFFER); + std::unique_ptr<Asset> asset = + assetmanager.OpenNonAsset(std::string(*layout_path), value->cookie, Asset::ACCESS_BUFFER); if (asset == nullptr) { state.SkipWithError("failed to load layout"); return; diff --git a/libs/androidfw/tests/ConfigDescription_test.cpp b/libs/androidfw/tests/ConfigDescription_test.cpp index ce7f8054e2ca..8fed0a4d22fc 100644 --- a/libs/androidfw/tests/ConfigDescription_test.cpp +++ b/libs/androidfw/tests/ConfigDescription_test.cpp @@ -25,8 +25,8 @@ namespace android { -static ::testing::AssertionResult TestParse( - const StringPiece& input, ConfigDescription* config = nullptr) { +static ::testing::AssertionResult TestParse(StringPiece input, + ConfigDescription* config = nullptr) { if (ConfigDescription::Parse(input, config)) { return ::testing::AssertionSuccess() << input << " was successfully parsed"; } @@ -138,7 +138,7 @@ TEST(ConfigDescriptionTest, ParseVrAttribute) { EXPECT_EQ(std::string("vrheadset-v26"), config.toString().string()); } -static inline ConfigDescription ParseConfigOrDie(const android::StringPiece& str) { +static inline ConfigDescription ParseConfigOrDie(android::StringPiece str) { ConfigDescription config; CHECK(ConfigDescription::Parse(str, &config)) << "invalid configuration: " << str; return config; diff --git a/libs/androidfw/tests/StringPiece_test.cpp b/libs/androidfw/tests/StringPiece_test.cpp index 316a5c1bf40e..822e527253df 100644 --- a/libs/androidfw/tests/StringPiece_test.cpp +++ b/libs/androidfw/tests/StringPiece_test.cpp @@ -60,36 +60,4 @@ TEST(StringPieceTest, PiecesHaveCorrectSortOrderUtf8) { EXPECT_TRUE(StringPiece(car) > banana); } -TEST(StringPieceTest, ContainsOtherStringPiece) { - StringPiece text("I am a leaf on the wind."); - StringPiece start_needle("I am"); - StringPiece end_needle("wind."); - StringPiece middle_needle("leaf"); - StringPiece empty_needle(""); - StringPiece missing_needle("soar"); - StringPiece long_needle("This string is longer than the text."); - - EXPECT_TRUE(text.contains(start_needle)); - EXPECT_TRUE(text.contains(end_needle)); - EXPECT_TRUE(text.contains(middle_needle)); - EXPECT_TRUE(text.contains(empty_needle)); - EXPECT_FALSE(text.contains(missing_needle)); - EXPECT_FALSE(text.contains(long_needle)); - - StringPiece16 text16(u"I am a leaf on the wind."); - StringPiece16 start_needle16(u"I am"); - StringPiece16 end_needle16(u"wind."); - StringPiece16 middle_needle16(u"leaf"); - StringPiece16 empty_needle16(u""); - StringPiece16 missing_needle16(u"soar"); - StringPiece16 long_needle16(u"This string is longer than the text."); - - EXPECT_TRUE(text16.contains(start_needle16)); - EXPECT_TRUE(text16.contains(end_needle16)); - EXPECT_TRUE(text16.contains(middle_needle16)); - EXPECT_TRUE(text16.contains(empty_needle16)); - EXPECT_FALSE(text16.contains(missing_needle16)); - EXPECT_FALSE(text16.contains(long_needle16)); -} - } // namespace android diff --git a/libs/androidfw/tests/StringPool_test.cpp b/libs/androidfw/tests/StringPool_test.cpp index 047d45785409..0e0acae165d9 100644 --- a/libs/androidfw/tests/StringPool_test.cpp +++ b/libs/androidfw/tests/StringPool_test.cpp @@ -321,15 +321,15 @@ TEST(StringPoolTest, ModifiedUTF8) { ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR); auto str = test.string8At(0); ASSERT_TRUE(str.has_value()); - EXPECT_THAT(str->to_string(), Eq("\xED\xA0\x81\xED\xB0\x80")); + EXPECT_THAT(*str, Eq("\xED\xA0\x81\xED\xB0\x80")); str = test.string8At(1); ASSERT_TRUE(str.has_value()); - EXPECT_THAT(str->to_string(), Eq("foo \xED\xA0\x81\xED\xB0\xB7 bar")); + EXPECT_THAT(*str, Eq("foo \xED\xA0\x81\xED\xB0\xB7 bar")); str = test.string8At(2); ASSERT_TRUE(str.has_value()); - EXPECT_THAT(str->to_string(), Eq("\xED\xA0\x81\xED\xB0\x80\xED\xA0\x81\xED\xB0\xB7")); + EXPECT_THAT(*str, Eq("\xED\xA0\x81\xED\xB0\x80\xED\xA0\x81\xED\xB0\xB7")); // Check that retrieving the strings returns the original UTF-8 character bytes EXPECT_THAT(android::util::GetString(test, 0), Eq("\xF0\x90\x90\x80")); diff --git a/libs/hwui/jni/Mesh.cpp b/libs/hwui/jni/Mesh.cpp new file mode 100644 index 000000000000..6dba6c1a966b --- /dev/null +++ b/libs/hwui/jni/Mesh.cpp @@ -0,0 +1,209 @@ +/* + * 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. + */ + +#include <GLES/gl.h> +#include <Mesh.h> +#include <SkMesh.h> + +#include "GraphicsJNI.h" +#include "graphics_jni_helpers.h" + +namespace android { + +sk_sp<SkMesh::VertexBuffer> genVertexBuffer(JNIEnv* env, jobject buffer, int size, + jboolean isDirect) { + auto buff = ScopedJavaNioBuffer(env, buffer, size, isDirect); + auto vertexBuffer = SkMesh::MakeVertexBuffer(nullptr, buff.data(), size); + return vertexBuffer; +} + +sk_sp<SkMesh::IndexBuffer> genIndexBuffer(JNIEnv* env, jobject buffer, int size, + jboolean isDirect) { + auto buff = ScopedJavaNioBuffer(env, buffer, size, isDirect); + auto indexBuffer = SkMesh::MakeIndexBuffer(nullptr, buff.data(), size); + return indexBuffer; +} + +static jlong make(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer, + jboolean isDirect, jint vertexCount, jint vertexOffset, jint left, jint top, + jint right, jint bottom) { + auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec)); + sk_sp<SkMesh::VertexBuffer> skVertexBuffer = + genVertexBuffer(env, vertexBuffer, skMeshSpec->attributes().size_bytes(), isDirect); + auto skRect = SkRect::MakeLTRB(left, top, right, bottom); + auto mesh = SkMesh::Make(skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount, + vertexOffset, nullptr, skRect); + auto meshPtr = std::make_unique<MeshWrapper>(MeshWrapper{mesh, MeshUniformBuilder(skMeshSpec)}); + return reinterpret_cast<jlong>(meshPtr.release()); +} + +static jlong makeIndexed(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer, + jboolean isVertexDirect, jint vertexCount, jint vertexOffset, + jobject indexBuffer, jboolean isIndexDirect, jint indexCount, + jint indexOffset, jint left, jint top, jint right, jint bottom) { + auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec)); + sk_sp<SkMesh::VertexBuffer> skVertexBuffer = genVertexBuffer( + env, vertexBuffer, skMeshSpec->attributes().size_bytes(), isVertexDirect); + sk_sp<SkMesh::IndexBuffer> skIndexBuffer = + genIndexBuffer(env, indexBuffer, indexCount * gIndexByteSize, isIndexDirect); + auto skRect = SkRect::MakeLTRB(left, top, right, bottom); + auto mesh = SkMesh::MakeIndexed(skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount, + vertexOffset, skIndexBuffer, indexCount, indexOffset, nullptr, + skRect); + auto meshPtr = std::make_unique<MeshWrapper>(MeshWrapper{mesh, MeshUniformBuilder(skMeshSpec)}); + return reinterpret_cast<jlong>(meshPtr.release()); +} + +static void updateMesh(JNIEnv* env, jobject, jlong meshWrapper, jboolean indexed) { + auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper); + auto mesh = wrapper->mesh; + if (indexed) { + wrapper->mesh = SkMesh::MakeIndexed( + sk_ref_sp(mesh.spec()), mesh.mode(), sk_ref_sp(mesh.vertexBuffer()), + mesh.vertexCount(), mesh.vertexOffset(), sk_ref_sp(mesh.indexBuffer()), + mesh.indexCount(), mesh.indexOffset(), wrapper->builder.fUniforms, mesh.bounds()); + } else { + wrapper->mesh = SkMesh::Make( + sk_ref_sp(mesh.spec()), mesh.mode(), sk_ref_sp(mesh.vertexBuffer()), + mesh.vertexCount(), mesh.vertexOffset(), wrapper->builder.fUniforms, mesh.bounds()); + } +} + +static inline int ThrowIAEFmt(JNIEnv* env, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + int ret = jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", fmt, args); + va_end(args); + return ret; +} + +static bool isIntUniformType(const SkRuntimeEffect::Uniform::Type& type) { + switch (type) { + case SkRuntimeEffect::Uniform::Type::kFloat: + case SkRuntimeEffect::Uniform::Type::kFloat2: + case SkRuntimeEffect::Uniform::Type::kFloat3: + case SkRuntimeEffect::Uniform::Type::kFloat4: + case SkRuntimeEffect::Uniform::Type::kFloat2x2: + case SkRuntimeEffect::Uniform::Type::kFloat3x3: + case SkRuntimeEffect::Uniform::Type::kFloat4x4: + return false; + case SkRuntimeEffect::Uniform::Type::kInt: + case SkRuntimeEffect::Uniform::Type::kInt2: + case SkRuntimeEffect::Uniform::Type::kInt3: + case SkRuntimeEffect::Uniform::Type::kInt4: + return true; + } +} + +static void nativeUpdateFloatUniforms(JNIEnv* env, MeshUniformBuilder* builder, + const char* uniformName, const float values[], int count, + bool isColor) { + MeshUniformBuilder::MeshUniform uniform = builder->uniform(uniformName); + if (uniform.fVar == nullptr) { + ThrowIAEFmt(env, "unable to find uniform named %s", uniformName); + } else if (isColor != ((uniform.fVar->flags & SkRuntimeEffect::Uniform::kColor_Flag) != 0)) { + if (isColor) { + jniThrowExceptionFmt( + env, "java/lang/IllegalArgumentException", + "attempting to set a color uniform using the non-color specific APIs: %s %x", + uniformName, uniform.fVar->flags); + } else { + ThrowIAEFmt(env, + "attempting to set a non-color uniform using the setColorUniform APIs: %s", + uniformName); + } + } else if (isIntUniformType(uniform.fVar->type)) { + ThrowIAEFmt(env, "attempting to set a int uniform using the setUniform APIs: %s", + uniformName); + } else if (!uniform.set<float>(values, count)) { + ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]", + uniform.fVar->sizeInBytes(), sizeof(float) * count); + } +} + +static void updateFloatUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring uniformName, + jfloat value1, jfloat value2, jfloat value3, jfloat value4, + jint count) { + auto* builder = reinterpret_cast<MeshUniformBuilder*>(uniBuilder); + ScopedUtfChars name(env, uniformName); + const float values[4] = {value1, value2, value3, value4}; + nativeUpdateFloatUniforms(env, builder, name.c_str(), values, count, false); +} + +static void updateFloatArrayUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring jUniformName, + jfloatArray jvalues, jboolean isColor) { + auto builder = reinterpret_cast<MeshUniformBuilder*>(uniBuilder); + ScopedUtfChars name(env, jUniformName); + AutoJavaFloatArray autoValues(env, jvalues, 0, kRO_JNIAccess); + nativeUpdateFloatUniforms(env, builder, name.c_str(), autoValues.ptr(), autoValues.length(), + isColor); +} + +static void nativeUpdateIntUniforms(JNIEnv* env, MeshUniformBuilder* builder, + const char* uniformName, const int values[], int count) { + MeshUniformBuilder::MeshUniform uniform = builder->uniform(uniformName); + if (uniform.fVar == nullptr) { + ThrowIAEFmt(env, "unable to find uniform named %s", uniformName); + } else if (!isIntUniformType(uniform.fVar->type)) { + ThrowIAEFmt(env, "attempting to set a non-int uniform using the setIntUniform APIs: %s", + uniformName); + } else if (!uniform.set<int>(values, count)) { + ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]", + uniform.fVar->sizeInBytes(), sizeof(float) * count); + } +} + +static void updateIntUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring uniformName, + jint value1, jint value2, jint value3, jint value4, jint count) { + auto builder = reinterpret_cast<MeshUniformBuilder*>(uniBuilder); + ScopedUtfChars name(env, uniformName); + const int values[4] = {value1, value2, value3, value4}; + nativeUpdateIntUniforms(env, builder, name.c_str(), values, count); +} + +static void updateIntArrayUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring uniformName, + jintArray values) { + auto builder = reinterpret_cast<MeshUniformBuilder*>(uniBuilder); + ScopedUtfChars name(env, uniformName); + AutoJavaIntArray autoValues(env, values, 0); + nativeUpdateIntUniforms(env, builder, name.c_str(), autoValues.ptr(), autoValues.length()); +} + +static void MeshWrapper_destroy(MeshWrapper* wrapper) { + delete wrapper; +} + +static jlong getMeshFinalizer(JNIEnv*, jobject) { + return static_cast<jlong>(reinterpret_cast<uintptr_t>(&MeshWrapper_destroy)); +} + +static const JNINativeMethod gMeshMethods[] = { + {"nativeGetFinalizer", "()J", (void*)getMeshFinalizer}, + {"nativeMake", "(JILjava/nio/Buffer;ZIIIIII)J", (void*)make}, + {"nativeMakeIndexed", "(JILjava/nio/Buffer;ZIILjava/nio/ShortBuffer;ZIIIIII)J", + (void*)makeIndexed}, + {"nativeUpdateMesh", "(JZ)V", (void*)updateMesh}, + {"nativeUpdateUniforms", "(JLjava/lang/String;[FZ)V", (void*)updateFloatArrayUniforms}, + {"nativeUpdateUniforms", "(JLjava/lang/String;FFFFI)V", (void*)updateFloatUniforms}, + {"nativeUpdateUniforms", "(JLjava/lang/String;[I)V", (void*)updateIntArrayUniforms}, + {"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V", (void*)updateIntUniforms}}; + +int register_android_graphics_Mesh(JNIEnv* env) { + android::RegisterMethodsOrDie(env, "android/graphics/Mesh", gMeshMethods, NELEM(gMeshMethods)); + return 0; +} + +} // namespace android diff --git a/libs/hwui/jni/Mesh.h b/libs/hwui/jni/Mesh.h new file mode 100644 index 000000000000..aa014a5e4f61 --- /dev/null +++ b/libs/hwui/jni/Mesh.h @@ -0,0 +1,261 @@ +/* + * 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. + */ + +#ifndef FRAMEWORKS_BASE_LIBS_HWUI_JNI_MESH_H_ +#define FRAMEWORKS_BASE_LIBS_HWUI_JNI_MESH_H_ + +#include <SkMesh.h> +#include <jni.h> + +#include <utility> + +#include "graphics_jni_helpers.h" + +#define gIndexByteSize 2 + +// A smart pointer that provides read only access to Java.nio.Buffer. This handles both +// direct and indrect buffers, allowing access to the underlying data in both +// situations. If passed a null buffer, we will throw NullPointerException, +// and c_data will return nullptr. +// +// This class draws from com_google_android_gles_jni_GLImpl.cpp for Buffer to void * +// conversion. +class ScopedJavaNioBuffer { +public: + ScopedJavaNioBuffer(JNIEnv* env, jobject buffer, jint size, jboolean isDirect) + : mEnv(env), mBuffer(buffer) { + if (buffer == nullptr) { + mDataBase = nullptr; + mData = nullptr; + jniThrowNullPointerException(env); + } else { + mArray = (jarray) nullptr; + if (isDirect) { + mData = getDirectBufferPointer(mEnv, mBuffer); + } else { + mData = setIndirectData(size); + } + } + } + + ScopedJavaNioBuffer(ScopedJavaNioBuffer&& rhs) noexcept { *this = std::move(rhs); } + + ~ScopedJavaNioBuffer() { reset(); } + + void reset() { + if (mDataBase) { + releasePointer(mEnv, mArray, mDataBase, JNI_FALSE); + mDataBase = nullptr; + } + } + + ScopedJavaNioBuffer& operator=(ScopedJavaNioBuffer&& rhs) noexcept { + if (this != &rhs) { + reset(); + + mEnv = rhs.mEnv; + mBuffer = rhs.mBuffer; + mDataBase = rhs.mDataBase; + mData = rhs.mData; + mArray = rhs.mArray; + rhs.mEnv = nullptr; + rhs.mData = nullptr; + rhs.mBuffer = nullptr; + rhs.mArray = nullptr; + rhs.mDataBase = nullptr; + } + return *this; + } + + const void* data() const { return mData; } + +private: + /** + * This code is taken and modified from com_google_android_gles_jni_GLImpl.cpp to extract data + * from a java.nio.Buffer. + */ + void* getDirectBufferPointer(JNIEnv* env, jobject buffer) { + if (buffer == nullptr) { + return nullptr; + } + + jint position; + jint limit; + jint elementSizeShift; + jlong pointer; + pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift); + if (pointer == 0) { + jniThrowException(mEnv, "java/lang/IllegalArgumentException", + "Must use a native order direct Buffer"); + return nullptr; + } + pointer += position << elementSizeShift; + return reinterpret_cast<void*>(pointer); + } + + static void releasePointer(JNIEnv* env, jarray array, void* data, jboolean commit) { + env->ReleasePrimitiveArrayCritical(array, data, commit ? 0 : JNI_ABORT); + } + + static void* getPointer(JNIEnv* env, jobject buffer, jarray* array, jint* remaining, + jint* offset) { + jint position; + jint limit; + jint elementSizeShift; + + jlong pointer; + pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift); + *remaining = (limit - position) << elementSizeShift; + if (pointer != 0L) { + *array = nullptr; + pointer += position << elementSizeShift; + return reinterpret_cast<void*>(pointer); + } + + *array = jniGetNioBufferBaseArray(env, buffer); + *offset = jniGetNioBufferBaseArrayOffset(env, buffer); + return nullptr; + } + + /** + * This is a copy of + * static void android_glBufferData__IILjava_nio_Buffer_2I + * from com_google_android_gles_jni_GLImpl.cpp + */ + void* setIndirectData(jint size) { + jint exception; + const char* exceptionType; + const char* exceptionMessage; + jint bufferOffset = (jint)0; + jint remaining; + void* tempData; + + if (mBuffer) { + tempData = + (void*)getPointer(mEnv, mBuffer, (jarray*)&mArray, &remaining, &bufferOffset); + if (remaining < size) { + exception = 1; + exceptionType = "java/lang/IllegalArgumentException"; + exceptionMessage = "remaining() < size < needed"; + goto exit; + } + } + if (mBuffer && tempData == nullptr) { + mDataBase = (char*)mEnv->GetPrimitiveArrayCritical(mArray, (jboolean*)0); + tempData = (void*)(mDataBase + bufferOffset); + } + return tempData; + exit: + if (mArray) { + releasePointer(mEnv, mArray, (void*)(mDataBase), JNI_FALSE); + } + if (exception) { + jniThrowException(mEnv, exceptionType, exceptionMessage); + } + return nullptr; + } + + JNIEnv* mEnv; + + // Java Buffer data + void* mData; + jobject mBuffer; + + // Indirect Buffer Data + jarray mArray; + char* mDataBase; +}; + +class MeshUniformBuilder { +public: + struct MeshUniform { + template <typename T> + std::enable_if_t<std::is_trivially_copyable<T>::value, MeshUniform> operator=( + const T& val) { + if (!fVar) { + SkDEBUGFAIL("Assigning to missing variable"); + } else if (sizeof(val) != fVar->sizeInBytes()) { + SkDEBUGFAIL("Incorrect value size"); + } else { + memcpy(SkTAddOffset<void>(fOwner->writableUniformData(), fVar->offset), &val, + szeof(val)); + } + } + + MeshUniform& operator=(const SkMatrix& val) { + if (!fVar) { + SkDEBUGFAIL("Assigning to missing variable"); + } else if (fVar->sizeInBytes() != 9 * sizeof(float)) { + SkDEBUGFAIL("Incorrect value size"); + } else { + float* data = + SkTAddOffset<float>(fOwner->writableUniformData(), (ptrdiff_t)fVar->offset); + data[0] = val.get(0); + data[1] = val.get(3); + data[2] = val.get(6); + data[3] = val.get(1); + data[4] = val.get(4); + data[5] = val.get(7); + data[6] = val.get(2); + data[7] = val.get(5); + data[8] = val.get(8); + } + return *this; + } + + template <typename T> + bool set(const T val[], const int count) { + static_assert(std::is_trivially_copyable<T>::value, "Value must be trivial copyable"); + if (!fVar) { + SkDEBUGFAIL("Assigning to missing variable"); + return false; + } else if (sizeof(T) * count != fVar->sizeInBytes()) { + SkDEBUGFAIL("Incorrect value size"); + return false; + } else { + memcpy(SkTAddOffset<void>(fOwner->writableUniformData(), fVar->offset), val, + sizeof(T) * count); + } + return true; + } + + MeshUniformBuilder* fOwner; + const SkRuntimeEffect::Uniform* fVar; + }; + MeshUniform uniform(std::string_view name) { return {this, fMeshSpec->findUniform(name)}; } + + explicit MeshUniformBuilder(sk_sp<SkMeshSpecification> meshSpec) { + fMeshSpec = sk_sp(meshSpec); + } + + sk_sp<SkData> fUniforms; + +private: + void* writableUniformData() { + if (!fUniforms->unique()) { + fUniforms = SkData::MakeWithCopy(fUniforms->data(), fUniforms->size()); + } + return fUniforms->writable_data(); + } + + sk_sp<SkMeshSpecification> fMeshSpec; +}; + +struct MeshWrapper { + SkMesh mesh; + MeshUniformBuilder builder; +}; +#endif // FRAMEWORKS_BASE_LIBS_HWUI_JNI_MESH_H_ diff --git a/location/java/android/location/provider/LocationProviderBase.java b/location/java/android/location/provider/LocationProviderBase.java index 529edddf4cf0..5acec79a2d3b 100644 --- a/location/java/android/location/provider/LocationProviderBase.java +++ b/location/java/android/location/provider/LocationProviderBase.java @@ -101,6 +101,15 @@ public abstract class LocationProviderBase { public static final String ACTION_FUSED_PROVIDER = "com.android.location.service.FusedLocationProvider"; + /** + * The action the wrapping service should have in its intent filter to implement the + * {@link android.location.LocationManager#GPS_PROVIDER}. + * + * @hide + */ + public static final String ACTION_GNSS_PROVIDER = + "android.location.provider.action.GNSS_PROVIDER"; + final String mTag; final @Nullable String mAttributionTag; final IBinder mBinder; diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index f3931df03327..9c5313aa3261 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -7670,8 +7670,10 @@ public class AudioManager { * or video calls. This method can be used by voice or video chat applications to select a * different audio device than the one selected by default by the platform. * <p>The device selection is expressed as an {@link AudioDeviceInfo} among devices returned by - * {@link #getAvailableCommunicationDevices()}. - * The selection is active as long as the requesting application process lives, until + * {@link #getAvailableCommunicationDevices()}. Note that only devices in a sink role + * (AKA output devices, see {@link AudioDeviceInfo#isSink()}) can be specified. The matching + * source device is selected automatically by the platform. + * <p>The selection is active as long as the requesting application process lives, until * {@link #clearCommunicationDevice} is called or until the device is disconnected. * It is therefore important for applications to clear the request when a call ends or the * the requesting activity or service is stopped or destroyed. diff --git a/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java index 980f63b2ae6c..59a0f7b88937 100644 --- a/media/java/android/media/AudioPlaybackConfiguration.java +++ b/media/java/android/media/AudioPlaybackConfiguration.java @@ -221,13 +221,6 @@ public final class AudioPlaybackConfiguration implements Parcelable { /** * @hide - * Mute state used for the initial state and when API is accessed without permission. - */ - @SystemApi - @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) - public static final int MUTED_BY_UNKNOWN = -1; - /** - * @hide * Flag used when muted by master volume. */ @SystemApi @@ -317,7 +310,7 @@ public final class AudioPlaybackConfiguration implements Parcelable { mPlayerType = pic.mPlayerType; mClientUid = uid; mClientPid = pid; - mMutedState = MUTED_BY_UNKNOWN; + mMutedState = 0; mDeviceId = PLAYER_DEVICEID_INVALID; mPlayerState = PLAYER_STATE_IDLE; mPlayerAttr = pic.mAttributes; @@ -366,7 +359,7 @@ public final class AudioPlaybackConfiguration implements Parcelable { anonymCopy.mPlayerAttr = builder.build(); anonymCopy.mDeviceId = in.mDeviceId; // anonymized data - anonymCopy.mMutedState = MUTED_BY_UNKNOWN; + anonymCopy.mMutedState = 0; anonymCopy.mPlayerType = PLAYER_TYPE_UNKNOWN; anonymCopy.mClientUid = PLAYER_UPID_INVALID; anonymCopy.mClientPid = PLAYER_UPID_INVALID; @@ -435,14 +428,13 @@ public final class AudioPlaybackConfiguration implements Parcelable { @SystemApi @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean isMuted() { - return mMutedState != 0 && mMutedState != MUTED_BY_UNKNOWN; + return mMutedState != 0; } /** * @hide * Returns a bitmask expressing the mute state as a combination of MUTED_BY_* flags. - * <br>Note that if the mute state is not set the result will be {@link #MUTED_BY_UNKNOWN}. A - * value of 0 represents a player which is not muted. + * <br>A value of 0 corresponds to an unmuted player. * @return the mute state. */ @SystemApi @@ -602,11 +594,6 @@ public final class AudioPlaybackConfiguration implements Parcelable { } private boolean isMuteAffectingActiveState() { - if (mMutedState == MUTED_BY_UNKNOWN) { - // mute state is not set, therefore it will not affect the active state - return false; - } - return (mMutedState & MUTED_BY_CLIENT_VOLUME) != 0 || (mMutedState & MUTED_BY_VOLUME_SHAPER) != 0 || (mMutedState & MUTED_BY_APP_OPS) != 0; @@ -726,9 +713,7 @@ public final class AudioPlaybackConfiguration implements Parcelable { "/").append(mClientPid).append(" state:").append( toLogFriendlyPlayerState(mPlayerState)).append(" attr:").append(mPlayerAttr).append( " sessionId:").append(mSessionId).append(" mutedState:"); - if (mMutedState == MUTED_BY_UNKNOWN) { - apcToString.append("unknown "); - } else if (mMutedState == 0) { + if (mMutedState == 0) { apcToString.append("none "); } else { if ((mMutedState & MUTED_BY_MASTER) != 0) { diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index d51f1e1327bd..c2c752ed394e 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -1233,18 +1233,6 @@ public class AudioTrack extends PlayerBase } /** - * Sets the tuner configuration for the {@code AudioTrack}. - * - * The {@link AudioTrack.TunerConfiguration} consists of parameters obtained from - * the Android TV tuner API which indicate the audio content stream id and the - * synchronization id for the {@code AudioTrack}. - * - * @param tunerConfiguration obtained by {@link AudioTrack.TunerConfiguration.Builder}. - * @return the same Builder instance. - * @hide - */ - - /** * @hide * Sets the {@link AudioTrack} call redirection mode. * Used when creating an AudioTrack to inject audio to call uplink path. The mode diff --git a/media/java/android/media/Image.java b/media/java/android/media/Image.java index 8a03afb77942..d6fe68253be6 100644 --- a/media/java/android/media/Image.java +++ b/media/java/android/media/Image.java @@ -86,8 +86,10 @@ public abstract class Image implements AutoCloseable { * * <p> * The format is one of the values from - * {@link android.graphics.ImageFormat ImageFormat}. The mapping between the - * formats and the planes is as follows: + * {@link android.graphics.ImageFormat ImageFormat}, + * {@link android.graphics.PixelFormat PixelFormat}, or + * {@link android.hardware.HardwareBuffer HardwareBuffer}. The mapping between the + * formats and the planes is as follows (any formats not listed will have 1 plane): * </p> * * <table> @@ -171,15 +173,18 @@ public abstract class Image implements AutoCloseable { * </tr> * <tr> * <td>{@link android.graphics.ImageFormat#YCBCR_P010 YCBCR_P010}</td> - * <td>1</td> + * <td>3</td> * <td>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. + * followed by a Wx(H/2) Cb and Cr planes. Each sample is represented by a 16-bit + * little-endian value, with the lower 6 bits set to zero. Since this is guaranteed to be + * a semi-planar format, the Cb plane can also be treated as an interleaved Cb/Cr plane. * </td> * </tr> * </table> * * @see android.graphics.ImageFormat + * @see android.graphics.PixelFormat + * @see android.hardware.HardwareBuffer */ public abstract int getFormat(); diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java index 538e64cf095c..e78dc31646ca 100644 --- a/media/java/android/media/Ringtone.java +++ b/media/java/android/media/Ringtone.java @@ -89,6 +89,7 @@ public class Ringtone { .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .build(); + private boolean mPreferBuiltinDevice; // playback properties, use synchronized with mPlaybackSettingsLock private boolean mIsLooping = false; private float mVolume = 1.0f; @@ -157,6 +158,37 @@ public class Ringtone { } /** + * Finds the output device of type {@link AudioDeviceInfo#TYPE_BUILTIN_SPEAKER}. This device is + * the one on which outgoing audio for SIM calls is played. + * + * @param audioManager the audio manage. + * @return the {@link AudioDeviceInfo} corresponding to the builtin device, or {@code null} if + * none can be found. + */ + private AudioDeviceInfo getBuiltinDevice(AudioManager audioManager) { + AudioDeviceInfo[] deviceList = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS); + for (AudioDeviceInfo device : deviceList) { + if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) { + return device; + } + } + return null; + } + + /** + * Sets the preferred device of the ringtong playback to the built-in device. + * + * @hide + */ + public boolean preferBuiltinDevice(boolean enable) { + mPreferBuiltinDevice = enable; + if (mLocalPlayer == null) { + return true; + } + return mLocalPlayer.setPreferredDevice(getBuiltinDevice(mAudioManager)); + } + + /** * Creates a local media player for the ringtone using currently set attributes. * @return true if media player creation succeeded or is deferred, * false if it did not succeed and can't be tried remotely. @@ -174,6 +206,8 @@ public class Ringtone { try { mLocalPlayer.setDataSource(mContext, mUri); mLocalPlayer.setAudioAttributes(mAudioAttributes); + mLocalPlayer.setPreferredDevice( + mPreferBuiltinDevice ? getBuiltinDevice(mAudioManager) : null); synchronized (mPlaybackSettingsLock) { applyPlaybackProperties_sync(); } diff --git a/native/android/Android.bp b/native/android/Android.bp index 8594ba5ca2da..f1b1d79265de 100644 --- a/native/android/Android.bp +++ b/native/android/Android.bp @@ -34,6 +34,7 @@ ndk_library { cc_defaults { name: "libandroid_defaults", + cpp_std: "gnu++20", cflags: [ "-Wall", "-Werror", diff --git a/native/android/system_fonts.cpp b/native/android/system_fonts.cpp index 30d0c35bcbb0..fe3132e3d2a3 100644 --- a/native/android/system_fonts.cpp +++ b/native/android/system_fonts.cpp @@ -66,9 +66,6 @@ struct AFont { return mFilePath == o.mFilePath && mLocale == o.mLocale && mWeight == o.mWeight && mItalic == o.mItalic && mCollectionIndex == o.mCollectionIndex && mAxes == o.mAxes; } - - AFont() = default; - AFont(const AFont&) = default; }; struct FontHasher { diff --git a/packages/CredentialManager/res/drawable/ic_other_sign_in.xml b/packages/CredentialManager/res/drawable/ic_other_sign_in.xml new file mode 100644 index 000000000000..81501972d3ba --- /dev/null +++ b/packages/CredentialManager/res/drawable/ic_other_sign_in.xml @@ -0,0 +1,36 @@ +<!-- + ~ 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. + --> + +<vector + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + tools:ignore="VectorPath" + android:name="vector" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:name="path" + android:pathData="M 20 19 L 12 19 L 12 21 L 20 21 C 21.1 21 22 20.1 22 19 L 22 5 C 22 3.9 21.1 3 20 3 L 12 3 L 12 5 L 20 5 L 20 19 Z" + android:fillColor="#000" + android:strokeWidth="1"/> + <path + android:name="path_1" + android:pathData="M 12 7 L 10.6 8.4 L 13.2 11 L 8.85 11 C 8.42 9.55 7.09 8.5 5.5 8.5 C 3.57 8.5 2 10.07 2 12 C 2 13.93 3.57 15.5 5.5 15.5 C 7.09 15.5 8.42 14.45 8.85 13 L 13.2 13 L 10.6 15.6 L 12 17 L 17 12 L 12 7 Z M 5.5 13.5 C 4.67 13.5 4 12.83 4 12 C 4 11.17 4.67 10.5 5.5 10.5 C 6.33 10.5 7 11.17 7 12 C 7 12.83 6.33 13.5 5.5 13.5 Z" + android:fillColor="#000" + android:strokeWidth="1"/> +</vector>
\ No newline at end of file diff --git a/packages/CredentialManager/res/drawable/ic_password.xml b/packages/CredentialManager/res/drawable/ic_password.xml new file mode 100644 index 000000000000..bf3056a115c1 --- /dev/null +++ b/packages/CredentialManager/res/drawable/ic_password.xml @@ -0,0 +1,31 @@ +<!-- + ~ 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. + --> + +<vector + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + tools:ignore="VectorPath" + android:name="vector" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:name="path" + android:pathData="M 8.71 10.29 C 8.52 10.1 8.28 10 8 10 L 7.75 10 L 7.75 8.75 C 7.75 7.98 7.48 7.33 6.95 6.8 C 6.42 6.27 5.77 6 5 6 C 4.23 6 3.58 6.27 3.05 6.8 C 2.52 7.33 2.25 7.98 2.25 8.75 L 2.25 10 L 2 10 C 1.72 10 1.48 10.1 1.29 10.29 C 1.1 10.48 1 10.72 1 11 L 1 16 C 1 16.28 1.1 16.52 1.29 16.71 C 1.48 16.9 1.72 17 2 17 L 8 17 C 8.28 17 8.52 16.9 8.71 16.71 C 8.9 16.52 9 16.28 9 16 L 9 11 C 9 10.72 8.9 10.48 8.71 10.29 Z M 6.25 10 L 3.75 10 L 3.75 8.75 C 3.75 8.4 3.87 8.1 4.11 7.86 C 4.35 7.62 4.65 7.5 5 7.5 C 5.35 7.5 5.65 7.62 5.89 7.86 C 6.13 8.1 6.25 8.4 6.25 8.75 L 6.25 10 Z M 10 14 L 23 14 L 23 16 L 10 16 Z M 21.5 9 C 21.102 9 20.721 9.158 20.439 9.439 C 20.158 9.721 20 10.102 20 10.5 C 20 10.898 20.158 11.279 20.439 11.561 C 20.721 11.842 21.102 12 21.5 12 C 21.898 12 22.279 11.842 22.561 11.561 C 22.842 11.279 23 10.898 23 10.5 C 23 10.102 22.842 9.721 22.561 9.439 C 22.279 9.158 21.898 9 21.5 9 Z M 16.5 9 C 16.102 9 15.721 9.158 15.439 9.439 C 15.158 9.721 15 10.102 15 10.5 C 15 10.898 15.158 11.279 15.439 11.561 C 15.721 11.842 16.102 12 16.5 12 C 16.898 12 17.279 11.842 17.561 11.561 C 17.842 11.279 18 10.898 18 10.5 C 18 10.102 17.842 9.721 17.561 9.439 C 17.279 9.158 16.898 9 16.5 9 Z M 11.5 9 C 11.102 9 10.721 9.158 10.439 9.439 C 10.158 9.721 10 10.102 10 10.5 C 10 10.898 10.158 11.279 10.439 11.561 C 10.721 11.842 11.102 12 11.5 12 C 11.898 12 12.279 11.842 12.561 11.561 C 12.842 11.279 13 10.898 13 10.5 C 13 10.102 12.842 9.721 12.561 9.439 C 12.279 9.158 11.898 9 11.5 9 Z" + android:fillColor="#000" + android:strokeWidth="1"/> +</vector>
\ No newline at end of file diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml index 1ee2a26d3acf..8c9023c55eff 100644 --- a/packages/CredentialManager/res/values/strings.xml +++ b/packages/CredentialManager/res/values/strings.xml @@ -1,43 +1,77 @@ <?xml version="1.0" encoding="utf-8"?> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="app_name">CredentialManager</string> + <!-- The name of this application. Credential Manager is a service that centralizes and provides + access to a user's credentials used to sign in to various apps. [CHAR LIMIT=80] --> + <string name="app_name">Credential Manager</string> + + <!-- Strings for the create flow. --> + <!-- Button label to close the dialog when the user does not want to create the credential. [CHAR LIMIT=40] --> <string name="string_cancel">Cancel</string> + <!-- Button label to confirm choosing the default dialog information and continue. [CHAR LIMIT=40] --> <string name="string_continue">Continue</string> - <string name="string_more_options">More options</string> + <!-- This appears as a text button where users can click to create this passkey in other available places. [CHAR LIMIT=80] --> <string name="string_create_in_another_place">Create in another place</string> + <!-- This appears as a text button where users can click to create this password or other credential types in other available places. [CHAR LIMIT=80] --> <string name="string_save_to_another_place">Save to another place</string> + <!-- This appears as a text button where users can click to use another device to create this credential. [CHAR LIMIT=80] --> <string name="string_use_another_device">Use another device</string> + <!-- This appears as a text button where users can click to save this credential to another device. [CHAR LIMIT=80] --> <string name="string_save_to_another_device">Save to another device</string> - <string name="string_no_thanks">No thanks</string> + <!-- This appears as the title of the modal bottom sheet introducing what is passkey to users. [CHAR LIMIT=200] --> <string name="passkey_creation_intro_title">A simple way to sign in safely</string> + <!-- This appears as the description body of the modal bottom sheet introducing what is passkey to users. [CHAR LIMIT=200] --> <string name="passkey_creation_intro_body">Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more</string> - <string name="choose_provider_title">Choose where to <xliff:g id="createTypes">%1$s</xliff:g></string> - <!-- TODO: Change the wording after design is completed. --> - <string name="choose_provider_body">This password manager will store your passwords, passkeys, and other sign-in info to help you easily sign in. You can change where to save your sign-in info at any time.</string> - <string name="choose_create_option_passkey_title">Create a passkey in <xliff:g id="providerInfoDisplayName">%1$s</xliff:g>?</string> - <string name="choose_create_option_password_title">Save your password to <xliff:g id="providerInfoDisplayName">%1$s</xliff:g>?</string> - <string name="choose_create_option_sign_in_title">Save your sign-in info to <xliff:g id="providerInfoDisplayName">%1$s</xliff:g>?</string> - <string name="choose_sign_in_title">Use saved sign in</string> - <string name="create_your_passkey">create your passkey</string> + <!-- This appears as the title of the modal bottom sheet which provides all available providers for users to choose. [CHAR LIMIT=200] --> + <string name="choose_provider_title">Choose where to <xliff:g id="createTypes" example="create your passkeys">%1$s</xliff:g></string> + <!-- Create types which are inserted as a placeholder for string choose_provider_title. [CHAR LIMIT=200] --> + <string name="create_your_passkeys">create your passkeys</string> <string name="save_your_password">save your password</string> <string name="save_your_sign_in_info">save your sign-in info</string> - <string name="create_passkey_in">Create passkey in</string> - <string name="save_password_to">Save password to</string> - <string name="save_sign_in_to">Save sign-in to</string> - <string name="use_provider_for_all_title">Use <xliff:g id="providerInfoDisplayName">%1$s</xliff:g> for all your sign-ins?</string> - <string name="set_as_default">Set as default</string> - <string name="use_once">Use once</string> - <string name="choose_create_option_description">You can use your <xliff:g id="appDomainName">%1$s</xliff:g> <xliff:g id="type">%2$s</xliff:g> on any device. It is saved to <xliff:g id="providerInfoDisplayName">%3$s</xliff:g> for <xliff:g id="createInfoDisplayName">%4$s</xliff:g></string> - <string name="more_options_usage_passwords_passkeys"><xliff:g id="passwordsNumber">%1$s</xliff:g> passwords, <xliff:g id="passkeysNumber">%2$s</xliff:g> passkeys</string> - <string name="more_options_usage_passwords"><xliff:g id="passwordsNumber">%1$s</xliff:g> passwords</string> - <string name="more_options_usage_passkeys"><xliff:g id="passkeysNumber">%1$s</xliff:g> passkeys</string> + + <!-- This appears as the description body of the modal bottom sheet which provides all available providers for users to choose. [CHAR LIMIT=200] --> + <string name="choose_provider_body">Set a default password manager to save your passwords and passkeys and sign in faster next time.</string> + <!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is passkey. [CHAR LIMIT=200] --> + <string name="choose_create_option_passkey_title">Create a passkey in <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%1$s</xliff:g>?</string> + <!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is password. [CHAR LIMIT=200] --> + <string name="choose_create_option_password_title">Save your password to <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%1$s</xliff:g>?</string> + <!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is others. [CHAR LIMIT=200] --> + <string name="choose_create_option_sign_in_title">Save your sign-in info to <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%1$s</xliff:g>?</string> + <!-- This appears as the description body of the modal bottom sheet for users to choose the create option inside a provider. [CHAR LIMIT=200] --> + <string name="choose_create_option_description">You can use your <xliff:g id="appDomainName" example="Tribank">%1$s</xliff:g> <xliff:g id="type" example="passkey">%2$s</xliff:g> on any device. It is saved to <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%3$s</xliff:g> for <xliff:g id="createInfoDisplayName" example="elisa.beckett@gmail.com">%4$s</xliff:g></string> + <!-- Types which are inserted as a placeholder for string choose_create_option_description. [CHAR LIMIT=200] --> <string name="passkey">passkey</string> <string name="password">password</string> <string name="sign_ins">sign-ins</string> + + <!-- This appears as the title of the modal bottom sheet for users to choose other available places the created passkey can be created to. [CHAR LIMIT=200] --> + <string name="create_passkey_in_title">Create passkey in</string> + <!-- This appears as the title of the modal bottom sheet for users to choose other available places the created password can be saved to. [CHAR LIMIT=200] --> + <string name="save_password_to_title">Save password to</string> + <!-- This appears as the title of the modal bottom sheet for users to choose other available places the created other credential types can be saved to. [CHAR LIMIT=200] --> + <string name="save_sign_in_to_title">Save sign-in to</string> + <!-- This appears as the title of the modal bottom sheet for users to choose to create a passkey on another device. [CHAR LIMIT=200] --> + <string name="create_passkey_in_other_device_title">Create a passkey in another device?</string> + <!-- This appears as the title of the modal bottom sheet for users to confirm whether they should use the selected provider as default or not. [CHAR LIMIT=200] --> + <string name="use_provider_for_all_title">Use <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%1$s</xliff:g> for all your sign-ins?</string> + <!-- TODO: Check the wording here. --> + <!-- This appears as the description body of the modal bottom sheet for users to confirm whether they should use the selected provider as default or not. [CHAR LIMIT=200] --> + <string name="use_provider_for_all_description">This password manager will store your passwords and passkeys to help you easily sign in.</string> + <!-- Button label to set the selected provider on the modal bottom sheet as default. [CHAR LIMIT=40] --> + <string name="set_as_default">Set as default</string> + <!-- Button label to set the selected provider on the modal bottom sheet not as default but just use once. [CHAR LIMIT=40] --> + <string name="use_once">Use once</string> + <!-- Appears as an option row subtitle to show how many passwords and passkeys are saved in this option when there are passwords and passkeys. [CHAR LIMIT=80] --> + <string name="more_options_usage_passwords_passkeys"><xliff:g id="passwordsNumber" example="1">%1$s</xliff:g> passwords, <xliff:g id="passkeysNumber" example="2">%2$s</xliff:g> passkeys</string> + <!-- Appears as an option row subtitle to show how many passwords and passkeys are saved in this option when there are only passwords. [CHAR LIMIT=80] --> + <string name="more_options_usage_passwords"><xliff:g id="passwordsNumber" example="3">%1$s</xliff:g> passwords</string> + <!-- Appears as an option row subtitle to show how many passwords and passkeys are saved in this option when there are only passkeys. [CHAR LIMIT=80] --> + <string name="more_options_usage_passkeys"><xliff:g id="passkeysNumber" example="4">%1$s</xliff:g> passkeys</string> + <!-- Appears before a request display name when the credential type is passkey . [CHAR LIMIT=80] --> + <string name="passkey_before_subtitle">Passkey</string> + <!-- Appears as an option row title that users can choose to use another device for this creation. [CHAR LIMIT=80] --> <string name="another_device">Another device</string> + <!-- Appears as an option row title that users can choose to view other disabled providers. [CHAR LIMIT=80] --> <string name="other_password_manager">Other password managers</string> - <!-- TODO: Check the wording here. --> - <string name="confirm_default_or_use_once_description">This password manager will store your passwords and passkeys to help you easily sign in.</string> <!-- Spoken content description of an element which will close the sheet when clicked. --> <string name="close_sheet">"Close sheet"</string> <!-- Spoken content description of the back arrow button. --> diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt index 94bd0573e023..0cc11946ca85 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt @@ -44,12 +44,12 @@ import com.android.credentialmanager.createflow.ActiveEntry import com.android.credentialmanager.createflow.CreateCredentialUiState import com.android.credentialmanager.createflow.CreateScreenState import com.android.credentialmanager.createflow.EnabledProviderInfo +import com.android.credentialmanager.createflow.RemoteInfo import com.android.credentialmanager.createflow.RequestDisplayInfo import com.android.credentialmanager.getflow.GetCredentialUiState import com.android.credentialmanager.getflow.GetScreenState -import com.android.credentialmanager.jetpack.developer.CreateCredentialRequest.Companion.createFrom -import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest.Companion.toBundle +import com.android.credentialmanager.jetpack.developer.CreatePublicKeyCredentialRequest import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL // Consider repo per screen, similar to view model? @@ -67,7 +67,7 @@ class CredentialManagerRepo( requestInfo = intent.extras?.getParcelable( RequestInfo.EXTRA_REQUEST_INFO, RequestInfo::class.java - ) ?: testCreateRequestInfo() + ) ?: testCreatePasskeyRequestInfo() providerEnabledList = when (requestInfo.type) { RequestInfo.TYPE_CREATE -> @@ -137,46 +137,29 @@ class CredentialManagerRepo( } fun createCredentialInitialUiState(): CreateCredentialUiState { + val requestDisplayInfo = CreateFlowUtils.toRequestDisplayInfo(requestInfo, context) val providerEnabledList = CreateFlowUtils.toEnabledProviderList( // Handle runtime cast error - providerEnabledList as List<CreateCredentialProviderData>, context) + providerEnabledList as List<CreateCredentialProviderData>, requestDisplayInfo, context) val providerDisabledList = CreateFlowUtils.toDisabledProviderList( // Handle runtime cast error providerDisabledList as List<DisabledProviderData>, context) - var hasDefault = false - var defaultProvider: EnabledProviderInfo = providerEnabledList.first() + var defaultProvider: EnabledProviderInfo? = null + var remoteEntry: RemoteInfo? = null providerEnabledList.forEach{providerInfo -> providerInfo.createOptions = providerInfo.createOptions.sortedWith(compareBy { it.lastUsedTimeMillis }).reversed() - if (providerInfo.isDefault) {hasDefault = true; defaultProvider = providerInfo} } - // TODO: covert from real requestInfo for create passkey - var requestDisplayInfo = RequestDisplayInfo( - "beckett-bakert@gmail.com", - "Elisa Beckett", - TYPE_PUBLIC_KEY_CREDENTIAL, - "tribank") - val createCredentialRequest = requestInfo.createCredentialRequest - val createCredentialRequestJetpack = createCredentialRequest?.let { createFrom(it) } - if (createCredentialRequestJetpack is CreatePasswordRequest) { - requestDisplayInfo = RequestDisplayInfo( - createCredentialRequestJetpack.id, - createCredentialRequestJetpack.password, - TYPE_PASSWORD_CREDENTIAL, - "tribank") + if (providerInfo.isDefault) {defaultProvider = providerInfo} + if (providerInfo.remoteEntry != null) { + remoteEntry = providerInfo.remoteEntry!! + } } return CreateCredentialUiState( enabledProviders = providerEnabledList, disabledProviders = providerDisabledList, - // TODO: Add the screen when defaultProvider has no createOption but - // there's remoteInfo under other providers - if (!hasDefault || defaultProvider.createOptions.isEmpty()) { - if (requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL) - {CreateScreenState.PASSKEY_INTRO} else {CreateScreenState.PROVIDER_SELECTION} - } else {CreateScreenState.CREATION_OPTION_SELECTION}, + toCreateScreenState(requestDisplayInfo, defaultProvider, remoteEntry), requestDisplayInfo, false, - if (hasDefault) { - ActiveEntry(defaultProvider, defaultProvider.createOptions.first()) - } else null + toActiveEntry(defaultProvider, remoteEntry), ) } @@ -440,8 +423,42 @@ class CredentialManagerRepo( ) } - private fun testCreateRequestInfo(): RequestInfo { - val data = toBundle("beckett-bakert@gmail.com", "password123") + private fun testCreatePasskeyRequestInfo(): RequestInfo { + val request = CreatePublicKeyCredentialRequest("{\"extensions\": {\n" + + " \"webauthn.loc\": true\n" + + " },\n" + + " \"attestation\": \"direct\",\n" + + " \"challenge\": \"-rSQHXSQUdaK1N-La5bE-JPt6EVAW4SxX1K_tXhZ_Gk\",\n" + + " \"user\": {\n" + + " \"displayName\": \"testName\",\n" + + " \"name\": \"credManTesting@gmail.com\",\n" + + " \"id\": \"eD4o2KoXLpgegAtnM5cDhhUPvvk2\"\n" + + " },\n" + + " \"excludeCredentials\": [],\n" + + " \"rp\": {\n" + + " \"name\": \"Address Book\",\n" + + " \"id\": \"addressbook-c7876.uc.r.appspot.com\"\n" + + " },\n" + + " \"timeout\": 60000,\n" + + " \"pubKeyCredParams\": [\n" + + " {\n" + + " \"type\": \"public-key\",\n" + + " \"alg\": -7\n" + + " },\n" + + " {\n" + + " \"type\": \"public-key\",\n" + + " \"alg\": -257\n" + + " },\n" + + " {\n" + + " \"type\": \"public-key\",\n" + + " \"alg\": -37\n" + + " }\n" + + " ],\n" + + " \"authenticatorSelection\": {\n" + + " \"residentKey\": \"required\",\n" + + " \"requireResidentKey\": true\n" + + " }}") + val data = request.data return RequestInfo.newCreateRequestInfo( Binder(), CreateCredentialRequest( @@ -453,6 +470,32 @@ class CredentialManagerRepo( ) } + private fun testCreatePasswordRequestInfo(): RequestInfo { + val data = toBundle("beckett-bakert@gmail.com", "password123") + return RequestInfo.newCreateRequestInfo( + Binder(), + CreateCredentialRequest( + TYPE_PASSWORD_CREDENTIAL, + data + ), + /*isFirstUsage=*/false, + "tribank" + ) + } + + private fun testCreateOtherCredentialRequestInfo(): RequestInfo { + val data = Bundle() + return RequestInfo.newCreateRequestInfo( + Binder(), + CreateCredentialRequest( + "other-sign-ins", + data + ), + /*isFirstUsage=*/false, + "tribank" + ) + } + private fun testGetRequestInfo(): RequestInfo { return RequestInfo.newGetRequestInfo( Binder(), @@ -465,4 +508,38 @@ class CredentialManagerRepo( "tribank.us" ) } + + private fun toCreateScreenState( + requestDisplayInfo: RequestDisplayInfo, + defaultProvider: EnabledProviderInfo?, + remoteEntry: RemoteInfo?, + ): CreateScreenState { + return if ( + defaultProvider != null && defaultProvider.createOptions.isEmpty() && remoteEntry != null + ){ + CreateScreenState.EXTERNAL_ONLY_SELECTION + } else if (defaultProvider == null || defaultProvider.createOptions.isEmpty()) { + if (requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL) { + CreateScreenState.PASSKEY_INTRO + } else { + CreateScreenState.PROVIDER_SELECTION + } + } else { + CreateScreenState.CREATION_OPTION_SELECTION + } + } + + private fun toActiveEntry( + defaultProvider: EnabledProviderInfo?, + remoteEntry: RemoteInfo?, + ): ActiveEntry? { + return if ( + defaultProvider != null && defaultProvider.createOptions.isNotEmpty() + ) { + ActiveEntry(defaultProvider, defaultProvider.createOptions.first()) + } else if ( + defaultProvider != null && defaultProvider.createOptions.isEmpty() && remoteEntry != null) { + ActiveEntry(defaultProvider, remoteEntry) + } else null + } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index 2eb328498571..b96f686c02fb 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -23,17 +23,23 @@ import android.credentials.ui.Entry import android.credentials.ui.GetCredentialProviderData import android.credentials.ui.CreateCredentialProviderData import android.credentials.ui.DisabledProviderData +import android.credentials.ui.RequestInfo import android.graphics.drawable.Drawable import com.android.credentialmanager.createflow.CreateOptionInfo import com.android.credentialmanager.createflow.RemoteInfo +import com.android.credentialmanager.createflow.RequestDisplayInfo import com.android.credentialmanager.getflow.ActionEntryInfo import com.android.credentialmanager.getflow.AuthenticationEntryInfo import com.android.credentialmanager.getflow.CredentialEntryInfo import com.android.credentialmanager.getflow.ProviderInfo import com.android.credentialmanager.getflow.RemoteEntryInfo +import com.android.credentialmanager.jetpack.developer.CreateCredentialRequest +import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest +import com.android.credentialmanager.jetpack.developer.CreatePublicKeyCredentialRequest import com.android.credentialmanager.jetpack.provider.ActionUi import com.android.credentialmanager.jetpack.provider.CredentialEntryUi import com.android.credentialmanager.jetpack.provider.SaveEntryUi +import org.json.JSONObject /** Utility functions for converting CredentialManager data structures to or from UI formats. */ class GetFlowUtils { @@ -172,6 +178,7 @@ class CreateFlowUtils { fun toEnabledProviderList( providerDataList: List<CreateCredentialProviderData>, + requestDisplayInfo: RequestDisplayInfo, context: Context, ): List<com.android.credentialmanager.createflow.EnabledProviderInfo> { // TODO: get from the actual service info @@ -194,7 +201,7 @@ class CreateFlowUtils { name = it.providerFlattenedComponentName, displayName = pkgInfo.applicationInfo.loadLabel(packageManager).toString(), createOptions = toCreationOptionInfoList( - it.providerFlattenedComponentName, it.saveEntries, context), + it.providerFlattenedComponentName, it.saveEntries, requestDisplayInfo, context), isDefault = it.isDefaultProvider, remoteEntry = toRemoteInfo(it.providerFlattenedComponentName, it.remoteEntry), ) @@ -219,9 +226,59 @@ class CreateFlowUtils { } } + fun toRequestDisplayInfo( + requestInfo: RequestInfo, + context: Context, + ): RequestDisplayInfo { + val createCredentialRequest = requestInfo.createCredentialRequest + val createCredentialRequestJetpack = createCredentialRequest?.let { + CreateCredentialRequest.createFrom( + it + ) + } + when (createCredentialRequestJetpack) { + is CreatePasswordRequest -> { + return RequestDisplayInfo( + createCredentialRequestJetpack.id, + createCredentialRequestJetpack.password, + createCredentialRequestJetpack.type, + requestInfo.appPackageName, + context.getDrawable(R.drawable.ic_password)!! + ) + } + is CreatePublicKeyCredentialRequest -> { + val requestJson = createCredentialRequestJetpack.requestJson + val json = JSONObject(requestJson) + var name = "" + var displayName = "" + if (json.has("user")) { + val user: JSONObject = json.getJSONObject("user") + name = user.getString("name") + displayName = user.getString("displayName") + } + return RequestDisplayInfo( + name, + displayName, + createCredentialRequestJetpack.type, + requestInfo.appPackageName, + context.getDrawable(R.drawable.ic_passkey)!!) + } + // TODO: correctly parsing for other sign-ins + else -> { + return RequestDisplayInfo( + "beckett-bakert@gmail.com", + "Elisa Beckett", + "other-sign-ins", + requestInfo.appPackageName, + context.getDrawable(R.drawable.ic_other_sign_in)!!) + } + } + } + private fun toCreationOptionInfoList( providerId: String, creationEntries: List<Entry>, + requestDisplayInfo: RequestDisplayInfo, context: Context, ): List<CreateOptionInfo> { return creationEntries.map { @@ -236,7 +293,7 @@ class CreateFlowUtils { fillInIntent = it.frameworkExtrasIntent, userProviderDisplayName = saveEntryUi.userProviderAccountName as String, profileIcon = saveEntryUi.profileIcon?.loadDrawable(context) - ?: context.getDrawable(R.drawable.ic_profile)!!, + ?: requestDisplayInfo.typeIcon, passwordCount = saveEntryUi.passwordCount ?: 0, passkeyCount = saveEntryUi.passkeyCount ?: 0, totalCredentialCount = saveEntryUi.totalCredentialCount ?: 0, diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt index 9f73aefa52b2..20b3d81c8b3c 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt @@ -60,7 +60,7 @@ fun CreateCredentialScreen( viewModel.onEntrySelected(it, providerActivityLauncher) } val confirmEntryCallback: () -> Unit = { - viewModel.onConfirmCreationSelected(providerActivityLauncher) + viewModel.onConfirmEntrySelected(providerActivityLauncher) } val state = rememberModalBottomSheetState( initialValue = ModalBottomSheetValue.Expanded, @@ -108,6 +108,13 @@ fun CreateCredentialScreen( providerInfo = uiState.activeEntry?.activeProvider!!, onDefaultOrNotSelected = viewModel::onDefaultOrNotSelected ) + CreateScreenState.EXTERNAL_ONLY_SELECTION -> ExternalOnlySelectionCard( + requestDisplayInfo = uiState.requestDisplayInfo, + activeRemoteEntry = uiState.activeEntry?.activeEntryInfo!!, + onOptionSelected = selectEntryCallback, + onConfirm = confirmEntryCallback, + onCancel = viewModel::onCancel, + ) } }, scrimColor = MaterialTheme.colorScheme.scrim, @@ -191,19 +198,18 @@ fun ProviderSelectionCard( ) { Card() { Column() { - // TODO: Change the icon for create passwords and sign-ins Icon( - painter = painterResource(R.drawable.ic_passkey), + bitmap = requestDisplayInfo.typeIcon.toBitmap().asImageBitmap(), contentDescription = null, tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant, modifier = Modifier.align(alignment = Alignment.CenterHorizontally) - .padding(top = 24.dp, bottom = 16.dp) + .padding(top = 24.dp, bottom = 16.dp).size(32.dp) ) Text( text = stringResource( R.string.choose_provider_title, when (requestDisplayInfo.type) { - TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.create_your_passkey) + TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.create_your_passkeys) TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.save_your_password) else -> stringResource(R.string.save_your_sign_in_info) }, @@ -274,6 +280,10 @@ fun ProviderSelectionCard( } } } + Divider( + thickness = 24.dp, + color = Color.Transparent + ) Row( horizontalArrangement = Arrangement.Start, modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) @@ -306,9 +316,9 @@ fun MoreOptionsSelectionCard( title = { Text( text = when (requestDisplayInfo.type) { - TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.create_passkey_in) - TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.save_password_to) - else -> stringResource(R.string.save_sign_in_to) + TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.create_passkey_in_title) + TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.save_password_to_title) + else -> stringResource(R.string.save_sign_in_to_title) }, style = MaterialTheme.typography.titleMedium ) @@ -399,7 +409,7 @@ fun MoreOptionsRowIntroCard( textAlign = TextAlign.Center, ) Text( - text = stringResource(R.string.confirm_default_or_use_once_description), + text = stringResource(R.string.use_provider_for_all_description), style = MaterialTheme.typography.bodyLarge, modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally) ) @@ -486,7 +496,7 @@ fun CreationSelectionCard( ) { PrimaryCreateOptionRow( requestDisplayInfo = requestDisplayInfo, - createOptionInfo = createOptionInfo, + entryInfo = createOptionInfo, onOptionSelected = onOptionSelected ) } @@ -559,43 +569,129 @@ fun CreationSelectionCard( @OptIn(ExperimentalMaterial3Api::class) @Composable +fun ExternalOnlySelectionCard( + requestDisplayInfo: RequestDisplayInfo, + activeRemoteEntry: EntryInfo, + onOptionSelected: (EntryInfo) -> Unit, + onConfirm: () -> Unit, + onCancel: () -> Unit, +) { + Card() { + Column() { + Icon( + painter = painterResource(R.drawable.ic_other_devices), + contentDescription = null, + tint = Color.Unspecified, + modifier = Modifier.align(alignment = Alignment.CenterHorizontally) + .padding(all = 24.dp).size(32.dp) + ) + Text( + text = stringResource(R.string.create_passkey_in_other_device_title), + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.padding(horizontal = 24.dp) + .align(alignment = Alignment.CenterHorizontally), + textAlign = TextAlign.Center, + ) + Divider( + thickness = 24.dp, + color = Color.Transparent + ) + Card( + shape = MaterialTheme.shapes.medium, + modifier = Modifier + .padding(horizontal = 24.dp) + .align(alignment = Alignment.CenterHorizontally), + ) { + PrimaryCreateOptionRow( + requestDisplayInfo = requestDisplayInfo, + entryInfo = activeRemoteEntry, + onOptionSelected = onOptionSelected + ) + } + Divider( + thickness = 24.dp, + color = Color.Transparent + ) + Row( + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) + ) { + CancelButton( + stringResource(R.string.string_cancel), + onClick = onCancel + ) + ConfirmButton( + stringResource(R.string.string_continue), + onClick = onConfirm + ) + } + Divider( + thickness = 18.dp, + color = Color.Transparent, + modifier = Modifier.padding(bottom = 16.dp) + ) + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable fun PrimaryCreateOptionRow( requestDisplayInfo: RequestDisplayInfo, - createOptionInfo: CreateOptionInfo, + entryInfo: EntryInfo, onOptionSelected: (EntryInfo) -> Unit ) { Entry( - onClick = {onOptionSelected(createOptionInfo)}, + onClick = {onOptionSelected(entryInfo)}, icon = { - // TODO: Upload the other two types icons and change it according to request types Icon( - painter = painterResource(R.drawable.ic_passkey), + bitmap = if (entryInfo is CreateOptionInfo) { + entryInfo.profileIcon.toBitmap().asImageBitmap() + } else {requestDisplayInfo.typeIcon.toBitmap().asImageBitmap()}, contentDescription = null, tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant, - modifier = Modifier.padding(start = 18.dp) + modifier = Modifier.padding(start = 18.dp).size(32.dp) ) }, label = { Column() { // TODO: Add the function to hide/view password when the type is create password - if (requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL || - requestDisplayInfo.type == TYPE_PASSWORD_CREDENTIAL) { - Text( - text = requestDisplayInfo.title, - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(top = 16.dp) - ) - Text( - text = requestDisplayInfo.subtitle, - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(bottom = 16.dp) - ) - } else { - Text( - text = requestDisplayInfo.title, - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(top = 16.dp, bottom = 16.dp) - ) + when (requestDisplayInfo.type) { + TYPE_PUBLIC_KEY_CREDENTIAL -> { + Text( + text = requestDisplayInfo.title, + style = MaterialTheme.typography.titleLarge, + modifier = Modifier.padding(top = 16.dp) + ) + Text( + text = if (requestDisplayInfo.subtitle != null) { + stringResource( + R.string.passkey_before_subtitle) + " - " + requestDisplayInfo.subtitle + } else {stringResource(R.string.passkey_before_subtitle)}, + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(bottom = 16.dp) + ) + } + TYPE_PASSWORD_CREDENTIAL -> { + Text( + text = requestDisplayInfo.title, + style = MaterialTheme.typography.titleLarge, + modifier = Modifier.padding(top = 16.dp) + ) + Text( + // This subtitle would never be null for create password + text = requestDisplayInfo.subtitle ?: "", + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.padding(bottom = 16.dp) + ) + } + else -> { + Text( + text = requestDisplayInfo.title, + style = MaterialTheme.typography.titleLarge, + modifier = Modifier.padding(top = 16.dp, bottom = 16.dp) + ) + } } } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt index 0f685a104329..393cf7d9ae01 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt @@ -155,7 +155,7 @@ class CreateCredentialViewModel( } } - fun onConfirmCreationSelected( + fun onConfirmEntrySelected( launcher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult> ) { val selectedEntry = uiState.activeEntry?.activeEntryInfo diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt index 6dd6afb81dc4..9ac524a4e6d9 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt @@ -73,9 +73,10 @@ class RemoteInfo( data class RequestDisplayInfo( val title: String, - val subtitle: String, + val subtitle: String?, val type: String, val appDomainName: String, + val typeIcon: Drawable, ) /** @@ -94,4 +95,5 @@ enum class CreateScreenState { CREATION_OPTION_SELECTION, MORE_OPTIONS_SELECTION, MORE_OPTIONS_ROW_INTRO, + EXTERNAL_ONLY_SELECTION, } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt index db0c16c0bb9e..8ccdf4cf972a 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt @@ -167,7 +167,7 @@ fun PrimarySelectionCard( horizontalArrangement = Arrangement.Start, modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) ) { - CancelButton(stringResource(R.string.string_no_thanks), onCancel) + CancelButton(stringResource(R.string.get_dialog_button_label_no_thanks), onCancel) } Divider( thickness = 18.dp, diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialBaseRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialBaseRequest.kt index 26d61f9eb7a9..37a4f7633988 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialBaseRequest.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialBaseRequest.kt @@ -47,7 +47,7 @@ abstract class CreatePublicKeyCredentialBaseRequest constructor( return when (data.getString(BUNDLE_KEY_SUBTYPE)) { CreatePublicKeyCredentialRequest .BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST -> - CreatePublicKeyCredentialRequestPrivileged.createFrom(data) + CreatePublicKeyCredentialRequest.createFrom(data) CreatePublicKeyCredentialRequestPrivileged .BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIVILEGED -> CreatePublicKeyCredentialRequestPrivileged.createFrom(data) diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml index 71c52d9f4a6a..37d6b426c337 100644 --- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml +++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml @@ -36,7 +36,7 @@ </activity> <provider - android:name="com.android.settingslib.spa.framework.SpaSearchProvider" + android:name="com.android.settingslib.spa.search.SpaSearchProvider" android:authorities="com.android.spa.gallery.search.provider" android:enabled="true" android:exported="false"> diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt index 6ed7481b2400..cd3ec96cd78d 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt @@ -16,21 +16,17 @@ package com.android.settingslib.spa.framework +import android.content.Intent import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.annotation.VisibleForTesting import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.core.view.WindowCompat -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleEventObserver import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable @@ -39,12 +35,13 @@ import com.android.settingslib.spa.R import com.android.settingslib.spa.framework.common.LogCategory import com.android.settingslib.spa.framework.common.SettingsPage import com.android.settingslib.spa.framework.common.SettingsPageProvider +import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory -import com.android.settingslib.spa.framework.common.createSettingsPage import com.android.settingslib.spa.framework.compose.LocalNavController import com.android.settingslib.spa.framework.compose.NavControllerWrapperImpl import com.android.settingslib.spa.framework.compose.localNavController import com.android.settingslib.spa.framework.theme.SettingsTheme +import com.android.settingslib.spa.framework.util.PageEvent import com.android.settingslib.spa.framework.util.navRoute private const val TAG = "BrowseActivity" @@ -77,12 +74,7 @@ open class BrowseActivity : ComponentActivity() { setContent { SettingsTheme { val sppRepository by spaEnvironment.pageProviderRepository - BrowseContent( - allProviders = sppRepository.getAllProviders(), - initialDestination = intent?.getStringExtra(KEY_DESTINATION) - ?: sppRepository.getDefaultStartPage(), - initialEntryId = intent?.getStringExtra(KEY_HIGHLIGHT_ENTRY) - ) + BrowseContent(sppRepository, intent) } } } @@ -90,44 +82,18 @@ open class BrowseActivity : ComponentActivity() { companion object { const val KEY_DESTINATION = "spaActivityDestination" const val KEY_HIGHLIGHT_ENTRY = "highlightEntry" + const val KEY_SESSION_SOURCE_NAME = "sessionSource" } } @VisibleForTesting @Composable -fun BrowseContent( - allProviders: Collection<SettingsPageProvider>, - initialDestination: String, - initialEntryId: String? -) { +fun BrowseContent(sppRepository: SettingsPageProviderRepository, initialIntent: Intent? = null) { val navController = rememberNavController() CompositionLocalProvider(navController.localNavController()) { val controller = LocalNavController.current as NavControllerWrapperImpl - controller.NavContent(allProviders) - controller.InitialDestination(initialDestination, initialEntryId) - } -} - -@Composable -private fun SettingsPageProvider.PageEvents(arguments: Bundle? = null) { - val page = remember(arguments) { createSettingsPage(arguments) } - val lifecycleOwner = LocalLifecycleOwner.current - DisposableEffect(lifecycleOwner) { - val observer = LifecycleEventObserver { _, event -> - if (event == Lifecycle.Event.ON_START) { - page.enterPage() - } else if (event == Lifecycle.Event.ON_STOP) { - page.leavePage() - } - } - - // Add the observer to the lifecycle - lifecycleOwner.lifecycle.addObserver(observer) - - // When the effect leaves the Composition, remove the observer - onDispose { - lifecycleOwner.lifecycle.removeObserver(observer) - } + controller.NavContent(sppRepository.getAllProviders()) + controller.InitialDestination(initialIntent, sppRepository.getDefaultStartPage()) } } @@ -144,7 +110,7 @@ private fun NavControllerWrapperImpl.NavContent(allProvider: Collection<Settings route = spp.name + spp.parameter.navRoute(), arguments = spp.parameter, ) { navBackStackEntry -> - spp.PageEvents(navBackStackEntry.arguments) + spp.PageEvent(navBackStackEntry.arguments) spp.Page(navBackStackEntry.arguments) } } @@ -153,17 +119,23 @@ private fun NavControllerWrapperImpl.NavContent(allProvider: Collection<Settings @Composable private fun NavControllerWrapperImpl.InitialDestination( - destination: String, - highlightEntryId: String? + initialIntent: Intent?, + defaultDestination: String ) { val destinationNavigated = rememberSaveable { mutableStateOf(false) } if (destinationNavigated.value) return destinationNavigated.value = true - if (destination.isEmpty()) return + val initialDestination = initialIntent?.getStringExtra(BrowseActivity.KEY_DESTINATION) + ?: defaultDestination + if (initialDestination.isEmpty()) return + val initialEntryId = initialIntent?.getStringExtra(BrowseActivity.KEY_HIGHLIGHT_ENTRY) + val sessionSourceName = initialIntent?.getStringExtra(BrowseActivity.KEY_SESSION_SOURCE_NAME) + LaunchedEffect(Unit) { - highlightId = highlightEntryId - navController.navigate(destination) { + highlightId = initialEntryId + sessionName = sessionSourceName + navController.navigate(initialDestination) { popUpTo(navController.graph.findStartDestination().id) { inclusive = true } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt index 121c07f1cd55..61b46be443c2 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt @@ -17,6 +17,7 @@ package com.android.settingslib.spa.framework.common import android.content.UriMatcher +import androidx.annotation.VisibleForTesting /** * Enum to define all column names in provider. @@ -125,14 +126,17 @@ enum class QueryEnum( ), } -internal fun QueryEnum.getColumns(): Array<String> { +@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) +fun QueryEnum.getColumns(): Array<String> { return columnNames.map { it.id }.toTypedArray() } -internal fun QueryEnum.getIndex(name: ColumnEnum): Int { +@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) +fun QueryEnum.getIndex(name: ColumnEnum): Int { return columnNames.indexOf(name) } -internal fun QueryEnum.addUri(uriMatcher: UriMatcher, authority: String) { +@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) +fun QueryEnum.addUri(uriMatcher: UriMatcher, authority: String) { uriMatcher.addURI(authority, queryPath, queryMatchCode) } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt index 9ee7f9ef7f86..702c07585be1 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt @@ -219,11 +219,6 @@ class SettingsEntryBuilder(private val name: String, private val owner: Settings return this } - fun setIsAllowSearch(isAllowSearch: Boolean): SettingsEntryBuilder { - this.isAllowSearch = isAllowSearch - return this - } - fun setIsSearchDataDynamic(isDynamic: Boolean): SettingsEntryBuilder { this.isSearchDataDynamic = isDynamic return this @@ -251,6 +246,13 @@ class SettingsEntryBuilder(private val name: String, private val owner: Settings fun setSearchDataFn(fn: SearchDataGetter): SettingsEntryBuilder { this.searchDataFn = fn + this.isAllowSearch = true + return this + } + + fun clearSearchDataFn(): SettingsEntryBuilder { + this.searchDataFn = { null } + this.isAllowSearch = false return this } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt index 82e05a55a64b..bc5dca8778d8 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt @@ -95,24 +95,6 @@ data class SettingsPage( return false } - fun enterPage() { - SpaEnvironmentFactory.instance.logger.event( - id, - LogEvent.PAGE_ENTER, - category = LogCategory.FRAMEWORK, - details = displayName, - ) - } - - fun leavePage() { - SpaEnvironmentFactory.instance.logger.event( - id, - LogEvent.PAGE_LEAVE, - category = LogCategory.FRAMEWORK, - details = displayName, - ) - } - fun createBrowseIntent(entryId: String? = null): Intent? { val context = SpaEnvironmentFactory.instance.appContext val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt index 382c498ca5b2..eb2bffe0e4fd 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt @@ -29,7 +29,10 @@ interface NavControllerWrapper { fun navigateBack() val highlightEntryId: String? - get() = null + get() = null + + val sessionSourceName: String? + get() = null } @Composable @@ -63,6 +66,7 @@ internal class NavControllerWrapperImpl( private val onBackPressedDispatcher: OnBackPressedDispatcher?, ) : NavControllerWrapper { var highlightId: String? = null + var sessionName: String? = null override fun navigate(route: String) { navController.navigate(route) @@ -73,5 +77,8 @@ internal class NavControllerWrapperImpl( } override val highlightEntryId: String? - get() = highlightId + get() = highlightId + + override val sessionSourceName: String? + get() = sessionName } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/WidgetLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt index 8d0a35c371e3..8d0a35c371e3 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/WidgetLogger.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt new file mode 100644 index 000000000000..b9e4b782455d --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt @@ -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.settingslib.spa.framework.util + +import android.os.Bundle +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import com.android.settingslib.spa.framework.common.LogCategory +import com.android.settingslib.spa.framework.common.LogEvent +import com.android.settingslib.spa.framework.common.SettingsPageProvider +import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory +import com.android.settingslib.spa.framework.common.createSettingsPage +import com.android.settingslib.spa.framework.compose.LocalNavController + +@Composable +internal fun SettingsPageProvider.PageEvent(arguments: Bundle? = null) { + val page = remember(arguments) { createSettingsPage(arguments) } + val lifecycleOwner = LocalLifecycleOwner.current + val navController = LocalNavController.current + DisposableEffect(lifecycleOwner) { + val observer = LifecycleEventObserver { _, event -> + val spaLogger = SpaEnvironmentFactory.instance.logger + if (event == Lifecycle.Event.ON_START) { + spaLogger.event( + page.id, + LogEvent.PAGE_ENTER, + category = LogCategory.FRAMEWORK, + details = navController.sessionSourceName ?: page.displayName, + ) + } else if (event == Lifecycle.Event.ON_STOP) { + spaLogger.event( + page.id, + LogEvent.PAGE_LEAVE, + category = LogCategory.FRAMEWORK, + details = navController.sessionSourceName ?: page.displayName, + ) + } + } + + // Add the observer to the lifecycle + lifecycleOwner.lifecycle.addObserver(observer) + + // When the effect leaves the Composition, remove the observer + onDispose { + lifecycleOwner.lifecycle.removeObserver(observer) + } + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSearchProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt index 3689e4e66d11..7f2f4fda44a9 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSearchProvider.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settingslib.spa.framework +package com.android.settingslib.spa.search import android.content.ContentProvider import android.content.ContentValues @@ -26,6 +26,7 @@ import android.database.Cursor import android.database.MatrixCursor import android.net.Uri import android.util.Log +import androidx.annotation.VisibleForTesting import com.android.settingslib.spa.framework.common.ColumnEnum import com.android.settingslib.spa.framework.common.QueryEnum import com.android.settingslib.spa.framework.common.SettingsEntry @@ -115,7 +116,8 @@ class SpaSearchProvider : ContentProvider() { } } - private fun querySearchImmutableStatusData(): Cursor { + @VisibleForTesting + fun querySearchImmutableStatusData(): Cursor { val entryRepository by spaEnvironment.entryRepository val cursor = MatrixCursor(QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY.getColumns()) for (entry in entryRepository.getAllEntries()) { @@ -125,7 +127,8 @@ class SpaSearchProvider : ContentProvider() { return cursor } - private fun querySearchMutableStatusData(): Cursor { + @VisibleForTesting + fun querySearchMutableStatusData(): Cursor { val entryRepository by spaEnvironment.entryRepository val cursor = MatrixCursor(QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY.getColumns()) for (entry in entryRepository.getAllEntries()) { @@ -135,7 +138,8 @@ class SpaSearchProvider : ContentProvider() { return cursor } - private fun querySearchStaticData(): Cursor { + @VisibleForTesting + fun querySearchStaticData(): Cursor { val entryRepository by spaEnvironment.entryRepository val cursor = MatrixCursor(QueryEnum.SEARCH_STATIC_DATA_QUERY.getColumns()) for (entry in entryRepository.getAllEntries()) { @@ -145,7 +149,8 @@ class SpaSearchProvider : ContentProvider() { return cursor } - private fun querySearchDynamicData(): Cursor { + @VisibleForTesting + fun querySearchDynamicData(): Cursor { val entryRepository by spaEnvironment.entryRepository val cursor = MatrixCursor(QueryEnum.SEARCH_DYNAMIC_DATA_QUERY.getColumns()) for (entry in entryRepository.getAllEntries()) { diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt index 77c564b6ec15..b6099e99c529 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt @@ -24,7 +24,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import com.android.settingslib.spa.framework.common.EntryMacro import com.android.settingslib.spa.framework.common.EntrySearchData -import com.android.settingslib.spa.framework.common.EntryStatusData import com.android.settingslib.spa.framework.compose.navigator import com.android.settingslib.spa.framework.compose.stateOf import com.android.settingslib.spa.framework.util.EntryHighlight @@ -56,10 +55,6 @@ data class SimplePreferenceMacro( keyword = searchKeywords ) } - - override fun getStatusData(): EntryStatusData { - return EntryStatusData(isDisabled = false) - } } /** diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt index bde3bba149a3..bd5884d6f2d9 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt @@ -30,6 +30,7 @@ import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory import com.android.settingslib.spa.framework.common.createSettingsPage import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest import com.android.settingslib.spa.tests.testutils.SpaLoggerForTest +import com.android.settingslib.spa.tests.testutils.SppHome import com.android.settingslib.spa.testutils.waitUntil import com.google.common.truth.Truth import org.junit.Rule @@ -45,7 +46,8 @@ class BrowseActivityTest { private val context: Context = ApplicationProvider.getApplicationContext() private val spaLogger = SpaLoggerForTest() - private val spaEnvironment = SpaEnvironmentForTest(context, logger = spaLogger) + private val spaEnvironment = + SpaEnvironmentForTest(context, listOf(SppHome.createSettingsPage()), logger = spaLogger) @Test fun testBrowsePage() { @@ -58,13 +60,7 @@ class BrowseActivityTest { val sppLayer1 = sppRepository.getProviderOrNull("SppLayer1")!! val pageLayer1 = sppLayer1.createSettingsPage() - composeTestRule.setContent { - BrowseContent( - allProviders = listOf(sppHome, sppLayer1), - initialDestination = pageHome.buildRoute(), - initialEntryId = null - ) - } + composeTestRule.setContent { BrowseContent(sppRepository) } composeTestRule.onNodeWithText(sppHome.getTitle(null)).assertIsDisplayed() spaLogger.verifyPageEvent(pageHome.id, 1, 0) diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt index f8339b6dd99b..934b8f599be5 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt @@ -30,7 +30,8 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class SettingsEntryRepositoryTest { private val context: Context = ApplicationProvider.getApplicationContext() - private val spaEnvironment = SpaEnvironmentForTest(context) + private val spaEnvironment = + SpaEnvironmentForTest(context, listOf(SppHome.createSettingsPage())) private val entryRepository by spaEnvironment.entryRepository @Test diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt index 2017d538ae81..a343f6c30cdb 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt @@ -64,6 +64,7 @@ class SettingsEntryTest { assertThat(entry.isAllowSearch).isFalse() assertThat(entry.isSearchDataDynamic).isFalse() assertThat(entry.hasMutableStatus).isFalse() + assertThat(entry.hasSliceSupport).isFalse() } @Test @@ -121,12 +122,13 @@ class SettingsEntryTest { @Test fun testSetAttributes() { val owner = SettingsPage.create("mySpp") - val entry = SettingsEntryBuilder.create(owner, "myEntry") + val entryBuilder = SettingsEntryBuilder.create(owner, "myEntry") .setDisplayName("myEntryDisplay") - .setIsAllowSearch(true) .setIsSearchDataDynamic(false) .setHasMutableStatus(true) - .build() + .setSearchDataFn { null } + .setSliceDataFn { _, _ -> null } + val entry = entryBuilder.build() assertThat(entry.id).isEqualTo(getUniqueEntryId("myEntry", owner)) assertThat(entry.displayName).isEqualTo("myEntryDisplay") assertThat(entry.fromPage).isNull() @@ -134,6 +136,10 @@ class SettingsEntryTest { assertThat(entry.isAllowSearch).isTrue() assertThat(entry.isSearchDataDynamic).isFalse() assertThat(entry.hasMutableStatus).isTrue() + assertThat(entry.hasSliceSupport).isTrue() + + val entry2 = entryBuilder.clearSearchDataFn().build() + assertThat(entry2.isAllowSearch).isFalse() } @Test @@ -150,6 +156,10 @@ class SettingsEntryTest { val rtArguments = bundleOf("rtParam" to "v2") composeTestRule.setContent { entry.UiLayout(rtArguments) } + assertThat(entry.isAllowSearch).isTrue() + assertThat(entry.isSearchDataDynamic).isFalse() + assertThat(entry.hasMutableStatus).isFalse() + assertThat(entry.hasSliceSupport).isFalse() val searchData = entry.getSearchData(rtArguments) val statusData = entry.getStatusData(rtArguments) assertThat(searchData?.title).isEqualTo("myTitle") diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt index 743b5e3df2f6..15c2db50c234 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt @@ -24,7 +24,6 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settingslib.spa.tests.testutils.BlankActivity import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest -import com.android.settingslib.spa.tests.testutils.SpaLoggerForTest import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @@ -32,8 +31,7 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class SettingsPageTest { private val context: Context = ApplicationProvider.getApplicationContext() - private val spaLogger = SpaLoggerForTest() - private val spaEnvironment = SpaEnvironmentForTest(context, logger = spaLogger) + private val spaEnvironment = SpaEnvironmentForTest(context) @Test fun testNullPage() { @@ -74,10 +72,14 @@ class SettingsPageTest { "int_param" to 10, ) val page = spaEnvironment.createPage("SppWithParam", arguments) - assertThat(page.id).isEqualTo(getUniquePageId("SppWithParam", listOf( - navArgument("string_param") { type = NavType.StringType }, - navArgument("int_param") { type = NavType.IntType }, - ), arguments)) + assertThat(page.id).isEqualTo( + getUniquePageId( + "SppWithParam", listOf( + navArgument("string_param") { type = NavType.StringType }, + navArgument("int_param") { type = NavType.IntType }, + ), arguments + ) + ) assertThat(page.sppName).isEqualTo("SppWithParam") assertThat(page.displayName).isEqualTo("SppWithParam") assertThat(page.buildRoute()).isEqualTo("SppWithParam/myStr/10") @@ -98,11 +100,15 @@ class SettingsPageTest { "rt_param" to "rtStr", ) val page = spaEnvironment.createPage("SppWithRtParam", arguments) - assertThat(page.id).isEqualTo(getUniquePageId("SppWithRtParam", listOf( - navArgument("string_param") { type = NavType.StringType }, - navArgument("int_param") { type = NavType.IntType }, - navArgument("rt_param") { type = NavType.StringType }, - ), arguments)) + assertThat(page.id).isEqualTo( + getUniquePageId( + "SppWithRtParam", listOf( + navArgument("string_param") { type = NavType.StringType }, + navArgument("int_param") { type = NavType.IntType }, + navArgument("rt_param") { type = NavType.StringType }, + ), arguments + ) + ) assertThat(page.sppName).isEqualTo("SppWithRtParam") assertThat(page.displayName).isEqualTo("SppWithRtParam") assertThat(page.buildRoute()).isEqualTo("SppWithRtParam/myStr/10/rtStr") @@ -112,19 +118,4 @@ class SettingsPageTest { assertThat(page.createBrowseIntent(context, BlankActivity::class.java)).isNull() assertThat(page.createBrowseAdbCommand(context, BlankActivity::class.java)).isNull() } - - @Test - fun testPageEvent() { - spaLogger.reset() - SpaEnvironmentFactory.reset(spaEnvironment) - val page = spaEnvironment.createPage("SppHome") - page.enterPage() - page.leavePage() - page.enterPage() - assertThat(page.createBrowseIntent()).isNotNull() - assertThat(spaLogger.getEventCount(page.id, LogEvent.PAGE_ENTER, LogCategory.FRAMEWORK)) - .isEqualTo(2) - assertThat(spaLogger.getEventCount(page.id, LogEvent.PAGE_LEAVE, LogCategory.FRAMEWORK)) - .isEqualTo(1) - } } diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt new file mode 100644 index 000000000000..cdb0f3a2283d --- /dev/null +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt @@ -0,0 +1,91 @@ +/* + * 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.spa.search + +import android.content.Context +import android.database.Cursor +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spa.framework.common.ColumnEnum +import com.android.settingslib.spa.framework.common.QueryEnum +import com.android.settingslib.spa.framework.common.SettingsEntryBuilder +import com.android.settingslib.spa.framework.common.SettingsPage +import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory +import com.android.settingslib.spa.framework.common.createSettingsPage +import com.android.settingslib.spa.framework.common.getIndex +import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest +import com.android.settingslib.spa.tests.testutils.SppForSearch +import com.google.common.truth.Truth +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class SpaSearchProviderTest { + private val context: Context = ApplicationProvider.getApplicationContext() + private val spaEnvironment = + SpaEnvironmentForTest(context, listOf(SppForSearch.createSettingsPage())) + private val searchProvider = SpaSearchProvider() + + @Test + fun testQuerySearchStatusData() { + SpaEnvironmentFactory.reset(spaEnvironment) + val pageOwner = spaEnvironment.createPage("SppForSearch") + + val immutableStatus = searchProvider.querySearchImmutableStatusData() + Truth.assertThat(immutableStatus.count).isEqualTo(1) + immutableStatus.moveToFirst() + immutableStatus.checkValue( + QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY, + ColumnEnum.ENTRY_ID, + pageOwner.getEntryId("SearchDynamicWithImmutableStatus") + ) + + val mutableStatus = searchProvider.querySearchMutableStatusData() + Truth.assertThat(mutableStatus.count).isEqualTo(2) + mutableStatus.moveToFirst() + mutableStatus.checkValue( + QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY, + ColumnEnum.ENTRY_ID, + pageOwner.getEntryId("SearchStaticWithMutableStatus") + ) + + mutableStatus.moveToNext() + mutableStatus.checkValue( + QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY, + ColumnEnum.ENTRY_ID, + pageOwner.getEntryId("SearchDynamicWithMutableStatus") + ) + } + + @Test + fun testQuerySearchIndexData() { + SpaEnvironmentFactory.reset(spaEnvironment) + val staticData = searchProvider.querySearchStaticData() + Truth.assertThat(staticData.count).isEqualTo(2) + + val dynamicData = searchProvider.querySearchDynamicData() + Truth.assertThat(dynamicData.count).isEqualTo(2) + } +} + +private fun Cursor.checkValue(query: QueryEnum, column: ColumnEnum, value: String) { + Truth.assertThat(getString(query.getIndex(column))).isEqualTo(value) +} + +private fun SettingsPage.getEntryId(name: String): String { + return SettingsEntryBuilder.create(this, name).build().id +} diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt index 6ebd64f25e48..7fc09ff254af 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt @@ -26,6 +26,7 @@ import com.android.settingslib.spa.framework.common.createSettingsPage import com.android.settingslib.spa.framework.common.getUniqueEntryId import com.android.settingslib.spa.testutils.InstantTaskExecutorRule import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest +import com.android.settingslib.spa.tests.testutils.SppHome import com.android.settingslib.spa.tests.testutils.SppLayer2 import com.google.common.truth.Truth.assertThat import org.junit.Rule @@ -37,7 +38,8 @@ class SettingsSliceDataRepositoryTest { @get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule() private val context: Context = ApplicationProvider.getApplicationContext() - private val spaEnvironment = SpaEnvironmentForTest(context) + private val spaEnvironment = + SpaEnvironmentForTest(context, listOf(SppHome.createSettingsPage())) private val sliceDataRepository by spaEnvironment.sliceDataRepository @Test diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt index ab269f2f024a..63859546d5f6 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt @@ -24,7 +24,9 @@ import android.os.Bundle import androidx.navigation.NavType import androidx.navigation.navArgument import com.android.settingslib.spa.framework.BrowseActivity +import com.android.settingslib.spa.framework.common.EntrySearchData import com.android.settingslib.spa.framework.common.EntrySliceData +import com.android.settingslib.spa.framework.common.EntryStatusData import com.android.settingslib.spa.framework.common.LogCategory import com.android.settingslib.spa.framework.common.LogEvent import com.android.settingslib.spa.framework.common.SettingsEntry @@ -141,8 +143,43 @@ object SppLayer2 : SettingsPageProvider { } } +object SppForSearch : SettingsPageProvider { + override val name = "SppForSearch" + + override fun buildEntry(arguments: Bundle?): List<SettingsEntry> { + val owner = this.createSettingsPage() + return listOf( + SettingsEntryBuilder.create(owner, "SearchStaticWithNoStatus") + .setSearchDataFn { EntrySearchData(title = "SearchStaticWithNoStatus") } + .build(), + SettingsEntryBuilder.create(owner, "SearchStaticWithMutableStatus") + .setHasMutableStatus(true) + .setSearchDataFn { EntrySearchData(title = "SearchStaticWithMutableStatus") } + .setStatusDataFn { EntryStatusData(isSwitchOff = true) } + .build(), + SettingsEntryBuilder.create(owner, "SearchDynamicWithMutableStatus") + .setIsSearchDataDynamic(true) + .setHasMutableStatus(true) + .setSearchDataFn { EntrySearchData(title = "SearchDynamicWithMutableStatus") } + .setStatusDataFn { EntryStatusData(isDisabled = true) } + .build(), + SettingsEntryBuilder.create(owner, "SearchDynamicWithImmutableStatus") + .setIsSearchDataDynamic(true) + .setSearchDataFn { + EntrySearchData( + title = "SearchDynamicWithImmutableStatus", + keyword = listOf("kw1", "kw2") + ) + } + .setStatusDataFn { EntryStatusData(isDisabled = true) } + .build(), + ) + } +} + class SpaEnvironmentForTest( context: Context, + rootPages: List<SettingsPage> = emptyList(), override val browseActivityClass: Class<out Activity>? = BlankActivity::class.java, override val sliceBroadcastReceiverClass: Class<out BroadcastReceiver>? = BlankSliceBroadcastReceiver::class.java, @@ -153,6 +190,7 @@ class SpaEnvironmentForTest( SettingsPageProviderRepository( listOf( SppHome, SppLayer1, SppLayer2, + SppForSearch, object : SettingsPageProvider { override val name = "SppWithParam" override val parameter = listOf( @@ -169,7 +207,7 @@ class SpaEnvironmentForTest( ) }, ), - listOf(SettingsPage.create("SppHome")) + rootPages ) } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt index 681eb1c3508e..15766e1b8e7c 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt @@ -55,19 +55,22 @@ internal data class AppListState( val searchQuery: State<String>, ) +internal data class AppListInput<T : AppRecord>( + val config: AppListConfig, + val listModel: AppListModel<T>, + val state: AppListState, + val header: @Composable () -> Unit, + val appItem: @Composable AppListItemModel<T>.() -> Unit, + val bottomPadding: Dp, +) + /** * The template to render an App List. * * This UI element will take the remaining space on the screen to show the App List. */ @Composable -internal fun <T : AppRecord> AppList( - config: AppListConfig, - listModel: AppListModel<T>, - state: AppListState, - header: @Composable () -> Unit, - appItem: @Composable (itemState: AppListItemModel<T>) -> Unit, - bottomPadding: Dp, +internal fun <T : AppRecord> AppListInput<T>.AppList( appListDataSupplier: @Composable () -> State<AppListData<T>?> = { loadAppListData(config, listModel, state) }, diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt index ac3f8ff79091..28bf8329a274 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt @@ -35,16 +35,13 @@ class AppListItemModel<T : AppRecord>( ) @Composable -fun <T : AppRecord> AppListItem( - itemModel: AppListItemModel<T>, - onClick: () -> Unit, -) { +fun <T : AppRecord> AppListItemModel<T>.AppListItem(onClick: () -> Unit) { Preference(remember { object : PreferenceModel { - override val title = itemModel.label - override val summary = itemModel.summary + override val title = label + override val summary = this@AppListItem.summary override val icon = @Composable { - AppIcon(app = itemModel.record.app, size = SettingsDimension.appIconItemSize) + AppIcon(app = record.app, size = SettingsDimension.appIconItemSize) } override val onClick = onClick } @@ -58,7 +55,6 @@ private fun AppListItemPreview() { val record = object : AppRecord { override val app = LocalContext.current.applicationInfo } - val itemModel = AppListItemModel<AppRecord>(record, "Chrome", "Allowed".toState()) - AppListItem(itemModel) {} + AppListItemModel<AppRecord>(record, "Chrome", "Allowed".toState()).AppListItem {} } } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt index f371ce97b16e..d452c740d992 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt @@ -47,7 +47,23 @@ fun <T : AppRecord> AppListPage( primaryUserOnly: Boolean = false, moreOptions: @Composable MoreOptionsScope.() -> Unit = {}, header: @Composable () -> Unit = {}, - appItem: @Composable (itemState: AppListItemModel<T>) -> Unit, + appItem: @Composable AppListItemModel<T>.() -> Unit, +) { + AppListPageImpl( + title, listModel, showInstantApps, primaryUserOnly, moreOptions, header, appItem, + ) { it.AppList() } +} + +@Composable +internal fun <T : AppRecord> AppListPageImpl( + title: String, + listModel: AppListModel<T>, + showInstantApps: Boolean = false, + primaryUserOnly: Boolean = false, + moreOptions: @Composable MoreOptionsScope.() -> Unit = {}, + header: @Composable () -> Unit = {}, + appItem: @Composable AppListItemModel<T>.() -> Unit, + appList: @Composable (input: AppListInput<T>) -> Unit, ) { val showSystem = rememberSaveable { mutableStateOf(false) } SearchScaffold( @@ -64,7 +80,7 @@ fun <T : AppRecord> AppListPage( val options = remember { listModel.getSpinnerOptions() } val selectedOption = rememberSaveable { mutableStateOf(0) } Spinner(options, selectedOption.value) { selectedOption.value = it } - AppList( + val appListInput = AppListInput( config = AppListConfig( userId = userInfo.id, showInstantApps = showInstantApps, @@ -79,6 +95,7 @@ fun <T : AppRecord> AppListPage( appItem = appItem, bottomPadding = bottomPadding, ) + appList(appListInput) } } } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt index 5290bec0ef36..452971b44a99 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt @@ -9,8 +9,7 @@ import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference import com.android.settingslib.spaprivileged.model.app.AppRecord @Composable -fun <T : AppRecord> AppListSwitchItem( - itemModel: AppListItemModel<T>, +fun <T : AppRecord> AppListItemModel<T>.AppListSwitchItem( onClick: () -> Unit, checked: State<Boolean?>, changeable: State<Boolean>, @@ -19,14 +18,14 @@ fun <T : AppRecord> AppListSwitchItem( TwoTargetSwitchPreference( model = remember { object : SwitchPreferenceModel { - override val title = itemModel.label - override val summary = itemModel.summary + override val title = label + override val summary = this@AppListSwitchItem.summary override val checked = checked override val changeable = changeable override val onCheckedChange = onCheckedChange } }, - icon = { AppIcon(itemModel.record.app, SettingsDimension.appIconItemSize) }, + icon = { AppIcon(record.app, SettingsDimension.appIconItemSize) }, onClick = onClick, ) } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt index de5a4a2e28db..8287693c90ee 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt @@ -66,7 +66,7 @@ internal class TogglePermissionAppInfoPageProvider( val owner = SettingsPage.create(name, parameter = parameter, arguments = arguments) val entryList = mutableListOf<SettingsEntry>() entryList.add( - SettingsEntryBuilder.create(ENTRY_NAME, owner).setIsAllowSearch(false).build() + SettingsEntryBuilder.create(ENTRY_NAME, owner).build() ) return entryList } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt index 6db27336b67f..00eb60793217 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt @@ -70,7 +70,6 @@ internal class TogglePermissionAppListPageProvider( entryList.add( SettingsEntryBuilder.createLinkFrom("${ENTRY_NAME}_$category", appListPage) .setLink(toPage = appInfoPage) - .setIsAllowSearch(false) .build() ) } @@ -92,12 +91,11 @@ internal class TogglePermissionAppListPageProvider( AppListPage( title = stringResource(listModel.pageTitleResId), listModel = internalListModel, - ) { itemModel -> + ) { AppListItem( - itemModel = itemModel, onClick = TogglePermissionAppInfoPageProvider.navigator( permissionType = permissionType, - app = itemModel.record.app, + app = record.app, ), ) } @@ -120,7 +118,7 @@ internal class TogglePermissionAppListPageProvider( parameter = PAGE_PARAMETER, arguments = bundleOf(PERMISSION to permissionType) ) - return SettingsEntryBuilder.createInject(owner = appListPage).setIsAllowSearch(false) + return SettingsEntryBuilder.createInject(owner = appListPage) .setUiLayoutFn { val listModel = rememberContext(listModelSupplier) Preference( diff --git a/packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml b/packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml new file mode 100644 index 000000000000..fb1e09acddf5 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml @@ -0,0 +1,26 @@ +<!-- + ~ 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. + --> + +<resources> + <!-- Test Permission title. [DO NOT TRANSLATE] --> + <string name="test_permission_title" translatable="false">Test Permission</string> + + <!-- Test Permission switch title. [DO NOT TRANSLATE] --> + <string name="test_permission_switch_title" translatable="false">Allow Test Permission</string> + + <!-- Test Permission footer. [DO NOT TRANSLATE] --> + <string name="test_permission_footer" translatable="false">Test Permission is for demo.</string> +</resources> diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt index bc6925baacc2..c4f2df2df50c 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt @@ -40,9 +40,7 @@ import org.mockito.Mockito.`when` as whenever @OptIn(ExperimentalCoroutinesApi::class) @RunWith(AndroidJUnit4::class) class AppListRepositoryTest { - - @JvmField - @Rule + @get:Rule val mockito: MockitoRule = MockitoJUnit.rule() @Mock diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt index b570815b4180..65c547a97fd3 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt @@ -39,8 +39,7 @@ import org.mockito.junit.MockitoRule @OptIn(ExperimentalCoroutinesApi::class) @RunWith(AndroidJUnit4::class) class AppListViewModelTest { - @JvmField - @Rule + @get:Rule val mockito: MockitoRule = MockitoJUnit.rule() @Mock diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt index 4207490cba80..40026554882e 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt @@ -40,8 +40,7 @@ import org.mockito.Mockito.`when` as whenever @RunWith(AndroidJUnit4::class) class PackageManagerExtTest { - @JvmField - @Rule + @get:Rule val mockito: MockitoRule = MockitoJUnit.rule() @Mock diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt new file mode 100644 index 000000000000..c3c96c645a15 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt @@ -0,0 +1,137 @@ +/* + * 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.spaprivileged.template.app + +import android.content.Context +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithContentDescription +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spaprivileged.R +import com.android.settingslib.spaprivileged.tests.testutils.TestAppListModel +import com.android.settingslib.spaprivileged.tests.testutils.TestAppRecord +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class AppListPageTest { + @get:Rule + val composeTestRule = createComposeRule() + + private var context: Context = ApplicationProvider.getApplicationContext() + + @Test + fun title_isDisplayed() { + setContent() + + composeTestRule.onNodeWithText(TITLE).assertIsDisplayed() + } + + @Test + fun appListState_hasCorrectInitialState() { + val inputState by setContent() + + val state = inputState!!.state + assertThat(state.showSystem.value).isFalse() + assertThat(state.option.value).isEqualTo(0) + assertThat(state.searchQuery.value).isEqualTo("") + } + + @Test + fun canShowSystem() { + val inputState by setContent() + + composeTestRule.onNodeWithContentDescription( + context.getString(R.string.abc_action_menu_overflow_description) + ).performClick() + composeTestRule.onNodeWithText(context.getString(R.string.menu_show_system)).performClick() + + val state = inputState!!.state + assertThat(state.showSystem.value).isTrue() + } + + @Test + fun afterShowSystem_displayHideSystem() { + setContent() + composeTestRule.onNodeWithContentDescription( + context.getString(R.string.abc_action_menu_overflow_description) + ).performClick() + composeTestRule.onNodeWithText(context.getString(R.string.menu_show_system)).performClick() + + composeTestRule.onNodeWithContentDescription( + context.getString(R.string.abc_action_menu_overflow_description) + ).performClick() + + composeTestRule.onNodeWithText(context.getString(R.string.menu_hide_system)) + .assertIsDisplayed() + } + + @Test + fun whenHasOptions_firstOptionDisplayed() { + val inputState by setContent(options = listOf(OPTION_0, OPTION_1)) + + composeTestRule.onNodeWithText(OPTION_0).assertIsDisplayed() + composeTestRule.onNodeWithText(OPTION_1).assertDoesNotExist() + val state = inputState!!.state + assertThat(state.option.value).isEqualTo(0) + } + + @Test + fun whenHasOptions_couldSwitchOption() { + val inputState by setContent(options = listOf(OPTION_0, OPTION_1)) + + composeTestRule.onNodeWithText(OPTION_0).performClick() + composeTestRule.onNodeWithText(OPTION_1).performClick() + + composeTestRule.onNodeWithText(OPTION_1).assertIsDisplayed() + composeTestRule.onNodeWithText(OPTION_0).assertDoesNotExist() + val state = inputState!!.state + assertThat(state.option.value).isEqualTo(1) + } + + private fun setContent( + options: List<String> = emptyList(), + header: @Composable () -> Unit = {}, + ): State<AppListInput<TestAppRecord>?> { + val appListState = mutableStateOf<AppListInput<TestAppRecord>?>(null) + composeTestRule.setContent { + AppListPageImpl( + title = TITLE, + listModel = TestAppListModel(options), + header = header, + appItem = { AppListItem {} }, + appList = { appListState.value = it }, + ) + } + return appListState + } + + private companion object { + const val TITLE = "Title" + const val OPTION_0 = "Option 1" + const val OPTION_1 = "Option 2" + } +} diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt index 9f20c78485a8..df80dd4007da 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt @@ -29,14 +29,12 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settingslib.spa.framework.compose.stateOf import com.android.settingslib.spa.framework.compose.toState -import com.android.settingslib.spa.framework.util.asyncMapItem import com.android.settingslib.spaprivileged.R import com.android.settingslib.spaprivileged.model.app.AppEntry import com.android.settingslib.spaprivileged.model.app.AppListConfig import com.android.settingslib.spaprivileged.model.app.AppListData -import com.android.settingslib.spaprivileged.model.app.AppListModel -import com.android.settingslib.spaprivileged.model.app.AppRecord -import kotlinx.coroutines.flow.Flow +import com.android.settingslib.spaprivileged.tests.testutils.TestAppListModel +import com.android.settingslib.spaprivileged.tests.testutils.TestAppRecord import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -92,21 +90,19 @@ class AppListTest { enableGrouping: Boolean = false, ) { composeTestRule.setContent { - AppList( + val appListInput = AppListInput( config = AppListConfig(userId = USER_ID, showInstantApps = false), - listModel = TestAppListModel(enableGrouping), + listModel = TestAppListModel(enableGrouping = enableGrouping), state = AppListState( showSystem = false.toState(), option = 0.toState(), searchQuery = "".toState(), ), header = header, - appItem = { AppListItem(it) {} }, + appItem = { AppListItem {} }, bottomPadding = 0.dp, - appListDataSupplier = { - stateOf(AppListData(appEntries, option = 0)) - } ) + appListInput.AppList { stateOf(AppListData(appEntries, option = 0)) } } } @@ -137,25 +133,3 @@ class AppListTest { ) } } - -private data class TestAppRecord( - override val app: ApplicationInfo, - val group: String? = null, -) : AppRecord - -private class TestAppListModel(val enableGrouping: Boolean) : AppListModel<TestAppRecord> { - override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) = - appListFlow.asyncMapItem { TestAppRecord(it) } - - @Composable - override fun getSummary(option: Int, record: TestAppRecord) = null - - override fun filter( - userIdFlow: Flow<Int>, - option: Int, - recordListFlow: Flow<List<TestAppRecord>>, - ) = recordListFlow - - override fun getGroupTitle(option: Int, record: TestAppRecord) = - if (enableGrouping) record.group else null -} diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt index b3638c21ccce..8e98d8cd6975 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt @@ -41,8 +41,7 @@ import org.mockito.Mockito.`when` as whenever @RunWith(AndroidJUnit4::class) class AppStorageSizeTest { - @JvmField - @Rule + @get:Rule val mockito: MockitoRule = MockitoJUnit.rule() @get:Rule diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt new file mode 100644 index 000000000000..4bc612abb1a4 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt @@ -0,0 +1,67 @@ +/* + * 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.spaprivileged.template.app + +import android.content.Context +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spaprivileged.R +import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListModel +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class TogglePermissionAppListPageTest { + @get:Rule + val composeTestRule = createComposeRule() + + private var context: Context = ApplicationProvider.getApplicationContext() + + @Test + fun appListInjectEntry_titleDisplayed() { + val entry = TogglePermissionAppListPageProvider.buildInjectEntry(PERMISSION_TYPE) { + TestTogglePermissionAppListModel() + }.build() + + composeTestRule.setContent { + CompositionLocalProvider(LocalContext provides context) { + entry.UiLayout() + } + } + + composeTestRule.onNodeWithText(context.getString(R.string.test_permission_title)) + .assertIsDisplayed() + } + + @Test + fun appListRoute() { + val route = TogglePermissionAppListPageProvider.getRoute(PERMISSION_TYPE) + + assertThat(route).isEqualTo("TogglePermissionAppList/test.PERMISSION") + } + + private companion object { + const val PERMISSION_TYPE = "test.PERMISSION" + } +} diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt new file mode 100644 index 000000000000..af3189f2ec41 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt @@ -0,0 +1,77 @@ +/* + * 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.spaprivileged.template.app + +import android.content.Context +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spaprivileged.R +import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListModel +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class TogglePermissionAppListTest { + @get:Rule + val composeTestRule = createComposeRule() + + private var context: Context = ApplicationProvider.getApplicationContext() + + @Test + fun appListInjectEntry_titleDisplayed() { + val entry = TestTogglePermissionAppListProvider.buildAppListInjectEntry().build() + composeTestRule.setContent { + CompositionLocalProvider(LocalContext provides context) { + entry.UiLayout() + } + } + + composeTestRule.onNodeWithText(context.getString(R.string.test_permission_title)) + .assertIsDisplayed() + } + + @Test + fun appListRoute() { + val route = TestTogglePermissionAppListProvider.getAppListRoute() + + assertThat(route).isEqualTo("TogglePermissionAppList/test.PERMISSION") + } + + @Test + fun togglePermissionAppListTemplate_createPageProviders() { + val togglePermissionAppListTemplate = + TogglePermissionAppListTemplate(listOf(TestTogglePermissionAppListProvider)) + + val createPageProviders = togglePermissionAppListTemplate.createPageProviders() + + assertThat(createPageProviders).hasSize(2) + assertThat(createPageProviders.any { it is TogglePermissionAppListPageProvider }).isTrue() + assertThat(createPageProviders.any { it is TogglePermissionAppInfoPageProvider }).isTrue() + } +} + +private object TestTogglePermissionAppListProvider : TogglePermissionAppListProvider { + override val permissionType = "test.PERMISSION" + override fun createModel(context: Context) = TestTogglePermissionAppListModel() +} diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt new file mode 100644 index 000000000000..d5564877f681 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt @@ -0,0 +1,51 @@ +/* + * 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.spaprivileged.tests.testutils + +import android.content.pm.ApplicationInfo +import androidx.compose.runtime.Composable +import com.android.settingslib.spa.framework.util.asyncMapItem +import com.android.settingslib.spaprivileged.model.app.AppListModel +import com.android.settingslib.spaprivileged.model.app.AppRecord +import kotlinx.coroutines.flow.Flow + +data class TestAppRecord( + override val app: ApplicationInfo, + val group: String? = null, +) : AppRecord + +class TestAppListModel( + private val options: List<String> = emptyList(), + private val enableGrouping: Boolean = false, +) : AppListModel<TestAppRecord> { + override fun getSpinnerOptions() = options + + override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) = + appListFlow.asyncMapItem { TestAppRecord(it) } + + @Composable + override fun getSummary(option: Int, record: TestAppRecord) = null + + override fun filter( + userIdFlow: Flow<Int>, + option: Int, + recordListFlow: Flow<List<TestAppRecord>>, + ) = recordListFlow + + override fun getGroupTitle(option: Int, record: TestAppRecord) = + if (enableGrouping) record.group else null +} diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt new file mode 100644 index 000000000000..91a9c6b620af --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt @@ -0,0 +1,42 @@ +/* + * 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.spaprivileged.tests.testutils + +import android.content.pm.ApplicationInfo +import androidx.compose.runtime.Composable +import com.android.settingslib.spa.framework.compose.stateOf +import com.android.settingslib.spaprivileged.R +import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListModel +import kotlinx.coroutines.flow.Flow + +class TestTogglePermissionAppListModel : TogglePermissionAppListModel<TestAppRecord> { + override val pageTitleResId = R.string.test_permission_title + override val switchTitleResId = R.string.test_permission_switch_title + override val footerResId = R.string.test_permission_footer + + override fun transformItem(app: ApplicationInfo) = TestAppRecord(app = app) + + override fun filter(userIdFlow: Flow<Int>, recordListFlow: Flow<List<TestAppRecord>>) = + recordListFlow + + @Composable + override fun isAllowed(record: TestAppRecord) = stateOf(null) + + override fun isChangeable(record: TestAppRecord) = false + + override fun setAllowed(record: TestAppRecord, newAllowed: Boolean) {} +} diff --git a/packages/SettingsLib/TwoTargetPreference/Android.bp b/packages/SettingsLib/TwoTargetPreference/Android.bp index 3baef4b9f11b..e9c6aed53d0f 100644 --- a/packages/SettingsLib/TwoTargetPreference/Android.bp +++ b/packages/SettingsLib/TwoTargetPreference/Android.bp @@ -23,5 +23,6 @@ android_library { apex_available: [ "//apex_available:platform", "com.android.permission", + "com.android.healthconnect", ], } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java index 5c796af84fef..a36cbc0dbf95 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java @@ -115,12 +115,24 @@ public class BluetoothUtils { } List<LocalBluetoothProfile> profiles = cachedDevice.getProfiles(); + int resId = 0; for (LocalBluetoothProfile profile : profiles) { - int resId = profile.getDrawableResource(btClass); - if (resId != 0) { - return new Pair<>(getBluetoothDrawable(context, resId), null); + int profileResId = profile.getDrawableResource(btClass); + if (profileResId != 0) { + // The device should show hearing aid icon if it contains any hearing aid related + // profiles + if (profile instanceof HearingAidProfile || profile instanceof HapClientProfile) { + return new Pair<>(getBluetoothDrawable(context, profileResId), null); + } + if (resId == 0) { + resId = profileResId; + } } } + if (resId != 0) { + return new Pair<>(getBluetoothDrawable(context, resId), null); + } + if (btClass != null) { if (doesClassMatch(btClass, BluetoothClass.PROFILE_HEADSET)) { return new Pair<>( diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index b929f8c03905..61c7fb916404 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -1164,14 +1164,22 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> // Try to show left/right information if can not get it from battery for hearing // aids specifically. - if (mIsActiveDeviceHearingAid + boolean isActiveAshaHearingAid = mIsActiveDeviceHearingAid; + boolean isActiveLeAudioHearingAid = mIsActiveDeviceLeAudio + && isConnectedHapClientDevice(); + if ((isActiveAshaHearingAid || isActiveLeAudioHearingAid) && stringRes == R.string.bluetooth_active_no_battery_level) { + final Set<CachedBluetoothDevice> memberDevices = getMemberDevice(); final CachedBluetoothDevice subDevice = getSubDevice(); - if (subDevice != null && subDevice.isConnected()) { + if (memberDevices.stream().anyMatch(m -> m.isConnected())) { + stringRes = R.string.bluetooth_hearing_aid_left_and_right_active; + } else if (subDevice != null && subDevice.isConnected()) { stringRes = R.string.bluetooth_hearing_aid_left_and_right_active; } else { int deviceSide = getDeviceSide(); - if (deviceSide == HearingAidInfo.DeviceSide.SIDE_LEFT) { + if (deviceSide == HearingAidInfo.DeviceSide.SIDE_LEFT_AND_RIGHT) { + stringRes = R.string.bluetooth_hearing_aid_left_and_right_active; + } else if (deviceSide == HearingAidInfo.DeviceSide.SIDE_LEFT) { stringRes = R.string.bluetooth_hearing_aid_left_active; } else if (deviceSide == HearingAidInfo.DeviceSide.SIDE_RIGHT) { stringRes = R.string.bluetooth_hearing_aid_right_active; diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java index 132a631e25cc..8b68a09bbf65 100644 --- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java @@ -66,7 +66,7 @@ public class BatteryStatus { public BatteryStatus(Intent batteryChangedIntent) { status = batteryChangedIntent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN); plugged = batteryChangedIntent.getIntExtra(EXTRA_PLUGGED, 0); - level = batteryChangedIntent.getIntExtra(EXTRA_LEVEL, 0); + level = getBatteryLevel(batteryChangedIntent); health = batteryChangedIntent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN); present = batteryChangedIntent.getBooleanExtra(EXTRA_PRESENT, true); @@ -188,7 +188,7 @@ public class BatteryStatus { */ public static boolean isCharged(Intent batteryChangedIntent) { int status = batteryChangedIntent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN); - int level = batteryChangedIntent.getIntExtra(EXTRA_LEVEL, 0); + int level = getBatteryLevel(batteryChangedIntent); return isCharged(status, level); } @@ -204,4 +204,13 @@ public class BatteryStatus { public static boolean isCharged(int status, int level) { return status == BATTERY_STATUS_FULL || level >= 100; } + + /** Gets the battery level from the intent. */ + public static int getBatteryLevel(Intent batteryChangedIntent) { + final int level = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); + final int scale = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 0); + return scale == 0 + ? -1 /*invalid battery level*/ + : Math.round((level / (float) scale) * 100f); + } } 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 336cdd3f259f..291f6a39105b 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java @@ -317,7 +317,9 @@ public class UtilsTest { @Test public void getBatteryStatus_statusIsFull_returnFullString() { - final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100); + final Intent intent = new Intent() + .putExtra(BatteryManager.EXTRA_LEVEL, 100) + .putExtra(BatteryManager.EXTRA_SCALE, 100); final Resources resources = mContext.getResources(); assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo( @@ -326,7 +328,9 @@ public class UtilsTest { @Test public void getBatteryStatus_statusIsFullAndUseCompactStatus_returnFullyChargedString() { - final Intent intent = new Intent().putExtra(BatteryManager.EXTRA_LEVEL, 100); + final Intent intent = new Intent() + .putExtra(BatteryManager.EXTRA_LEVEL, 100) + .putExtra(BatteryManager.EXTRA_SCALE, 100); final Resources resources = mContext.getResources(); assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ true)).isEqualTo( diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java index 1f518ec17f55..65671a26690e 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java @@ -29,6 +29,7 @@ import static org.mockito.Mockito.when; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothLeAudio; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothStatusCodes; import android.content.Context; @@ -447,6 +448,26 @@ public class CachedBluetoothDeviceTest { } @Test + public void getConnectionSummary_testActiveDeviceLeAudioHearingAid() { + // Test without battery level + // Set HAP Client and LE Audio profile to be connected and test connection state summary + when(mProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile); + updateProfileStatus(mHapClientProfile, BluetoothProfile.STATE_CONNECTED); + updateProfileStatus(mLeAudioProfile, BluetoothProfile.STATE_CONNECTED); + assertThat(mCachedDevice.getConnectionSummary()).isNull(); + + // Set device as Active for LE Audio and test connection state summary + mCachedDevice.setHearingAidInfo(getLeftLeAudioHearingAidInfo()); + mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.LE_AUDIO); + assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active, left only"); + + // Set LE Audio profile to be disconnected and test connection state summary + mCachedDevice.onActiveDeviceChanged(false, BluetoothProfile.LE_AUDIO); + mCachedDevice.onProfileStateChanged(mLeAudioProfile, BluetoothProfile.STATE_DISCONNECTED); + assertThat(mCachedDevice.getConnectionSummary()).isNull(); + } + + @Test public void getConnectionSummary_testMultipleProfilesActiveDevice() { // Test without battery level // Set A2DP and HFP profiles to be connected and test connection state summary @@ -1110,9 +1131,16 @@ public class CachedBluetoothDeviceTest { .setAshaDeviceSide(HearingAidProfile.DeviceSide.SIDE_LEFT) .build(); } + private HearingAidInfo getRightAshaHearingAidInfo() { return new HearingAidInfo.Builder() .setAshaDeviceSide(HearingAidProfile.DeviceSide.SIDE_RIGHT) .build(); } + + private HearingAidInfo getLeftLeAudioHearingAidInfo() { + return new HearingAidInfo.Builder() + .setLeAudioLocation(BluetoothLeAudio.AUDIO_LOCATION_SIDE_LEFT) + .build(); + } } diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 7c3948a19792..87354c7b01f4 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -168,25 +168,14 @@ java_library { } android_library { - name: "SystemUI-tests", + name: "SystemUI-tests-base", manifest: "tests/AndroidManifest-base.xml", - additional_manifests: ["tests/AndroidManifest.xml"], - resource_dirs: [ "tests/res", "res-product", "res-keyguard", "res", ], - srcs: [ - "tests/src/**/*.kt", - "tests/src/**/*.java", - "src/**/*.kt", - "src/**/*.java", - "src/**/I*.aidl", - ":ReleaseJavaFiles", - ":SystemUI-tests-utils", - ], static_libs: [ "WifiTrackerLib", "SystemUIAnimationLib", @@ -225,9 +214,6 @@ android_library { "metrics-helper-lib", "hamcrest-library", "androidx.test.rules", - "androidx.test.uiautomator_uiautomator", - "mockito-target-extended-minus-junit4", - "androidx.test.ext.junit", "testables", "truth-prebuilt", "monet", @@ -237,6 +223,27 @@ android_library { "LowLightDreamLib", "motion_tool_lib", ], +} + +android_library { + name: "SystemUI-tests", + manifest: "tests/AndroidManifest-base.xml", + additional_manifests: ["tests/AndroidManifest.xml"], + srcs: [ + "tests/src/**/*.kt", + "tests/src/**/*.java", + "src/**/*.kt", + "src/**/*.java", + "src/**/I*.aidl", + ":ReleaseJavaFiles", + ":SystemUI-tests-utils", + ], + static_libs: [ + "SystemUI-tests-base", + "androidx.test.uiautomator_uiautomator", + "mockito-target-extended-minus-junit4", + "androidx.test.ext.junit", + ], libs: [ "android.test.runner", "android.test.base", @@ -253,6 +260,45 @@ android_library { }, } +android_app { + name: "SystemUIRobo-stub", + defaults: [ + "platform_app_defaults", + "SystemUI_app_defaults", + ], + manifest: "tests/AndroidManifest-base.xml", + static_libs: [ + "SystemUI-tests-base", + ], + aaptflags: [ + "--extra-packages", + "com.android.systemui", + ], + dont_merge_manifests: true, + platform_apis: true, + system_ext_specific: true, + certificate: "platform", + privileged: true, + resource_dirs: [], +} + +android_robolectric_test { + name: "SystemUiRoboTests", + srcs: [ + "tests/robolectric/src/**/*.kt", + "tests/robolectric/src/**/*.java", + ], + libs: [ + "android.test.runner", + "android.test.base", + "android.test.mock", + "truth-prebuilt", + ], + kotlincflags: ["-Xjvm-default=enable"], + instrumentation_for: "SystemUIRobo-stub", + java_resource_dirs: ["tests/robolectric/config"], +} + // Opt-out config for optimizing the SystemUI target using R8. // Disabled via `export SYSTEMUI_OPTIMIZE_JAVA=false`, or explicitly in Make via // `SYSTEMUI_OPTIMIZE_JAVA := false`. diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt index f9c6841f96b5..43bfa74119b3 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt @@ -320,9 +320,7 @@ class RemoteTransitionAdapter { counterWallpaper.cleanUp(finishTransaction) // Release surface references now. This is apparently to free GPU // memory while doing quick operations (eg. during CTS). - for (i in info.changes.indices.reversed()) { - info.changes[i].leash.release() - } + info.releaseAllSurfaces() for (i in leashMap.size - 1 downTo 0) { leashMap.valueAt(i).release() } @@ -331,6 +329,7 @@ class RemoteTransitionAdapter { null /* wct */, finishTransaction ) + finishTransaction.close() } catch (e: RemoteException) { Log.e( "ActivityOptionsCompat", @@ -364,6 +363,9 @@ class RemoteTransitionAdapter { ) { // TODO: hook up merge to recents onTaskAppeared if applicable. Until then, // ignore any incoming merges. + // Clean up stuff though cuz GC takes too long for benchmark tests. + t.close() + info.releaseAllSurfaces() } } } diff --git a/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt b/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt index e611e8bf0068..979e1a08b7d4 100644 --- a/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt +++ b/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt @@ -38,12 +38,18 @@ import platform.test.screenshot.ScreenshotTestRule import platform.test.screenshot.getEmulatedDevicePathConfig /** A rule for Compose screenshot diff tests. */ -class ComposeScreenshotTestRule(emulationSpec: DeviceEmulationSpec) : TestRule { +class ComposeScreenshotTestRule( + emulationSpec: DeviceEmulationSpec, + assetPathRelativeToBuildRoot: String +) : TestRule { private val colorsRule = MaterialYouColorsRule() private val deviceEmulationRule = DeviceEmulationRule(emulationSpec) private val screenshotRule = ScreenshotTestRule( - SystemUIGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec)) + SystemUIGoldenImagePathManager( + getEmulatedDevicePathConfig(emulationSpec), + assetPathRelativeToBuildRoot + ) ) private val composeRule = createAndroidComposeRule<ScreenshotActivity>() private val delegateRule = diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt index 49cc48321d77..e032bb9b7e30 100644 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt +++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ExternalViewScreenshotTestRule.kt @@ -34,13 +34,19 @@ import platform.test.screenshot.* /** * A rule that allows to run a screenshot diff test on a view that is hosted in another activity. */ -class ExternalViewScreenshotTestRule(emulationSpec: DeviceEmulationSpec) : TestRule { +class ExternalViewScreenshotTestRule( + emulationSpec: DeviceEmulationSpec, + assetPathRelativeToBuildRoot: String +) : TestRule { private val colorsRule = MaterialYouColorsRule() private val deviceEmulationRule = DeviceEmulationRule(emulationSpec) private val screenshotRule = ScreenshotTestRule( - SystemUIGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec)) + SystemUIGoldenImagePathManager( + getEmulatedDevicePathConfig(emulationSpec), + assetPathRelativeToBuildRoot + ) ) private val delegateRule = RuleChain.outerRule(colorsRule).around(deviceEmulationRule).around(screenshotRule) diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt index fafc7744f439..72d8c5a09852 100644 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt +++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt @@ -23,11 +23,11 @@ import platform.test.screenshot.PathConfig /** A [GoldenImagePathManager] that should be used for all SystemUI screenshot tests. */ class SystemUIGoldenImagePathManager( pathConfig: PathConfig, - override val assetsPathRelativeToRepo: String = "tests/screenshot/assets" + assetsPathRelativeToBuildRoot: String ) : GoldenImagePathManager( appContext = InstrumentationRegistry.getInstrumentation().context, - assetsPathRelativeToRepo = assetsPathRelativeToRepo, + assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot, deviceLocalPath = InstrumentationRegistry.getInstrumentation() .targetContext diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt index 36ac1ff9ad30..6d0cc5ee4feb 100644 --- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt +++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt @@ -44,17 +44,16 @@ class ViewScreenshotTestRule( emulationSpec: DeviceEmulationSpec, private val matcher: BitmapMatcher = UnitTestBitmapMatcher, pathConfig: PathConfig = getEmulatedDevicePathConfig(emulationSpec), - assetsPathRelativeToRepo: String = "" + assetsPathRelativeToBuildRoot: String ) : TestRule { private val colorsRule = MaterialYouColorsRule() private val deviceEmulationRule = DeviceEmulationRule(emulationSpec) private val screenshotRule = ScreenshotTestRule( - if (assetsPathRelativeToRepo.isBlank()) { - SystemUIGoldenImagePathManager(pathConfig) - } else { - SystemUIGoldenImagePathManager(pathConfig, assetsPathRelativeToRepo) - } + SystemUIGoldenImagePathManager( + getEmulatedDevicePathConfig(emulationSpec), + assetsPathRelativeToBuildRoot + ) ) private val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java) private val delegateRule = diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java index 93c807352521..1b0dacc327c1 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java @@ -166,15 +166,14 @@ public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner counterLauncher.cleanUp(finishTransaction); counterWallpaper.cleanUp(finishTransaction); // Release surface references now. This is apparently to free GPU memory - // while doing quick operations (eg. during CTS). - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - info.getChanges().get(i).getLeash().release(); - } + // before GC would. + info.releaseAllSurfaces(); // Don't release here since launcher might still be using them. Instead // let launcher release them (eg. via RemoteAnimationTargets) leashMap.clear(); try { finishCallback.onTransitionFinished(null /* wct */, finishTransaction); + finishTransaction.close(); } catch (RemoteException e) { Log.e("ActivityOptionsCompat", "Failed to call app controlled animation" + " finished callback", e); @@ -203,10 +202,13 @@ public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner synchronized (mFinishRunnables) { finishRunnable = mFinishRunnables.remove(mergeTarget); } + // Since we're not actually animating, release native memory now + t.close(); + info.releaseAllSurfaces(); if (finishRunnable == null) return; onAnimationCancelled(false /* isKeyguardOccluded */); finishRunnable.run(); } }; } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java index d4d3d2579b10..b7e2494ab839 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java @@ -126,15 +126,18 @@ public class RemoteTransitionCompat { public void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, IBinder mergeTarget, IRemoteTransitionFinishedCallback finishedCallback) { - if (!mergeTarget.equals(mToken)) return; - if (!mRecentsSession.merge(info, t, recents)) return; - try { - finishedCallback.onTransitionFinished(null /* wct */, null /* sct */); - } catch (RemoteException e) { - Log.e(TAG, "Error merging transition.", e); + if (mergeTarget.equals(mToken) && mRecentsSession.merge(info, t, recents)) { + try { + finishedCallback.onTransitionFinished(null /* wct */, null /* sct */); + } catch (RemoteException e) { + Log.e(TAG, "Error merging transition.", e); + } + // commit taskAppeared after merge transition finished. + mRecentsSession.commitTasksAppearedIfNeeded(recents); + } else { + t.close(); + info.releaseAllSurfaces(); } - // commit taskAppeared after merge transition finished. - mRecentsSession.commitTasksAppearedIfNeeded(recents); } }; return new RemoteTransition(remote, appThread); @@ -248,6 +251,8 @@ public class RemoteTransitionCompat { } // In this case, we are "returning" to an already running app, so just consume // the merge and do nothing. + info.releaseAllSurfaces(); + t.close(); return true; } final int layer = mInfo.getChanges().size() * 3; @@ -264,6 +269,8 @@ public class RemoteTransitionCompat { t.setLayer(targets[i].leash, layer); } t.apply(); + // not using the incoming anim-only surfaces + info.releaseAnimSurfaces(); mAppearedTargets = targets; return true; } @@ -380,9 +387,7 @@ public class RemoteTransitionCompat { } // Only release the non-local created surface references. The animator is responsible // for releasing the leashes created by local. - for (int i = 0; i < mInfo.getChanges().size(); ++i) { - mInfo.getChanges().get(i).getLeash().release(); - } + mInfo.releaseAllSurfaces(); // Reset all members. mWrapped = null; mFinishCB = null; diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java index c5190e828e35..ea808eb19b90 100644 --- a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java +++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java @@ -135,7 +135,7 @@ public class EmergencyButtonController extends ViewController<EmergencyButton> { mPowerManager.userActivity(SystemClock.uptimeMillis(), true); } mActivityTaskManager.stopSystemLockTaskMode(); - mShadeController.collapsePanel(false); + mShadeController.collapseShade(false); if (mTelecomManager != null && mTelecomManager.isInCall()) { mTelecomManager.showInCallScreen(false); if (mEmergencyButtonCallback != null) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt index 819768544b0c..e6283b86283b 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt @@ -26,7 +26,6 @@ data class KeyguardFingerprintListenModel( val credentialAttempted: Boolean, val deviceInteractive: Boolean, val dreaming: Boolean, - val encryptedOrLockdown: Boolean, val fingerprintDisabled: Boolean, val fingerprintLockedOut: Boolean, val goingToSleep: Boolean, @@ -37,6 +36,7 @@ data class KeyguardFingerprintListenModel( val primaryUser: Boolean, val shouldListenSfpsState: Boolean, val shouldListenForFingerprintAssistant: Boolean, + val strongerAuthRequired: Boolean, val switchingUser: Boolean, val udfps: Boolean, val userDoesNotHaveTrust: Boolean diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 01be33e1e156..4d0a273a2189 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -363,16 +363,18 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard final boolean sfpsEnabled = getResources().getBoolean( R.bool.config_show_sidefps_hint_on_bouncer); final boolean fpsDetectionRunning = mUpdateMonitor.isFingerprintDetectionRunning(); - final boolean needsStrongAuth = mUpdateMonitor.userNeedsStrongAuth(); + final boolean isUnlockingWithFpAllowed = + mUpdateMonitor.isUnlockingWithFingerprintAllowed(); - boolean toShow = mBouncerVisible && sfpsEnabled && fpsDetectionRunning && !needsStrongAuth; + boolean toShow = mBouncerVisible && sfpsEnabled && fpsDetectionRunning + && isUnlockingWithFpAllowed; if (DEBUG) { Log.d(TAG, "sideFpsToShow=" + toShow + ", " + "mBouncerVisible=" + mBouncerVisible + ", " + "configEnabled=" + sfpsEnabled + ", " + "fpsDetectionRunning=" + fpsDetectionRunning + ", " - + "needsStrongAuth=" + needsStrongAuth); + + "isUnlockingWithFpAllowed=" + isUnlockingWithFpAllowed); } if (toShow) { mSideFpsController.get().show(SideFpsUiRequestSource.PRIMARY_BOUNCER); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 39ade347dd9b..993d80f49182 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -27,6 +27,8 @@ import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_P import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_TIMED; import static android.hardware.biometrics.BiometricConstants.LockoutMode; import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START; +import static android.hardware.biometrics.BiometricSourceType.FACE; +import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT; import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT; @@ -228,7 +230,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * Biometric authentication: Cancelling and waiting for the relevant biometric service to * send us the confirmation that cancellation has happened. */ - private static final int BIOMETRIC_STATE_CANCELLING = 2; + @VisibleForTesting + protected static final int BIOMETRIC_STATE_CANCELLING = 2; + + /** + * Biometric state: During cancelling we got another request to start listening, so when we + * receive the cancellation done signal, we should start listening again. + */ + @VisibleForTesting + protected static final int BIOMETRIC_STATE_CANCELLING_RESTARTING = 3; /** * Action indicating keyguard *can* start biometric authentiation. @@ -243,12 +253,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab */ private static final int BIOMETRIC_ACTION_UPDATE = 2; - /** - * Biometric state: During cancelling we got another request to start listening, so when we - * receive the cancellation done signal, we should start listening again. - */ - private static final int BIOMETRIC_STATE_CANCELLING_RESTARTING = 3; - @VisibleForTesting public static final int BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED = -1; public static final int BIOMETRIC_HELP_FACE_NOT_RECOGNIZED = -2; @@ -356,7 +360,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private KeyguardBypassController mKeyguardBypassController; private List<SubscriptionInfo> mSubscriptionInfo; - private int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED; + @VisibleForTesting + protected int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED; private int mFaceRunningState = BIOMETRIC_STATE_STOPPED; private boolean mIsDreaming; private boolean mLogoutEnabled; @@ -790,7 +795,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab new BiometricAuthenticated(true, isStrongBiometric)); // Update/refresh trust state only if user can skip bouncer if (getUserCanSkipBouncer(userId)) { - mTrustManager.unlockedByBiometricForUser(userId, BiometricSourceType.FINGERPRINT); + mTrustManager.unlockedByBiometricForUser(userId, FINGERPRINT); } // Don't send cancel if authentication succeeds mFingerprintCancelSignal = null; @@ -800,7 +805,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { - cb.onBiometricAuthenticated(userId, BiometricSourceType.FINGERPRINT, + cb.onBiometricAuthenticated(userId, FINGERPRINT, isStrongBiometric); } } @@ -833,7 +838,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { - cb.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT); + cb.onBiometricAuthFailed(FINGERPRINT); } } if (isUdfpsSupported()) { @@ -858,7 +863,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { - cb.onBiometricAcquired(BiometricSourceType.FINGERPRINT, acquireInfo); + cb.onBiometricAcquired(FINGERPRINT, acquireInfo); } } } @@ -892,7 +897,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { - cb.onBiometricHelp(msgId, helpString, BiometricSourceType.FINGERPRINT); + cb.onBiometricHelp(msgId, helpString, FINGERPRINT); } } } @@ -944,7 +949,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) { lockedOutStateChanged = !mFingerprintLockedOutPermanent; mFingerprintLockedOutPermanent = true; - mLogger.d("Fingerprint locked out - requiring strong auth"); + mLogger.d("Fingerprint permanently locked out - requiring stronger auth"); mLockPatternUtils.requireStrongAuth( STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, getCurrentUser()); } @@ -953,6 +958,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab || msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) { lockedOutStateChanged |= !mFingerprintLockedOut; mFingerprintLockedOut = true; + mLogger.d("Fingerprint temporarily locked out - requiring stronger auth"); if (isUdfpsEnrolled()) { updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); } @@ -963,12 +969,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { - cb.onBiometricError(msgId, errString, BiometricSourceType.FINGERPRINT); + cb.onBiometricError(msgId, errString, FINGERPRINT); } } if (lockedOutStateChanged) { - notifyLockedOutStateChanged(BiometricSourceType.FINGERPRINT); + notifyLockedOutStateChanged(FINGERPRINT); } } @@ -996,7 +1002,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } if (changed) { - notifyLockedOutStateChanged(BiometricSourceType.FINGERPRINT); + notifyLockedOutStateChanged(FINGERPRINT); } } @@ -1019,7 +1025,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { cb.onBiometricRunningStateChanged(isFingerprintDetectionRunning(), - BiometricSourceType.FINGERPRINT); + FINGERPRINT); } } } @@ -1032,7 +1038,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab new BiometricAuthenticated(true, isStrongBiometric)); // Update/refresh trust state only if user can skip bouncer if (getUserCanSkipBouncer(userId)) { - mTrustManager.unlockedByBiometricForUser(userId, BiometricSourceType.FACE); + mTrustManager.unlockedByBiometricForUser(userId, FACE); } // Don't send cancel if authentication succeeds mFaceCancelSignal = null; @@ -1043,7 +1049,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { cb.onBiometricAuthenticated(userId, - BiometricSourceType.FACE, + FACE, isStrongBiometric); } } @@ -1065,7 +1071,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { - cb.onBiometricAuthFailed(BiometricSourceType.FACE); + cb.onBiometricAuthFailed(FACE); } } handleFaceHelp(BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, @@ -1078,7 +1084,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { - cb.onBiometricAcquired(BiometricSourceType.FACE, acquireInfo); + cb.onBiometricAcquired(FACE, acquireInfo); } } } @@ -1113,7 +1119,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { - cb.onBiometricHelp(msgId, helpString, BiometricSourceType.FACE); + cb.onBiometricHelp(msgId, helpString, FACE); } } } @@ -1181,12 +1187,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { cb.onBiometricError(msgId, errString, - BiometricSourceType.FACE); + FACE); } } if (lockedOutStateChanged) { - notifyLockedOutStateChanged(BiometricSourceType.FACE); + notifyLockedOutStateChanged(FACE); } } @@ -1200,7 +1206,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab FACE_AUTH_TRIGGERED_FACE_LOCKOUT_RESET), getBiometricLockoutDelay()); if (changed) { - notifyLockedOutStateChanged(BiometricSourceType.FACE); + notifyLockedOutStateChanged(FACE); } } @@ -1223,7 +1229,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { cb.onBiometricRunningStateChanged(isFaceDetectionRunning(), - BiometricSourceType.FACE); + FACE); } } } @@ -1364,7 +1370,39 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } public boolean isUnlockingWithBiometricAllowed(boolean isStrongBiometric) { - return mStrongAuthTracker.isUnlockingWithBiometricAllowed(isStrongBiometric); + // StrongAuthTracker#isUnlockingWithBiometricAllowed includes + // STRONG_AUTH_REQUIRED_AFTER_LOCKOUT which is the same as mFingerprintLockedOutPermanent; + // however the strong auth tracker does not include the temporary lockout + // mFingerprintLockedOut. + return mStrongAuthTracker.isUnlockingWithBiometricAllowed(isStrongBiometric) + && !mFingerprintLockedOut; + } + + private boolean isUnlockingWithFaceAllowed() { + return mStrongAuthTracker.isUnlockingWithBiometricAllowed(false); + } + + /** + * Whether fingerprint is allowed ot be used for unlocking based on the strongAuthTracker + * and temporary lockout state (tracked by FingerprintManager via error codes). + */ + public boolean isUnlockingWithFingerprintAllowed() { + return isUnlockingWithBiometricAllowed(true); + } + + /** + * Whether the given biometric is allowed based on strongAuth & lockout states. + */ + public boolean isUnlockingWithBiometricAllowed( + @NonNull BiometricSourceType biometricSourceType) { + switch (biometricSourceType) { + case FINGERPRINT: + return isUnlockingWithFingerprintAllowed(); + case FACE: + return isUnlockingWithFaceAllowed(); + default: + return false; + } } public boolean isUserInLockdown(int userId) { @@ -1386,11 +1424,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return isEncrypted || isLockDown; } - public boolean userNeedsStrongAuth() { - return mStrongAuthTracker.getStrongAuthForUser(getCurrentUser()) - != LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED; - } - private boolean containsFlag(int haystack, int needle) { return (haystack & needle) != 0; } @@ -1560,12 +1593,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } }; - private final FingerprintManager.FingerprintDetectionCallback mFingerprintDetectionCallback - = (sensorId, userId, isStrongBiometric) -> { - // Trigger the fingerprint success path so the bouncer can be shown - handleFingerprintAuthenticated(userId, isStrongBiometric); - }; - /** * Propagates a pointer down event to keyguard. */ @@ -2636,27 +2663,25 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab && (!mKeyguardGoingAway || !mDeviceInteractive) && mIsPrimaryUser && biometricEnabledForUser; - - final boolean shouldListenBouncerState = !(mFingerprintLockedOut - && mPrimaryBouncerIsOrWillBeShowing && mCredentialAttempted); - - final boolean isEncryptedOrLockdownForUser = isEncryptedOrLockdown(user); + final boolean strongerAuthRequired = !isUnlockingWithFingerprintAllowed(); + final boolean isSideFps = isSfpsSupported() && isSfpsEnrolled(); + final boolean shouldListenBouncerState = + !strongerAuthRequired || !mPrimaryBouncerIsOrWillBeShowing; final boolean shouldListenUdfpsState = !isUdfps || (!userCanSkipBouncer - && !isEncryptedOrLockdownForUser + && !strongerAuthRequired && userDoesNotHaveTrust); boolean shouldListenSideFpsState = true; - if (isSfpsSupported() && isSfpsEnrolled()) { + if (isSideFps) { shouldListenSideFpsState = mSfpsRequireScreenOnToAuthPrefEnabled ? isDeviceInteractive() : true; } boolean shouldListen = shouldListenKeyguardState && shouldListenUserState - && shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut() + && shouldListenBouncerState && shouldListenUdfpsState && shouldListenSideFpsState; - maybeLogListenerModelData( new KeyguardFingerprintListenModel( System.currentTimeMillis(), @@ -2668,7 +2693,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mCredentialAttempted, mDeviceInteractive, mIsDreaming, - isEncryptedOrLockdownForUser, fingerprintDisabledForUser, mFingerprintLockedOut, mGoingToSleep, @@ -2679,6 +2703,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mIsPrimaryUser, shouldListenSideFpsState, shouldListenForFingerprintAssistant, + strongerAuthRequired, mSwitchingUser, isUdfps, userDoesNotHaveTrust)); @@ -2706,10 +2731,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab final boolean isEncryptedOrTimedOut = containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT) || containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_TIMEOUT); - - // TODO: always disallow when fp is already locked out? - final boolean fpLockedOut = mFingerprintLockedOut || mFingerprintLockedOutPermanent; - + final boolean fpLockedOut = isFingerprintLockedOut(); final boolean canBypass = mKeyguardBypassController != null && mKeyguardBypassController.canBypass(); // There's no reason to ask the HAL for authentication when the user can dismiss the @@ -2831,15 +2853,22 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab // Waiting for restart via handleFingerprintError(). return; } - mLogger.v("startListeningForFingerprint()"); if (unlockPossible) { mFingerprintCancelSignal = new CancellationSignal(); - if (isEncryptedOrLockdown(userId)) { - mFpm.detectFingerprint(mFingerprintCancelSignal, mFingerprintDetectionCallback, + if (!isUnlockingWithFingerprintAllowed()) { + mLogger.v("startListeningForFingerprint - detect"); + mFpm.detectFingerprint( + mFingerprintCancelSignal, + (sensorId, user, isStrongBiometric) -> { + mLogger.d("fingerprint detected"); + // Trigger the fingerprint success path so the bouncer can be shown + handleFingerprintAuthenticated(user, isStrongBiometric); + }, userId); } else { + mLogger.v("startListeningForFingerprint - authenticate"); mFpm.authenticate(null /* crypto */, mFingerprintCancelSignal, mFingerprintAuthenticationCallback, null /* handler */, FingerprintManager.SENSOR_ID_ANY, userId, 0 /* flags */); @@ -3056,11 +3085,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } + // Immediately stop previous biometric listening states. + // Resetting lockout states updates the biometric listening states. if (mFaceManager != null && !mFaceSensorProperties.isEmpty()) { + stopListeningForFace(FACE_AUTH_UPDATED_USER_SWITCHING); handleFaceLockoutReset(mFaceManager.getLockoutModeForUser( mFaceSensorProperties.get(0).sensorId, userId)); } if (mFpm != null && !mFingerprintSensorProperties.isEmpty()) { + stopListeningForFingerprint(); handleFingerprintLockoutReset(mFpm.getLockoutModeForUser( mFingerprintSensorProperties.get(0).sensorId, userId)); } @@ -3467,7 +3500,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @AnyThread public void setSwitchingUser(boolean switching) { mSwitchingUser = switching; - // Since this comes in on a binder thread, we need to post if first + // Since this comes in on a binder thread, we need to post it first mHandler.post(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, FACE_AUTH_UPDATED_USER_SWITCHING)); } @@ -3559,8 +3592,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab Assert.isMainThread(); mUserFingerprintAuthenticated.clear(); mUserFaceAuthenticated.clear(); - mTrustManager.clearAllBiometricRecognized(BiometricSourceType.FINGERPRINT, unlockedUser); - mTrustManager.clearAllBiometricRecognized(BiometricSourceType.FACE, unlockedUser); + mTrustManager.clearAllBiometricRecognized(FINGERPRINT, unlockedUser); + mTrustManager.clearAllBiometricRecognized(FACE, unlockedUser); mLogger.d("clearBiometricRecognized"); for (int i = 0; i < mCallbacks.size(); i++) { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java index d60cc7579ea8..50449b0936aa 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java @@ -52,6 +52,7 @@ import com.android.internal.util.ScreenshotHelper; import com.android.systemui.CoreStartable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.recents.Recents; +import com.android.systemui.shade.ShadeController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.CentralSurfaces; @@ -183,15 +184,18 @@ public class SystemActions implements CoreStartable { private final AccessibilityManager mA11yManager; private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy; private final NotificationShadeWindowController mNotificationShadeController; + private final ShadeController mShadeController; private final StatusBarWindowCallback mNotificationShadeCallback; private boolean mDismissNotificationShadeActionRegistered; @Inject public SystemActions(Context context, NotificationShadeWindowController notificationShadeController, + ShadeController shadeController, Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy, Optional<Recents> recentsOptional) { mContext = context; + mShadeController = shadeController; mRecentsOptional = recentsOptional; mReceiver = new SystemActionsBroadcastReceiver(); mLocale = mContext.getResources().getConfiguration().getLocales().get(0); @@ -529,9 +533,7 @@ public class SystemActions implements CoreStartable { } private void handleAccessibilityDismissNotificationShade() { - mCentralSurfacesOptionalLazy.get().ifPresent( - centralSurfaces -> centralSurfaces.animateCollapsePanels( - CommandQueue.FLAG_EXCLUDE_NONE, false /* force */)); + mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE); } private void handleDpadUp() { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index fc5f4470cc3c..6ac54feeb935 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -116,9 +116,9 @@ class AuthRippleController @Inject constructor( notificationShadeWindowController.setForcePluginOpen(false, this) } - fun showUnlockRipple(biometricSourceType: BiometricSourceType?) { + fun showUnlockRipple(biometricSourceType: BiometricSourceType) { if (!keyguardStateController.isShowing || - keyguardUpdateMonitor.userNeedsStrongAuth()) { + !keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(biometricSourceType)) { return } @@ -246,7 +246,7 @@ class AuthRippleController @Inject constructor( object : KeyguardUpdateMonitorCallback() { override fun onBiometricAuthenticated( userId: Int, - biometricSourceType: BiometricSourceType?, + biometricSourceType: BiometricSourceType, isStrongBiometric: Boolean ) { if (biometricSourceType == BiometricSourceType.FINGERPRINT) { @@ -255,14 +255,14 @@ class AuthRippleController @Inject constructor( showUnlockRipple(biometricSourceType) } - override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType?) { + override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType) { if (biometricSourceType == BiometricSourceType.FINGERPRINT) { mView.retractDwellRipple() } } override fun onBiometricAcquired( - biometricSourceType: BiometricSourceType?, + biometricSourceType: BiometricSourceType, acquireInfo: Int ) { if (biometricSourceType == BiometricSourceType.FINGERPRINT && diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 021431399ab6..e631816991aa 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -220,6 +220,7 @@ public class KeyguardService extends Service { synchronized (mFinishCallbacks) { if (mFinishCallbacks.remove(transition) == null) return; } + info.releaseAllSurfaces(); Slog.d(TAG, "Finish IRemoteAnimationRunner."); finishCallback.onTransitionFinished(null /* wct */, null /* t */); } @@ -235,6 +236,8 @@ public class KeyguardService extends Service { synchronized (mFinishCallbacks) { origFinishCB = mFinishCallbacks.remove(transition); } + info.releaseAllSurfaces(); + t.close(); if (origFinishCB == null) { // already finished (or not started yet), so do nothing. return; @@ -423,12 +426,15 @@ public class KeyguardService extends Service { t.apply(); mBinder.setOccluded(true /* isOccluded */, true /* animate */); finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); + info.releaseAllSurfaces(); } @Override public void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, IBinder mergeTarget, IRemoteTransitionFinishedCallback finishCallback) { + t.close(); + info.releaseAllSurfaces(); } }; @@ -440,12 +446,15 @@ public class KeyguardService extends Service { t.apply(); mBinder.setOccluded(false /* isOccluded */, true /* animate */); finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); + info.releaseAllSurfaces(); } @Override public void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, IBinder mergeTarget, IRemoteTransitionFinishedCallback finishCallback) { + t.close(); + info.releaseAllSurfaces(); } }; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 5ed3ba76cc22..948239a58840 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -870,7 +870,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, @Override public void onLaunchAnimationEnd(boolean launchIsFullScreen) { if (launchIsFullScreen) { - mCentralSurfaces.instantCollapseNotificationPanel(); + mShadeController.get().instantCollapseShade(); } mOccludeAnimationPlaying = false; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt index 84a80744185e..2cf5fb98d07e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.domain.interactor import android.content.res.ColorStateList +import android.hardware.biometrics.BiometricSourceType import android.os.Handler import android.os.Trace import android.os.UserHandle @@ -71,7 +72,7 @@ constructor( KeyguardUpdateMonitor.getCurrentUser() ) && !needsFullscreenBouncer() && - !keyguardUpdateMonitor.userNeedsStrongAuth() && + keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE) && !keyguardBypassController.bypassEnabled /** Runnable to show the primary bouncer. */ diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt index b252be1b4cdc..f7a9bc760caf 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt @@ -1053,18 +1053,9 @@ constructor( rootOverlay!!.add(mediaFrame) } else { val targetHost = getHost(newLocation)!!.hostView - // When adding back to the host, let's make sure to reset the bounds. - // Usually adding the view will trigger a layout that does this automatically, - // but we sometimes suppress this. + // This will either do a full layout pass and remeasure, or it will bypass + // that and directly set the mediaFrame's bounds within the premeasured host. targetHost.addView(mediaFrame) - val left = targetHost.paddingLeft - val top = targetHost.paddingTop - mediaFrame.setLeftTopRightBottom( - left, - top, - left + currentBounds.width(), - top + currentBounds.height() - ) if (mediaFrame.childCount > 0) { val child = mediaFrame.getChildAt(0) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt index 4bf3031c02b4..4feb9844cb8b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt @@ -420,7 +420,9 @@ constructor( */ fun getMeasurementsForState(hostState: MediaHostState): MeasurementOutput? = traceSection("MediaViewController#getMeasurementsForState") { - val viewState = obtainViewState(hostState) ?: return null + // measurements should never factor in the squish fraction + val viewState = + obtainViewState(hostState.copy().also { it.squishFraction = 1.0f }) ?: return null measurement.measuredWidth = viewState.width measurement.measuredHeight = viewState.height return measurement diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 10d31ea2d277..57b256e7b4a9 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -84,6 +84,8 @@ import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityManager; import android.widget.Toast; +import android.window.OnBackInvokedCallback; +import android.window.OnBackInvokedDispatcher; import android.window.WindowContext; import androidx.concurrent.futures.CallbackToFutureAdapter; @@ -279,6 +281,13 @@ public class ScreenshotController { private final ActionIntentExecutor mActionExecutor; private final UserManager mUserManager; + private final OnBackInvokedCallback mOnBackInvokedCallback = () -> { + if (DEBUG_INPUT) { + Log.d(TAG, "Predictive Back callback dispatched"); + } + respondToBack(); + }; + private ScreenshotView mScreenshotView; private Bitmap mScreenBitmap; private SaveImageInBackgroundTask mSaveInBgTask; @@ -465,6 +474,10 @@ public class ScreenshotController { } } + private void respondToBack() { + dismissScreenshot(SCREENSHOT_DISMISSED_OTHER); + } + /** * Update resources on configuration change. Reinflate for theme/color changes. */ @@ -476,6 +489,26 @@ public class ScreenshotController { // Inflate the screenshot layout mScreenshotView = (ScreenshotView) LayoutInflater.from(mContext).inflate(R.layout.screenshot, null); + mScreenshotView.addOnAttachStateChangeListener( + new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(@NonNull View v) { + if (DEBUG_INPUT) { + Log.d(TAG, "Registering Predictive Back callback"); + } + mScreenshotView.findOnBackInvokedDispatcher().registerOnBackInvokedCallback( + OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback); + } + + @Override + public void onViewDetachedFromWindow(@NonNull View v) { + if (DEBUG_INPUT) { + Log.d(TAG, "Unregistering Predictive Back callback"); + } + mScreenshotView.findOnBackInvokedDispatcher() + .unregisterOnBackInvokedCallback(mOnBackInvokedCallback); + } + }); mScreenshotView.init(mUiEventLogger, new ScreenshotView.ScreenshotViewCallback() { @Override public void onUserInteraction() { @@ -503,7 +536,7 @@ public class ScreenshotController { if (DEBUG_INPUT) { Log.d(TAG, "onKeyEvent: KeyEvent.KEYCODE_BACK"); } - dismissScreenshot(SCREENSHOT_DISMISSED_OTHER); + respondToBack(); return true; } return false; diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java index aa610bdcc90e..de9dcf99cde8 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java @@ -16,6 +16,9 @@ package com.android.systemui.shade; +import android.view.MotionEvent; + +import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; @@ -29,31 +32,32 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; */ public interface ShadeController { - /** - * Make our window larger and the panel expanded - */ - void instantExpandNotificationsPanel(); + /** Make our window larger and the shade expanded */ + void instantExpandShade(); + + /** Collapse the shade instantly with no animation. */ + void instantCollapseShade(); + + /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */ + void animateCollapseShade(); - /** See {@link #animateCollapsePanels(int, boolean)}. */ - void animateCollapsePanels(); + /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */ + void animateCollapseShade(int flags); - /** See {@link #animateCollapsePanels(int, boolean)}. */ - void animateCollapsePanels(int flags); + /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */ + void animateCollapseShadeForced(); + + /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */ + void animateCollapseShadeDelayed(); /** * Collapse the shade animated, showing the bouncer when on {@link StatusBarState#KEYGUARD} or - * dismissing {@link CentralSurfaces} when on {@link StatusBarState#SHADE}. + * dismissing status bar when on {@link StatusBarState#SHADE}. */ - void animateCollapsePanels(int flags, boolean force); - - /** See {@link #animateCollapsePanels(int, boolean)}. */ - void animateCollapsePanels(int flags, boolean force, boolean delayed); - - /** See {@link #animateCollapsePanels(int, boolean)}. */ void animateCollapsePanels(int flags, boolean force, boolean delayed, float speedUpFactor); /** - * If the notifications panel is not fully expanded, collapse it animated. + * If the shade is not fully expanded, collapse it animated. * * @return Seems to always return false */ @@ -77,9 +81,7 @@ public interface ShadeController { */ void addPostCollapseAction(Runnable action); - /** - * Run all of the runnables added by {@link #addPostCollapseAction}. - */ + /** Run all of the runnables added by {@link #addPostCollapseAction}. */ void runPostCollapseRunnables(); /** @@ -87,13 +89,48 @@ public interface ShadeController { * * @return true if the shade was open, else false */ - boolean collapsePanel(); + boolean collapseShade(); /** - * If animate is true, does the same as {@link #collapsePanel()}. Otherwise, instantly collapse - * the panel. Post collapse runnables will be executed + * If animate is true, does the same as {@link #collapseShade()}. Otherwise, instantly collapse + * the shade. Post collapse runnables will be executed * * @param animate true to animate the collapse, false for instantaneous collapse */ - void collapsePanel(boolean animate); + void collapseShade(boolean animate); + + /** Makes shade expanded but not visible. */ + void makeExpandedInvisible(); + + /** Makes shade expanded and visible. */ + void makeExpandedVisible(boolean force); + + /** Returns whether the shade is expanded and visible. */ + boolean isExpandedVisible(); + + /** Handle status bar touch event. */ + void onStatusBarTouch(MotionEvent event); + + /** Sets the listener for when the visibility of the shade changes. */ + void setVisibilityListener(ShadeVisibilityListener listener); + + /** */ + void setNotificationPresenter(NotificationPresenter presenter); + + /** */ + void setNotificationShadeWindowViewController( + NotificationShadeWindowViewController notificationShadeWindowViewController); + + /** */ + void setNotificationPanelViewController( + NotificationPanelViewController notificationPanelViewController); + + /** Listens for shade visibility changes. */ + interface ShadeVisibilityListener { + /** Called when the visibility of the shade changes. */ + void visibilityChanged(boolean visible); + + /** Called when shade expanded and visible state changed. */ + void expandedVisibleChanged(boolean expandedVisible); + } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java index d783293b95d4..807e2e63156f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java @@ -16,9 +16,12 @@ package com.android.systemui.shade; +import android.content.ComponentCallbacks2; import android.util.Log; +import android.view.MotionEvent; import android.view.ViewTreeObserver; import android.view.WindowManager; +import android.view.WindowManagerGlobal; import com.android.systemui.assist.AssistManager; import com.android.systemui.dagger.SysUISingleton; @@ -27,11 +30,12 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.phone.CentralSurfaces; +import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; +import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.statusbar.window.StatusBarWindowController; import java.util.ArrayList; -import java.util.Optional; import javax.inject.Inject; @@ -39,68 +43,81 @@ import dagger.Lazy; /** An implementation of {@link ShadeController}. */ @SysUISingleton -public class ShadeControllerImpl implements ShadeController { +public final class ShadeControllerImpl implements ShadeController { private static final String TAG = "ShadeControllerImpl"; private static final boolean SPEW = false; + private final int mDisplayId; + private final CommandQueue mCommandQueue; + private final KeyguardStateController mKeyguardStateController; + private final NotificationShadeWindowController mNotificationShadeWindowController; private final StatusBarStateController mStatusBarStateController; - protected final NotificationShadeWindowController mNotificationShadeWindowController; private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; - private final int mDisplayId; - protected final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy; + private final StatusBarWindowController mStatusBarWindowController; + private final Lazy<AssistManager> mAssistManagerLazy; + private final Lazy<NotificationGutsManager> mGutsManager; private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>(); + private boolean mExpandedVisible; + + private NotificationPanelViewController mNotificationPanelViewController; + private NotificationPresenter mPresenter; + private NotificationShadeWindowViewController mNotificationShadeWindowViewController; + private ShadeVisibilityListener mShadeVisibilityListener; + @Inject public ShadeControllerImpl( CommandQueue commandQueue, + KeyguardStateController keyguardStateController, StatusBarStateController statusBarStateController, - NotificationShadeWindowController notificationShadeWindowController, StatusBarKeyguardViewManager statusBarKeyguardViewManager, + StatusBarWindowController statusBarWindowController, + NotificationShadeWindowController notificationShadeWindowController, WindowManager windowManager, - Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy, - Lazy<AssistManager> assistManagerLazy + Lazy<AssistManager> assistManagerLazy, + Lazy<NotificationGutsManager> gutsManager ) { mCommandQueue = commandQueue; mStatusBarStateController = statusBarStateController; + mStatusBarWindowController = statusBarWindowController; + mGutsManager = gutsManager; mNotificationShadeWindowController = notificationShadeWindowController; mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mDisplayId = windowManager.getDefaultDisplay().getDisplayId(); - // TODO: Remove circular reference to CentralSurfaces when possible. - mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy; + mKeyguardStateController = keyguardStateController; mAssistManagerLazy = assistManagerLazy; } @Override - public void instantExpandNotificationsPanel() { + public void instantExpandShade() { // Make our window larger and the panel expanded. - getCentralSurfaces().makeExpandedVisible(true /* force */); - getNotificationPanelViewController().expand(false /* animate */); + makeExpandedVisible(true /* force */); + mNotificationPanelViewController.expand(false /* animate */); mCommandQueue.recomputeDisableFlags(mDisplayId, false /* animate */); } @Override - public void animateCollapsePanels() { - animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); + public void animateCollapseShade() { + animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE); } @Override - public void animateCollapsePanels(int flags) { - animateCollapsePanels(flags, false /* force */, false /* delayed */, - 1.0f /* speedUpFactor */); + public void animateCollapseShade(int flags) { + animateCollapsePanels(flags, false, false, 1.0f); } @Override - public void animateCollapsePanels(int flags, boolean force) { - animateCollapsePanels(flags, force, false /* delayed */, 1.0f /* speedUpFactor */); + public void animateCollapseShadeForced() { + animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true, false, 1.0f); } @Override - public void animateCollapsePanels(int flags, boolean force, boolean delayed) { - animateCollapsePanels(flags, force, delayed, 1.0f /* speedUpFactor */); + public void animateCollapseShadeDelayed() { + animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true, true, 1.0f); } @Override @@ -111,34 +128,26 @@ public class ShadeControllerImpl implements ShadeController { return; } if (SPEW) { - Log.d(TAG, "animateCollapse():" - + " mExpandedVisible=" + getCentralSurfaces().isExpandedVisible() - + " flags=" + flags); + Log.d(TAG, + "animateCollapse(): mExpandedVisible=" + mExpandedVisible + "flags=" + flags); } - - // TODO(b/62444020): remove when this bug is fixed - Log.v(TAG, "NotificationShadeWindow: " + getNotificationShadeWindowView() - + " canPanelBeCollapsed(): " - + getNotificationPanelViewController().canPanelBeCollapsed()); if (getNotificationShadeWindowView() != null - && getNotificationPanelViewController().canPanelBeCollapsed() + && mNotificationPanelViewController.canPanelBeCollapsed() && (flags & CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL) == 0) { // release focus immediately to kick off focus change transition mNotificationShadeWindowController.setNotificationShadeFocusable(false); - getCentralSurfaces().getNotificationShadeWindowViewController().cancelExpandHelper(); - getNotificationPanelViewController() - .collapsePanel(true /* animate */, delayed, speedUpFactor); + mNotificationShadeWindowViewController.cancelExpandHelper(); + mNotificationPanelViewController.collapsePanel(true, delayed, speedUpFactor); } } - @Override public boolean closeShadeIfOpen() { - if (!getNotificationPanelViewController().isFullyCollapsed()) { + if (!mNotificationPanelViewController.isFullyCollapsed()) { mCommandQueue.animateCollapsePanels( CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */); - getCentralSurfaces().visibilityChanged(false); + notifyVisibilityChanged(false); mAssistManagerLazy.get().hideAssist(); } return false; @@ -146,21 +155,19 @@ public class ShadeControllerImpl implements ShadeController { @Override public boolean isShadeOpen() { - NotificationPanelViewController controller = - getNotificationPanelViewController(); - return controller.isExpanding() || controller.isFullyExpanded(); + return mNotificationPanelViewController.isExpanding() + || mNotificationPanelViewController.isFullyExpanded(); } @Override public void postOnShadeExpanded(Runnable executable) { - getNotificationPanelViewController().addOnGlobalLayoutListener( + mNotificationPanelViewController.addOnGlobalLayoutListener( new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { - if (getCentralSurfaces().getNotificationShadeWindowView() - .isVisibleToUser()) { - getNotificationPanelViewController().removeOnGlobalLayoutListener(this); - getNotificationPanelViewController().postToView(executable); + if (getNotificationShadeWindowView().isVisibleToUser()) { + mNotificationPanelViewController.removeOnGlobalLayoutListener(this); + mNotificationPanelViewController.postToView(executable); } } }); @@ -183,12 +190,11 @@ public class ShadeControllerImpl implements ShadeController { } @Override - public boolean collapsePanel() { - if (!getNotificationPanelViewController().isFullyCollapsed()) { + public boolean collapseShade() { + if (!mNotificationPanelViewController.isFullyCollapsed()) { // close the shade if it was open - animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, - true /* force */, true /* delayed */); - getCentralSurfaces().visibilityChanged(false); + animateCollapseShadeDelayed(); + notifyVisibilityChanged(false); return true; } else { @@ -197,33 +203,131 @@ public class ShadeControllerImpl implements ShadeController { } @Override - public void collapsePanel(boolean animate) { + public void collapseShade(boolean animate) { if (animate) { - boolean willCollapse = collapsePanel(); + boolean willCollapse = collapseShade(); if (!willCollapse) { runPostCollapseRunnables(); } - } else if (!getPresenter().isPresenterFullyCollapsed()) { - getCentralSurfaces().instantCollapseNotificationPanel(); - getCentralSurfaces().visibilityChanged(false); + } else if (!mPresenter.isPresenterFullyCollapsed()) { + instantCollapseShade(); + notifyVisibilityChanged(false); } else { runPostCollapseRunnables(); } } - private CentralSurfaces getCentralSurfaces() { - return mCentralSurfacesOptionalLazy.get().get(); + @Override + public void onStatusBarTouch(MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_UP) { + if (mExpandedVisible) { + animateCollapseShade(); + } + } } - private NotificationPresenter getPresenter() { - return getCentralSurfaces().getPresenter(); + @Override + public void instantCollapseShade() { + mNotificationPanelViewController.instantCollapse(); + runPostCollapseRunnables(); } - protected NotificationShadeWindowView getNotificationShadeWindowView() { - return getCentralSurfaces().getNotificationShadeWindowView(); + @Override + public void makeExpandedVisible(boolean force) { + if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible); + if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) { + return; + } + + mExpandedVisible = true; + + // Expand the window to encompass the full screen in anticipation of the drag. + // It's only possible to do atomically because the status bar is at the top of the screen! + mNotificationShadeWindowController.setPanelVisible(true); + + notifyVisibilityChanged(true); + mCommandQueue.recomputeDisableFlags(mDisplayId, !force /* animate */); + notifyExpandedVisibleChanged(true); } - private NotificationPanelViewController getNotificationPanelViewController() { - return getCentralSurfaces().getNotificationPanelViewController(); + @Override + public void makeExpandedInvisible() { + if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible); + + if (!mExpandedVisible || getNotificationShadeWindowView() == null) { + return; + } + + // Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868) + mNotificationPanelViewController.collapsePanel(false, false, 1.0f); + + mNotificationPanelViewController.closeQs(); + + mExpandedVisible = false; + notifyVisibilityChanged(false); + + // Update the visibility of notification shade and status bar window. + mNotificationShadeWindowController.setPanelVisible(false); + mStatusBarWindowController.setForceStatusBarVisible(false); + + // Close any guts that might be visible + mGutsManager.get().closeAndSaveGuts( + true /* removeLeavebehind */, + true /* force */, + true /* removeControls */, + -1 /* x */, + -1 /* y */, + true /* resetMenu */); + + runPostCollapseRunnables(); + notifyExpandedVisibleChanged(false); + mCommandQueue.recomputeDisableFlags( + mDisplayId, + mNotificationPanelViewController.hideStatusBarIconsWhenExpanded() /* animate */); + + // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in + // the bouncer appear animation. + if (!mKeyguardStateController.isShowing()) { + WindowManagerGlobal.getInstance().trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN); + } + } + + @Override + public boolean isExpandedVisible() { + return mExpandedVisible; + } + + @Override + public void setVisibilityListener(ShadeVisibilityListener listener) { + mShadeVisibilityListener = listener; + } + + private void notifyVisibilityChanged(boolean visible) { + mShadeVisibilityListener.visibilityChanged(visible); + } + + private void notifyExpandedVisibleChanged(boolean expandedVisible) { + mShadeVisibilityListener.expandedVisibleChanged(expandedVisible); + } + + @Override + public void setNotificationPresenter(NotificationPresenter presenter) { + mPresenter = presenter; + } + + @Override + public void setNotificationShadeWindowViewController( + NotificationShadeWindowViewController controller) { + mNotificationShadeWindowViewController = controller; + } + + private NotificationShadeWindowView getNotificationShadeWindowView() { + return mNotificationShadeWindowViewController.getView(); + } + + @Override + public void setNotificationPanelViewController( + NotificationPanelViewController notificationPanelViewController) { + mNotificationPanelViewController = notificationPanelViewController; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt index 143c6979c590..bd5b8f0b17a5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt @@ -94,7 +94,11 @@ open class PrivacyDotViewController @Inject constructor( private val views: Sequence<View> get() = if (!this::tl.isInitialized) sequenceOf() else sequenceOf(tl, tr, br, bl) - private var showingListener: ShowingListener? = null + var showingListener: ShowingListener? = null + set(value) { + field = value + } + get() = field init { contentInsetsProvider.addCallback(object : StatusBarContentInsetsChangedListener { @@ -147,10 +151,6 @@ open class PrivacyDotViewController @Inject constructor( return uiExecutor } - fun setShowingListener(l: ShowingListener?) { - showingListener = l - } - @UiThread fun setNewRotation(rot: Int) { dlog("updateRotation: $rot") @@ -219,7 +219,7 @@ open class PrivacyDotViewController @Inject constructor( // Update the gravity and margins of the privacy views @UiThread - private fun updateRotations(rotation: Int, paddingTop: Int) { + open fun updateRotations(rotation: Int, paddingTop: Int) { // To keep a view in the corner, its gravity is always the description of its current corner // Therefore, just figure out which view is in which corner. This turns out to be something // like (myCorner - rot) mod 4, where topLeft = 0, topRight = 1, etc. and portrait = 0, and @@ -250,7 +250,7 @@ open class PrivacyDotViewController @Inject constructor( } @UiThread - private fun setCornerSizes(state: ViewState) { + open fun setCornerSizes(state: ViewState) { // StatusBarContentInsetsProvider can tell us the location of the privacy indicator dot // in every rotation. The only thing we need to check is rtl val rtl = state.layoutRtl 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 64f87cabaf74..b56bae12be6c 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 @@ -54,8 +54,6 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger; import com.android.systemui.statusbar.policy.HeadsUpManager; -import java.util.Collections; - import javax.inject.Inject; /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java index 0ce9656a21b5..f21db0bde59a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java @@ -154,7 +154,7 @@ public class NotificationConversationInfo extends LinearLayout implements // If the user selected Priority and the previous selection was not priority, show a // People Tile add request. if (mSelectedAction == ACTION_FAVORITE && getPriority() != mSelectedAction) { - mShadeController.animateCollapsePanels(); + mShadeController.animateCollapseShade(); mPeopleSpaceWidgetManager.requestPinAppWidget(mShortcutInfo, new Bundle()); } mGutsContainer.closeControls(v, /* save= */ true); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 073bd4bf302b..b519aefcd4c9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -1401,10 +1401,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mExpandedHeight = height; setIsExpanded(height > 0); int minExpansionHeight = getMinExpansionHeight(); - if (height < minExpansionHeight) { + if (height < minExpansionHeight && !mShouldUseSplitNotificationShade) { mClipRect.left = 0; mClipRect.right = getWidth(); - mClipRect.top = getNotificationsClippingTopBound(); + mClipRect.top = 0; mClipRect.bottom = (int) height; height = minExpansionHeight; setRequestedClipBounds(mClipRect); @@ -1466,17 +1466,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable notifyAppearChangedListeners(); } - private int getNotificationsClippingTopBound() { - if (isHeadsUpTransition()) { - // HUN in split shade can go higher than bottom of NSSL when swiping up so we want - // to give it extra clipping margin. Because clipping has rounded corners, we also - // need to account for that corner clipping. - return -mAmbientState.getStackTopMargin() - mCornerRadius; - } else { - return 0; - } - } - private void notifyAppearChangedListeners() { float appear; float expandAmount; @@ -4236,7 +4225,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mShadeNeedsToClose = false; postDelayed( () -> { - mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); + mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE); }, DELAY_BEFORE_SHADE_CLOSE /* delayMillis */); } @@ -5139,6 +5128,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable println(pw, "intrinsicPadding", mIntrinsicPadding); println(pw, "topPadding", mTopPadding); println(pw, "bottomPadding", mBottomPadding); + mNotificationStackSizeCalculator.dump(pw, args); }); pw.println(); pw.println("Contents:"); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt index ae854e2df91a..25f99c69d454 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt @@ -30,6 +30,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView import com.android.systemui.util.Compile import com.android.systemui.util.children +import java.io.PrintWriter import javax.inject.Inject import kotlin.math.max import kotlin.math.min @@ -53,6 +54,8 @@ constructor( @Main private val resources: Resources ) { + private lateinit var lastComputeHeightLog : String + /** * Maximum # notifications to show on Keyguard; extras will be collapsed in an overflow shelf. * If there are exactly 1 + mMaxKeyguardNotifications, and they fit in the available space @@ -114,7 +117,9 @@ constructor( shelfIntrinsicHeight: Float ): Int { log { "\n" } - val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight) + + val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight, + /* computeHeight= */ false) var maxNotifications = stackHeightSequence.lastIndexWhile { heightResult -> @@ -157,18 +162,21 @@ constructor( shelfIntrinsicHeight: Float ): Float { log { "\n" } + lastComputeHeightLog = "" val heightPerMaxNotifications = - computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight) + computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight, + /* computeHeight= */ true) val (notificationsHeight, shelfHeightWithSpaceBefore) = heightPerMaxNotifications.elementAtOrElse(maxNotifications) { heightPerMaxNotifications.last() // Height with all notifications visible. } - log { - "computeHeight(maxNotifications=$maxNotifications," + + lastComputeHeightLog += "\ncomputeHeight(maxNotifications=$maxNotifications," + "shelfIntrinsicHeight=$shelfIntrinsicHeight) -> " + "${notificationsHeight + shelfHeightWithSpaceBefore}" + " = ($notificationsHeight + $shelfHeightWithSpaceBefore)" + log { + lastComputeHeightLog } return notificationsHeight + shelfHeightWithSpaceBefore } @@ -184,7 +192,8 @@ constructor( private fun computeHeightPerNotificationLimit( stack: NotificationStackScrollLayout, - shelfHeight: Float + shelfHeight: Float, + computeHeight: Boolean ): Sequence<StackHeight> = sequence { log { "computeHeightPerNotificationLimit" } @@ -213,9 +222,14 @@ constructor( currentIndex = firstViewInShelfIndex) spaceBeforeShelf + shelfHeight } + + val currentLog = "computeHeight | i=$i notificationsHeight=$notifications " + + "shelfHeightWithSpaceBefore=$shelfWithSpaceBefore" + if (computeHeight) { + lastComputeHeightLog += "\n" + currentLog + } log { - "i=$i notificationsHeight=$notifications " + - "shelfHeightWithSpaceBefore=$shelfWithSpaceBefore" + currentLog } yield( StackHeight( @@ -260,6 +274,10 @@ constructor( return size } + fun dump(pw: PrintWriter, args: Array<out String>) { + pw.println("NotificationStackSizeCalculator lastComputeHeightLog = $lastComputeHeightLog") + } + private fun ExpandableView.isShowable(onLockscreen: Boolean): Boolean { if (visibility == GONE || hasNoContentHeight()) return false if (onLockscreen) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java index 01ca66742287..0ec7c622ba5f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -193,8 +193,6 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn void animateExpandSettingsPanel(@Nullable String subpanel); - void animateCollapsePanels(int flags, boolean force); - void collapsePanelOnMainThread(); void togglePanel(); @@ -282,8 +280,6 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn void postAnimateOpenPanels(); - boolean isExpandedVisible(); - boolean isPanelExpanded(); void onInputFocusTransfer(boolean start, boolean cancel, float velocity); @@ -495,12 +491,13 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn void updateNotificationPanelTouchState(); + /** + * TODO(b/257041702) delete this + * @deprecated Use ShadeController#makeExpandedVisible + */ + @Deprecated void makeExpandedVisible(boolean force); - void instantCollapseNotificationPanel(); - - void visibilityChanged(boolean visible); - int getDisplayId(); int getRotation(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java index f3482f490d92..6b72e9696f83 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java @@ -209,7 +209,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba public void animateExpandNotificationsPanel() { if (CentralSurfaces.SPEW) { Log.d(CentralSurfaces.TAG, - "animateExpand: mExpandedVisible=" + mCentralSurfaces.isExpandedVisible()); + "animateExpand: mExpandedVisible=" + mShadeController.isExpandedVisible()); } if (!mCommandQueue.panelsEnabled()) { return; @@ -222,7 +222,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba public void animateExpandSettingsPanel(@Nullable String subPanel) { if (CentralSurfaces.SPEW) { Log.d(CentralSurfaces.TAG, - "animateExpand: mExpandedVisible=" + mCentralSurfaces.isExpandedVisible()); + "animateExpand: mExpandedVisible=" + mShadeController.isExpandedVisible()); } if (!mCommandQueue.panelsEnabled()) { return; @@ -276,7 +276,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba if ((diff1 & StatusBarManager.DISABLE_EXPAND) != 0) { if ((state1 & StatusBarManager.DISABLE_EXPAND) != 0) { - mShadeController.animateCollapsePanels(); + mShadeController.animateCollapseShade(); } } @@ -293,7 +293,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba if ((diff2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) { mCentralSurfaces.updateQsExpansionEnabled(); if ((state2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) { - mShadeController.animateCollapsePanels(); + mShadeController.animateCollapseShade(); } } @@ -550,7 +550,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba @Override public void togglePanel() { if (mCentralSurfaces.isPanelExpanded()) { - mShadeController.animateCollapsePanels(); + mShadeController.animateCollapseShade(); } else { animateExpandNotificationsPanel(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 32ea8d6fa695..d98877237dae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -58,7 +58,6 @@ import android.app.WallpaperInfo; import android.app.WallpaperManager; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; -import android.content.ComponentCallbacks2; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -406,12 +405,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { /** */ @Override - public void animateCollapsePanels(int flags, boolean force) { - mCommandQueueCallbacks.animateCollapsePanels(flags, force); - } - - /** */ - @Override public void togglePanel() { mCommandQueueCallbacks.togglePanel(); } @@ -493,8 +486,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private View mReportRejectedTouch; - private boolean mExpandedVisible; - private final NotificationGutsManager mGutsManager; private final NotificationLogger mNotificationLogger; private final ShadeExpansionStateManager mShadeExpansionStateManager; @@ -893,6 +884,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { updateDisplaySize(); mStatusBarHideIconsForBouncerManager.setDisplayId(mDisplayId); + initShadeVisibilityListener(); + // start old BaseStatusBar.start(). mWindowManagerService = WindowManagerGlobal.getWindowManagerService(); mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService( @@ -1083,6 +1076,25 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { requestTopUi, componentTag)))); } + @VisibleForTesting + void initShadeVisibilityListener() { + mShadeController.setVisibilityListener(new ShadeController.ShadeVisibilityListener() { + @Override + public void visibilityChanged(boolean visible) { + onShadeVisibilityChanged(visible); + } + + @Override + public void expandedVisibleChanged(boolean expandedVisible) { + if (expandedVisible) { + setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true); + } else { + onExpandedInvisible(); + } + } + }); + } + private void onFoldedStateChanged(boolean isFolded, boolean willGoToSleep) { Trace.beginSection("CentralSurfaces#onFoldedStateChanged"); onFoldedStateChangedInternal(isFolded, willGoToSleep); @@ -1228,7 +1240,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mNotificationPanelViewController.initDependencies( this, - this::makeExpandedInvisible, + mShadeController::makeExpandedInvisible, mNotificationShelfController); BackDropView backdrop = mNotificationShadeWindowView.findViewById(R.id.backdrop); @@ -1431,6 +1443,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mRemoteInputManager.addControllerCallback(mNotificationShadeWindowController); mStackScrollerController.setNotificationActivityStarter(mNotificationActivityStarter); mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter); + mShadeController.setNotificationPresenter(mPresenter); mNotificationsController.initialize( this, mPresenter, @@ -1480,11 +1493,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { return (v, event) -> { mAutoHideController.checkUserAutoHide(event); mRemoteInputManager.checkRemoteInputOutside(event); - if (event.getAction() == MotionEvent.ACTION_UP) { - if (mExpandedVisible) { - mShadeController.animateCollapsePanels(); - } - } + mShadeController.onStatusBarTouch(event); return mNotificationShadeWindowView.onTouchEvent(event); }; } @@ -1506,6 +1515,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mNotificationShadeWindowViewController.setupExpandedStatusBar(); mNotificationPanelViewController = mCentralSurfacesComponent.getNotificationPanelViewController(); + mShadeController.setNotificationPanelViewController(mNotificationPanelViewController); + mShadeController.setNotificationShadeWindowViewController( + mNotificationShadeWindowViewController); mCentralSurfacesComponent.getLockIconViewController().init(); mStackScrollerController = mCentralSurfacesComponent.getNotificationStackScrollLayoutController(); @@ -1827,7 +1839,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { && isLaunchForActivity) { onClosingFinished(); } else { - mShadeController.collapsePanel(true /* animate */); + mShadeController.collapseShade(true /* animate */); } } @@ -1838,7 +1850,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { onClosingFinished(); } if (launchIsFullScreen) { - instantCollapseNotificationPanel(); + mShadeController.instantCollapseShade(); } } @@ -1928,33 +1940,13 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } @Override - public void makeExpandedVisible(boolean force) { - if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible); - if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) { - return; - } - - mExpandedVisible = true; - - // Expand the window to encompass the full screen in anticipation of the drag. - // This is only possible to do atomically because the status bar is at the top of the screen! - mNotificationShadeWindowController.setPanelVisible(true); - - visibilityChanged(true); - mCommandQueue.recomputeDisableFlags(mDisplayId, !force /* animate */); - setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true); - } - - @Override public void postAnimateCollapsePanels() { - mMainExecutor.execute(mShadeController::animateCollapsePanels); + mMainExecutor.execute(mShadeController::animateCollapseShade); } @Override public void postAnimateForceCollapsePanels() { - mMainExecutor.execute( - () -> mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, - true /* force */)); + mMainExecutor.execute(mShadeController::animateCollapseShadeForced); } @Override @@ -1963,11 +1955,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } @Override - public boolean isExpandedVisible() { - return mExpandedVisible; - } - - @Override public boolean isPanelExpanded() { return mPanelExpanded; } @@ -1996,46 +1983,13 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } } - void makeExpandedInvisible() { - if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible); - - if (!mExpandedVisible || mNotificationShadeWindowView == null) { - return; - } - - // Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868) - mNotificationPanelViewController.collapsePanel(/*animate=*/ false, false /* delayed*/, - 1.0f /* speedUpFactor */); - - mNotificationPanelViewController.closeQs(); - - mExpandedVisible = false; - visibilityChanged(false); - - // Update the visibility of notification shade and status bar window. - mNotificationShadeWindowController.setPanelVisible(false); - mStatusBarWindowController.setForceStatusBarVisible(false); - - // Close any guts that might be visible - mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */, - true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */); - - mShadeController.runPostCollapseRunnables(); + private void onExpandedInvisible() { setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false); if (!mNotificationActivityStarter.isCollapsingToShowActivityOverLockscreen()) { showBouncerOrLockScreenIfKeyguard(); } else if (DEBUG) { Log.d(TAG, "Not showing bouncer due to activity showing over lockscreen"); } - mCommandQueue.recomputeDisableFlags( - mDisplayId, - mNotificationPanelViewController.hideStatusBarIconsWhenExpanded() /* animate */); - - // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in - // the bouncer appear animation. - if (!mKeyguardStateController.isShowing()) { - WindowManagerGlobal.getInstance().trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN); - } } /** Called when a touch event occurred on {@link PhoneStatusBarView}. */ @@ -2072,7 +2026,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { final boolean upOrCancel = event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL; - setInteracting(StatusBarManager.WINDOW_STATUS_BAR, !upOrCancel || mExpandedVisible); + setInteracting(StatusBarManager.WINDOW_STATUS_BAR, + !upOrCancel || mShadeController.isExpandedVisible()); } } @@ -2221,7 +2176,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal); synchronized (mQueueLock) { pw.println("Current Status Bar state:"); - pw.println(" mExpandedVisible=" + mExpandedVisible); + pw.println(" mExpandedVisible=" + mShadeController.isExpandedVisible()); pw.println(" mDisplayMetrics=" + mDisplayMetrics); pw.println(" mStackScroller: " + CentralSurfaces.viewInfo(mStackScroller)); pw.println(" mStackScroller: " + CentralSurfaces.viewInfo(mStackScroller) @@ -2536,10 +2491,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } } if (dismissShade) { - if (mExpandedVisible && !mBouncerShowing) { - mShadeController.animateCollapsePanels( - CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, - true /* force */, true /* delayed*/); + if (mShadeController.isExpandedVisible() && !mBouncerShowing) { + mShadeController.animateCollapseShadeDelayed(); } else { // Do it after DismissAction has been processed to conserve the needed // ordering. @@ -2581,7 +2534,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { flags |= CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL; } } - mShadeController.animateCollapsePanels(flags); + mShadeController.animateCollapseShade(flags); } } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { if (mNotificationShadeWindowController != null) { @@ -2696,10 +2649,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { com.android.systemui.R.dimen.physical_power_button_center_screen_location_y)); } - // Visibility reporting protected void handleVisibleToUserChanged(boolean visibleToUser) { if (visibleToUser) { - handleVisibleToUserChangedImpl(visibleToUser); + onVisibleToUser(); mNotificationLogger.startNotificationLogging(); if (!mIsBackCallbackRegistered) { @@ -2716,7 +2668,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } } else { mNotificationLogger.stopNotificationLogging(); - handleVisibleToUserChangedImpl(visibleToUser); + onInvisibleToUser(); if (mIsBackCallbackRegistered) { ViewRootImpl viewRootImpl = getViewRootImpl(); @@ -2736,41 +2688,38 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } } - // Visibility reporting - void handleVisibleToUserChangedImpl(boolean visibleToUser) { - if (visibleToUser) { - /* The LEDs are turned off when the notification panel is shown, even just a little bit. - * See also CentralSurfaces.setPanelExpanded for another place where we attempt to do - * this. - */ - boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp(); - boolean clearNotificationEffects = - !mPresenter.isPresenterFullyCollapsed() && - (mState == StatusBarState.SHADE - || mState == StatusBarState.SHADE_LOCKED); - int notificationLoad = mNotificationsController.getActiveNotificationsCount(); - if (pinnedHeadsUp && mPresenter.isPresenterFullyCollapsed()) { - notificationLoad = 1; - } - final int finalNotificationLoad = notificationLoad; - mUiBgExecutor.execute(() -> { - try { - mBarService.onPanelRevealed(clearNotificationEffects, - finalNotificationLoad); - } catch (RemoteException ex) { - // Won't fail unless the world has ended. - } - }); - } else { - mUiBgExecutor.execute(() -> { - try { - mBarService.onPanelHidden(); - } catch (RemoteException ex) { - // Won't fail unless the world has ended. - } - }); + void onVisibleToUser() { + /* The LEDs are turned off when the notification panel is shown, even just a little bit. + * See also CentralSurfaces.setPanelExpanded for another place where we attempt to do + * this. + */ + boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp(); + boolean clearNotificationEffects = + !mPresenter.isPresenterFullyCollapsed() && (mState == StatusBarState.SHADE + || mState == StatusBarState.SHADE_LOCKED); + int notificationLoad = mNotificationsController.getActiveNotificationsCount(); + if (pinnedHeadsUp && mPresenter.isPresenterFullyCollapsed()) { + notificationLoad = 1; } + final int finalNotificationLoad = notificationLoad; + mUiBgExecutor.execute(() -> { + try { + mBarService.onPanelRevealed(clearNotificationEffects, + finalNotificationLoad); + } catch (RemoteException ex) { + // Won't fail unless the world has ended. + } + }); + } + void onInvisibleToUser() { + mUiBgExecutor.execute(() -> { + try { + mBarService.onPanelHidden(); + } catch (RemoteException ex) { + // Won't fail unless the world has ended. + } + }); } private void logStateToEventlog() { @@ -2948,7 +2897,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private void updatePanelExpansionForKeyguard() { if (mState == StatusBarState.KEYGUARD && mBiometricUnlockController.getMode() != BiometricUnlockController.MODE_WAKE_AND_UNLOCK && !mBouncerShowing) { - mShadeController.instantExpandNotificationsPanel(); + mShadeController.instantExpandShade(); } } @@ -3067,7 +3016,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // too heavy for the CPU and GPU on any device. mNavigationBarController.disableAnimationsDuringHide(mDisplayId, delay); } else if (!mNotificationPanelViewController.isCollapsing()) { - instantCollapseNotificationPanel(); + mShadeController.instantCollapseShade(); } // Keyguard state has changed, but QS is not listening anymore. Make sure to update the tile @@ -3225,8 +3174,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @Override public boolean onMenuPressed() { if (shouldUnlockOnMenuPressed()) { - mShadeController.animateCollapsePanels( - CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL /* flags */, true /* force */); + mShadeController.animateCollapseShadeForced(); return true; } return false; @@ -3271,7 +3219,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED && !isBouncerShowingOverDream()) { if (mNotificationPanelViewController.canPanelBeCollapsed()) { - mShadeController.animateCollapsePanels(); + mShadeController.animateCollapseShade(); } return true; } @@ -3281,8 +3229,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @Override public boolean onSpacePressed() { if (mDeviceInteractive && mState != StatusBarState.SHADE) { - mShadeController.animateCollapsePanels( - CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL /* flags */, true /* force */); + mShadeController.animateCollapseShadeForced(); return true; } return false; @@ -3322,12 +3269,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } } - @Override - public void instantCollapseNotificationPanel() { - mNotificationPanelViewController.instantCollapse(); - mShadeController.runPostCollapseRunnables(); - } - /** * Collapse the panel directly if we are on the main thread, post the collapsing on the main * thread if we are not. @@ -3335,9 +3276,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @Override public void collapsePanelOnMainThread() { if (Looper.getMainLooper().isCurrentThread()) { - mShadeController.collapsePanel(); + mShadeController.collapseShade(); } else { - mContext.getMainExecutor().execute(mShadeController::collapsePanel); + mContext.getMainExecutor().execute(mShadeController::collapseShade); } } @@ -3477,7 +3418,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mNotificationShadeWindowViewController.cancelCurrentTouch(); } if (mPanelExpanded && mState == StatusBarState.SHADE) { - mShadeController.animateCollapsePanels(); + mShadeController.animateCollapseShade(); } } @@ -3540,7 +3481,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // The unlocked screen off and fold to aod animations might use our LightRevealScrim - // we need to be expanded for it to be visible. if (mDozeParameters.shouldShowLightRevealScrim()) { - makeExpandedVisible(true); + mShadeController.makeExpandedVisible(true); } DejankUtils.stopDetectingBlockingIpcs(tag); @@ -3569,7 +3510,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // If we are waking up during the screen off animation, we should undo making the // expanded visible (we did that so the LightRevealScrim would be visible). if (mScreenOffAnimationController.shouldHideLightRevealScrimOnWakeUp()) { - makeExpandedInvisible(); + mShadeController.makeExpandedInvisible(); } }); @@ -3624,6 +3565,12 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mNotificationIconAreaController.setAnimationsEnabled(!disabled); } + //TODO(b/257041702) delete + @Override + public void makeExpandedVisible(boolean force) { + mShadeController.makeExpandedVisible(force); + } + final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() { @Override public void onScreenTurningOn(Runnable onDrawn) { @@ -3904,8 +3851,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0); if (BANNER_ACTION_SETUP.equals(action)) { - mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, - true /* force */); + mShadeController.animateCollapseShadeForced(); mContext.startActivity(new Intent(Settings.ACTION_APP_NOTIFICATION_REDACTION) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) @@ -3967,7 +3913,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { action.run(); }).start(); - return collapsePanel ? mShadeController.collapsePanel() : willAnimateOnKeyguard; + return collapsePanel ? mShadeController.collapseShade() : willAnimateOnKeyguard; } @Override @@ -4062,8 +4008,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mMainExecutor.execute(runnable); } - @Override - public void visibilityChanged(boolean visible) { + private void onShadeVisibilityChanged(boolean visible) { if (mVisible != visible) { mVisible = visible; if (!visible) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index aa0757e1d572..000fe140882c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -240,8 +240,8 @@ public class KeyguardBouncer { && !mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( KeyguardUpdateMonitor.getCurrentUser()) && !needsFullscreenBouncer() - && !mKeyguardUpdateMonitor.isFaceLockedOut() - && !mKeyguardUpdateMonitor.userNeedsStrongAuth() + && mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed( + BiometricSourceType.FACE) && !mKeyguardBypassController.getBypassEnabled()) { mHandler.postDelayed(mShowRunnable, BOUNCER_FACE_DELAY); } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 44ad60473925..f9d316b2ff20 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -469,6 +469,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb // Don't expand to the bouncer. Instead transition back to the lock screen (see // CentralSurfaces#showBouncerOrLockScreenIfKeyguard) return; + } else if (mKeyguardStateController.isOccluded() + && !mDreamOverlayStateController.isOverlayActive()) { + return; } else if (needsFullscreenBouncer()) { if (mPrimaryBouncer != null) { mPrimaryBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index b6ae4a088880..05bf8604c2c0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -260,11 +260,11 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte if (showOverLockscreen) { mShadeController.addPostCollapseAction(runnable); - mShadeController.collapsePanel(true /* animate */); + mShadeController.collapseShade(true /* animate */); } else if (mKeyguardStateController.isShowing() && mCentralSurfaces.isOccluded()) { mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable); - mShadeController.collapsePanel(); + mShadeController.collapseShade(); } else { runnable.run(); } @@ -406,7 +406,7 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte private void expandBubbleStack(NotificationEntry entry) { mBubblesManagerOptional.get().expandStackAndSelectBubble(entry); - mShadeController.collapsePanel(); + mShadeController.collapseShade(); } private void startNotificationIntent( @@ -593,9 +593,9 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte private void collapseOnMainThread() { if (Looper.getMainLooper().isCurrentThread()) { - mShadeController.collapsePanel(); + mShadeController.collapseShade(); } else { - mMainThreadHandler.post(mShadeController::collapsePanel); + mMainThreadHandler.post(mShadeController::collapseShade); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java index 8a49850b1822..7fe01825890f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java @@ -180,7 +180,7 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks, } }; mShadeController.postOnShadeExpanded(clickPendingViewRunnable); - mShadeController.instantExpandNotificationsPanel(); + mShadeController.instantExpandShade(); } } diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java index ad97ef4a79bc..5df4a5b54ccc 100644 --- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java @@ -29,6 +29,7 @@ import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; import android.os.Handler; import android.os.HandlerThread; +import android.os.Looper; import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; @@ -102,6 +103,15 @@ public class ImageWallpaper extends WallpaperService { } @Override + public Looper onProvideEngineLooper() { + // Receive messages on mWorker thread instead of SystemUI's main handler. + // All other wallpapers have their own process, and they can receive messages on their own + // main handler without any delay. But since ImageWallpaper lives in SystemUI, performance + // of the image wallpaper could be negatively affected when SystemUI's main handler is busy. + return mWorker != null ? mWorker.getLooper() : super.onProvideEngineLooper(); + } + + @Override public void onCreate() { super.onCreate(); mWorker = new HandlerThread(TAG); diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java index a4384d5810ce..7033ccde8c7d 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java @@ -549,7 +549,7 @@ public class BubblesManager { } catch (RemoteException e) { Log.e(TAG, e.getMessage()); } - mShadeController.collapsePanel(true); + mShadeController.collapseShade(true); if (entry.getRow() != null) { entry.getRow().updateBubbleButton(); } @@ -597,7 +597,7 @@ public class BubblesManager { } if (shouldBubble) { - mShadeController.collapsePanel(true); + mShadeController.collapseShade(true); if (entry.getRow() != null) { entry.getRow().updateBubbleButton(); } diff --git a/packages/SystemUI/tests/robolectric/config/robolectric.properties b/packages/SystemUI/tests/robolectric/config/robolectric.properties new file mode 100644 index 000000000000..2a75bd98bfe8 --- /dev/null +++ b/packages/SystemUI/tests/robolectric/config/robolectric.properties @@ -0,0 +1,16 @@ +# +# 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. +# +sdk=NEWEST_SDK
\ No newline at end of file diff --git a/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiResourceLoadingTest.java b/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiResourceLoadingTest.java new file mode 100644 index 000000000000..188dff21efa4 --- /dev/null +++ b/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiResourceLoadingTest.java @@ -0,0 +1,35 @@ +/* + * 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.robotests; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; +import static com.google.common.truth.Truth.assertThat; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class SysuiResourceLoadingTest extends SysuiRoboBase { + @Test + public void testResources() { + assertThat(getContext().getString(com.android.systemui.R.string.app_label)) + .isEqualTo("System UI"); + assertThat(getContext().getString(com.android.systemui.tests.R.string.test_content)) + .isNotEmpty(); + } +} diff --git a/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiRoboBase.java b/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiRoboBase.java new file mode 100644 index 000000000000..d9686bbeb0cd --- /dev/null +++ b/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiRoboBase.java @@ -0,0 +1,27 @@ +/* + * 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.robotests; + +import android.content.Context; + +import androidx.test.InstrumentationRegistry; + +public class SysuiRoboBase { + public Context getContext() { + return InstrumentationRegistry.getContext(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt index 88396628017b..afd582a3b822 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt @@ -63,7 +63,6 @@ private fun fingerprintModel(user: Int) = KeyguardFingerprintListenModel( credentialAttempted = false, deviceInteractive = false, dreaming = false, - encryptedOrLockdown = false, fingerprintDisabled = false, fingerprintLockedOut = false, goingToSleep = false, @@ -74,6 +73,7 @@ private fun fingerprintModel(user: Int) = KeyguardFingerprintListenModel( primaryUser = false, shouldListenSfpsState = false, shouldListenForFingerprintAssistant = false, + strongerAuthRequired = false, switchingUser = false, udfps = false, userDoesNotHaveTrust = false diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java index 4d58b09f1076..e39b9b58158f 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java @@ -379,9 +379,9 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { } @Test - public void onBouncerVisibilityChanged_needsStrongAuth_sideFpsHintHidden() { + public void onBouncerVisibilityChanged_unlockingWithFingerprintNotAllowed_sideFpsHintHidden() { setupConditionsToEnableSideFpsHint(); - setNeedsStrongAuth(true); + setUnlockingWithFingerprintAllowed(false); reset(mSideFpsController); mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE); @@ -574,7 +574,7 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { attachView(); setSideFpsHintEnabledFromResources(true); setFingerprintDetectionRunning(true); - setNeedsStrongAuth(false); + setUnlockingWithFingerprintAllowed(true); } private void attachView() { @@ -593,9 +593,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { enabled); } - private void setNeedsStrongAuth(boolean needed) { - when(mKeyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(needed); - mKeyguardUpdateMonitorCallback.getValue().onStrongAuthStateChanged(/* userId= */ 0); + private void setUnlockingWithFingerprintAllowed(boolean allowed) { + when(mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed()).thenReturn(allowed); } private void setupGetSecurityView() { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 7231b3427a71..63e160331e6c 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -28,6 +28,7 @@ import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT; import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED; +import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING_RESTARTING; import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT; import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser; @@ -281,7 +282,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { componentInfo, FaceSensorProperties.TYPE_UNKNOWN, false /* supportsFaceDetection */, true /* supportsSelfIllumination */, false /* resetLockoutRequiresChallenge */)); - when(mFingerprintManager.isHardwareDetected()).thenReturn(true); when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true); when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(List.of( @@ -594,30 +594,13 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void testFingerprintDoesNotAuth_whenEncrypted() { - testFingerprintWhenStrongAuth( - STRONG_AUTH_REQUIRED_AFTER_BOOT); - } - - @Test - public void testFingerprintDoesNotAuth_whenDpmLocked() { - testFingerprintWhenStrongAuth( - KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW); - } - - @Test - public void testFingerprintDoesNotAuth_whenUserLockdown() { - testFingerprintWhenStrongAuth( - KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); - } - - private void testFingerprintWhenStrongAuth(int strongAuth) { + public void testOnlyDetectFingerprint_whenFingerprintUnlockNotAllowed() { // Clear invocations, since previous setup (e.g. registering BiometricManager callbacks) // will trigger updateBiometricListeningState(); clearInvocations(mFingerprintManager); mKeyguardUpdateMonitor.resetBiometricListeningState(); - when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(strongAuth); + when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false); mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */); mTestableLooper.processAllMessages(); @@ -928,10 +911,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { faceLockoutMode != BiometricConstants.BIOMETRIC_LOCKOUT_NONE; final boolean fpLocked = fingerprintLockoutMode != BiometricConstants.BIOMETRIC_LOCKOUT_NONE; - when(mFingerprintManager.getLockoutModeForUser(eq(FINGERPRINT_SENSOR_ID), eq(newUser))) - .thenReturn(fingerprintLockoutMode); - when(mFaceManager.getLockoutModeForUser(eq(FACE_SENSOR_ID), eq(newUser))) - .thenReturn(faceLockoutMode); mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); mTestableLooper.processAllMessages(); @@ -940,7 +919,13 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean()); verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(), anyInt()); +// resetFaceManager(); +// resetFingerprintManager(); + when(mFingerprintManager.getLockoutModeForUser(eq(FINGERPRINT_SENSOR_ID), eq(newUser))) + .thenReturn(fingerprintLockoutMode); + when(mFaceManager.getLockoutModeForUser(eq(FACE_SENSOR_ID), eq(newUser))) + .thenReturn(faceLockoutMode); final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal); final CancellationSignal fpCancel = spy(mKeyguardUpdateMonitor.mFingerprintCancelSignal); mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel; @@ -951,14 +936,22 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mKeyguardUpdateMonitor.handleUserSwitchComplete(newUser); mTestableLooper.processAllMessages(); - verify(faceCancel, faceLocked ? times(1) : never()).cancel(); - verify(fpCancel, fpLocked ? times(1) : never()).cancel(); - verify(callback, faceLocked ? times(1) : never()).onBiometricRunningStateChanged( + // THEN face and fingerprint listening are always cancelled immediately + verify(faceCancel).cancel(); + verify(callback).onBiometricRunningStateChanged( eq(false), eq(BiometricSourceType.FACE)); - verify(callback, fpLocked ? times(1) : never()).onBiometricRunningStateChanged( + verify(fpCancel).cancel(); + verify(callback).onBiometricRunningStateChanged( eq(false), eq(BiometricSourceType.FINGERPRINT)); + + // THEN locked out states are updated assertThat(mKeyguardUpdateMonitor.isFingerprintLockedOut()).isEqualTo(fpLocked); assertThat(mKeyguardUpdateMonitor.isFaceLockedOut()).isEqualTo(faceLocked); + + // Fingerprint should be restarted once its cancelled bc on lockout, the device + // can still detectFingerprint (and if it's not locked out, fingerprint can listen) + assertThat(mKeyguardUpdateMonitor.mFingerprintRunningState) + .isEqualTo(BIOMETRIC_STATE_CANCELLING_RESTARTING); } @Test @@ -1144,9 +1137,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { // GIVEN status bar state is on the keyguard mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD); - // WHEN user hasn't authenticated since last boot - when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser())) - .thenReturn(STRONG_AUTH_REQUIRED_AFTER_BOOT); + // WHEN user hasn't authenticated since last boot, cannot unlock with FP + when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false); // THEN we shouldn't listen for udfps assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(false); @@ -1258,8 +1250,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { when(mStrongAuthTracker.hasUserAuthenticatedSinceBoot()).thenReturn(true); // WHEN device in lock down - when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn( - KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN); + when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false); // THEN we shouldn't listen for udfps assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt index 0b528a5c8d1e..eb8c823ffe1c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt @@ -37,7 +37,7 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.leak.RotationUtils -import javax.inject.Provider +import com.android.systemui.util.mockito.any import org.junit.After import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -46,15 +46,16 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers +import org.mockito.ArgumentMatchers.eq import org.mockito.Mock -import org.mockito.Mockito.any +import org.mockito.Mockito.`when` import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations import org.mockito.MockitoSession import org.mockito.quality.Strictness +import javax.inject.Provider @SmallTest @RunWith(AndroidTestingRunner::class) @@ -118,12 +119,13 @@ class AuthRippleControllerTest : SysuiTestCase() { @Test fun testFingerprintTrigger_KeyguardShowing_Ripple() { - // GIVEN fp exists, keyguard is showing, user doesn't need strong auth + // GIVEN fp exists, keyguard is showing, unlocking with fp allowed val fpsLocation = Point(5, 5) `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation) controller.onViewAttached() `when`(keyguardStateController.isShowing).thenReturn(true) - `when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false) + `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( + eq(BiometricSourceType.FINGERPRINT))).thenReturn(true) // WHEN fingerprint authenticated val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java) @@ -140,11 +142,12 @@ class AuthRippleControllerTest : SysuiTestCase() { @Test fun testFingerprintTrigger_KeyguardNotShowing_NoRipple() { - // GIVEN fp exists & user doesn't need strong auth + // GIVEN fp exists & unlocking with fp allowed val fpsLocation = Point(5, 5) `when`(authController.udfpsLocation).thenReturn(fpsLocation) controller.onViewAttached() - `when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false) + `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( + eq(BiometricSourceType.FINGERPRINT))).thenReturn(true) // WHEN keyguard is NOT showing & fingerprint authenticated `when`(keyguardStateController.isShowing).thenReturn(false) @@ -160,15 +163,16 @@ class AuthRippleControllerTest : SysuiTestCase() { } @Test - fun testFingerprintTrigger_StrongAuthRequired_NoRipple() { + fun testFingerprintTrigger_biometricUnlockNotAllowed_NoRipple() { // GIVEN fp exists & keyguard is showing val fpsLocation = Point(5, 5) `when`(authController.udfpsLocation).thenReturn(fpsLocation) controller.onViewAttached() `when`(keyguardStateController.isShowing).thenReturn(true) - // WHEN user needs strong auth & fingerprint authenticated - `when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(true) + // WHEN unlocking with fingerprint is NOT allowed & fingerprint authenticated + `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( + eq(BiometricSourceType.FINGERPRINT))).thenReturn(false) val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java) verify(keyguardUpdateMonitor).registerCallback(captor.capture()) captor.value.onBiometricAuthenticated( @@ -182,13 +186,14 @@ class AuthRippleControllerTest : SysuiTestCase() { @Test fun testFaceTriggerBypassEnabled_Ripple() { - // GIVEN face auth sensor exists, keyguard is showing & strong auth isn't required + // GIVEN face auth sensor exists, keyguard is showing & unlocking with face is allowed val faceLocation = Point(5, 5) `when`(authController.faceSensorLocation).thenReturn(faceLocation) controller.onViewAttached() `when`(keyguardStateController.isShowing).thenReturn(true) - `when`(keyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(false) + `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( + BiometricSourceType.FACE)).thenReturn(true) // WHEN bypass is enabled & face authenticated `when`(bypassController.canBypass()).thenReturn(true) @@ -275,6 +280,8 @@ class AuthRippleControllerTest : SysuiTestCase() { `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation) controller.onViewAttached() `when`(keyguardStateController.isShowing).thenReturn(true) + `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( + BiometricSourceType.FINGERPRINT)).thenReturn(true) `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true) controller.showUnlockRipple(BiometricSourceType.FINGERPRINT) @@ -295,6 +302,8 @@ class AuthRippleControllerTest : SysuiTestCase() { `when`(keyguardStateController.isShowing).thenReturn(true) `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true) `when`(authController.isUdfpsFingerDown).thenReturn(true) + `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( + eq(BiometricSourceType.FACE))).thenReturn(true) controller.showUnlockRipple(BiometricSourceType.FACE) assertTrue("reveal didn't start on keyguardFadingAway", diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java index e1346ead3e7f..0000c32aa60d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java @@ -118,13 +118,6 @@ public class CommandQueueTest extends SysuiTestCase { } @Test - public void testCollapsePanels() { - mCommandQueue.animateCollapsePanels(); - waitForIdleSync(); - verify(mCallbacks).animateCollapsePanels(eq(0), eq(false)); - } - - @Test public void testExpandSettings() { String panel = "some_panel"; mCommandQueue.animateExpandSettingsPanel(panel); 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 ed2afe753a5e..915924f13197 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 @@ -41,7 +41,6 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.shade.ShadeController; import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger; -import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerFake; import com.android.systemui.statusbar.policy.HeadsUpManager; import org.junit.Before; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java index d5bfe1f7c2ce..c17c5b097db3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java @@ -136,7 +136,7 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { StatusBarManager.DISABLE2_NOTIFICATION_SHADE, false); verify(mCentralSurfaces).updateQsExpansionEnabled(); - verify(mShadeController).animateCollapsePanels(); + verify(mShadeController).animateCollapseShade(); // Trying to open it does nothing. mSbcqCallbacks.animateExpandNotificationsPanel(); @@ -154,7 +154,7 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { mSbcqCallbacks.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE, StatusBarManager.DISABLE2_NONE, false); verify(mCentralSurfaces).updateQsExpansionEnabled(); - verify(mShadeController, never()).animateCollapsePanels(); + verify(mShadeController, never()).animateCollapseShade(); // Can now be opened. mSbcqCallbacks.animateExpandNotificationsPanel(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 013e7278753d..ed84e4268c90 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -392,10 +392,21 @@ public class CentralSurfacesImplTest extends SysuiTestCase { return null; }).when(mNotificationShadeWindowController).batchApplyWindowLayoutParams(any()); - mShadeController = spy(new ShadeControllerImpl(mCommandQueue, - mStatusBarStateController, mNotificationShadeWindowController, - mStatusBarKeyguardViewManager, mContext.getSystemService(WindowManager.class), - () -> Optional.of(mCentralSurfaces), () -> mAssistManager)); + mShadeController = spy(new ShadeControllerImpl( + mCommandQueue, + mKeyguardStateController, + mStatusBarStateController, + mStatusBarKeyguardViewManager, + mStatusBarWindowController, + mNotificationShadeWindowController, + mContext.getSystemService(WindowManager.class), + () -> mAssistManager, + () -> mNotificationGutsManager + )); + mShadeController.setNotificationPanelViewController(mNotificationPanelViewController); + mShadeController.setNotificationShadeWindowViewController( + mNotificationShadeWindowViewController); + mShadeController.setNotificationPresenter(mNotificationPresenter); when(mOperatorNameViewControllerFactory.create(any())) .thenReturn(mOperatorNameViewController); @@ -492,6 +503,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { return mViewRootImpl; } }; + mCentralSurfaces.initShadeVisibilityListener(); when(mViewRootImpl.getOnBackInvokedDispatcher()) .thenReturn(mOnBackInvokedDispatcher); when(mKeyguardViewMediator.registerCentralSurfaces( @@ -807,7 +819,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { when(mNotificationPanelViewController.canPanelBeCollapsed()).thenReturn(true); mOnBackInvokedCallback.getValue().onBackInvoked(); - verify(mShadeController).animateCollapsePanels(); + verify(mShadeController).animateCollapseShade(); } @Test @@ -1030,7 +1042,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test - public void collapseShade_callsAnimateCollapsePanels_whenExpanded() { + public void collapseShade_callsanimateCollapseShade_whenExpanded() { // GIVEN the shade is expanded mCentralSurfaces.onShadeExpansionFullyChanged(true); mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE); @@ -1038,12 +1050,12 @@ public class CentralSurfacesImplTest extends SysuiTestCase { // WHEN collapseShade is called mCentralSurfaces.collapseShade(); - // VERIFY that animateCollapsePanels is called - verify(mShadeController).animateCollapsePanels(); + // VERIFY that animateCollapseShade is called + verify(mShadeController).animateCollapseShade(); } @Test - public void collapseShade_doesNotCallAnimateCollapsePanels_whenCollapsed() { + public void collapseShade_doesNotCallanimateCollapseShade_whenCollapsed() { // GIVEN the shade is collapsed mCentralSurfaces.onShadeExpansionFullyChanged(false); mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE); @@ -1051,12 +1063,12 @@ public class CentralSurfacesImplTest extends SysuiTestCase { // WHEN collapseShade is called mCentralSurfaces.collapseShade(); - // VERIFY that animateCollapsePanels is NOT called - verify(mShadeController, never()).animateCollapsePanels(); + // VERIFY that animateCollapseShade is NOT called + verify(mShadeController, never()).animateCollapseShade(); } @Test - public void collapseShadeForBugReport_callsAnimateCollapsePanels_whenFlagDisabled() { + public void collapseShadeForBugReport_callsanimateCollapseShade_whenFlagDisabled() { // GIVEN the shade is expanded & flag enabled mCentralSurfaces.onShadeExpansionFullyChanged(true); mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE); @@ -1065,12 +1077,12 @@ public class CentralSurfacesImplTest extends SysuiTestCase { // WHEN collapseShadeForBugreport is called mCentralSurfaces.collapseShadeForBugreport(); - // VERIFY that animateCollapsePanels is called - verify(mShadeController).animateCollapsePanels(); + // VERIFY that animateCollapseShade is called + verify(mShadeController).animateCollapseShade(); } @Test - public void collapseShadeForBugReport_doesNotCallAnimateCollapsePanels_whenFlagEnabled() { + public void collapseShadeForBugReport_doesNotCallanimateCollapseShade_whenFlagEnabled() { // GIVEN the shade is expanded & flag enabled mCentralSurfaces.onShadeExpansionFullyChanged(true); mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE); @@ -1079,8 +1091,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { // WHEN collapseShadeForBugreport is called mCentralSurfaces.collapseShadeForBugreport(); - // VERIFY that animateCollapsePanels is called - verify(mShadeController, never()).animateCollapsePanels(); + // VERIFY that animateCollapseShade is called + verify(mShadeController, never()).animateCollapseShade(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java index d3b541899635..df7ee432e79e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java @@ -39,6 +39,7 @@ import static org.mockito.Mockito.when; import android.content.res.ColorStateList; import android.graphics.Color; +import android.hardware.biometrics.BiometricSourceType; import android.os.Handler; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -398,6 +399,8 @@ public class KeyguardBouncerTest extends SysuiTestCase { @Test public void testShow_delaysIfFaceAuthIsRunning() { + when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE)) + .thenReturn(true); when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true); mBouncer.show(true /* reset */); @@ -410,9 +413,10 @@ public class KeyguardBouncerTest extends SysuiTestCase { } @Test - public void testShow_doesNotDelaysIfFaceAuthIsLockedOut() { + public void testShow_doesNotDelaysIfFaceAuthIsNotAllowed() { when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true); - when(mKeyguardUpdateMonitor.isFaceLockedOut()).thenReturn(true); + when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE)) + .thenReturn(false); mBouncer.show(true /* reset */); verify(mHandler, never()).postDelayed(any(), anyLong()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index bf5186b6324d..e467d9399059 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -307,6 +307,17 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test + public void onPanelExpansionChanged_neverTranslatesBouncerWhenOccluded() { + when(mKeyguardStateController.isOccluded()).thenReturn(true); + mStatusBarKeyguardViewManager.onPanelExpansionChanged( + expansionEvent( + /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE, + /* expanded= */ true, + /* tracking= */ false)); + verify(mPrimaryBouncer, never()).setExpansion(anyFloat()); + } + + @Test public void onPanelExpansionChanged_neverTranslatesBouncerWhenShowBouncer() { // Since KeyguardBouncer.EXPANSION_VISIBLE = 0 panel expansion, if the unlock is dismissing // the bouncer, there may be an onPanelExpansionChanged(0) call to collapse the panel diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java index ce54d784520c..cae414a3dc67 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java @@ -263,7 +263,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { while (!runnables.isEmpty()) runnables.remove(0).run(); // Then - verify(mShadeController, atLeastOnce()).collapsePanel(); + verify(mShadeController, atLeastOnce()).collapseShade(); verify(mActivityLaunchAnimator).startPendingIntentWithAnimation(any(), eq(false) /* animate */, any(), any()); @@ -296,7 +296,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { verify(mBubblesManager).expandStackAndSelectBubble(eq(mBubbleNotificationRow.getEntry())); // This is called regardless, and simply short circuits when there is nothing to do. - verify(mShadeController, atLeastOnce()).collapsePanel(); + verify(mShadeController, atLeastOnce()).collapseShade(); verify(mAssistManager).hideAssist(); @@ -329,7 +329,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { // Then verify(mBubblesManager).expandStackAndSelectBubble(eq(mBubbleNotificationRow.getEntry())); - verify(mShadeController, atLeastOnce()).collapsePanel(); + verify(mShadeController, atLeastOnce()).collapseShade(); verify(mAssistManager).hideAssist(); @@ -357,7 +357,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { // Then verify(mBubblesManager).expandStackAndSelectBubble(mBubbleNotificationRow.getEntry()); - verify(mShadeController, atLeastOnce()).collapsePanel(); + verify(mShadeController, atLeastOnce()).collapseShade(); verify(mAssistManager).hideAssist(); diff --git a/services/companion/java/com/android/server/companion/virtual/SensorController.java b/services/companion/java/com/android/server/companion/virtual/SensorController.java new file mode 100644 index 000000000000..ec7e993ec30e --- /dev/null +++ b/services/companion/java/com/android/server/companion/virtual/SensorController.java @@ -0,0 +1,235 @@ +/* + * 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.companion.virtual; + +import android.annotation.NonNull; +import android.companion.virtual.sensor.IVirtualSensorStateChangeCallback; +import android.companion.virtual.sensor.VirtualSensorConfig; +import android.companion.virtual.sensor.VirtualSensorEvent; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalServices; +import com.android.server.sensors.SensorManagerInternal; + +import java.io.PrintWriter; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; + +/** Controls virtual sensors, including their lifecycle and sensor event dispatch. */ +public class SensorController { + + private static final String TAG = "SensorController"; + + private final Object mLock; + private final int mVirtualDeviceId; + @GuardedBy("mLock") + private final Map<IBinder, SensorDescriptor> mSensorDescriptors = new ArrayMap<>(); + + private final SensorManagerInternal mSensorManagerInternal; + + public SensorController(@NonNull Object lock, int virtualDeviceId) { + mLock = lock; + mVirtualDeviceId = virtualDeviceId; + mSensorManagerInternal = LocalServices.getService(SensorManagerInternal.class); + } + + void close() { + synchronized (mLock) { + final Iterator<Map.Entry<IBinder, SensorDescriptor>> iterator = + mSensorDescriptors.entrySet().iterator(); + if (iterator.hasNext()) { + final Map.Entry<IBinder, SensorDescriptor> entry = iterator.next(); + final IBinder token = entry.getKey(); + final SensorDescriptor sensorDescriptor = entry.getValue(); + iterator.remove(); + closeSensorDescriptorLocked(token, sensorDescriptor); + } + } + } + + void createSensor(@NonNull IBinder deviceToken, @NonNull VirtualSensorConfig config) { + Objects.requireNonNull(deviceToken); + Objects.requireNonNull(config); + try { + createSensorInternal(deviceToken, config); + } catch (SensorCreationException e) { + throw new RuntimeException( + "Failed to create virtual sensor '" + config.getName() + "'.", e); + } + } + + private void createSensorInternal(IBinder deviceToken, VirtualSensorConfig config) + throws SensorCreationException { + final SensorManagerInternal.RuntimeSensorStateChangeCallback runtimeSensorCallback = + (enabled, samplingPeriodMicros, batchReportLatencyMicros) -> { + IVirtualSensorStateChangeCallback callback = config.getStateChangeCallback(); + if (callback != null) { + try { + callback.onStateChanged( + enabled, samplingPeriodMicros, batchReportLatencyMicros); + } catch (RemoteException e) { + throw new RuntimeException("Failed to call sensor callback.", e); + } + } + }; + + final int handle = mSensorManagerInternal.createRuntimeSensor(mVirtualDeviceId, + config.getType(), config.getName(), + config.getVendor() == null ? "" : config.getVendor(), + runtimeSensorCallback); + if (handle <= 0) { + throw new SensorCreationException("Received an invalid virtual sensor handle."); + } + + // The handle is valid from here, so ensure that all failures clean it up. + final BinderDeathRecipient binderDeathRecipient; + try { + binderDeathRecipient = new BinderDeathRecipient(deviceToken); + deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0); + } catch (RemoteException e) { + mSensorManagerInternal.removeRuntimeSensor(handle); + throw new SensorCreationException("Client died before sensor could be created.", e); + } + + synchronized (mLock) { + SensorDescriptor sensorDescriptor = new SensorDescriptor( + handle, config.getType(), config.getName(), binderDeathRecipient); + mSensorDescriptors.put(deviceToken, sensorDescriptor); + } + } + + boolean sendSensorEvent(@NonNull IBinder token, @NonNull VirtualSensorEvent event) { + Objects.requireNonNull(token); + Objects.requireNonNull(event); + synchronized (mLock) { + final SensorDescriptor sensorDescriptor = mSensorDescriptors.get(token); + if (sensorDescriptor == null) { + throw new IllegalArgumentException("Could not send sensor event for given token"); + } + return mSensorManagerInternal.sendSensorEvent( + sensorDescriptor.getHandle(), sensorDescriptor.getType(), + event.getTimestampNanos(), event.getValues()); + } + } + + void unregisterSensor(@NonNull IBinder token) { + Objects.requireNonNull(token); + synchronized (mLock) { + final SensorDescriptor sensorDescriptor = mSensorDescriptors.remove(token); + if (sensorDescriptor == null) { + throw new IllegalArgumentException("Could not unregister sensor for given token"); + } + closeSensorDescriptorLocked(token, sensorDescriptor); + } + } + + @GuardedBy("mLock") + private void closeSensorDescriptorLocked(IBinder token, SensorDescriptor sensorDescriptor) { + token.unlinkToDeath(sensorDescriptor.getDeathRecipient(), /* flags= */ 0); + final int handle = sensorDescriptor.getHandle(); + mSensorManagerInternal.removeRuntimeSensor(handle); + } + + + void dump(@NonNull PrintWriter fout) { + fout.println(" SensorController: "); + synchronized (mLock) { + fout.println(" Active descriptors: "); + for (SensorDescriptor sensorDescriptor : mSensorDescriptors.values()) { + fout.println(" handle: " + sensorDescriptor.getHandle()); + fout.println(" type: " + sensorDescriptor.getType()); + fout.println(" name: " + sensorDescriptor.getName()); + } + } + } + + @VisibleForTesting + void addSensorForTesting(IBinder deviceToken, int handle, int type, String name) { + synchronized (mLock) { + mSensorDescriptors.put(deviceToken, + new SensorDescriptor(handle, type, name, () -> {})); + } + } + + @VisibleForTesting + Map<IBinder, SensorDescriptor> getSensorDescriptors() { + synchronized (mLock) { + return mSensorDescriptors; + } + } + + @VisibleForTesting + static final class SensorDescriptor { + + private final int mHandle; + private final IBinder.DeathRecipient mDeathRecipient; + private final int mType; + private final String mName; + + SensorDescriptor(int handle, int type, String name, IBinder.DeathRecipient deathRecipient) { + mHandle = handle; + mDeathRecipient = deathRecipient; + mType = type; + mName = name; + } + public int getHandle() { + return mHandle; + } + public int getType() { + return mType; + } + public String getName() { + return mName; + } + public IBinder.DeathRecipient getDeathRecipient() { + return mDeathRecipient; + } + } + + private final class BinderDeathRecipient implements IBinder.DeathRecipient { + private final IBinder mDeviceToken; + + BinderDeathRecipient(IBinder deviceToken) { + mDeviceToken = deviceToken; + } + + @Override + public void binderDied() { + // All callers are expected to call {@link VirtualDevice#unregisterSensor} before + // quitting, which removes this death recipient. If this is invoked, the remote end + // died, or they disposed of the object without properly unregistering. + Slog.e(TAG, "Virtual sensor controller binder died"); + unregisterSensor(mDeviceToken); + } + } + + /** An internal exception that is thrown to indicate an error when opening a virtual sensor. */ + private static class SensorCreationException extends Exception { + SensorCreationException(String message) { + super(message); + } + SensorCreationException(String message, Exception cause) { + super(message, cause); + } + } +} diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index 7e82918d621f..828f302e631a 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -38,6 +38,8 @@ import android.companion.virtual.VirtualDeviceManager.ActivityListener; import android.companion.virtual.VirtualDeviceParams; import android.companion.virtual.audio.IAudioConfigChangedCallback; import android.companion.virtual.audio.IAudioRoutingCallback; +import android.companion.virtual.sensor.VirtualSensorConfig; +import android.companion.virtual.sensor.VirtualSensorEvent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -76,6 +78,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.function.Consumer; @@ -98,6 +101,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub private final int mOwnerUid; private final int mDeviceId; private final InputController mInputController; + private final SensorController mSensorController; private VirtualAudioController mVirtualAudioController; @VisibleForTesting final Set<Integer> mVirtualDisplayIds = new ArraySet<>(); @@ -160,6 +164,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub ownerUid, deviceId, /* inputController= */ null, + /* sensorController= */ null, listener, pendingTrampolineCallback, activityListener, @@ -175,6 +180,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub int ownerUid, int deviceId, InputController inputController, + SensorController sensorController, OnDeviceCloseListener listener, PendingTrampolineCallback pendingTrampolineCallback, IVirtualDeviceActivityListener activityListener, @@ -198,6 +204,11 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } else { mInputController = inputController; } + if (sensorController == null) { + mSensorController = new SensorController(mVirtualDeviceLock, mDeviceId); + } else { + mSensorController = sensorController; + } mListener = listener; try { token.linkToDeath(this, 0); @@ -319,11 +330,12 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub mListener.onClose(mAssociationInfo.getId()); mAppToken.unlinkToDeath(this, 0); - final long token = Binder.clearCallingIdentity(); + final long ident = Binder.clearCallingIdentity(); try { mInputController.close(); + mSensorController.close(); } finally { - Binder.restoreCallingIdentity(token); + Binder.restoreCallingIdentity(ident); } } @@ -403,12 +415,12 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub + "this virtual device"); } } - final long token = Binder.clearCallingIdentity(); + final long ident = Binder.clearCallingIdentity(); try { mInputController.createDpad(deviceName, vendorId, productId, deviceToken, displayId); } finally { - Binder.restoreCallingIdentity(token); + Binder.restoreCallingIdentity(ident); } } @@ -430,12 +442,12 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub + "this virtual device"); } } - final long token = Binder.clearCallingIdentity(); + final long ident = Binder.clearCallingIdentity(); try { mInputController.createKeyboard(deviceName, vendorId, productId, deviceToken, displayId); } finally { - Binder.restoreCallingIdentity(token); + Binder.restoreCallingIdentity(ident); } } @@ -457,11 +469,11 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub + "virtual device"); } } - final long token = Binder.clearCallingIdentity(); + final long ident = Binder.clearCallingIdentity(); try { mInputController.createMouse(deviceName, vendorId, productId, deviceToken, displayId); } finally { - Binder.restoreCallingIdentity(token); + Binder.restoreCallingIdentity(ident); } } @@ -491,12 +503,12 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub + screenSize); } - final long token = Binder.clearCallingIdentity(); + final long ident = Binder.clearCallingIdentity(); try { mInputController.createTouchscreen(deviceName, vendorId, productId, deviceToken, displayId, screenSize); } finally { - Binder.restoreCallingIdentity(token); + Binder.restoreCallingIdentity(ident); } } @@ -506,92 +518,92 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub android.Manifest.permission.CREATE_VIRTUAL_DEVICE, "Permission required to unregister this input device"); - final long binderToken = Binder.clearCallingIdentity(); + final long ident = Binder.clearCallingIdentity(); try { mInputController.unregisterInputDevice(token); } finally { - Binder.restoreCallingIdentity(binderToken); + Binder.restoreCallingIdentity(ident); } } @Override // Binder call public int getInputDeviceId(IBinder token) { - final long binderToken = Binder.clearCallingIdentity(); + final long ident = Binder.clearCallingIdentity(); try { return mInputController.getInputDeviceId(token); } finally { - Binder.restoreCallingIdentity(binderToken); + Binder.restoreCallingIdentity(ident); } } @Override // Binder call public boolean sendDpadKeyEvent(IBinder token, VirtualKeyEvent event) { - final long binderToken = Binder.clearCallingIdentity(); + final long ident = Binder.clearCallingIdentity(); try { return mInputController.sendDpadKeyEvent(token, event); } finally { - Binder.restoreCallingIdentity(binderToken); + Binder.restoreCallingIdentity(ident); } } @Override // Binder call public boolean sendKeyEvent(IBinder token, VirtualKeyEvent event) { - final long binderToken = Binder.clearCallingIdentity(); + final long ident = Binder.clearCallingIdentity(); try { return mInputController.sendKeyEvent(token, event); } finally { - Binder.restoreCallingIdentity(binderToken); + Binder.restoreCallingIdentity(ident); } } @Override // Binder call public boolean sendButtonEvent(IBinder token, VirtualMouseButtonEvent event) { - final long binderToken = Binder.clearCallingIdentity(); + final long ident = Binder.clearCallingIdentity(); try { return mInputController.sendButtonEvent(token, event); } finally { - Binder.restoreCallingIdentity(binderToken); + Binder.restoreCallingIdentity(ident); } } @Override // Binder call public boolean sendTouchEvent(IBinder token, VirtualTouchEvent event) { - final long binderToken = Binder.clearCallingIdentity(); + final long ident = Binder.clearCallingIdentity(); try { return mInputController.sendTouchEvent(token, event); } finally { - Binder.restoreCallingIdentity(binderToken); + Binder.restoreCallingIdentity(ident); } } @Override // Binder call public boolean sendRelativeEvent(IBinder token, VirtualMouseRelativeEvent event) { - final long binderToken = Binder.clearCallingIdentity(); + final long ident = Binder.clearCallingIdentity(); try { return mInputController.sendRelativeEvent(token, event); } finally { - Binder.restoreCallingIdentity(binderToken); + Binder.restoreCallingIdentity(ident); } } @Override // Binder call public boolean sendScrollEvent(IBinder token, VirtualMouseScrollEvent event) { - final long binderToken = Binder.clearCallingIdentity(); + final long ident = Binder.clearCallingIdentity(); try { return mInputController.sendScrollEvent(token, event); } finally { - Binder.restoreCallingIdentity(binderToken); + Binder.restoreCallingIdentity(ident); } } @Override // Binder call public PointF getCursorPosition(IBinder token) { - final long binderToken = Binder.clearCallingIdentity(); + final long ident = Binder.clearCallingIdentity(); try { return mInputController.getCursorPosition(token); } finally { - Binder.restoreCallingIdentity(binderToken); + Binder.restoreCallingIdentity(ident); } } @@ -601,7 +613,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub android.Manifest.permission.CREATE_VIRTUAL_DEVICE, "Permission required to unregister this input device"); - final long binderToken = Binder.clearCallingIdentity(); + final long ident = Binder.clearCallingIdentity(); try { synchronized (mVirtualDeviceLock) { mDefaultShowPointerIcon = showPointerIcon; @@ -610,7 +622,50 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } } } finally { - Binder.restoreCallingIdentity(binderToken); + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call + public void createVirtualSensor( + @NonNull IBinder deviceToken, + @NonNull VirtualSensorConfig config) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CREATE_VIRTUAL_DEVICE, + "Permission required to create a virtual sensor"); + Objects.requireNonNull(config); + Objects.requireNonNull(deviceToken); + final long ident = Binder.clearCallingIdentity(); + try { + mSensorController.createSensor(deviceToken, config); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call + public void unregisterSensor(@NonNull IBinder token) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CREATE_VIRTUAL_DEVICE, + "Permission required to unregister a virtual sensor"); + final long ident = Binder.clearCallingIdentity(); + try { + mSensorController.unregisterSensor(token); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call + public boolean sendSensorEvent(@NonNull IBinder token, @NonNull VirtualSensorEvent event) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.CREATE_VIRTUAL_DEVICE, + "Permission required to send a virtual sensor event"); + final long ident = Binder.clearCallingIdentity(); + try { + return mSensorController.sendSensorEvent(token, event); + } finally { + Binder.restoreCallingIdentity(ident); } } @@ -627,6 +682,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub fout.println(" mDefaultShowPointerIcon: " + mDefaultShowPointerIcon); } mInputController.dump(fout); + mSensorController.dump(fout); } GenericWindowPolicyController createWindowPolicyController( diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index fe26700f180b..2b62f69d78f3 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -32,6 +32,7 @@ import android.companion.virtual.VirtualDevice; import android.companion.virtual.VirtualDeviceManager; import android.companion.virtual.VirtualDeviceParams; import android.content.Context; +import android.content.Intent; import android.hardware.display.DisplayManagerInternal; import android.hardware.display.IVirtualDisplayCallback; import android.hardware.display.VirtualDisplayConfig; @@ -280,7 +281,22 @@ public class VirtualDeviceManagerService extends SystemService { @Override public void onClose(int associationId) { synchronized (mVirtualDeviceManagerLock) { - mVirtualDevices.remove(associationId); + VirtualDeviceImpl removedDevice = + mVirtualDevices.removeReturnOld(associationId); + if (removedDevice != null) { + Intent i = new Intent( + VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED); + i.putExtra( + VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID, + removedDevice.getDeviceId()); + i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + final long identity = Binder.clearCallingIdentity(); + try { + getContext().sendBroadcastAsUser(i, UserHandle.ALL); + } finally { + Binder.restoreCallingIdentity(identity); + } + } mAppsOnVirtualDevices.remove(associationId); if (cameraAccessController != null) { cameraAccessController.stopObservingIfNeeded(); diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java index 544dd4e6dcff..68880bd09a40 100644 --- a/services/core/java/com/android/server/BinaryTransparencyService.java +++ b/services/core/java/com/android/server/BinaryTransparencyService.java @@ -73,6 +73,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.Executors; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * @hide @@ -104,6 +105,9 @@ public class BinaryTransparencyService extends SystemService { @VisibleForTesting static final String BUNDLE_CONTENT_DIGEST = "content-digest"; + static final String APEX_PRELOAD_LOCATION = "/system/apex/"; + static final String APEX_PRELOAD_LOCATION_ERROR = "could-not-be-determined"; + // used for indicating any type of error during MBA measurement static final int MBA_STATUS_ERROR = 0; // used for indicating factory condition preloads @@ -1110,18 +1114,22 @@ public class BinaryTransparencyService extends SystemService { } } - // TODO(b/259349011): Need to be more robust against package name mismatch in the filename + @NonNull private String getOriginalApexPreinstalledLocation(String packageName, String currentInstalledLocation) { - if (currentInstalledLocation.contains("/decompressed/")) { - String resultPath = "system/apex" + packageName + ".capex"; - File f = new File(resultPath); - if (f.exists()) { - return resultPath; + // get a listing of all apex files in /system/apex/ + Set<String> originalApexs = Stream.of(new File(APEX_PRELOAD_LOCATION).listFiles()) + .filter(f -> !f.isDirectory()) + .map(File::getName) + .collect(Collectors.toSet()); + + for (String originalApex : originalApexs) { + if (originalApex.startsWith(packageName)) { + return APEX_PRELOAD_LOCATION + originalApex; } - return "/system/apex/" + packageName + ".next.capex"; } - return "/system/apex" + packageName + "apex"; + + return APEX_PRELOAD_LOCATION_ERROR; } /** diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java index 104d10de93c8..540ed4cdb330 100644 --- a/services/core/java/com/android/server/DockObserver.java +++ b/services/core/java/com/android/server/DockObserver.java @@ -302,6 +302,7 @@ final class DockObserver extends SystemService { getContext(), soundUri); if (sfx != null) { sfx.setStreamType(AudioManager.STREAM_SYSTEM); + sfx.preferBuiltinDevice(true); sfx.play(); } } diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java index 04ba757d03e7..1eebd0127818 100644 --- a/services/core/java/com/android/server/am/BroadcastConstants.java +++ b/services/core/java/com/android/server/am/BroadcastConstants.java @@ -153,6 +153,11 @@ public class BroadcastConstants { "bcast_extra_running_urgent_process_queues"; private static final int DEFAULT_EXTRA_RUNNING_URGENT_PROCESS_QUEUES = 1; + public int MAX_CONSECUTIVE_URGENT_DISPATCHES = DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES; + private static final String KEY_MAX_CONSECUTIVE_URGENT_DISPATCHES = + "bcast_max_consecutive_urgent_dispatches"; + private static final int DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES = 3; + /** * For {@link BroadcastQueueModernImpl}: Maximum number of active broadcasts * to dispatch to a "running" process queue before we retire them back to @@ -333,6 +338,9 @@ public class BroadcastConstants { EXTRA_RUNNING_URGENT_PROCESS_QUEUES = getDeviceConfigInt( KEY_EXTRA_RUNNING_URGENT_PROCESS_QUEUES, DEFAULT_EXTRA_RUNNING_URGENT_PROCESS_QUEUES); + MAX_CONSECUTIVE_URGENT_DISPATCHES = getDeviceConfigInt( + KEY_MAX_CONSECUTIVE_URGENT_DISPATCHES, + DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES); MAX_RUNNING_ACTIVE_BROADCASTS = getDeviceConfigInt(KEY_MAX_RUNNING_ACTIVE_BROADCASTS, DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS); MAX_PENDING_BROADCASTS = getDeviceConfigInt(KEY_MAX_PENDING_BROADCASTS, diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index 0f9c775751af..66d7fc92340a 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -147,6 +147,12 @@ class BroadcastProcessQueue { private boolean mActiveViaColdStart; /** + * Number of consecutive urgent broadcasts that have been dispatched + * since the last non-urgent dispatch. + */ + private int mActiveCountConsecutiveUrgent; + + /** * Count of pending broadcasts of these various flavors. */ private int mCountForeground; @@ -546,19 +552,47 @@ class BroadcastProcessQueue { * {@link #isEmpty()} being false. */ SomeArgs removeNextBroadcast() { - ArrayDeque<SomeArgs> queue = queueForNextBroadcast(); + final ArrayDeque<SomeArgs> queue = queueForNextBroadcast(); + if (queue == mPendingUrgent) { + mActiveCountConsecutiveUrgent++; + } else { + mActiveCountConsecutiveUrgent = 0; + } return queue.removeFirst(); } @Nullable ArrayDeque<SomeArgs> queueForNextBroadcast() { - if (!mPendingUrgent.isEmpty()) { - return mPendingUrgent; - } else if (!mPending.isEmpty()) { - return mPending; + ArrayDeque<SomeArgs> nextUrgent = mPendingUrgent.isEmpty() ? null : mPendingUrgent; + ArrayDeque<SomeArgs> nextNormal = null; + if (!mPending.isEmpty()) { + nextNormal = mPending; } else if (!mPendingOffload.isEmpty()) { - return mPendingOffload; + nextNormal = mPendingOffload; } - return null; + // nothing urgent pending, no further decisionmaking + if (nextUrgent == null) { + return nextNormal; + } + // nothing but urgent pending, also no further decisionmaking + if (nextNormal == null) { + return nextUrgent; + } + + // Starvation mitigation: although we prioritize urgent broadcasts by default, + // we allow non-urgent deliveries to make steady progress even if urgent + // broadcasts are arriving faster than they can be dispatched. + // + // We do not try to defer to the next non-urgent broadcast if that broadcast + // is ordered and still blocked on delivery to other recipients. + final SomeArgs nextNormalArgs = nextNormal.peekFirst(); + final BroadcastRecord rNormal = (BroadcastRecord) nextNormalArgs.arg1; + final int nextNormalIndex = nextNormalArgs.argi1; + final BroadcastRecord rUrgent = (BroadcastRecord) nextUrgent.peekFirst().arg1; + final boolean canTakeNormal = + mActiveCountConsecutiveUrgent >= constants.MAX_CONSECUTIVE_URGENT_DISPATCHES + && rNormal.enqueueTime <= rUrgent.enqueueTime + && !blockedOnOrderedDispatch(rNormal, nextNormalIndex); + return canTakeNormal ? nextNormal : nextUrgent; } /** @@ -710,6 +744,18 @@ class BroadcastProcessQueue { } } + private boolean blockedOnOrderedDispatch(BroadcastRecord r, int index) { + final int blockedUntilTerminalCount = r.blockedUntilTerminalCount[index]; + + // We might be blocked waiting for other receivers to finish, + // typically for an ordered broadcast or priority traunches + if (r.terminalCount < blockedUntilTerminalCount + && !isDeliveryStateTerminal(r.getDeliveryState(index))) { + return true; + } + return false; + } + /** * Update {@link #getRunnableAt()} if it's currently invalidated. */ @@ -718,13 +764,11 @@ class BroadcastProcessQueue { if (next != null) { final BroadcastRecord r = (BroadcastRecord) next.arg1; final int index = next.argi1; - final int blockedUntilTerminalCount = r.blockedUntilTerminalCount[index]; final long runnableAt = r.enqueueTime; - // We might be blocked waiting for other receivers to finish, - // typically for an ordered broadcast or priority traunches - if (r.terminalCount < blockedUntilTerminalCount - && !isDeliveryStateTerminal(r.getDeliveryState(index))) { + // If we're specifically queued behind other ordered dispatch activity, + // we aren't runnable yet + if (blockedOnOrderedDispatch(r, index)) { mRunnableAt = Long.MAX_VALUE; mRunnableAtReason = REASON_BLOCKED; return; diff --git a/services/core/java/com/android/server/app/TEST_MAPPING b/services/core/java/com/android/server/app/TEST_MAPPING index 0ba4d8c35523..82840ee5fcbb 100644 --- a/services/core/java/com/android/server/app/TEST_MAPPING +++ b/services/core/java/com/android/server/app/TEST_MAPPING @@ -9,6 +9,23 @@ ] }, { + "name": "CtsStatsdAtomHostTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "include-filter": "android.cts.statsdatom.gamemanager" + } + ], + "file_patterns": [ + "(/|^)GameManagerService.java" + ] + }, + { "name": "FrameworksMockingServicesTests", "options": [ { @@ -18,6 +35,23 @@ "exclude-annotation": "androidx.test.filters.FlakyTest" } ] + }, + { + "name": "FrameworksCoreGameManagerTests", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "include-filter": "android.app" + } + ], + "file_patterns": [ + "(/|^)GameManagerService.java", "(/|^)GameManagerSettings.java" + ] } ] }
\ No newline at end of file diff --git a/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java index f6fff351c232..8d1da71c95c1 100644 --- a/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java +++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java @@ -20,7 +20,7 @@ import static android.app.AppOpsManager.OP_NONE; import static android.app.AppOpsManager.WATCH_FOREGROUND_CHANGES; import static android.app.AppOpsManager.opRestrictsRead; -import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS; +import static com.android.server.appop.AppOpsServiceImpl.ModeCallback.ALL_OPS; import android.Manifest; import android.annotation.NonNull; @@ -56,7 +56,7 @@ import java.util.Objects; * Legacy implementation for App-ops service's app-op mode (uid and package) storage and access. * In the future this class will also include mode callbacks and op restrictions. */ -public class LegacyAppOpsServiceInterfaceImpl implements AppOpsServiceInterface { +public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface { static final String TAG = "LegacyAppOpsServiceInterfaceImpl"; @@ -84,9 +84,9 @@ public class LegacyAppOpsServiceInterfaceImpl implements AppOpsServiceInterface private static final int UID_ANY = -2; - LegacyAppOpsServiceInterfaceImpl(PersistenceScheduler persistenceScheduler, - @NonNull Object lock, Handler handler, Context context, - SparseArray<int[]> switchedOps) { + AppOpsCheckingServiceImpl(PersistenceScheduler persistenceScheduler, + @NonNull Object lock, Handler handler, Context context, + SparseArray<int[]> switchedOps) { this.mPersistenceScheduler = persistenceScheduler; this.mLock = lock; this.mHandler = handler; @@ -456,7 +456,7 @@ public class LegacyAppOpsServiceInterfaceImpl implements AppOpsServiceInterface final ArraySet<String> reportedPackageNames = callbackSpecs.valueAt(i); if (reportedPackageNames == null) { mHandler.sendMessage(PooledLambda.obtainMessage( - LegacyAppOpsServiceInterfaceImpl::notifyOpChanged, + AppOpsCheckingServiceImpl::notifyOpChanged, this, callback, code, uid, (String) null)); } else { @@ -464,7 +464,7 @@ public class LegacyAppOpsServiceInterfaceImpl implements AppOpsServiceInterface for (int j = 0; j < reportedPackageCount; j++) { final String reportedPackageName = reportedPackageNames.valueAt(j); mHandler.sendMessage(PooledLambda.obtainMessage( - LegacyAppOpsServiceInterfaceImpl::notifyOpChanged, + AppOpsCheckingServiceImpl::notifyOpChanged, this, callback, code, uid, reportedPackageName)); } } diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java new file mode 100644 index 000000000000..d8d0d48965ea --- /dev/null +++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java @@ -0,0 +1,209 @@ +/* + * 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.appop; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.app.AppOpsManager.Mode; +import android.util.ArraySet; +import android.util.SparseBooleanArray; +import android.util.SparseIntArray; + +import java.io.PrintWriter; + +/** + * Interface for accessing and modifying modes for app-ops i.e. package and uid modes. + * This interface also includes functions for added and removing op mode watchers. + * In the future this interface will also include op restrictions. + */ +public interface AppOpsCheckingServiceInterface { + /** + * Returns a copy of non-default app-ops with op as keys and their modes as values for a uid. + * Returns an empty SparseIntArray if nothing is set. + * @param uid for which we need the app-ops and their modes. + */ + SparseIntArray getNonDefaultUidModes(int uid); + + /** + * Returns the app-op mode for a particular app-op of a uid. + * Returns default op mode if the op mode for particular uid and op is not set. + * @param uid user id for which we need the mode. + * @param op app-op for which we need the mode. + * @return mode of the app-op. + */ + int getUidMode(int uid, int op); + + /** + * Set the app-op mode for a particular uid and op. + * The mode is not set if the mode is the same as the default mode for the op. + * @param uid user id for which we want to set the mode. + * @param op app-op for which we want to set the mode. + * @param mode mode for the app-op. + * @return true if op mode is changed. + */ + boolean setUidMode(int uid, int op, @Mode int mode); + + /** + * Gets the app-op mode for a particular package. + * Returns default op mode if the op mode for the particular package is not set. + * @param packageName package name for which we need the op mode. + * @param op app-op for which we need the mode. + * @param userId user id associated with the package. + * @return the mode of the app-op. + */ + int getPackageMode(@NonNull String packageName, int op, @UserIdInt int userId); + + /** + * Sets the app-op mode for a particular package. + * @param packageName package name for which we need to set the op mode. + * @param op app-op for which we need to set the mode. + * @param mode the mode of the app-op. + * @param userId user id associated with the package. + * + */ + void setPackageMode(@NonNull String packageName, int op, @Mode int mode, @UserIdInt int userId); + + /** + * Stop tracking any app-op modes for a package. + * @param packageName Name of the package for which we want to remove all mode tracking. + * @param userId user id associated with the package. + */ + boolean removePackage(@NonNull String packageName, @UserIdInt int userId); + + /** + * Stop tracking any app-op modes for this uid. + * @param uid user id for which we want to remove all tracking. + */ + void removeUid(int uid); + + /** + * Returns true if all uid modes for this uid are + * in default state. + * @param uid user id + */ + boolean areUidModesDefault(int uid); + + /** + * Returns true if all package modes for this package name are + * in default state. + * @param packageName package name. + * @param userId user id associated with the package. + */ + boolean arePackageModesDefault(String packageName, @UserIdInt int userId); + + /** + * Stop tracking app-op modes for all uid and packages. + */ + void clearAllModes(); + + /** + * Registers changedListener to listen to op's mode change. + * @param changedListener the listener that must be trigger on the op's mode change. + * @param op op representing the app-op whose mode change needs to be listened to. + */ + void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener, int op); + + /** + * Registers changedListener to listen to package's app-op's mode change. + * @param changedListener the listener that must be trigger on the mode change. + * @param packageName of the package whose app-op's mode change needs to be listened to. + */ + void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener, + @NonNull String packageName); + + /** + * Stop the changedListener from triggering on any mode change. + * @param changedListener the listener that needs to be removed. + */ + void removeListener(@NonNull OnOpModeChangedListener changedListener); + + /** + * Temporary API which will be removed once we can safely untangle the methods that use this. + * Returns a set of OnOpModeChangedListener that are listening for op's mode changes. + * @param op app-op whose mode change is being listened to. + */ + ArraySet<OnOpModeChangedListener> getOpModeChangedListeners(int op); + + /** + * Temporary API which will be removed once we can safely untangle the methods that use this. + * Returns a set of OnOpModeChangedListener that are listening for package's op's mode changes. + * @param packageName of package whose app-op's mode change is being listened to. + */ + ArraySet<OnOpModeChangedListener> getPackageModeChangedListeners(@NonNull String packageName); + + /** + * Temporary API which will be removed once we can safely untangle the methods that use this. + * Notify that the app-op's mode is changed by triggering the change listener. + * @param op App-op whose mode has changed + * @param uid user id associated with the app-op (or, if UID_ANY, notifies all users) + */ + void notifyWatchersOfChange(int op, int uid); + + /** + * Temporary API which will be removed once we can safely untangle the methods that use this. + * Notify that the app-op's mode is changed by triggering the change listener. + * @param changedListener the change listener. + * @param op App-op whose mode has changed + * @param uid user id associated with the app-op + * @param packageName package name that is associated with the app-op + */ + void notifyOpChanged(@NonNull OnOpModeChangedListener changedListener, int op, int uid, + @Nullable String packageName); + + /** + * Temporary API which will be removed once we can safely untangle the methods that use this. + * Notify that the app-op's mode is changed to all packages associated with the uid by + * triggering the appropriate change listener. + * @param op App-op whose mode has changed + * @param uid user id associated with the app-op + * @param onlyForeground true if only watchers that + * @param callbackToIgnore callback that should be ignored. + */ + void notifyOpChangedForAllPkgsInUid(int op, int uid, boolean onlyForeground, + @Nullable OnOpModeChangedListener callbackToIgnore); + + /** + * TODO: Move hasForegroundWatchers and foregroundOps into this. + * Go over the list of app-ops for the uid and mark app-ops with MODE_FOREGROUND in + * foregroundOps. + * @param uid for which the app-op's mode needs to be marked. + * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true. + * @return foregroundOps. + */ + SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps); + + /** + * Go over the list of app-ops for the package name and mark app-ops with MODE_FOREGROUND in + * foregroundOps. + * @param packageName for which the app-op's mode needs to be marked. + * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true. + * @param userId user id associated with the package. + * @return foregroundOps. + */ + SparseBooleanArray evalForegroundPackageOps(String packageName, + SparseBooleanArray foregroundOps, @UserIdInt int userId); + + /** + * Dump op mode and package mode listeners and their details. + * @param dumpOp if -1 then op mode listeners for all app-ops are dumped. If it's set to an + * app-op, only the watchers for that app-op are dumped. + * @param dumpUid uid for which we want to dump op mode watchers. + * @param dumpPackage if not null and if dumpOp is -1, dumps watchers for the package name. + * @param printWriter writer to dump to. + */ + boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, PrintWriter printWriter); +} diff --git a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java index adfd2afffd78..af5b07e0bffc 100644 --- a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java +++ b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java @@ -42,7 +42,7 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions { private Context mContext; private Handler mHandler; - private AppOpsServiceInterface mAppOpsServiceInterface; + private AppOpsCheckingServiceInterface mAppOpsServiceInterface; // Map from (Object token) to (int code) to (boolean restricted) private final ArrayMap<Object, SparseBooleanArray> mGlobalRestrictions = new ArrayMap<>(); @@ -56,7 +56,7 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions { mUserRestrictionExcludedPackageTags = new ArrayMap<>(); public AppOpsRestrictionsImpl(Context context, Handler handler, - AppOpsServiceInterface appOpsServiceInterface) { + AppOpsCheckingServiceInterface appOpsServiceInterface) { mContext = context; mHandler = handler; mAppOpsServiceInterface = appOpsServiceInterface; diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 7e00c3212c95..39338c6f43ad 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -18,55 +18,22 @@ package com.android.server.appop; import static android.app.AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE; import static android.app.AppOpsManager.ATTRIBUTION_FLAG_TRUSTED; -import static android.app.AppOpsManager.CALL_BACK_ON_SWITCHED_OP; -import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG; -import static android.app.AppOpsManager.FILTER_BY_OP_NAMES; -import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME; -import static android.app.AppOpsManager.FILTER_BY_UID; -import static android.app.AppOpsManager.HISTORY_FLAG_GET_ATTRIBUTION_CHAINS; -import static android.app.AppOpsManager.HistoricalOpsRequestFilter; -import static android.app.AppOpsManager.KEY_BG_STATE_SETTLE_TIME; -import static android.app.AppOpsManager.KEY_FG_SERVICE_STATE_SETTLE_TIME; -import static android.app.AppOpsManager.KEY_TOP_STATE_SETTLE_TIME; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_DEFAULT; -import static android.app.AppOpsManager.MODE_ERRORED; -import static android.app.AppOpsManager.MODE_FOREGROUND; -import static android.app.AppOpsManager.MODE_IGNORED; -import static android.app.AppOpsManager.OP_CAMERA; import static android.app.AppOpsManager.OP_FLAGS_ALL; import static android.app.AppOpsManager.OP_FLAG_SELF; import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED; import static android.app.AppOpsManager.OP_NONE; -import static android.app.AppOpsManager.OP_PLAY_AUDIO; -import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO; -import static android.app.AppOpsManager.OP_RECORD_AUDIO; -import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD; -import static android.app.AppOpsManager.OP_VIBRATE; -import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED; -import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED; -import static android.app.AppOpsManager.OpEventProxyInfo; -import static android.app.AppOpsManager.RestrictionBypass; import static android.app.AppOpsManager.SAMPLING_STRATEGY_BOOT_TIME_SAMPLING; import static android.app.AppOpsManager.SAMPLING_STRATEGY_RARELY_USED; import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM; import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM_OPS; -import static android.app.AppOpsManager.SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE; import static android.app.AppOpsManager._NUM_OP; -import static android.app.AppOpsManager.extractFlagsFromKey; -import static android.app.AppOpsManager.extractUidStateFromKey; -import static android.app.AppOpsManager.modeToName; -import static android.app.AppOpsManager.opAllowSystemBypassRestriction; import static android.app.AppOpsManager.opRestrictsRead; -import static android.app.AppOpsManager.opToName; import static android.app.AppOpsManager.opToPublicName; -import static android.content.Intent.ACTION_PACKAGE_REMOVED; -import static android.content.Intent.EXTRA_REPLACING; import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS; import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP; -import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS; - import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; @@ -75,21 +42,16 @@ import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AppGlobals; import android.app.AppOpsManager; -import android.app.AppOpsManager.AttributedOpEntry; import android.app.AppOpsManager.AttributionFlags; import android.app.AppOpsManager.HistoricalOps; -import android.app.AppOpsManager.Mode; -import android.app.AppOpsManager.OpEntry; import android.app.AppOpsManager.OpFlags; import android.app.AppOpsManagerInternal; import android.app.AppOpsManagerInternal.CheckOpsDelegate; import android.app.AsyncNotedAppOp; import android.app.RuntimeAppOpAccessMessage; import android.app.SyncNotedAppOp; -import android.app.admin.DevicePolicyManagerInternal; import android.content.AttributionSource; import android.content.BroadcastReceiver; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -97,15 +59,11 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.PermissionInfo; -import android.database.ContentObserver; import android.hardware.camera2.CameraDevice.CAMERA_AUDIO_RESTRICTION; -import android.net.Uri; import android.os.AsyncTask; import android.os.Binder; -import android.os.Build; import android.os.Bundle; import android.os.Handler; -import android.os.HandlerExecutor; import android.os.IBinder; import android.os.PackageTagsList; import android.os.Process; @@ -116,22 +74,14 @@ import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.ShellCallback; import android.os.ShellCommand; -import android.os.SystemClock; import android.os.UserHandle; -import android.os.storage.StorageManagerInternal; -import android.permission.PermissionManager; -import android.provider.Settings; import android.util.ArrayMap; import android.util.ArraySet; -import android.util.AtomicFile; -import android.util.KeyValueListParser; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; -import android.util.SparseBooleanArray; import android.util.SparseIntArray; import android.util.TimeUtils; -import android.util.Xml; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.Immutable; @@ -143,59 +93,37 @@ import com.android.internal.app.IAppOpsNotedCallback; import com.android.internal.app.IAppOpsService; import com.android.internal.app.IAppOpsStartedCallback; import com.android.internal.app.MessageSamplingConfig; -import com.android.internal.compat.IPlatformCompat; -import com.android.internal.os.Clock; -import com.android.internal.util.ArrayUtils; -import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; -import com.android.internal.util.XmlUtils; import com.android.internal.util.function.pooled.PooledLambda; -import com.android.modules.utils.TypedXmlPullParser; -import com.android.modules.utils.TypedXmlSerializer; import com.android.server.LocalServices; -import com.android.server.LockGuard; -import com.android.server.SystemServerInitThreadPool; import com.android.server.SystemServiceManager; import com.android.server.pm.PackageList; -import com.android.server.pm.pkg.AndroidPackage; -import com.android.server.pm.pkg.component.ParsedAttribution; import com.android.server.policy.AppOpsPolicy; -import dalvik.annotation.optimization.NeverCompile; - -import libcore.util.EmptyArray; - import org.json.JSONException; import org.json.JSONObject; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; import java.io.File; import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; -import java.text.SimpleDateFormat; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Scanner; -import java.util.Set; import java.util.concurrent.ThreadLocalRandom; import java.util.function.Consumer; -public class AppOpsService extends IAppOpsService.Stub implements PersistenceScheduler { +/** + * The system service component to {@link AppOpsManager}. + */ +public class AppOpsService extends IAppOpsService.Stub { + + private final AppOpsServiceInterface mAppOpsService; + static final String TAG = "AppOps"; static final boolean DEBUG = false; @@ -204,57 +132,19 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch */ private final ArraySet<NoteOpTrace> mNoteOpCallerStacktraces = new ArraySet<>(); - private static final int NO_VERSION = -1; - /** Increment by one every time and add the corresponding upgrade logic in - * {@link #upgradeLocked(int)} below. The first version was 1 */ - private static final int CURRENT_VERSION = 1; - - // Write at most every 30 minutes. - static final long WRITE_DELAY = DEBUG ? 1000 : 30*60*1000; - // Constant meaning that any UID should be matched when dispatching callbacks private static final int UID_ANY = -2; - private static final int[] OPS_RESTRICTED_ON_SUSPEND = { - OP_PLAY_AUDIO, - OP_RECORD_AUDIO, - OP_CAMERA, - OP_VIBRATE, - }; - private static final int MAX_UNFORWARDED_OPS = 10; - private static final int MAX_UNUSED_POOLED_OBJECTS = 3; + private static final int RARELY_USED_PACKAGES_INITIALIZATION_DELAY_MILLIS = 300000; final Context mContext; - final AtomicFile mFile; private final @Nullable File mNoteOpCallerStacktracesFile; final Handler mHandler; - /** - * Pool for {@link AttributedOp.OpEventProxyInfoPool} to avoid to constantly reallocate new - * objects - */ - @GuardedBy("this") - final AttributedOp.OpEventProxyInfoPool mOpEventProxyInfoPool = - new AttributedOp.OpEventProxyInfoPool(MAX_UNUSED_POOLED_OBJECTS); - - /** - * Pool for {@link AttributedOp.InProgressStartOpEventPool} to avoid to constantly reallocate - * new objects - */ - @GuardedBy("this") - final AttributedOp.InProgressStartOpEventPool mInProgressStartOpEventPool = - new AttributedOp.InProgressStartOpEventPool(mOpEventProxyInfoPool, - MAX_UNUSED_POOLED_OBJECTS); - private final AppOpsManagerInternalImpl mAppOpsManagerInternal = new AppOpsManagerInternalImpl(); - @Nullable private final DevicePolicyManagerInternal dpmi = - LocalServices.getService(DevicePolicyManagerInternal.class); - - private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface( - ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); /** * Registered callbacks, called from {@link #collectAsyncNotedOp}. @@ -281,54 +171,9 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch boolean mWriteNoteOpsScheduled; - boolean mWriteScheduled; - boolean mFastWriteScheduled; - final Runnable mWriteRunner = new Runnable() { - public void run() { - synchronized (AppOpsService.this) { - mWriteScheduled = false; - mFastWriteScheduled = false; - AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() { - @Override protected Void doInBackground(Void... params) { - writeState(); - return null; - } - }; - task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null); - } - } - }; - - @GuardedBy("this") - @VisibleForTesting - final SparseArray<UidState> mUidStates = new SparseArray<>(); - - volatile @NonNull HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this); - - /* - * These are app op restrictions imposed per user from various parties. - */ - private final ArrayMap<IBinder, ClientUserRestrictionState> mOpUserRestrictions = - new ArrayMap<>(); - - /* - * These are app op restrictions imposed globally from various parties within the system. - */ - private final ArrayMap<IBinder, ClientGlobalRestrictionState> mOpGlobalRestrictions = - new ArrayMap<>(); - - SparseIntArray mProfileOwners; - private volatile CheckOpsDelegateDispatcher mCheckOpsDelegateDispatcher = new CheckOpsDelegateDispatcher(/*policy*/ null, /*delegate*/ null); - /** - * Reverse lookup for {@link AppOpsManager#opToSwitch(int)}. Initialized once and never - * changed - */ - private final SparseArray<int[]> mSwitchedOps = new SparseArray<>(); - - private ActivityManagerInternal mActivityManagerInternal; /** Package sampled for message collection in the current session */ @GuardedBy("this") @@ -362,545 +207,8 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch /** Package Manager internal. Access via {@link #getPackageManagerInternal()} */ private @Nullable PackageManagerInternal mPackageManagerInternal; - /** Interface for app-op modes.*/ - @VisibleForTesting AppOpsServiceInterface mAppOpsServiceInterface; - - /** Interface for app-op restrictions.*/ - @VisibleForTesting AppOpsRestrictions mAppOpsRestrictions; - - private AppOpsUidStateTracker mUidStateTracker; - - /** Hands the definition of foreground and uid states */ - @GuardedBy("this") - public AppOpsUidStateTracker getUidStateTracker() { - if (mUidStateTracker == null) { - mUidStateTracker = new AppOpsUidStateTrackerImpl( - LocalServices.getService(ActivityManagerInternal.class), - mHandler, - r -> { - synchronized (AppOpsService.this) { - r.run(); - } - }, - Clock.SYSTEM_CLOCK, mConstants); - - mUidStateTracker.addUidStateChangedCallback(new HandlerExecutor(mHandler), - this::onUidStateChanged); - } - return mUidStateTracker; - } - - /** - * All times are in milliseconds. These constants are kept synchronized with the system - * global Settings. Any access to this class or its fields should be done while - * holding the AppOpsService lock. - */ - final class Constants extends ContentObserver { - - /** - * How long we want for a drop in uid state from top to settle before applying it. - * @see Settings.Global#APP_OPS_CONSTANTS - * @see AppOpsManager#KEY_TOP_STATE_SETTLE_TIME - */ - public long TOP_STATE_SETTLE_TIME; - - /** - * How long we want for a drop in uid state from foreground to settle before applying it. - * @see Settings.Global#APP_OPS_CONSTANTS - * @see AppOpsManager#KEY_FG_SERVICE_STATE_SETTLE_TIME - */ - public long FG_SERVICE_STATE_SETTLE_TIME; - - /** - * How long we want for a drop in uid state from background to settle before applying it. - * @see Settings.Global#APP_OPS_CONSTANTS - * @see AppOpsManager#KEY_BG_STATE_SETTLE_TIME - */ - public long BG_STATE_SETTLE_TIME; - - private final KeyValueListParser mParser = new KeyValueListParser(','); - private ContentResolver mResolver; - - public Constants(Handler handler) { - super(handler); - updateConstants(); - } - - public void startMonitoring(ContentResolver resolver) { - mResolver = resolver; - mResolver.registerContentObserver( - Settings.Global.getUriFor(Settings.Global.APP_OPS_CONSTANTS), - false, this); - updateConstants(); - } - - @Override - public void onChange(boolean selfChange, Uri uri) { - updateConstants(); - } - - private void updateConstants() { - String value = mResolver != null ? Settings.Global.getString(mResolver, - Settings.Global.APP_OPS_CONSTANTS) : ""; - - synchronized (AppOpsService.this) { - try { - mParser.setString(value); - } catch (IllegalArgumentException e) { - // Failed to parse the settings string, log this and move on - // with defaults. - Slog.e(TAG, "Bad app ops settings", e); - } - TOP_STATE_SETTLE_TIME = mParser.getDurationMillis( - KEY_TOP_STATE_SETTLE_TIME, 5 * 1000L); - FG_SERVICE_STATE_SETTLE_TIME = mParser.getDurationMillis( - KEY_FG_SERVICE_STATE_SETTLE_TIME, 5 * 1000L); - BG_STATE_SETTLE_TIME = mParser.getDurationMillis( - KEY_BG_STATE_SETTLE_TIME, 1 * 1000L); - } - } - - void dump(PrintWriter pw) { - pw.println(" Settings:"); - - pw.print(" "); pw.print(KEY_TOP_STATE_SETTLE_TIME); pw.print("="); - TimeUtils.formatDuration(TOP_STATE_SETTLE_TIME, pw); - pw.println(); - pw.print(" "); pw.print(KEY_FG_SERVICE_STATE_SETTLE_TIME); pw.print("="); - TimeUtils.formatDuration(FG_SERVICE_STATE_SETTLE_TIME, pw); - pw.println(); - pw.print(" "); pw.print(KEY_BG_STATE_SETTLE_TIME); pw.print("="); - TimeUtils.formatDuration(BG_STATE_SETTLE_TIME, pw); - pw.println(); - } - } - - @VisibleForTesting - final Constants mConstants; - - @VisibleForTesting - final class UidState { - public final int uid; - - public ArrayMap<String, Ops> pkgOps; - - // true indicates there is an interested observer, false there isn't but it has such an op - //TODO: Move foregroundOps and hasForegroundWatchers into the AppOpsServiceInterface. - public SparseBooleanArray foregroundOps; - public boolean hasForegroundWatchers; - - public UidState(int uid) { - this.uid = uid; - } - - public void clear() { - mAppOpsServiceInterface.removeUid(uid); - if (pkgOps != null) { - for (String packageName : pkgOps.keySet()) { - mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid)); - } - } - pkgOps = null; - } - - public boolean isDefault() { - boolean areAllPackageModesDefault = true; - if (pkgOps != null) { - for (String packageName : pkgOps.keySet()) { - if (!mAppOpsServiceInterface.arePackageModesDefault(packageName, - UserHandle.getUserId(uid))) { - areAllPackageModesDefault = false; - break; - } - } - } - return (pkgOps == null || pkgOps.isEmpty()) - && mAppOpsServiceInterface.areUidModesDefault(uid) - && areAllPackageModesDefault; - } - - // Functions for uid mode access and manipulation. - public SparseIntArray getNonDefaultUidModes() { - return mAppOpsServiceInterface.getNonDefaultUidModes(uid); - } - - public int getUidMode(int op) { - return mAppOpsServiceInterface.getUidMode(uid, op); - } - - public boolean setUidMode(int op, int mode) { - return mAppOpsServiceInterface.setUidMode(uid, op, mode); - } - - @SuppressWarnings("GuardedBy") - int evalMode(int op, int mode) { - return getUidStateTracker().evalMode(uid, op, mode); - } - - public void evalForegroundOps() { - foregroundOps = null; - foregroundOps = mAppOpsServiceInterface.evalForegroundUidOps(uid, foregroundOps); - if (pkgOps != null) { - for (int i = pkgOps.size() - 1; i >= 0; i--) { - foregroundOps = mAppOpsServiceInterface - .evalForegroundPackageOps(pkgOps.valueAt(i).packageName, foregroundOps, - UserHandle.getUserId(uid)); - } - } - hasForegroundWatchers = false; - if (foregroundOps != null) { - for (int i = 0; i < foregroundOps.size(); i++) { - if (foregroundOps.valueAt(i)) { - hasForegroundWatchers = true; - break; - } - } - } - } - - @SuppressWarnings("GuardedBy") - public int getState() { - return getUidStateTracker().getUidState(uid); - } - - @SuppressWarnings("GuardedBy") - public void dump(PrintWriter pw, long nowElapsed) { - getUidStateTracker().dumpUidState(pw, uid, nowElapsed); - } - } - - final static class Ops extends SparseArray<Op> { - final String packageName; - final UidState uidState; - - /** - * The restriction properties of the package. If {@code null} it could not have been read - * yet and has to be refreshed. - */ - @Nullable RestrictionBypass bypass; - - /** Lazily populated cache of attributionTags of this package */ - final @NonNull ArraySet<String> knownAttributionTags = new ArraySet<>(); - - /** - * Lazily populated cache of <b>valid</b> attributionTags of this package, a set smaller - * than or equal to {@link #knownAttributionTags}. - */ - final @NonNull ArraySet<String> validAttributionTags = new ArraySet<>(); - - Ops(String _packageName, UidState _uidState) { - packageName = _packageName; - uidState = _uidState; - } - } - - /** Returned from {@link #verifyAndGetBypass(int, String, String, String)}. */ - private static final class PackageVerificationResult { - - final RestrictionBypass bypass; - final boolean isAttributionTagValid; - - PackageVerificationResult(RestrictionBypass bypass, boolean isAttributionTagValid) { - this.bypass = bypass; - this.isAttributionTagValid = isAttributionTagValid; - } - } - - final class Op { - int op; - int uid; - final UidState uidState; - final @NonNull String packageName; - - /** attributionTag -> AttributedOp */ - final ArrayMap<String, AttributedOp> mAttributions = new ArrayMap<>(1); - - Op(UidState uidState, String packageName, int op, int uid) { - this.op = op; - this.uid = uid; - this.uidState = uidState; - this.packageName = packageName; - } - - @Mode int getMode() { - return mAppOpsServiceInterface.getPackageMode(packageName, this.op, - UserHandle.getUserId(this.uid)); - } - void setMode(@Mode int mode) { - mAppOpsServiceInterface.setPackageMode(packageName, this.op, mode, - UserHandle.getUserId(this.uid)); - } - - void removeAttributionsWithNoTime() { - for (int i = mAttributions.size() - 1; i >= 0; i--) { - if (!mAttributions.valueAt(i).hasAnyTime()) { - mAttributions.removeAt(i); - } - } - } - - private @NonNull AttributedOp getOrCreateAttribution(@NonNull Op parent, - @Nullable String attributionTag) { - AttributedOp attributedOp; - - attributedOp = mAttributions.get(attributionTag); - if (attributedOp == null) { - attributedOp = new AttributedOp(AppOpsService.this, attributionTag, parent); - mAttributions.put(attributionTag, attributedOp); - } - - return attributedOp; - } - - @NonNull OpEntry createEntryLocked() { - final int numAttributions = mAttributions.size(); - - final ArrayMap<String, AppOpsManager.AttributedOpEntry> attributionEntries = - new ArrayMap<>(numAttributions); - for (int i = 0; i < numAttributions; i++) { - attributionEntries.put(mAttributions.keyAt(i), - mAttributions.valueAt(i).createAttributedOpEntryLocked()); - } - - return new OpEntry(op, getMode(), attributionEntries); - } - - @NonNull OpEntry createSingleAttributionEntryLocked(@Nullable String attributionTag) { - final int numAttributions = mAttributions.size(); - - final ArrayMap<String, AttributedOpEntry> attributionEntries = new ArrayMap<>(1); - for (int i = 0; i < numAttributions; i++) { - if (Objects.equals(mAttributions.keyAt(i), attributionTag)) { - attributionEntries.put(mAttributions.keyAt(i), - mAttributions.valueAt(i).createAttributedOpEntryLocked()); - break; - } - } - - return new OpEntry(op, getMode(), attributionEntries); - } - - boolean isRunning() { - final int numAttributions = mAttributions.size(); - for (int i = 0; i < numAttributions; i++) { - if (mAttributions.valueAt(i).isRunning()) { - return true; - } - } - - return false; - } - } - - final ArrayMap<IBinder, ModeCallback> mModeWatchers = new ArrayMap<>(); - final ArrayMap<IBinder, SparseArray<ActiveCallback>> mActiveWatchers = new ArrayMap<>(); - final ArrayMap<IBinder, SparseArray<StartedCallback>> mStartedWatchers = new ArrayMap<>(); - final ArrayMap<IBinder, SparseArray<NotedCallback>> mNotedWatchers = new ArrayMap<>(); final AudioRestrictionManager mAudioRestrictionManager = new AudioRestrictionManager(); - final class ModeCallback extends OnOpModeChangedListener implements DeathRecipient { - /** If mWatchedOpCode==ALL_OPS notify for ops affected by the switch-op */ - public static final int ALL_OPS = -2; - - // Need to keep this only because stopWatchingMode needs an IAppOpsCallback. - // Otherwise we can just use the IBinder object. - private final IAppOpsCallback mCallback; - - ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int watchedOpCode, - int callingUid, int callingPid) { - super(watchingUid, flags, watchedOpCode, callingUid, callingPid); - this.mCallback = callback; - try { - mCallback.asBinder().linkToDeath(this, 0); - } catch (RemoteException e) { - /*ignored*/ - } - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(128); - sb.append("ModeCallback{"); - sb.append(Integer.toHexString(System.identityHashCode(this))); - sb.append(" watchinguid="); - UserHandle.formatUid(sb, getWatchingUid()); - sb.append(" flags=0x"); - sb.append(Integer.toHexString(getFlags())); - switch (getWatchedOpCode()) { - case OP_NONE: - break; - case ALL_OPS: - sb.append(" op=(all)"); - break; - default: - sb.append(" op="); - sb.append(opToName(getWatchedOpCode())); - break; - } - sb.append(" from uid="); - UserHandle.formatUid(sb, getCallingUid()); - sb.append(" pid="); - sb.append(getCallingPid()); - sb.append('}'); - return sb.toString(); - } - - void unlinkToDeath() { - mCallback.asBinder().unlinkToDeath(this, 0); - } - - @Override - public void binderDied() { - stopWatchingMode(mCallback); - } - - @Override - public void onOpModeChanged(int op, int uid, String packageName) throws RemoteException { - mCallback.opChanged(op, uid, packageName); - } - } - - final class ActiveCallback implements DeathRecipient { - final IAppOpsActiveCallback mCallback; - final int mWatchingUid; - final int mCallingUid; - final int mCallingPid; - - ActiveCallback(IAppOpsActiveCallback callback, int watchingUid, int callingUid, - int callingPid) { - mCallback = callback; - mWatchingUid = watchingUid; - mCallingUid = callingUid; - mCallingPid = callingPid; - try { - mCallback.asBinder().linkToDeath(this, 0); - } catch (RemoteException e) { - /*ignored*/ - } - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(128); - sb.append("ActiveCallback{"); - sb.append(Integer.toHexString(System.identityHashCode(this))); - sb.append(" watchinguid="); - UserHandle.formatUid(sb, mWatchingUid); - sb.append(" from uid="); - UserHandle.formatUid(sb, mCallingUid); - sb.append(" pid="); - sb.append(mCallingPid); - sb.append('}'); - return sb.toString(); - } - - void destroy() { - mCallback.asBinder().unlinkToDeath(this, 0); - } - - @Override - public void binderDied() { - stopWatchingActive(mCallback); - } - } - - final class StartedCallback implements DeathRecipient { - final IAppOpsStartedCallback mCallback; - final int mWatchingUid; - final int mCallingUid; - final int mCallingPid; - - StartedCallback(IAppOpsStartedCallback callback, int watchingUid, int callingUid, - int callingPid) { - mCallback = callback; - mWatchingUid = watchingUid; - mCallingUid = callingUid; - mCallingPid = callingPid; - try { - mCallback.asBinder().linkToDeath(this, 0); - } catch (RemoteException e) { - /*ignored*/ - } - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(128); - sb.append("StartedCallback{"); - sb.append(Integer.toHexString(System.identityHashCode(this))); - sb.append(" watchinguid="); - UserHandle.formatUid(sb, mWatchingUid); - sb.append(" from uid="); - UserHandle.formatUid(sb, mCallingUid); - sb.append(" pid="); - sb.append(mCallingPid); - sb.append('}'); - return sb.toString(); - } - - void destroy() { - mCallback.asBinder().unlinkToDeath(this, 0); - } - - @Override - public void binderDied() { - stopWatchingStarted(mCallback); - } - } - - final class NotedCallback implements DeathRecipient { - final IAppOpsNotedCallback mCallback; - final int mWatchingUid; - final int mCallingUid; - final int mCallingPid; - - NotedCallback(IAppOpsNotedCallback callback, int watchingUid, int callingUid, - int callingPid) { - mCallback = callback; - mWatchingUid = watchingUid; - mCallingUid = callingUid; - mCallingPid = callingPid; - try { - mCallback.asBinder().linkToDeath(this, 0); - } catch (RemoteException e) { - /*ignored*/ - } - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(128); - sb.append("NotedCallback{"); - sb.append(Integer.toHexString(System.identityHashCode(this))); - sb.append(" watchinguid="); - UserHandle.formatUid(sb, mWatchingUid); - sb.append(" from uid="); - UserHandle.formatUid(sb, mCallingUid); - sb.append(" pid="); - sb.append(mCallingPid); - sb.append('}'); - return sb.toString(); - } - - void destroy() { - mCallback.asBinder().unlinkToDeath(this, 0); - } - - @Override - public void binderDied() { - stopWatchingNoted(mCallback); - } - } - - /** - * Call {@link AttributedOp#onClientDeath attributedOp.onClientDeath(clientId)}. - */ - static void onClientDeath(@NonNull AttributedOp attributedOp, - @NonNull IBinder clientId) { - attributedOp.onClientDeath(clientId); - } - - /** * Loads the OpsValidation file results into a hashmap {@link #mNoteOpCallerStacktraces} * so that we do not log the same operation twice between instances @@ -925,20 +233,12 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } public AppOpsService(File storagePath, Handler handler, Context context) { - mContext = context; - - for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) { - int switchCode = AppOpsManager.opToSwitch(switchedCode); - mSwitchedOps.put(switchCode, - ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode)); - } - mAppOpsServiceInterface = - new LegacyAppOpsServiceInterfaceImpl(this, this, handler, context, mSwitchedOps); - mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler, - mAppOpsServiceInterface); + this(handler, context, new AppOpsServiceImpl(storagePath, handler, context)); + } - LockGuard.installLock(this, LockGuard.INDEX_APP_OPS); - mFile = new AtomicFile(storagePath, "appops"); + @VisibleForTesting + public AppOpsService(Handler handler, Context context, + AppOpsServiceInterface appOpsServiceInterface) { if (AppOpsManager.NOTE_OP_COLLECTION_ENABLED) { mNoteOpCallerStacktracesFile = new File(SystemServiceManager.ensureSystemDir(), "noteOpStackTraces.json"); @@ -946,185 +246,25 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } else { mNoteOpCallerStacktracesFile = null; } + + mAppOpsService = appOpsServiceInterface; + mContext = context; mHandler = handler; - mConstants = new Constants(mHandler); - readState(); } + /** + * Publishes binder and local service. + */ public void publish() { ServiceManager.addService(Context.APP_OPS_SERVICE, asBinder()); LocalServices.addService(AppOpsManagerInternal.class, mAppOpsManagerInternal); } - /** Handler for work when packages are removed or updated */ - private BroadcastReceiver mOnPackageUpdatedReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - String pkgName = intent.getData().getEncodedSchemeSpecificPart(); - int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID); - - if (action.equals(ACTION_PACKAGE_REMOVED) && !intent.hasExtra(EXTRA_REPLACING)) { - synchronized (AppOpsService.this) { - UidState uidState = mUidStates.get(uid); - if (uidState == null || uidState.pkgOps == null) { - return; - } - mAppOpsServiceInterface.removePackage(pkgName, UserHandle.getUserId(uid)); - Ops removedOps = uidState.pkgOps.remove(pkgName); - if (removedOps != null) { - scheduleFastWriteLocked(); - } - } - } else if (action.equals(Intent.ACTION_PACKAGE_REPLACED)) { - AndroidPackage pkg = getPackageManagerInternal().getPackage(pkgName); - if (pkg == null) { - return; - } - - ArrayMap<String, String> dstAttributionTags = new ArrayMap<>(); - ArraySet<String> attributionTags = new ArraySet<>(); - attributionTags.add(null); - if (pkg.getAttributions() != null) { - int numAttributions = pkg.getAttributions().size(); - for (int attributionNum = 0; attributionNum < numAttributions; - attributionNum++) { - ParsedAttribution attribution = pkg.getAttributions().get(attributionNum); - attributionTags.add(attribution.getTag()); - - int numInheritFrom = attribution.getInheritFrom().size(); - for (int inheritFromNum = 0; inheritFromNum < numInheritFrom; - inheritFromNum++) { - dstAttributionTags.put(attribution.getInheritFrom().get(inheritFromNum), - attribution.getTag()); - } - } - } - - synchronized (AppOpsService.this) { - UidState uidState = mUidStates.get(uid); - if (uidState == null || uidState.pkgOps == null) { - return; - } - - Ops ops = uidState.pkgOps.get(pkgName); - if (ops == null) { - return; - } - - // Reset cached package properties to re-initialize when needed - ops.bypass = null; - ops.knownAttributionTags.clear(); - - // Merge data collected for removed attributions into their successor - // attributions - int numOps = ops.size(); - for (int opNum = 0; opNum < numOps; opNum++) { - Op op = ops.valueAt(opNum); - - int numAttributions = op.mAttributions.size(); - for (int attributionNum = numAttributions - 1; attributionNum >= 0; - attributionNum--) { - String attributionTag = op.mAttributions.keyAt(attributionNum); - - if (attributionTags.contains(attributionTag)) { - // attribution still exist after upgrade - continue; - } - - String newAttributionTag = dstAttributionTags.get(attributionTag); - - AttributedOp newAttributedOp = op.getOrCreateAttribution(op, - newAttributionTag); - newAttributedOp.add(op.mAttributions.valueAt(attributionNum)); - op.mAttributions.removeAt(attributionNum); - - scheduleFastWriteLocked(); - } - } - } - } - } - }; - + /** + * Finishes boot sequence. + */ public void systemReady() { - mConstants.startMonitoring(mContext.getContentResolver()); - mHistoricalRegistry.systemReady(mContext.getContentResolver()); - - IntentFilter packageUpdateFilter = new IntentFilter(); - packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); - packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REPLACED); - packageUpdateFilter.addDataScheme("package"); - - mContext.registerReceiverAsUser(mOnPackageUpdatedReceiver, UserHandle.ALL, - packageUpdateFilter, null, null); - - synchronized (this) { - for (int uidNum = mUidStates.size() - 1; uidNum >= 0; uidNum--) { - int uid = mUidStates.keyAt(uidNum); - UidState uidState = mUidStates.valueAt(uidNum); - - String[] pkgsInUid = getPackagesForUid(uidState.uid); - if (ArrayUtils.isEmpty(pkgsInUid)) { - uidState.clear(); - mUidStates.removeAt(uidNum); - scheduleFastWriteLocked(); - continue; - } - - ArrayMap<String, Ops> pkgs = uidState.pkgOps; - if (pkgs == null) { - continue; - } - - int numPkgs = pkgs.size(); - for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) { - String pkg = pkgs.keyAt(pkgNum); - - String action; - if (!ArrayUtils.contains(pkgsInUid, pkg)) { - action = Intent.ACTION_PACKAGE_REMOVED; - } else { - action = Intent.ACTION_PACKAGE_REPLACED; - } - - SystemServerInitThreadPool.submit( - () -> mOnPackageUpdatedReceiver.onReceive(mContext, new Intent(action) - .setData(Uri.fromParts("package", pkg, null)) - .putExtra(Intent.EXTRA_UID, uid)), - "Update app-ops uidState in case package " + pkg + " changed"); - } - } - } - - final IntentFilter packageSuspendFilter = new IntentFilter(); - packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED); - packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED); - mContext.registerReceiverAsUser(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - final int[] changedUids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST); - final String[] changedPkgs = intent.getStringArrayExtra( - Intent.EXTRA_CHANGED_PACKAGE_LIST); - for (int code : OPS_RESTRICTED_ON_SUSPEND) { - ArraySet<OnOpModeChangedListener> onModeChangedListeners; - synchronized (AppOpsService.this) { - onModeChangedListeners = - mAppOpsServiceInterface.getOpModeChangedListeners(code); - if (onModeChangedListeners == null) { - continue; - } - } - for (int i = 0; i < changedUids.length; i++) { - final int changedUid = changedUids[i]; - final String changedPkg = changedPkgs[i]; - // We trust packagemanager to insert matching uid and packageNames in the - // extras - notifyOpChanged(onModeChangedListeners, code, changedUid, changedPkg); - } - } - } - }, UserHandle.ALL, packageSuspendFilter, null, null); + mAppOpsService.systemReady(); final IntentFilter packageAddedFilter = new IntentFilter(); packageAddedFilter.addAction(Intent.ACTION_PACKAGE_ADDED); @@ -1132,9 +272,8 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - final Uri data = intent.getData(); - final String packageName = data.getSchemeSpecificPart(); + final String packageName = intent.getData().getSchemeSpecificPart(); PackageInfo pi = getPackageManagerInternal().getPackageInfo(packageName, PackageManager.GET_PERMISSIONS, Process.myUid(), mContext.getUserId()); if (isSamplingTarget(pi)) { @@ -1169,8 +308,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } }); - - mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); } /** @@ -1185,132 +322,18 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch mCheckOpsDelegateDispatcher = new CheckOpsDelegateDispatcher(policy, delegate); } + /** + * Notify when a package is removed + */ public void packageRemoved(int uid, String packageName) { - synchronized (this) { - UidState uidState = mUidStates.get(uid); - if (uidState == null) { - return; - } - - Ops removedOps = null; - - // Remove any package state if such. - if (uidState.pkgOps != null) { - removedOps = uidState.pkgOps.remove(packageName); - mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid)); - } - - // If we just nuked the last package state check if the UID is valid. - if (removedOps != null && uidState.pkgOps.isEmpty() - && getPackagesForUid(uid).length <= 0) { - uidState.clear(); - mUidStates.remove(uid); - } - - if (removedOps != null) { - scheduleFastWriteLocked(); - - final int numOps = removedOps.size(); - for (int opNum = 0; opNum < numOps; opNum++) { - final Op op = removedOps.valueAt(opNum); - - final int numAttributions = op.mAttributions.size(); - for (int attributionNum = 0; attributionNum < numAttributions; - attributionNum++) { - AttributedOp attributedOp = op.mAttributions.valueAt(attributionNum); - - while (attributedOp.isRunning()) { - attributedOp.finished(attributedOp.mInProgressEvents.keyAt(0)); - } - while (attributedOp.isPaused()) { - attributedOp.finished(attributedOp.mPausedInProgressEvents.keyAt(0)); - } - } - } - } - } - - mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::clearHistory, - mHistoricalRegistry, uid, packageName)); + mAppOpsService.packageRemoved(uid, packageName); } + /** + * Notify when a uid is removed. + */ public void uidRemoved(int uid) { - synchronized (this) { - if (mUidStates.indexOfKey(uid) >= 0) { - mUidStates.get(uid).clear(); - mUidStates.remove(uid); - scheduleFastWriteLocked(); - } - } - } - - // The callback method from ForegroundPolicyInterface - private void onUidStateChanged(int uid, int state, boolean foregroundModeMayChange) { - synchronized (this) { - UidState uidState = getUidStateLocked(uid, true); - - if (uidState != null && foregroundModeMayChange && uidState.hasForegroundWatchers) { - for (int fgi = uidState.foregroundOps.size() - 1; fgi >= 0; fgi--) { - if (!uidState.foregroundOps.valueAt(fgi)) { - continue; - } - final int code = uidState.foregroundOps.keyAt(fgi); - - if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code) - && uidState.getUidMode(code) == AppOpsManager.MODE_FOREGROUND) { - mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsService::notifyOpChangedForAllPkgsInUid, - this, code, uidState.uid, true, null)); - } else if (uidState.pkgOps != null) { - final ArraySet<OnOpModeChangedListener> listenerSet = - mAppOpsServiceInterface.getOpModeChangedListeners(code); - if (listenerSet != null) { - for (int cbi = listenerSet.size() - 1; cbi >= 0; cbi--) { - final OnOpModeChangedListener listener = listenerSet.valueAt(cbi); - if ((listener.getFlags() - & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0 - || !listener.isWatchingUid(uidState.uid)) { - continue; - } - for (int pkgi = uidState.pkgOps.size() - 1; pkgi >= 0; pkgi--) { - final Op op = uidState.pkgOps.valueAt(pkgi).get(code); - if (op == null) { - continue; - } - if (op.getMode() == AppOpsManager.MODE_FOREGROUND) { - mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsService::notifyOpChanged, - this, listenerSet.valueAt(cbi), code, uidState.uid, - uidState.pkgOps.keyAt(pkgi))); - } - } - } - } - } - } - } - - if (uidState != null && uidState.pkgOps != null) { - int numPkgs = uidState.pkgOps.size(); - for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) { - Ops ops = uidState.pkgOps.valueAt(pkgNum); - - int numOps = ops.size(); - for (int opNum = 0; opNum < numOps; opNum++) { - Op op = ops.valueAt(opNum); - - int numAttributions = op.mAttributions.size(); - for (int attributionNum = 0; attributionNum < numAttributions; - attributionNum++) { - AttributedOp attributedOp = op.mAttributions.valueAt( - attributionNum); - - attributedOp.onUidStateChanged(state); - } - } - } - } - } + mAppOpsService.uidRemoved(uid); } /** @@ -1318,542 +341,60 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch */ public void updateUidProcState(int uid, int procState, @ActivityManager.ProcessCapability int capability) { - synchronized (this) { - getUidStateTracker().updateUidProcState(uid, procState, capability); - if (!mUidStates.contains(uid)) { - UidState uidState = new UidState(uid); - mUidStates.put(uid, uidState); - onUidStateChanged(uid, - AppOpsUidStateTracker.processStateToUidState(procState), false); - } - } + mAppOpsService.updateUidProcState(uid, procState, capability); } + /** + * Initiates shutdown. + */ public void shutdown() { - Slog.w(TAG, "Writing app ops before shutdown..."); - boolean doWrite = false; - synchronized (this) { - if (mWriteScheduled) { - mWriteScheduled = false; - mFastWriteScheduled = false; - mHandler.removeCallbacks(mWriteRunner); - doWrite = true; - } - } - if (doWrite) { - writeState(); - } + mAppOpsService.shutdown(); + if (AppOpsManager.NOTE_OP_COLLECTION_ENABLED && mWriteNoteOpsScheduled) { writeNoteOps(); } - - mHistoricalRegistry.shutdown(); - } - - private ArrayList<AppOpsManager.OpEntry> collectOps(Ops pkgOps, int[] ops) { - ArrayList<AppOpsManager.OpEntry> resOps = null; - if (ops == null) { - resOps = new ArrayList<>(); - for (int j=0; j<pkgOps.size(); j++) { - Op curOp = pkgOps.valueAt(j); - resOps.add(getOpEntryForResult(curOp)); - } - } else { - for (int j=0; j<ops.length; j++) { - Op curOp = pkgOps.get(ops[j]); - if (curOp != null) { - if (resOps == null) { - resOps = new ArrayList<>(); - } - resOps.add(getOpEntryForResult(curOp)); - } - } - } - return resOps; - } - - @Nullable - private ArrayList<AppOpsManager.OpEntry> collectUidOps(@NonNull UidState uidState, - @Nullable int[] ops) { - final SparseIntArray opModes = uidState.getNonDefaultUidModes(); - if (opModes == null) { - return null; - } - - int opModeCount = opModes.size(); - if (opModeCount == 0) { - return null; - } - ArrayList<AppOpsManager.OpEntry> resOps = null; - if (ops == null) { - resOps = new ArrayList<>(); - for (int i = 0; i < opModeCount; i++) { - int code = opModes.keyAt(i); - resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap())); - } - } else { - for (int j=0; j<ops.length; j++) { - int code = ops[j]; - if (opModes.indexOfKey(code) >= 0) { - if (resOps == null) { - resOps = new ArrayList<>(); - } - resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap())); - } - } - } - return resOps; - } - - private static @NonNull OpEntry getOpEntryForResult(@NonNull Op op) { - return op.createEntryLocked(); } @Override public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) { - final int callingUid = Binder.getCallingUid(); - final boolean hasAllPackageAccess = mContext.checkPermission( - Manifest.permission.GET_APP_OPS_STATS, Binder.getCallingPid(), - Binder.getCallingUid(), null) == PackageManager.PERMISSION_GRANTED; - ArrayList<AppOpsManager.PackageOps> res = null; - synchronized (this) { - final int uidStateCount = mUidStates.size(); - for (int i = 0; i < uidStateCount; i++) { - UidState uidState = mUidStates.valueAt(i); - if (uidState.pkgOps == null || uidState.pkgOps.isEmpty()) { - continue; - } - ArrayMap<String, Ops> packages = uidState.pkgOps; - final int packageCount = packages.size(); - for (int j = 0; j < packageCount; j++) { - Ops pkgOps = packages.valueAt(j); - ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops); - if (resOps != null) { - if (res == null) { - res = new ArrayList<>(); - } - AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps( - pkgOps.packageName, pkgOps.uidState.uid, resOps); - // Caller can always see their packages and with a permission all. - if (hasAllPackageAccess || callingUid == pkgOps.uidState.uid) { - res.add(resPackage); - } - } - } - } - } - return res; + return mAppOpsService.getPackagesForOps(ops); } @Override public List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName, int[] ops) { - enforceGetAppOpsStatsPermissionIfNeeded(uid,packageName); - String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName); - if (resolvedPackageName == null) { - return Collections.emptyList(); - } - synchronized (this) { - Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null, - /* edit */ false); - if (pkgOps == null) { - return null; - } - ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops); - if (resOps == null) { - return null; - } - ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>(); - AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps( - pkgOps.packageName, pkgOps.uidState.uid, resOps); - res.add(resPackage); - return res; - } - } - - private void enforceGetAppOpsStatsPermissionIfNeeded(int uid, String packageName) { - final int callingUid = Binder.getCallingUid(); - // We get to access everything - if (callingUid == Process.myPid()) { - return; - } - // Apps can access their own data - if (uid == callingUid && packageName != null - && checkPackage(uid, packageName) == MODE_ALLOWED) { - return; - } - // Otherwise, you need a permission... - mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS, - Binder.getCallingPid(), callingUid, null); - } - - /** - * Verify that historical appop request arguments are valid. - */ - private void ensureHistoricalOpRequestIsValid(int uid, String packageName, - String attributionTag, List<String> opNames, int filter, long beginTimeMillis, - long endTimeMillis, int flags) { - if ((filter & FILTER_BY_UID) != 0) { - Preconditions.checkArgument(uid != Process.INVALID_UID); - } else { - Preconditions.checkArgument(uid == Process.INVALID_UID); - } - - if ((filter & FILTER_BY_PACKAGE_NAME) != 0) { - Objects.requireNonNull(packageName); - } else { - Preconditions.checkArgument(packageName == null); - } - - if ((filter & FILTER_BY_ATTRIBUTION_TAG) == 0) { - Preconditions.checkArgument(attributionTag == null); - } - - if ((filter & FILTER_BY_OP_NAMES) != 0) { - Objects.requireNonNull(opNames); - } else { - Preconditions.checkArgument(opNames == null); - } - - Preconditions.checkFlagsArgument(filter, - FILTER_BY_UID | FILTER_BY_PACKAGE_NAME | FILTER_BY_ATTRIBUTION_TAG - | FILTER_BY_OP_NAMES); - Preconditions.checkArgumentNonnegative(beginTimeMillis); - Preconditions.checkArgument(endTimeMillis > beginTimeMillis); - Preconditions.checkFlagsArgument(flags, OP_FLAGS_ALL); + return mAppOpsService.getOpsForPackage(uid, packageName, ops); } @Override public void getHistoricalOps(int uid, String packageName, String attributionTag, List<String> opNames, int dataType, int filter, long beginTimeMillis, long endTimeMillis, int flags, RemoteCallback callback) { - PackageManager pm = mContext.getPackageManager(); - - ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter, - beginTimeMillis, endTimeMillis, flags); - Objects.requireNonNull(callback, "callback cannot be null"); - ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class); - boolean isSelfRequest = (filter & FILTER_BY_UID) != 0 && uid == Binder.getCallingUid(); - if (!isSelfRequest) { - boolean isCallerInstrumented = - ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID; - boolean isCallerSystem = Binder.getCallingPid() == Process.myPid(); - boolean isCallerPermissionController; - try { - isCallerPermissionController = pm.getPackageUidAsUser( - mContext.getPackageManager().getPermissionControllerPackageName(), 0, - UserHandle.getUserId(Binder.getCallingUid())) - == Binder.getCallingUid(); - } catch (PackageManager.NameNotFoundException doesNotHappen) { - return; - } - - boolean doesCallerHavePermission = mContext.checkPermission( - android.Manifest.permission.GET_HISTORICAL_APP_OPS_STATS, - Binder.getCallingPid(), Binder.getCallingUid()) - == PackageManager.PERMISSION_GRANTED; - - if (!isCallerSystem && !isCallerInstrumented && !isCallerPermissionController - && !doesCallerHavePermission) { - mHandler.post(() -> callback.sendResult(new Bundle())); - return; - } - - mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS, - Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps"); - } - - final String[] opNamesArray = (opNames != null) - ? opNames.toArray(new String[opNames.size()]) : null; - - Set<String> attributionChainExemptPackages = null; - if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) { - attributionChainExemptPackages = - PermissionManager.getIndicatorExemptedPackages(mContext); - } - - final String[] chainExemptPkgArray = attributionChainExemptPackages != null - ? attributionChainExemptPackages.toArray( - new String[attributionChainExemptPackages.size()]) : null; - - // Must not hold the appops lock - mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOps, - mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType, - filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray, - callback).recycleOnUse()); + mAppOpsService.getHistoricalOps(uid, packageName, attributionTag, opNames, + dataType, filter, beginTimeMillis, endTimeMillis, flags, callback); } @Override public void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag, List<String> opNames, int dataType, int filter, long beginTimeMillis, long endTimeMillis, int flags, RemoteCallback callback) { - ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter, - beginTimeMillis, endTimeMillis, flags); - Objects.requireNonNull(callback, "callback cannot be null"); - - mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS, - Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps"); - - final String[] opNamesArray = (opNames != null) - ? opNames.toArray(new String[opNames.size()]) : null; - - Set<String> attributionChainExemptPackages = null; - if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) { - attributionChainExemptPackages = - PermissionManager.getIndicatorExemptedPackages(mContext); - } - - final String[] chainExemptPkgArray = attributionChainExemptPackages != null - ? attributionChainExemptPackages.toArray( - new String[attributionChainExemptPackages.size()]) : null; - - // Must not hold the appops lock - mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOpsFromDiskRaw, - mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType, - filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray, - callback).recycleOnUse()); + mAppOpsService.getHistoricalOpsFromDiskRaw(uid, packageName, attributionTag, + opNames, dataType, filter, beginTimeMillis, endTimeMillis, flags, callback); } @Override public void reloadNonHistoricalState() { - mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS, - Binder.getCallingPid(), Binder.getCallingUid(), "reloadNonHistoricalState"); - writeState(); - readState(); + mAppOpsService.reloadNonHistoricalState(); } @Override public List<AppOpsManager.PackageOps> getUidOps(int uid, int[] ops) { - mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS, - Binder.getCallingPid(), Binder.getCallingUid(), null); - synchronized (this) { - UidState uidState = getUidStateLocked(uid, false); - if (uidState == null) { - return null; - } - ArrayList<AppOpsManager.OpEntry> resOps = collectUidOps(uidState, ops); - if (resOps == null) { - return null; - } - ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>(); - AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps( - null, uidState.uid, resOps); - res.add(resPackage); - return res; - } - } - - private void pruneOpLocked(Op op, int uid, String packageName) { - op.removeAttributionsWithNoTime(); - - if (op.mAttributions.isEmpty()) { - Ops ops = getOpsLocked(uid, packageName, null, false, null, /* edit */ false); - if (ops != null) { - ops.remove(op.op); - op.setMode(AppOpsManager.opToDefaultMode(op.op)); - if (ops.size() <= 0) { - UidState uidState = ops.uidState; - ArrayMap<String, Ops> pkgOps = uidState.pkgOps; - if (pkgOps != null) { - pkgOps.remove(ops.packageName); - mAppOpsServiceInterface.removePackage(ops.packageName, - UserHandle.getUserId(uidState.uid)); - if (pkgOps.isEmpty()) { - uidState.pkgOps = null; - } - if (uidState.isDefault()) { - uidState.clear(); - mUidStates.remove(uid); - } - } - } - } - } - } - - private void enforceManageAppOpsModes(int callingPid, int callingUid, int targetUid) { - if (callingPid == Process.myPid()) { - return; - } - final int callingUser = UserHandle.getUserId(callingUid); - synchronized (this) { - if (mProfileOwners != null && mProfileOwners.get(callingUser, -1) == callingUid) { - if (targetUid >= 0 && callingUser == UserHandle.getUserId(targetUid)) { - // Profile owners are allowed to change modes but only for apps - // within their user. - return; - } - } - } - mContext.enforcePermission(android.Manifest.permission.MANAGE_APP_OPS_MODES, - Binder.getCallingPid(), Binder.getCallingUid(), null); + return mAppOpsService.getUidOps(uid, ops); } @Override public void setUidMode(int code, int uid, int mode) { - setUidMode(code, uid, mode, null); - } - - private void setUidMode(int code, int uid, int mode, - @Nullable IAppOpsCallback permissionPolicyCallback) { - if (DEBUG) { - Slog.i(TAG, "uid " + uid + " OP_" + opToName(code) + " := " + modeToName(mode) - + " by uid " + Binder.getCallingUid()); - } - - enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid); - verifyIncomingOp(code); - code = AppOpsManager.opToSwitch(code); - - if (permissionPolicyCallback == null) { - updatePermissionRevokedCompat(uid, code, mode); - } - - int previousMode; - synchronized (this) { - final int defaultMode = AppOpsManager.opToDefaultMode(code); - - UidState uidState = getUidStateLocked(uid, false); - if (uidState == null) { - if (mode == defaultMode) { - return; - } - uidState = new UidState(uid); - mUidStates.put(uid, uidState); - } - if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) { - previousMode = uidState.getUidMode(code); - } else { - // doesn't look right but is legacy behavior. - previousMode = MODE_DEFAULT; - } - - if (!uidState.setUidMode(code, mode)) { - return; - } - uidState.evalForegroundOps(); - if (mode != MODE_ERRORED && mode != previousMode) { - updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid); - } - } - - notifyOpChangedForAllPkgsInUid(code, uid, false, permissionPolicyCallback); - notifyOpChangedSync(code, uid, null, mode, previousMode); - } - - /** - * Notify that an op changed for all packages in an uid. - * - * @param code The op that changed - * @param uid The uid the op was changed for - * @param onlyForeground Only notify watchers that watch for foreground changes - */ - private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground, - @Nullable IAppOpsCallback callbackToIgnore) { - ModeCallback listenerToIgnore = callbackToIgnore != null - ? mModeWatchers.get(callbackToIgnore.asBinder()) : null; - mAppOpsServiceInterface.notifyOpChangedForAllPkgsInUid(code, uid, onlyForeground, - listenerToIgnore); - } - - private void updatePermissionRevokedCompat(int uid, int switchCode, int mode) { - PackageManager packageManager = mContext.getPackageManager(); - if (packageManager == null) { - // This can only happen during early boot. At this time the permission state and appop - // state are in sync - return; - } - - String[] packageNames = packageManager.getPackagesForUid(uid); - if (ArrayUtils.isEmpty(packageNames)) { - return; - } - String packageName = packageNames[0]; - - int[] ops = mSwitchedOps.get(switchCode); - for (int code : ops) { - String permissionName = AppOpsManager.opToPermission(code); - if (permissionName == null) { - continue; - } - - if (packageManager.checkPermission(permissionName, packageName) - != PackageManager.PERMISSION_GRANTED) { - continue; - } - - PermissionInfo permissionInfo; - try { - permissionInfo = packageManager.getPermissionInfo(permissionName, 0); - } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - continue; - } - - if (!permissionInfo.isRuntime()) { - continue; - } - - boolean supportsRuntimePermissions = getPackageManagerInternal() - .getUidTargetSdkVersion(uid) >= Build.VERSION_CODES.M; - - UserHandle user = UserHandle.getUserHandleForUid(uid); - boolean isRevokedCompat; - if (permissionInfo.backgroundPermission != null) { - if (packageManager.checkPermission(permissionInfo.backgroundPermission, packageName) - == PackageManager.PERMISSION_GRANTED) { - boolean isBackgroundRevokedCompat = mode != AppOpsManager.MODE_ALLOWED; - - if (isBackgroundRevokedCompat && supportsRuntimePermissions) { - Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime" - + " permission state, this is discouraged and you should revoke the" - + " runtime permission instead: uid=" + uid + ", switchCode=" - + switchCode + ", mode=" + mode + ", permission=" - + permissionInfo.backgroundPermission); - } - - final long identity = Binder.clearCallingIdentity(); - try { - packageManager.updatePermissionFlags(permissionInfo.backgroundPermission, - packageName, PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, - isBackgroundRevokedCompat - ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user); - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED - && mode != AppOpsManager.MODE_FOREGROUND; - } else { - isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED; - } - - if (isRevokedCompat && supportsRuntimePermissions) { - Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime" - + " permission state, this is discouraged and you should revoke the" - + " runtime permission instead: uid=" + uid + ", switchCode=" - + switchCode + ", mode=" + mode + ", permission=" + permissionName); - } - - final long identity = Binder.clearCallingIdentity(); - try { - packageManager.updatePermissionFlags(permissionName, packageName, - PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, isRevokedCompat - ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user); - } finally { - Binder.restoreCallingIdentity(identity); - } - } - } - - private void notifyOpChangedSync(int code, int uid, @NonNull String packageName, int mode, - int previousMode) { - final StorageManagerInternal storageManagerInternal = - LocalServices.getService(StorageManagerInternal.class); - if (storageManagerInternal != null) { - storageManagerInternal.onAppOpsChanged(code, uid, packageName, mode, previousMode); - } + mAppOpsService.setUidMode(code, uid, mode, null); } /** @@ -1866,309 +407,12 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch */ @Override public void setMode(int code, int uid, @NonNull String packageName, int mode) { - setMode(code, uid, packageName, mode, null); - } - - private void setMode(int code, int uid, @NonNull String packageName, int mode, - @Nullable IAppOpsCallback permissionPolicyCallback) { - enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid); - verifyIncomingOp(code); - if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) { - return; - } - - ArraySet<OnOpModeChangedListener> repCbs = null; - code = AppOpsManager.opToSwitch(code); - - PackageVerificationResult pvr; - try { - pvr = verifyAndGetBypass(uid, packageName, null); - } catch (SecurityException e) { - Slog.e(TAG, "Cannot setMode", e); - return; - } - - int previousMode = MODE_DEFAULT; - synchronized (this) { - UidState uidState = getUidStateLocked(uid, false); - Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ true); - if (op != null) { - if (op.getMode() != mode) { - previousMode = op.getMode(); - op.setMode(mode); - - if (uidState != null) { - uidState.evalForegroundOps(); - } - ArraySet<OnOpModeChangedListener> cbs = - mAppOpsServiceInterface.getOpModeChangedListeners(code); - if (cbs != null) { - if (repCbs == null) { - repCbs = new ArraySet<>(); - } - repCbs.addAll(cbs); - } - cbs = mAppOpsServiceInterface.getPackageModeChangedListeners(packageName); - if (cbs != null) { - if (repCbs == null) { - repCbs = new ArraySet<>(); - } - repCbs.addAll(cbs); - } - if (repCbs != null && permissionPolicyCallback != null) { - repCbs.remove(mModeWatchers.get(permissionPolicyCallback.asBinder())); - } - if (mode == AppOpsManager.opToDefaultMode(op.op)) { - // If going into the default mode, prune this op - // if there is nothing else interesting in it. - pruneOpLocked(op, uid, packageName); - } - scheduleFastWriteLocked(); - if (mode != MODE_ERRORED) { - updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid); - } - } - } - } - if (repCbs != null) { - mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsService::notifyOpChanged, - this, repCbs, code, uid, packageName)); - } - - notifyOpChangedSync(code, uid, packageName, mode, previousMode); - } - - private void notifyOpChanged(ArraySet<OnOpModeChangedListener> callbacks, int code, - int uid, String packageName) { - for (int i = 0; i < callbacks.size(); i++) { - final OnOpModeChangedListener callback = callbacks.valueAt(i); - notifyOpChanged(callback, code, uid, packageName); - } - } - - private void notifyOpChanged(OnOpModeChangedListener callback, int code, - int uid, String packageName) { - mAppOpsServiceInterface.notifyOpChanged(callback, code, uid, packageName); - } - - private static ArrayList<ChangeRec> addChange(ArrayList<ChangeRec> reports, - int op, int uid, String packageName, int previousMode) { - boolean duplicate = false; - if (reports == null) { - reports = new ArrayList<>(); - } else { - final int reportCount = reports.size(); - for (int j = 0; j < reportCount; j++) { - ChangeRec report = reports.get(j); - if (report.op == op && report.pkg.equals(packageName)) { - duplicate = true; - break; - } - } - } - if (!duplicate) { - reports.add(new ChangeRec(op, uid, packageName, previousMode)); - } - - return reports; - } - - private static HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> addCallbacks( - HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks, - int op, int uid, String packageName, int previousMode, - ArraySet<OnOpModeChangedListener> cbs) { - if (cbs == null) { - return callbacks; - } - if (callbacks == null) { - callbacks = new HashMap<>(); - } - final int N = cbs.size(); - for (int i=0; i<N; i++) { - OnOpModeChangedListener cb = cbs.valueAt(i); - ArrayList<ChangeRec> reports = callbacks.get(cb); - ArrayList<ChangeRec> changed = addChange(reports, op, uid, packageName, previousMode); - if (changed != reports) { - callbacks.put(cb, changed); - } - } - return callbacks; - } - - static final class ChangeRec { - final int op; - final int uid; - final String pkg; - final int previous_mode; - - ChangeRec(int _op, int _uid, String _pkg, int _previous_mode) { - op = _op; - uid = _uid; - pkg = _pkg; - previous_mode = _previous_mode; - } + mAppOpsService.setMode(code, uid, packageName, mode, null); } @Override public void resetAllModes(int reqUserId, String reqPackageName) { - final int callingPid = Binder.getCallingPid(); - final int callingUid = Binder.getCallingUid(); - reqUserId = ActivityManager.handleIncomingUser(callingPid, callingUid, reqUserId, - true, true, "resetAllModes", null); - - int reqUid = -1; - if (reqPackageName != null) { - try { - reqUid = AppGlobals.getPackageManager().getPackageUid( - reqPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, reqUserId); - } catch (RemoteException e) { - /* ignore - local call */ - } - } - - enforceManageAppOpsModes(callingPid, callingUid, reqUid); - - HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks = null; - ArrayList<ChangeRec> allChanges = new ArrayList<>(); - synchronized (this) { - boolean changed = false; - for (int i = mUidStates.size() - 1; i >= 0; i--) { - UidState uidState = mUidStates.valueAt(i); - - SparseIntArray opModes = uidState.getNonDefaultUidModes(); - if (opModes != null && (uidState.uid == reqUid || reqUid == -1)) { - final int uidOpCount = opModes.size(); - for (int j = uidOpCount - 1; j >= 0; j--) { - final int code = opModes.keyAt(j); - if (AppOpsManager.opAllowsReset(code)) { - int previousMode = opModes.valueAt(j); - uidState.setUidMode(code, AppOpsManager.opToDefaultMode(code)); - for (String packageName : getPackagesForUid(uidState.uid)) { - callbacks = addCallbacks(callbacks, code, uidState.uid, packageName, - previousMode, - mAppOpsServiceInterface.getOpModeChangedListeners(code)); - callbacks = addCallbacks(callbacks, code, uidState.uid, packageName, - previousMode, mAppOpsServiceInterface - .getPackageModeChangedListeners(packageName)); - - allChanges = addChange(allChanges, code, uidState.uid, - packageName, previousMode); - } - } - } - } - - if (uidState.pkgOps == null) { - continue; - } - - if (reqUserId != UserHandle.USER_ALL - && reqUserId != UserHandle.getUserId(uidState.uid)) { - // Skip any ops for a different user - continue; - } - - Map<String, Ops> packages = uidState.pkgOps; - Iterator<Map.Entry<String, Ops>> it = packages.entrySet().iterator(); - boolean uidChanged = false; - while (it.hasNext()) { - Map.Entry<String, Ops> ent = it.next(); - String packageName = ent.getKey(); - if (reqPackageName != null && !reqPackageName.equals(packageName)) { - // Skip any ops for a different package - continue; - } - Ops pkgOps = ent.getValue(); - for (int j=pkgOps.size()-1; j>=0; j--) { - Op curOp = pkgOps.valueAt(j); - if (shouldDeferResetOpToDpm(curOp.op)) { - deferResetOpToDpm(curOp.op, reqPackageName, reqUserId); - continue; - } - if (AppOpsManager.opAllowsReset(curOp.op) - && curOp.getMode() != AppOpsManager.opToDefaultMode(curOp.op)) { - int previousMode = curOp.getMode(); - curOp.setMode(AppOpsManager.opToDefaultMode(curOp.op)); - changed = true; - uidChanged = true; - final int uid = curOp.uidState.uid; - callbacks = addCallbacks(callbacks, curOp.op, uid, packageName, - previousMode, - mAppOpsServiceInterface.getOpModeChangedListeners(curOp.op)); - callbacks = addCallbacks(callbacks, curOp.op, uid, packageName, - previousMode, mAppOpsServiceInterface - .getPackageModeChangedListeners(packageName)); - - allChanges = addChange(allChanges, curOp.op, uid, packageName, - previousMode); - curOp.removeAttributionsWithNoTime(); - if (curOp.mAttributions.isEmpty()) { - pkgOps.removeAt(j); - } - } - } - if (pkgOps.size() == 0) { - it.remove(); - mAppOpsServiceInterface.removePackage(packageName, - UserHandle.getUserId(uidState.uid)); - } - } - if (uidState.isDefault()) { - uidState.clear(); - mUidStates.remove(uidState.uid); - } - if (uidChanged) { - uidState.evalForegroundOps(); - } - } - - if (changed) { - scheduleFastWriteLocked(); - } - } - if (callbacks != null) { - for (Map.Entry<OnOpModeChangedListener, ArrayList<ChangeRec>> ent - : callbacks.entrySet()) { - OnOpModeChangedListener cb = ent.getKey(); - ArrayList<ChangeRec> reports = ent.getValue(); - for (int i=0; i<reports.size(); i++) { - ChangeRec rep = reports.get(i); - mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsService::notifyOpChanged, - this, cb, rep.op, rep.uid, rep.pkg)); - } - } - } - - int numChanges = allChanges.size(); - for (int i = 0; i < numChanges; i++) { - ChangeRec change = allChanges.get(i); - notifyOpChangedSync(change.op, change.uid, change.pkg, - AppOpsManager.opToDefaultMode(change.op), change.previous_mode); - } - } - - private boolean shouldDeferResetOpToDpm(int op) { - // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission - // pre-grants to a role-based mechanism or another general-purpose mechanism. - return dpmi != null && dpmi.supportsResetOp(op); - } - - /** Assumes {@link #shouldDeferResetOpToDpm(int)} is true. */ - private void deferResetOpToDpm(int op, String packageName, @UserIdInt int userId) { - // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission - // pre-grants to a role-based mechanism or another general-purpose mechanism. - dpmi.resetOp(op, packageName, userId); - } - - private void evalAllForegroundOpsLocked() { - for (int uidi = mUidStates.size() - 1; uidi >= 0; uidi--) { - final UidState uidState = mUidStates.valueAt(uidi); - if (uidState.foregroundOps != null) { - uidState.evalForegroundOps(); - } - } + mAppOpsService.resetAllModes(reqUserId, reqPackageName); } @Override @@ -2179,66 +423,17 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch @Override public void startWatchingModeWithFlags(int op, String packageName, int flags, IAppOpsCallback callback) { - int watchedUid = -1; - final int callingUid = Binder.getCallingUid(); - final int callingPid = Binder.getCallingPid(); - // TODO: should have a privileged permission to protect this. - // Also, if the caller has requested WATCH_FOREGROUND_CHANGES, should we require - // the USAGE_STATS permission since this can provide information about when an - // app is in the foreground? - Preconditions.checkArgumentInRange(op, AppOpsManager.OP_NONE, - AppOpsManager._NUM_OP - 1, "Invalid op code: " + op); - if (callback == null) { - return; - } - final boolean mayWatchPackageName = packageName != null - && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(callingUid)); - synchronized (this) { - int switchOp = (op != AppOpsManager.OP_NONE) ? AppOpsManager.opToSwitch(op) : op; - - int notifiedOps; - if ((flags & CALL_BACK_ON_SWITCHED_OP) == 0) { - if (op == OP_NONE) { - notifiedOps = ALL_OPS; - } else { - notifiedOps = op; - } - } else { - notifiedOps = switchOp; - } - - ModeCallback cb = mModeWatchers.get(callback.asBinder()); - if (cb == null) { - cb = new ModeCallback(callback, watchedUid, flags, notifiedOps, callingUid, - callingPid); - mModeWatchers.put(callback.asBinder(), cb); - } - if (switchOp != AppOpsManager.OP_NONE) { - mAppOpsServiceInterface.startWatchingOpModeChanged(cb, switchOp); - } - if (mayWatchPackageName) { - mAppOpsServiceInterface.startWatchingPackageModeChanged(cb, packageName); - } - evalAllForegroundOpsLocked(); - } + mAppOpsService.startWatchingModeWithFlags(op, packageName, flags, callback); } @Override public void stopWatchingMode(IAppOpsCallback callback) { - if (callback == null) { - return; - } - synchronized (this) { - ModeCallback cb = mModeWatchers.remove(callback.asBinder()); - if (cb != null) { - cb.unlinkToDeath(); - mAppOpsServiceInterface.removeListener(cb); - } - - evalAllForegroundOpsLocked(); - } + mAppOpsService.stopWatchingMode(callback); } + /** + * @return the current {@link CheckOpsDelegate}. + */ public CheckOpsDelegate getAppOpsServiceDelegate() { synchronized (AppOpsService.this) { final CheckOpsDelegateDispatcher dispatcher = mCheckOpsDelegateDispatcher; @@ -2246,6 +441,9 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } + /** + * Sets the appops {@link CheckOpsDelegate} + */ public void setAppOpsServiceDelegate(CheckOpsDelegate delegate) { synchronized (AppOpsService.this) { final CheckOpsDelegateDispatcher oldDispatcher = mCheckOpsDelegateDispatcher; @@ -2269,58 +467,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch private int checkOperationImpl(int code, int uid, String packageName, @Nullable String attributionTag, boolean raw) { - verifyIncomingOp(code); - if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) { - return AppOpsManager.opToDefaultMode(code); - } - - String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName); - if (resolvedPackageName == null) { - return AppOpsManager.MODE_IGNORED; - } - return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag, raw); - } - - /** - * Get the mode of an app-op. - * - * @param code The code of the op - * @param uid The uid of the package the op belongs to - * @param packageName The package the op belongs to - * @param raw If the raw state of eval-ed state should be checked. - * - * @return The mode of the op - */ - private @Mode int checkOperationUnchecked(int code, int uid, @NonNull String packageName, - @Nullable String attributionTag, boolean raw) { - PackageVerificationResult pvr; - try { - pvr = verifyAndGetBypass(uid, packageName, null); - } catch (SecurityException e) { - Slog.e(TAG, "checkOperation", e); - return AppOpsManager.opToDefaultMode(code); - } - - if (isOpRestrictedDueToSuspend(code, packageName, uid)) { - return AppOpsManager.MODE_IGNORED; - } - synchronized (this) { - if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, true)) { - return AppOpsManager.MODE_IGNORED; - } - code = AppOpsManager.opToSwitch(code); - UidState uidState = getUidStateLocked(uid, false); - if (uidState != null - && uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) { - final int rawMode = uidState.getUidMode(code); - return raw ? rawMode : uidState.evalMode(code, rawMode); - } - Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ false); - if (op == null) { - return AppOpsManager.opToDefaultMode(code); - } - return raw ? op.getMode() : op.uidState.evalMode(op.op, op.getMode()); - } + return mAppOpsService.checkOperation(code, uid, packageName, attributionTag, raw); } @Override @@ -2340,7 +487,8 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch @Override public void setAudioRestriction(int code, int usage, int uid, int mode, String[] exceptionPackages) { - enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid); + mAppOpsService.enforceManageAppOpsModes(Binder.getCallingPid(), + Binder.getCallingUid(), uid); verifyIncomingUid(uid); verifyIncomingOp(code); @@ -2348,58 +496,35 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch code, usage, uid, mode, exceptionPackages); mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsService::notifyWatchersOfChange, this, code, UID_ANY)); + AppOpsServiceInterface::notifyWatchersOfChange, mAppOpsService, code, + UID_ANY)); } @Override public void setCameraAudioRestriction(@CAMERA_AUDIO_RESTRICTION int mode) { - enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), -1); + mAppOpsService.enforceManageAppOpsModes(Binder.getCallingPid(), + Binder.getCallingUid(), -1); mAudioRestrictionManager.setCameraAudioRestriction(mode); mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsService::notifyWatchersOfChange, this, + AppOpsServiceInterface::notifyWatchersOfChange, mAppOpsService, AppOpsManager.OP_PLAY_AUDIO, UID_ANY)); mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsService::notifyWatchersOfChange, this, + AppOpsServiceInterface::notifyWatchersOfChange, mAppOpsService, AppOpsManager.OP_VIBRATE, UID_ANY)); } @Override public int checkPackage(int uid, String packageName) { - Objects.requireNonNull(packageName); - try { - verifyAndGetBypass(uid, packageName, null); - // When the caller is the system, it's possible that the packageName is the special - // one (e.g., "root") which isn't actually existed. - if (resolveUid(packageName) == uid - || (isPackageExisted(packageName) - && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(uid)))) { - return AppOpsManager.MODE_ALLOWED; - } - return AppOpsManager.MODE_ERRORED; - } catch (SecurityException ignored) { - return AppOpsManager.MODE_ERRORED; - } + return mAppOpsService.checkPackage(uid, packageName); } private boolean isPackageExisted(String packageName) { return getPackageManagerInternal().getPackageStateInternal(packageName) != null; } - /** - * This method will check with PackageManager to determine if the package provided should - * be visible to the {@link Binder#getCallingUid()}. - * - * NOTE: This must not be called while synchronized on {@code this} to avoid dead locks - */ - private boolean filterAppAccessUnlocked(String packageName, int userId) { - final int callingUid = Binder.getCallingUid(); - return LocalServices.getService(PackageManagerInternal.class) - .filterAppAccess(packageName, callingUid, userId); - } - @Override public SyncNotedAppOp noteProxyOperation(int code, AttributionSource attributionSource, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, @@ -2445,13 +570,20 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY : AppOpsManager.OP_FLAG_UNTRUSTED_PROXY; - final SyncNotedAppOp proxyReturn = noteOperationUnchecked(code, proxyUid, + final int proxyReturn = mAppOpsService.noteOperationUnchecked(code, proxyUid, resolveProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null, - proxyFlags, !isProxyTrusted, "proxy " + message, shouldCollectMessage); - if (proxyReturn.getOpMode() != AppOpsManager.MODE_ALLOWED) { - return new SyncNotedAppOp(proxyReturn.getOpMode(), code, proxiedAttributionTag, + proxyFlags); + if (proxyReturn != AppOpsManager.MODE_ALLOWED) { + return new SyncNotedAppOp(proxyReturn, code, proxiedAttributionTag, proxiedPackageName); } + if (shouldCollectAsyncNotedOp) { + boolean isProxyAttributionTagValid = mAppOpsService.isAttributionTagValid(proxyUid, + resolveProxyPackageName, proxyAttributionTag, null); + collectAsyncNotedOp(proxyUid, resolveProxyPackageName, code, + isProxyAttributionTagValid ? proxyAttributionTag : null, proxyFlags, + message, shouldCollectMessage); + } } String resolveProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid, @@ -2463,9 +595,32 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch final int proxiedFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXIED : AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED; - return noteOperationUnchecked(code, proxiedUid, resolveProxiedPackageName, - proxiedAttributionTag, proxyUid, resolveProxyPackageName, proxyAttributionTag, - proxiedFlags, shouldCollectAsyncNotedOp, message, shouldCollectMessage); + final int result = mAppOpsService.noteOperationUnchecked(code, proxiedUid, + resolveProxiedPackageName, proxiedAttributionTag, proxyUid, resolveProxyPackageName, + proxyAttributionTag, proxiedFlags); + + boolean isProxiedAttributionTagValid = mAppOpsService.isAttributionTagValid(proxiedUid, + resolveProxiedPackageName, proxiedAttributionTag, resolveProxyPackageName); + if (shouldCollectAsyncNotedOp && result == AppOpsManager.MODE_ALLOWED) { + collectAsyncNotedOp(proxiedUid, resolveProxiedPackageName, code, + isProxiedAttributionTagValid ? proxiedAttributionTag : null, proxiedFlags, + message, shouldCollectMessage); + } + + + return new SyncNotedAppOp(result, code, + isProxiedAttributionTagValid ? proxiedAttributionTag : null, + resolveProxiedPackageName); + } + + private boolean isCallerAndAttributionTrusted(@NonNull AttributionSource attributionSource) { + if (attributionSource.getUid() != Binder.getCallingUid() + && attributionSource.isTrusted(mContext)) { + return true; + } + return mContext.checkPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS, + Binder.getCallingPid(), Binder.getCallingUid(), null) + == PackageManager.PERMISSION_GRANTED; } @Override @@ -2479,258 +634,58 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch private SyncNotedAppOp noteOperationImpl(int code, int uid, @Nullable String packageName, @Nullable String attributionTag, boolean shouldCollectAsyncNotedOp, @Nullable String message, boolean shouldCollectMessage) { - verifyIncomingUid(uid); - verifyIncomingOp(code); if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) { return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag, packageName); } - String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName); - if (resolvedPackageName == null) { - return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag, - packageName); - } - return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag, - Process.INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF, - shouldCollectAsyncNotedOp, message, shouldCollectMessage); - } - - private SyncNotedAppOp noteOperationUnchecked(int code, int uid, @NonNull String packageName, - @Nullable String attributionTag, int proxyUid, String proxyPackageName, - @Nullable String proxyAttributionTag, @OpFlags int flags, - boolean shouldCollectAsyncNotedOp, @Nullable String message, - boolean shouldCollectMessage) { - PackageVerificationResult pvr; - try { - pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName); - boolean wasNull = attributionTag == null; - if (!pvr.isAttributionTagValid) { - attributionTag = null; - } - } catch (SecurityException e) { - Slog.e(TAG, "noteOperation", e); - return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag, - packageName); - } - - synchronized (this) { - final Ops ops = getOpsLocked(uid, packageName, attributionTag, - pvr.isAttributionTagValid, pvr.bypass, /* edit */ true); - if (ops == null) { - scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags, - AppOpsManager.MODE_IGNORED); - if (DEBUG) Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid - + " package " + packageName + "flags: " + - AppOpsManager.flagsToString(flags)); - return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag, - packageName); - } - final Op op = getOpLocked(ops, code, uid, true); - final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag); - if (attributedOp.isRunning()) { - Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code " - + code + " startTime of in progress event=" - + attributedOp.mInProgressEvents.valueAt(0).getStartTime()); - } + int result = mAppOpsService.noteOperation(code, uid, packageName, + attributionTag, message); - final int switchCode = AppOpsManager.opToSwitch(code); - final UidState uidState = ops.uidState; - if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, false)) { - attributedOp.rejected(uidState.getState(), flags); - scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags, - AppOpsManager.MODE_IGNORED); - return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag, - packageName); - } - // If there is a non-default per UID policy (we set UID op mode only if - // non-default) it takes over, otherwise use the per package policy. - if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) { - final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode)); - if (uidMode != AppOpsManager.MODE_ALLOWED) { - if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code " - + switchCode + " (" + code + ") uid " + uid + " package " - + packageName + " flags: " + AppOpsManager.flagsToString(flags)); - attributedOp.rejected(uidState.getState(), flags); - scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags, - uidMode); - return new SyncNotedAppOp(uidMode, code, attributionTag, packageName); - } - } else { - final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true) - : op; - final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode()); - if (mode != AppOpsManager.MODE_ALLOWED) { - if (DEBUG) Slog.d(TAG, "noteOperation: reject #" + mode + " for code " - + switchCode + " (" + code + ") uid " + uid + " package " - + packageName + " flags: " + AppOpsManager.flagsToString(flags)); - attributedOp.rejected(uidState.getState(), flags); - scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags, - mode); - return new SyncNotedAppOp(mode, code, attributionTag, packageName); - } - } - if (DEBUG) { - Slog.d(TAG, - "noteOperation: allowing code " + code + " uid " + uid + " package " - + packageName + (attributionTag == null ? "" - : "." + attributionTag) + " flags: " - + AppOpsManager.flagsToString(flags)); - } - scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags, - AppOpsManager.MODE_ALLOWED); - attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag, - uidState.getState(), - flags); + String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName); - if (shouldCollectAsyncNotedOp) { - collectAsyncNotedOp(uid, packageName, code, attributionTag, flags, message, - shouldCollectMessage); - } + boolean isAttributionTagValid = mAppOpsService.isAttributionTagValid(uid, + resolvedPackageName, attributionTag, null); - return new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, code, attributionTag, - packageName); + if (shouldCollectAsyncNotedOp && result == MODE_ALLOWED) { + collectAsyncNotedOp(uid, resolvedPackageName, code, + isAttributionTagValid ? attributionTag : null, AppOpsManager.OP_FLAG_SELF, + message, shouldCollectMessage); } + + return new SyncNotedAppOp(result, code, isAttributionTagValid ? attributionTag : null, + resolvedPackageName); } // TODO moltmann: Allow watching for attribution ops @Override public void startWatchingActive(int[] ops, IAppOpsActiveCallback callback) { - int watchedUid = Process.INVALID_UID; - final int callingUid = Binder.getCallingUid(); - final int callingPid = Binder.getCallingPid(); - if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS) - != PackageManager.PERMISSION_GRANTED) { - watchedUid = callingUid; - } - if (ops != null) { - Preconditions.checkArrayElementsInRange(ops, 0, - AppOpsManager._NUM_OP - 1, "Invalid op code in: " + Arrays.toString(ops)); - } - if (callback == null) { - return; - } - synchronized (this) { - SparseArray<ActiveCallback> callbacks = mActiveWatchers.get(callback.asBinder()); - if (callbacks == null) { - callbacks = new SparseArray<>(); - mActiveWatchers.put(callback.asBinder(), callbacks); - } - final ActiveCallback activeCallback = new ActiveCallback(callback, watchedUid, - callingUid, callingPid); - for (int op : ops) { - callbacks.put(op, activeCallback); - } - } + mAppOpsService.startWatchingActive(ops, callback); } @Override public void stopWatchingActive(IAppOpsActiveCallback callback) { - if (callback == null) { - return; - } - synchronized (this) { - final SparseArray<ActiveCallback> activeCallbacks = - mActiveWatchers.remove(callback.asBinder()); - if (activeCallbacks == null) { - return; - } - final int callbackCount = activeCallbacks.size(); - for (int i = 0; i < callbackCount; i++) { - activeCallbacks.valueAt(i).destroy(); - } - } + mAppOpsService.stopWatchingActive(callback); } @Override public void startWatchingStarted(int[] ops, @NonNull IAppOpsStartedCallback callback) { - int watchedUid = Process.INVALID_UID; - final int callingUid = Binder.getCallingUid(); - final int callingPid = Binder.getCallingPid(); - if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS) - != PackageManager.PERMISSION_GRANTED) { - watchedUid = callingUid; - } - - Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty"); - Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1, - "Invalid op code in: " + Arrays.toString(ops)); - Objects.requireNonNull(callback, "Callback cannot be null"); - - synchronized (this) { - SparseArray<StartedCallback> callbacks = mStartedWatchers.get(callback.asBinder()); - if (callbacks == null) { - callbacks = new SparseArray<>(); - mStartedWatchers.put(callback.asBinder(), callbacks); - } - - final StartedCallback startedCallback = new StartedCallback(callback, watchedUid, - callingUid, callingPid); - for (int op : ops) { - callbacks.put(op, startedCallback); - } - } + mAppOpsService.startWatchingStarted(ops, callback); } @Override public void stopWatchingStarted(IAppOpsStartedCallback callback) { - Objects.requireNonNull(callback, "Callback cannot be null"); - - synchronized (this) { - final SparseArray<StartedCallback> startedCallbacks = - mStartedWatchers.remove(callback.asBinder()); - if (startedCallbacks == null) { - return; - } - - final int callbackCount = startedCallbacks.size(); - for (int i = 0; i < callbackCount; i++) { - startedCallbacks.valueAt(i).destroy(); - } - } + mAppOpsService.stopWatchingStarted(callback); } @Override public void startWatchingNoted(@NonNull int[] ops, @NonNull IAppOpsNotedCallback callback) { - int watchedUid = Process.INVALID_UID; - final int callingUid = Binder.getCallingUid(); - final int callingPid = Binder.getCallingPid(); - if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS) - != PackageManager.PERMISSION_GRANTED) { - watchedUid = callingUid; - } - Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty"); - Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1, - "Invalid op code in: " + Arrays.toString(ops)); - Objects.requireNonNull(callback, "Callback cannot be null"); - synchronized (this) { - SparseArray<NotedCallback> callbacks = mNotedWatchers.get(callback.asBinder()); - if (callbacks == null) { - callbacks = new SparseArray<>(); - mNotedWatchers.put(callback.asBinder(), callbacks); - } - final NotedCallback notedCallback = new NotedCallback(callback, watchedUid, - callingUid, callingPid); - for (int op : ops) { - callbacks.put(op, notedCallback); - } - } + mAppOpsService.startWatchingNoted(ops, callback); } @Override public void stopWatchingNoted(IAppOpsNotedCallback callback) { - Objects.requireNonNull(callback, "Callback cannot be null"); - synchronized (this) { - final SparseArray<NotedCallback> notedCallbacks = - mNotedWatchers.remove(callback.asBinder()); - if (notedCallbacks == null) { - return; - } - final int callbackCount = notedCallbacks.size(); - for (int i = 0; i < callbackCount; i++) { - notedCallbacks.valueAt(i).destroy(); - } - } + mAppOpsService.stopWatchingNoted(callback); } /** @@ -2817,7 +772,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch int uid = Binder.getCallingUid(); Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid); - verifyAndGetBypass(uid, packageName, null); + mAppOpsService.verifyPackage(uid, packageName); synchronized (this) { RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key); @@ -2847,7 +802,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch int uid = Binder.getCallingUid(); Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid); - verifyAndGetBypass(uid, packageName, null); + mAppOpsService.verifyPackage(uid, packageName); synchronized (this) { RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key); @@ -2866,7 +821,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch int uid = Binder.getCallingUid(); - verifyAndGetBypass(uid, packageName, null); + mAppOpsService.verifyPackage(uid, packageName); synchronized (this) { return mUnforwardedAsyncNotedOps.remove(getAsyncNotedOpsKey(packageName, uid)); @@ -2889,54 +844,49 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @NonNull String message, boolean shouldCollectMessage, @AttributionFlags int attributionFlags, int attributionChainId) { - verifyIncomingUid(uid); - verifyIncomingOp(code); if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) { return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag, packageName); } + int result = mAppOpsService.startOperation(clientId, code, uid, packageName, + attributionTag, startIfModeDefault, message, + attributionFlags, attributionChainId); + String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName); - if (resolvedPackageName == null) { - return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag, - packageName); - } - // As a special case for OP_RECORD_AUDIO_HOTWORD, which we use only for attribution - // purposes and not as a check, also make sure that the caller is allowed to access - // the data gated by OP_RECORD_AUDIO. - // - // TODO: Revert this change before Android 12. - if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO) { - int result = checkOperation(OP_RECORD_AUDIO, uid, packageName); - if (result != AppOpsManager.MODE_ALLOWED) { - return new SyncNotedAppOp(result, code, attributionTag, packageName); - } + boolean isAttributionTagValid = mAppOpsService.isAttributionTagValid(uid, + resolvedPackageName, attributionTag, null); + + if (shouldCollectAsyncNotedOp && result == MODE_ALLOWED) { + collectAsyncNotedOp(uid, resolvedPackageName, code, + isAttributionTagValid ? attributionTag : null, AppOpsManager.OP_FLAG_SELF, + message, shouldCollectMessage); } - return startOperationUnchecked(clientId, code, uid, packageName, attributionTag, - Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault, - shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags, - attributionChainId, /*dryRun*/ false); + + return new SyncNotedAppOp(result, code, isAttributionTagValid ? attributionTag : null, + resolvedPackageName); } @Override - public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code, + public SyncNotedAppOp startProxyOperation(IBinder clientId, int code, @NonNull AttributionSource attributionSource, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags, int attributionChainId) { - return mCheckOpsDelegateDispatcher.startProxyOperation(clientId, code, attributionSource, - startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage, - skipProxyOperation, proxyAttributionFlags, proxiedAttributionFlags, - attributionChainId); + return mCheckOpsDelegateDispatcher.startProxyOperation(clientId, code, + attributionSource, startIfModeDefault, shouldCollectAsyncNotedOp, message, + shouldCollectMessage, skipProxyOperation, proxyAttributionFlags, + proxiedAttributionFlags, attributionChainId); } - private SyncNotedAppOp startProxyOperationImpl(@NonNull IBinder clientId, int code, + private SyncNotedAppOp startProxyOperationImpl(IBinder clientId, int code, @NonNull AttributionSource attributionSource, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags, int attributionChainId) { + final int proxyUid = attributionSource.getUid(); final String proxyPackageName = attributionSource.getPackageName(); final String proxyAttributionTag = attributionSource.getAttributionTag(); @@ -2984,145 +934,66 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch if (!skipProxyOperation) { // Test if the proxied operation will succeed before starting the proxy operation - final SyncNotedAppOp testProxiedOp = startOperationUnchecked(clientId, code, + final int testProxiedOp = mAppOpsService.startOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag, proxyUid, resolvedProxyPackageName, proxyAttributionTag, proxiedFlags, startIfModeDefault, - shouldCollectAsyncNotedOp, message, shouldCollectMessage, proxiedAttributionFlags, attributionChainId, /*dryRun*/ true); - if (!shouldStartForMode(testProxiedOp.getOpMode(), startIfModeDefault)) { - return testProxiedOp; + + boolean isTestProxiedAttributionTagValid = + mAppOpsService.isAttributionTagValid(proxiedUid, resolvedProxiedPackageName, + proxiedAttributionTag, resolvedProxyPackageName); + + if (!shouldStartForMode(testProxiedOp, startIfModeDefault)) { + return new SyncNotedAppOp(testProxiedOp, code, + isTestProxiedAttributionTagValid ? proxiedAttributionTag : null, + resolvedProxiedPackageName); } final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY : AppOpsManager.OP_FLAG_UNTRUSTED_PROXY; - final SyncNotedAppOp proxyAppOp = startOperationUnchecked(clientId, code, proxyUid, + final int proxyAppOp = mAppOpsService.startOperationUnchecked(clientId, code, proxyUid, resolvedProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null, - proxyFlags, startIfModeDefault, !isProxyTrusted, "proxy " + message, - shouldCollectMessage, proxyAttributionFlags, attributionChainId, + proxyFlags, startIfModeDefault, proxyAttributionFlags, attributionChainId, /*dryRun*/ false); - if (!shouldStartForMode(proxyAppOp.getOpMode(), startIfModeDefault)) { - return proxyAppOp; - } - } - return startOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName, - proxiedAttributionTag, proxyUid, resolvedProxyPackageName, proxyAttributionTag, - proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp, message, - shouldCollectMessage, proxiedAttributionFlags, attributionChainId, - /*dryRun*/ false); - } + boolean isProxyAttributionTagValid = mAppOpsService.isAttributionTagValid(proxyUid, + resolvedProxyPackageName, proxyAttributionTag, null); - private boolean shouldStartForMode(int mode, boolean startIfModeDefault) { - return (mode == MODE_ALLOWED || (mode == MODE_DEFAULT && startIfModeDefault)); - } - - private SyncNotedAppOp startOperationUnchecked(IBinder clientId, int code, int uid, - @NonNull String packageName, @Nullable String attributionTag, int proxyUid, - String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags, - boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @Nullable String message, - boolean shouldCollectMessage, @AttributionFlags int attributionFlags, - int attributionChainId, boolean dryRun) { - PackageVerificationResult pvr; - try { - pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName); - if (!pvr.isAttributionTagValid) { - attributionTag = null; + if (!shouldStartForMode(proxyAppOp, startIfModeDefault)) { + return new SyncNotedAppOp(proxyAppOp, code, + isProxyAttributionTagValid ? proxyAttributionTag : null, + resolvedProxyPackageName); } - } catch (SecurityException e) { - Slog.e(TAG, "startOperation", e); - return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag, - packageName); - } - boolean isRestricted = false; - int startType = START_TYPE_FAILED; - synchronized (this) { - final Ops ops = getOpsLocked(uid, packageName, attributionTag, - pvr.isAttributionTagValid, pvr.bypass, /* edit */ true); - if (ops == null) { - if (!dryRun) { - scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, - flags, AppOpsManager.MODE_IGNORED, startType, attributionFlags, - attributionChainId); - } - if (DEBUG) Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid - + " package " + packageName + " flags: " - + AppOpsManager.flagsToString(flags)); - return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag, - packageName); - } - final Op op = getOpLocked(ops, code, uid, true); - final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag); - final UidState uidState = ops.uidState; - isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, - false); - final int switchCode = AppOpsManager.opToSwitch(code); - // If there is a non-default per UID policy (we set UID op mode only if - // non-default) it takes over, otherwise use the per package policy. - if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) { - final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode)); - if (!shouldStartForMode(uidMode, startIfModeDefault)) { - if (DEBUG) { - Slog.d(TAG, "startOperation: uid reject #" + uidMode + " for code " - + switchCode + " (" + code + ") uid " + uid + " package " - + packageName + " flags: " + AppOpsManager.flagsToString(flags)); - } - if (!dryRun) { - attributedOp.rejected(uidState.getState(), flags); - scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, - flags, uidMode, startType, attributionFlags, attributionChainId); - } - return new SyncNotedAppOp(uidMode, code, attributionTag, packageName); - } - } else { - final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true) - : op; - final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode()); - if (mode != AppOpsManager.MODE_ALLOWED - && (!startIfModeDefault || mode != MODE_DEFAULT)) { - if (DEBUG) Slog.d(TAG, "startOperation: reject #" + mode + " for code " - + switchCode + " (" + code + ") uid " + uid + " package " - + packageName + " flags: " + AppOpsManager.flagsToString(flags)); - if (!dryRun) { - attributedOp.rejected(uidState.getState(), flags); - scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, - flags, mode, startType, attributionFlags, attributionChainId); - } - return new SyncNotedAppOp(mode, code, attributionTag, packageName); - } - } - if (DEBUG) Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid - + " package " + packageName + " restricted: " + isRestricted - + " flags: " + AppOpsManager.flagsToString(flags)); - if (!dryRun) { - try { - if (isRestricted) { - attributedOp.createPaused(clientId, proxyUid, proxyPackageName, - proxyAttributionTag, uidState.getState(), flags, - attributionFlags, attributionChainId); - } else { - attributedOp.started(clientId, proxyUid, proxyPackageName, - proxyAttributionTag, uidState.getState(), flags, - attributionFlags, attributionChainId); - startType = START_TYPE_STARTED; - } - } catch (RemoteException e) { - throw new RuntimeException(e); - } - scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags, - isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags, - attributionChainId); + if (shouldCollectAsyncNotedOp) { + collectAsyncNotedOp(proxyUid, resolvedProxyPackageName, code, + isProxyAttributionTagValid ? proxyAttributionTag : null, proxyFlags, + message, shouldCollectMessage); } } - if (shouldCollectAsyncNotedOp && !dryRun && !isRestricted) { - collectAsyncNotedOp(uid, packageName, code, attributionTag, AppOpsManager.OP_FLAG_SELF, - message, shouldCollectMessage); + final int proxiedAppOp = mAppOpsService.startOperationUnchecked(clientId, code, proxiedUid, + resolvedProxiedPackageName, proxiedAttributionTag, proxyUid, + resolvedProxyPackageName, proxyAttributionTag, proxiedFlags, startIfModeDefault, + proxiedAttributionFlags, attributionChainId,/*dryRun*/ false); + + boolean isProxiedAttributionTagValid = mAppOpsService.isAttributionTagValid(proxiedUid, + resolvedProxiedPackageName, proxiedAttributionTag, resolvedProxyPackageName); + + if (shouldCollectAsyncNotedOp && proxiedAppOp == MODE_ALLOWED) { + collectAsyncNotedOp(proxyUid, resolvedProxiedPackageName, code, + isProxiedAttributionTagValid ? proxiedAttributionTag : null, + proxiedAttributionFlags, message, shouldCollectMessage); } - return new SyncNotedAppOp(isRestricted ? MODE_IGNORED : MODE_ALLOWED, code, attributionTag, - packageName); + return new SyncNotedAppOp(proxiedAppOp, code, + isProxiedAttributionTagValid ? proxiedAttributionTag : null, + resolvedProxiedPackageName); + } + + private boolean shouldStartForMode(int mode, boolean startIfModeDefault) { + return (mode == MODE_ALLOWED || (mode == MODE_DEFAULT && startIfModeDefault)); } @Override @@ -3134,22 +1005,11 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch private void finishOperationImpl(IBinder clientId, int code, int uid, String packageName, String attributionTag) { - verifyIncomingUid(uid); - verifyIncomingOp(code); - if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) { - return; - } - - String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName); - if (resolvedPackageName == null) { - return; - } - - finishOperationUnchecked(clientId, code, uid, resolvedPackageName, attributionTag); + mAppOpsService.finishOperation(clientId, code, uid, packageName, attributionTag); } @Override - public void finishProxyOperation(@NonNull IBinder clientId, int code, + public void finishProxyOperation(IBinder clientId, int code, @NonNull AttributionSource attributionSource, boolean skipProxyOperation) { mCheckOpsDelegateDispatcher.finishProxyOperation(clientId, code, attributionSource, skipProxyOperation); @@ -3181,8 +1041,8 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } if (!skipProxyOperation) { - finishOperationUnchecked(clientId, code, proxyUid, resolvedProxyPackageName, - proxyAttributionTag); + mAppOpsService.finishOperationUnchecked(clientId, code, proxyUid, + resolvedProxyPackageName, proxyAttributionTag); } String resolvedProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid, @@ -3191,209 +1051,12 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch return null; } - finishOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName, - proxiedAttributionTag); + mAppOpsService.finishOperationUnchecked(clientId, code, proxiedUid, + resolvedProxiedPackageName, proxiedAttributionTag); return null; } - private void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName, - String attributionTag) { - PackageVerificationResult pvr; - try { - pvr = verifyAndGetBypass(uid, packageName, attributionTag); - if (!pvr.isAttributionTagValid) { - attributionTag = null; - } - } catch (SecurityException e) { - Slog.e(TAG, "Cannot finishOperation", e); - return; - } - - synchronized (this) { - Op op = getOpLocked(code, uid, packageName, attributionTag, pvr.isAttributionTagValid, - pvr.bypass, /* edit */ true); - if (op == null) { - Slog.e(TAG, "Operation not found: uid=" + uid + " pkg=" + packageName + "(" - + attributionTag + ") op=" + AppOpsManager.opToName(code)); - return; - } - final AttributedOp attributedOp = op.mAttributions.get(attributionTag); - if (attributedOp == null) { - Slog.e(TAG, "Attribution not found: uid=" + uid + " pkg=" + packageName + "(" - + attributionTag + ") op=" + AppOpsManager.opToName(code)); - return; - } - - if (attributedOp.isRunning() || attributedOp.isPaused()) { - attributedOp.finished(clientId); - } else { - Slog.e(TAG, "Operation not started: uid=" + uid + " pkg=" + packageName + "(" - + attributionTag + ") op=" + AppOpsManager.opToName(code)); - } - } - } - - void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull - String packageName, @Nullable String attributionTag, boolean active, @AttributionFlags - int attributionFlags, int attributionChainId) { - ArraySet<ActiveCallback> dispatchedCallbacks = null; - final int callbackListCount = mActiveWatchers.size(); - for (int i = 0; i < callbackListCount; i++) { - final SparseArray<ActiveCallback> callbacks = mActiveWatchers.valueAt(i); - ActiveCallback callback = callbacks.get(code); - if (callback != null) { - if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) { - continue; - } - if (dispatchedCallbacks == null) { - dispatchedCallbacks = new ArraySet<>(); - } - dispatchedCallbacks.add(callback); - } - } - if (dispatchedCallbacks == null) { - return; - } - mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsService::notifyOpActiveChanged, - this, dispatchedCallbacks, code, uid, packageName, attributionTag, active, - attributionFlags, attributionChainId)); - } - - private void notifyOpActiveChanged(ArraySet<ActiveCallback> callbacks, - int code, int uid, @NonNull String packageName, @Nullable String attributionTag, - boolean active, @AttributionFlags int attributionFlags, int attributionChainId) { - // There are features watching for mode changes such as window manager - // and location manager which are in our process. The callbacks in these - // features may require permissions our remote caller does not have. - final long identity = Binder.clearCallingIdentity(); - try { - final int callbackCount = callbacks.size(); - for (int i = 0; i < callbackCount; i++) { - final ActiveCallback callback = callbacks.valueAt(i); - try { - if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) { - continue; - } - callback.mCallback.opActiveChanged(code, uid, packageName, attributionTag, - active, attributionFlags, attributionChainId); - } catch (RemoteException e) { - /* do nothing */ - } - } - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName, - String attributionTag, @OpFlags int flags, @Mode int result, - @AppOpsManager.OnOpStartedListener.StartedType int startedType, - @AttributionFlags int attributionFlags, int attributionChainId) { - ArraySet<StartedCallback> dispatchedCallbacks = null; - final int callbackListCount = mStartedWatchers.size(); - for (int i = 0; i < callbackListCount; i++) { - final SparseArray<StartedCallback> callbacks = mStartedWatchers.valueAt(i); - - StartedCallback callback = callbacks.get(code); - if (callback != null) { - if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) { - continue; - } - - if (dispatchedCallbacks == null) { - dispatchedCallbacks = new ArraySet<>(); - } - dispatchedCallbacks.add(callback); - } - } - - if (dispatchedCallbacks == null) { - return; - } - - mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsService::notifyOpStarted, - this, dispatchedCallbacks, code, uid, pkgName, attributionTag, flags, - result, startedType, attributionFlags, attributionChainId)); - } - - private void notifyOpStarted(ArraySet<StartedCallback> callbacks, - int code, int uid, String packageName, String attributionTag, @OpFlags int flags, - @Mode int result, @AppOpsManager.OnOpStartedListener.StartedType int startedType, - @AttributionFlags int attributionFlags, int attributionChainId) { - final long identity = Binder.clearCallingIdentity(); - try { - final int callbackCount = callbacks.size(); - for (int i = 0; i < callbackCount; i++) { - final StartedCallback callback = callbacks.valueAt(i); - try { - if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) { - continue; - } - callback.mCallback.opStarted(code, uid, packageName, attributionTag, flags, - result, startedType, attributionFlags, attributionChainId); - } catch (RemoteException e) { - /* do nothing */ - } - } - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - private void scheduleOpNotedIfNeededLocked(int code, int uid, String packageName, - String attributionTag, @OpFlags int flags, @Mode int result) { - ArraySet<NotedCallback> dispatchedCallbacks = null; - final int callbackListCount = mNotedWatchers.size(); - for (int i = 0; i < callbackListCount; i++) { - final SparseArray<NotedCallback> callbacks = mNotedWatchers.valueAt(i); - final NotedCallback callback = callbacks.get(code); - if (callback != null) { - if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) { - continue; - } - if (dispatchedCallbacks == null) { - dispatchedCallbacks = new ArraySet<>(); - } - dispatchedCallbacks.add(callback); - } - } - if (dispatchedCallbacks == null) { - return; - } - mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsService::notifyOpChecked, - this, dispatchedCallbacks, code, uid, packageName, attributionTag, flags, - result)); - } - - private void notifyOpChecked(ArraySet<NotedCallback> callbacks, - int code, int uid, String packageName, String attributionTag, @OpFlags int flags, - @Mode int result) { - // There are features watching for checks in our process. The callbacks in - // these features may require permissions our remote caller does not have. - final long identity = Binder.clearCallingIdentity(); - try { - final int callbackCount = callbacks.size(); - for (int i = 0; i < callbackCount; i++) { - final NotedCallback callback = callbacks.valueAt(i); - try { - if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) { - continue; - } - callback.mCallback.opNoted(code, uid, packageName, attributionTag, flags, - result); - } catch (RemoteException e) { - /* do nothing */ - } - } - } finally { - Binder.restoreCallingIdentity(identity); - } - } - @Override public int permissionToOpCode(String permission) { if (permission == null) { @@ -3451,13 +1114,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch Binder.getCallingPid(), Binder.getCallingUid(), null); } - private boolean shouldIgnoreCallback(int op, int watcherPid, int watcherUid) { - // If it's a restricted read op, ignore it if watcher doesn't have manage ops permission, - // as watcher should not use this to signal if the value is changed. - return opRestrictsRead(op) && mContext.checkPermission(Manifest.permission.MANAGE_APPOPS, - watcherPid, watcherUid) != PackageManager.PERMISSION_GRANTED; - } - private void verifyIncomingOp(int op) { if (op >= 0 && op < AppOpsManager._NUM_OP) { // Enforce manage appops permission if it's a restricted read op. @@ -3498,35 +1154,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch || resolveUid(resolvedPackage) != Process.INVALID_UID; } - private boolean isCallerAndAttributionTrusted(@NonNull AttributionSource attributionSource) { - if (attributionSource.getUid() != Binder.getCallingUid() - && attributionSource.isTrusted(mContext)) { - return true; - } - return mContext.checkPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS, - Binder.getCallingPid(), Binder.getCallingUid(), null) - == PackageManager.PERMISSION_GRANTED; - } - - private @Nullable UidState getUidStateLocked(int uid, boolean edit) { - UidState uidState = mUidStates.get(uid); - if (uidState == null) { - if (!edit) { - return null; - } - uidState = new UidState(uid); - mUidStates.put(uid, uidState); - } - - return uidState; - } - - private void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible) { - synchronized (this) { - getUidStateTracker().updateAppWidgetVisibility(uidPackageNames, visible); - } - } - /** * @return {@link PackageManagerInternal} */ @@ -3538,764 +1165,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch return mPackageManagerInternal; } - /** - * Create a restriction description matching the properties of the package. - * - * @param pkg The package to create the restriction description for - * - * @return The restriction matching the package - */ - private RestrictionBypass getBypassforPackage(@NonNull AndroidPackage pkg) { - return new RestrictionBypass(pkg.getUid() == Process.SYSTEM_UID, pkg.isPrivileged(), - mContext.checkPermission(android.Manifest.permission - .EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS, -1, pkg.getUid()) - == PackageManager.PERMISSION_GRANTED); - } - - /** - * @see #verifyAndGetBypass(int, String, String, String) - */ - private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName, - @Nullable String attributionTag) { - return verifyAndGetBypass(uid, packageName, attributionTag, null); - } - - /** - * Verify that package belongs to uid and return the {@link RestrictionBypass bypass - * description} for the package, along with a boolean indicating whether the attribution tag is - * valid. - * - * @param uid The uid the package belongs to - * @param packageName The package the might belong to the uid - * @param attributionTag attribution tag or {@code null} if no need to verify - * @param proxyPackageName The proxy package, from which the attribution tag is to be pulled - * - * @return PackageVerificationResult containing {@link RestrictionBypass} and whether the - * attribution tag is valid - */ - private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName, - @Nullable String attributionTag, @Nullable String proxyPackageName) { - if (uid == Process.ROOT_UID) { - // For backwards compatibility, don't check package name for root UID. - return new PackageVerificationResult(null, - /* isAttributionTagValid */ true); - } - if (Process.isSdkSandboxUid(uid)) { - // SDK sandbox processes run in their own UID range, but their associated - // UID for checks should always be the UID of the package implementing SDK sandbox - // service. - // TODO: We will need to modify the callers of this function instead, so - // modifications and checks against the app ops state are done with the - // correct UID. - try { - final PackageManager pm = mContext.getPackageManager(); - final String supplementalPackageName = pm.getSdkSandboxPackageName(); - if (Objects.equals(packageName, supplementalPackageName)) { - uid = pm.getPackageUidAsUser(supplementalPackageName, - PackageManager.PackageInfoFlags.of(0), UserHandle.getUserId(uid)); - } - } catch (PackageManager.NameNotFoundException e) { - // Shouldn't happen for the supplemental package - e.printStackTrace(); - } - } - - - // Do not check if uid/packageName/attributionTag is already known. - synchronized (this) { - UidState uidState = mUidStates.get(uid); - if (uidState != null && uidState.pkgOps != null) { - Ops ops = uidState.pkgOps.get(packageName); - - if (ops != null && (attributionTag == null || ops.knownAttributionTags.contains( - attributionTag)) && ops.bypass != null) { - return new PackageVerificationResult(ops.bypass, - ops.validAttributionTags.contains(attributionTag)); - } - } - } - - int callingUid = Binder.getCallingUid(); - - // Allow any attribution tag for resolvable uids - int pkgUid; - if (Objects.equals(packageName, "com.android.shell")) { - // Special case for the shell which is a package but should be able - // to bypass app attribution tag restrictions. - pkgUid = Process.SHELL_UID; - } else { - pkgUid = resolveUid(packageName); - } - if (pkgUid != Process.INVALID_UID) { - if (pkgUid != UserHandle.getAppId(uid)) { - Slog.e(TAG, "Bad call made by uid " + callingUid + ". " - + "Package \"" + packageName + "\" does not belong to uid " + uid + "."); - String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not"; - throw new SecurityException("Specified package \"" + packageName + "\" under uid " - + UserHandle.getAppId(uid) + otherUidMessage); - } - return new PackageVerificationResult(RestrictionBypass.UNRESTRICTED, - /* isAttributionTagValid */ true); - } - - int userId = UserHandle.getUserId(uid); - RestrictionBypass bypass = null; - boolean isAttributionTagValid = false; - - final long ident = Binder.clearCallingIdentity(); - try { - PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class); - AndroidPackage pkg = pmInt.getPackage(packageName); - if (pkg != null) { - isAttributionTagValid = isAttributionInPackage(pkg, attributionTag); - pkgUid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.getUid())); - bypass = getBypassforPackage(pkg); - } - if (!isAttributionTagValid) { - AndroidPackage proxyPkg = proxyPackageName != null - ? pmInt.getPackage(proxyPackageName) : null; - // Re-check in proxy. - isAttributionTagValid = isAttributionInPackage(proxyPkg, attributionTag); - String msg; - if (pkg != null && isAttributionTagValid) { - msg = "attributionTag " + attributionTag + " declared in manifest of the proxy" - + " package " + proxyPackageName + ", this is not advised"; - } else if (pkg != null) { - msg = "attributionTag " + attributionTag + " not declared in manifest of " - + packageName; - } else { - msg = "package " + packageName + " not found, can't check for " - + "attributionTag " + attributionTag; - } - - try { - if (!mPlatformCompat.isChangeEnabledByPackageName( - SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE, packageName, - userId) || !mPlatformCompat.isChangeEnabledByUid( - SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE, - callingUid)) { - // Do not override tags if overriding is not enabled for this package - isAttributionTagValid = true; - } - Slog.e(TAG, msg); - } catch (RemoteException neverHappens) { - } - } - } finally { - Binder.restoreCallingIdentity(ident); - } - - if (pkgUid != uid) { - Slog.e(TAG, "Bad call made by uid " + callingUid + ". " - + "Package \"" + packageName + "\" does not belong to uid " + uid + "."); - String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not"; - throw new SecurityException("Specified package \"" + packageName + "\" under uid " + uid - + otherUidMessage); - } - - return new PackageVerificationResult(bypass, isAttributionTagValid); - } - - private boolean isAttributionInPackage(@Nullable AndroidPackage pkg, - @Nullable String attributionTag) { - if (pkg == null) { - return false; - } else if (attributionTag == null) { - return true; - } - if (pkg.getAttributions() != null) { - int numAttributions = pkg.getAttributions().size(); - for (int i = 0; i < numAttributions; i++) { - if (pkg.getAttributions().get(i).getTag().equals(attributionTag)) { - return true; - } - } - } - - return false; - } - - /** - * Get (and potentially create) ops. - * - * @param uid The uid the package belongs to - * @param packageName The name of the package - * @param attributionTag attribution tag - * @param isAttributionTagValid whether the given attribution tag is valid - * @param bypass When to bypass certain op restrictions (can be null if edit == false) - * @param edit If an ops does not exist, create the ops? - - * @return The ops - */ - private Ops getOpsLocked(int uid, String packageName, @Nullable String attributionTag, - boolean isAttributionTagValid, @Nullable RestrictionBypass bypass, boolean edit) { - UidState uidState = getUidStateLocked(uid, edit); - if (uidState == null) { - return null; - } - - if (uidState.pkgOps == null) { - if (!edit) { - return null; - } - uidState.pkgOps = new ArrayMap<>(); - } - - Ops ops = uidState.pkgOps.get(packageName); - if (ops == null) { - if (!edit) { - return null; - } - ops = new Ops(packageName, uidState); - uidState.pkgOps.put(packageName, ops); - } - - if (edit) { - if (bypass != null) { - ops.bypass = bypass; - } - - if (attributionTag != null) { - ops.knownAttributionTags.add(attributionTag); - if (isAttributionTagValid) { - ops.validAttributionTags.add(attributionTag); - } else { - ops.validAttributionTags.remove(attributionTag); - } - } - } - - return ops; - } - - @Override - public void scheduleWriteLocked() { - if (!mWriteScheduled) { - mWriteScheduled = true; - mHandler.postDelayed(mWriteRunner, WRITE_DELAY); - } - } - - @Override - public void scheduleFastWriteLocked() { - if (!mFastWriteScheduled) { - mWriteScheduled = true; - mFastWriteScheduled = true; - mHandler.removeCallbacks(mWriteRunner); - mHandler.postDelayed(mWriteRunner, 10*1000); - } - } - - /** - * Get the state of an op for a uid. - * - * @param code The code of the op - * @param uid The uid the of the package - * @param packageName The package name for which to get the state for - * @param attributionTag The attribution tag - * @param isAttributionTagValid Whether the given attribution tag is valid - * @param bypass When to bypass certain op restrictions (can be null if edit == false) - * @param edit Iff {@code true} create the {@link Op} object if not yet created - * - * @return The {@link Op state} of the op - */ - private @Nullable Op getOpLocked(int code, int uid, @NonNull String packageName, - @Nullable String attributionTag, boolean isAttributionTagValid, - @Nullable RestrictionBypass bypass, boolean edit) { - Ops ops = getOpsLocked(uid, packageName, attributionTag, isAttributionTagValid, bypass, - edit); - if (ops == null) { - return null; - } - return getOpLocked(ops, code, uid, edit); - } - - private Op getOpLocked(Ops ops, int code, int uid, boolean edit) { - Op op = ops.get(code); - if (op == null) { - if (!edit) { - return null; - } - op = new Op(ops.uidState, ops.packageName, code, uid); - ops.put(code, op); - } - if (edit) { - scheduleWriteLocked(); - } - return op; - } - - private boolean isOpRestrictedDueToSuspend(int code, String packageName, int uid) { - if (!ArrayUtils.contains(OPS_RESTRICTED_ON_SUSPEND, code)) { - return false; - } - final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class); - return pmi.isPackageSuspended(packageName, UserHandle.getUserId(uid)); - } - - private boolean isOpRestrictedLocked(int uid, int code, String packageName, - String attributionTag, @Nullable RestrictionBypass appBypass, boolean isCheckOp) { - int restrictionSetCount = mOpGlobalRestrictions.size(); - - for (int i = 0; i < restrictionSetCount; i++) { - ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.valueAt(i); - if (restrictionState.hasRestriction(code)) { - return true; - } - } - - int userHandle = UserHandle.getUserId(uid); - restrictionSetCount = mOpUserRestrictions.size(); - - for (int i = 0; i < restrictionSetCount; i++) { - // For each client, check that the given op is not restricted, or that the given - // package is exempt from the restriction. - ClientUserRestrictionState restrictionState = mOpUserRestrictions.valueAt(i); - if (restrictionState.hasRestriction(code, packageName, attributionTag, userHandle, - isCheckOp)) { - RestrictionBypass opBypass = opAllowSystemBypassRestriction(code); - if (opBypass != null) { - // If we are the system, bypass user restrictions for certain codes - synchronized (this) { - if (opBypass.isSystemUid && appBypass != null && appBypass.isSystemUid) { - return false; - } - if (opBypass.isPrivileged && appBypass != null && appBypass.isPrivileged) { - return false; - } - if (opBypass.isRecordAudioRestrictionExcept && appBypass != null - && appBypass.isRecordAudioRestrictionExcept) { - return false; - } - } - } - return true; - } - } - return false; - } - - void readState() { - int oldVersion = NO_VERSION; - synchronized (mFile) { - synchronized (this) { - FileInputStream stream; - try { - stream = mFile.openRead(); - } catch (FileNotFoundException e) { - Slog.i(TAG, "No existing app ops " + mFile.getBaseFile() + "; starting empty"); - return; - } - boolean success = false; - mUidStates.clear(); - mAppOpsServiceInterface.clearAllModes(); - try { - TypedXmlPullParser parser = Xml.resolvePullParser(stream); - int type; - while ((type = parser.next()) != XmlPullParser.START_TAG - && type != XmlPullParser.END_DOCUMENT) { - ; - } - - if (type != XmlPullParser.START_TAG) { - throw new IllegalStateException("no start tag found"); - } - - oldVersion = parser.getAttributeInt(null, "v", NO_VERSION); - - int outerDepth = parser.getDepth(); - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { - continue; - } - - String tagName = parser.getName(); - if (tagName.equals("pkg")) { - readPackage(parser); - } else if (tagName.equals("uid")) { - readUidOps(parser); - } else { - Slog.w(TAG, "Unknown element under <app-ops>: " - + parser.getName()); - XmlUtils.skipCurrentTag(parser); - } - } - success = true; - } catch (IllegalStateException e) { - Slog.w(TAG, "Failed parsing " + e); - } catch (NullPointerException e) { - Slog.w(TAG, "Failed parsing " + e); - } catch (NumberFormatException e) { - Slog.w(TAG, "Failed parsing " + e); - } catch (XmlPullParserException e) { - Slog.w(TAG, "Failed parsing " + e); - } catch (IOException e) { - Slog.w(TAG, "Failed parsing " + e); - } catch (IndexOutOfBoundsException e) { - Slog.w(TAG, "Failed parsing " + e); - } finally { - if (!success) { - mUidStates.clear(); - mAppOpsServiceInterface.clearAllModes(); - } - try { - stream.close(); - } catch (IOException e) { - } - } - } - } - synchronized (this) { - upgradeLocked(oldVersion); - } - } - - private void upgradeRunAnyInBackgroundLocked() { - for (int i = 0; i < mUidStates.size(); i++) { - final UidState uidState = mUidStates.valueAt(i); - if (uidState == null) { - continue; - } - SparseIntArray opModes = uidState.getNonDefaultUidModes(); - if (opModes != null) { - final int idx = opModes.indexOfKey(AppOpsManager.OP_RUN_IN_BACKGROUND); - if (idx >= 0) { - uidState.setUidMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, - opModes.valueAt(idx)); - } - } - if (uidState.pkgOps == null) { - continue; - } - boolean changed = false; - for (int j = 0; j < uidState.pkgOps.size(); j++) { - Ops ops = uidState.pkgOps.valueAt(j); - if (ops != null) { - final Op op = ops.get(AppOpsManager.OP_RUN_IN_BACKGROUND); - if (op != null && op.getMode() != AppOpsManager.opToDefaultMode(op.op)) { - final Op copy = new Op(op.uidState, op.packageName, - AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uidState.uid); - copy.setMode(op.getMode()); - ops.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, copy); - changed = true; - } - } - } - if (changed) { - uidState.evalForegroundOps(); - } - } - } - - private void upgradeLocked(int oldVersion) { - if (oldVersion >= CURRENT_VERSION) { - return; - } - Slog.d(TAG, "Upgrading app-ops xml from version " + oldVersion + " to " + CURRENT_VERSION); - switch (oldVersion) { - case NO_VERSION: - upgradeRunAnyInBackgroundLocked(); - // fall through - case 1: - // for future upgrades - } - scheduleFastWriteLocked(); - } - - private void readUidOps(TypedXmlPullParser parser) throws NumberFormatException, - XmlPullParserException, IOException { - final int uid = parser.getAttributeInt(null, "n"); - int outerDepth = parser.getDepth(); - int type; - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { - continue; - } - - String tagName = parser.getName(); - if (tagName.equals("op")) { - final int code = parser.getAttributeInt(null, "n"); - final int mode = parser.getAttributeInt(null, "m"); - setUidMode(code, uid, mode); - } else { - Slog.w(TAG, "Unknown element under <uid-ops>: " - + parser.getName()); - XmlUtils.skipCurrentTag(parser); - } - } - } - - private void readPackage(TypedXmlPullParser parser) - throws NumberFormatException, XmlPullParserException, IOException { - String pkgName = parser.getAttributeValue(null, "n"); - int outerDepth = parser.getDepth(); - int type; - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { - continue; - } - - String tagName = parser.getName(); - if (tagName.equals("uid")) { - readUid(parser, pkgName); - } else { - Slog.w(TAG, "Unknown element under <pkg>: " - + parser.getName()); - XmlUtils.skipCurrentTag(parser); - } - } - } - - private void readUid(TypedXmlPullParser parser, String pkgName) - throws NumberFormatException, XmlPullParserException, IOException { - int uid = parser.getAttributeInt(null, "n"); - final UidState uidState = getUidStateLocked(uid, true); - int outerDepth = parser.getDepth(); - int type; - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { - continue; - } - String tagName = parser.getName(); - if (tagName.equals("op")) { - readOp(parser, uidState, pkgName); - } else { - Slog.w(TAG, "Unknown element under <pkg>: " - + parser.getName()); - XmlUtils.skipCurrentTag(parser); - } - } - uidState.evalForegroundOps(); - } - - private void readAttributionOp(TypedXmlPullParser parser, @NonNull Op parent, - @Nullable String attribution) - throws NumberFormatException, IOException, XmlPullParserException { - final AttributedOp attributedOp = parent.getOrCreateAttribution(parent, attribution); - - final long key = parser.getAttributeLong(null, "n"); - final int uidState = extractUidStateFromKey(key); - final int opFlags = extractFlagsFromKey(key); - - final long accessTime = parser.getAttributeLong(null, "t", 0); - final long rejectTime = parser.getAttributeLong(null, "r", 0); - final long accessDuration = parser.getAttributeLong(null, "d", -1); - final String proxyPkg = XmlUtils.readStringAttribute(parser, "pp"); - final int proxyUid = parser.getAttributeInt(null, "pu", Process.INVALID_UID); - final String proxyAttributionTag = XmlUtils.readStringAttribute(parser, "pc"); - - if (accessTime > 0) { - attributedOp.accessed(accessTime, accessDuration, proxyUid, proxyPkg, - proxyAttributionTag, uidState, opFlags); - } - if (rejectTime > 0) { - attributedOp.rejected(rejectTime, uidState, opFlags); - } - } - - private void readOp(TypedXmlPullParser parser, - @NonNull UidState uidState, @NonNull String pkgName) - throws NumberFormatException, XmlPullParserException, IOException { - int opCode = parser.getAttributeInt(null, "n"); - Op op = new Op(uidState, pkgName, opCode, uidState.uid); - - final int mode = parser.getAttributeInt(null, "m", AppOpsManager.opToDefaultMode(op.op)); - op.setMode(mode); - - int outerDepth = parser.getDepth(); - int type; - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { - if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { - continue; - } - String tagName = parser.getName(); - if (tagName.equals("st")) { - readAttributionOp(parser, op, XmlUtils.readStringAttribute(parser, "id")); - } else { - Slog.w(TAG, "Unknown element under <op>: " - + parser.getName()); - XmlUtils.skipCurrentTag(parser); - } - } - - if (uidState.pkgOps == null) { - uidState.pkgOps = new ArrayMap<>(); - } - Ops ops = uidState.pkgOps.get(pkgName); - if (ops == null) { - ops = new Ops(pkgName, uidState); - uidState.pkgOps.put(pkgName, ops); - } - ops.put(op.op, op); - } - - void writeState() { - synchronized (mFile) { - FileOutputStream stream; - try { - stream = mFile.startWrite(); - } catch (IOException e) { - Slog.w(TAG, "Failed to write state: " + e); - return; - } - - List<AppOpsManager.PackageOps> allOps = getPackagesForOps(null); - - try { - TypedXmlSerializer out = Xml.resolveSerializer(stream); - out.startDocument(null, true); - out.startTag(null, "app-ops"); - out.attributeInt(null, "v", CURRENT_VERSION); - - SparseArray<SparseIntArray> uidStatesClone; - synchronized (this) { - uidStatesClone = new SparseArray<>(mUidStates.size()); - - final int uidStateCount = mUidStates.size(); - for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) { - UidState uidState = mUidStates.valueAt(uidStateNum); - int uid = mUidStates.keyAt(uidStateNum); - - SparseIntArray opModes = uidState.getNonDefaultUidModes(); - if (opModes != null && opModes.size() > 0) { - uidStatesClone.put(uid, opModes); - } - } - } - - final int uidStateCount = uidStatesClone.size(); - for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) { - SparseIntArray opModes = uidStatesClone.valueAt(uidStateNum); - if (opModes != null && opModes.size() > 0) { - out.startTag(null, "uid"); - out.attributeInt(null, "n", uidStatesClone.keyAt(uidStateNum)); - final int opCount = opModes.size(); - for (int opCountNum = 0; opCountNum < opCount; opCountNum++) { - final int op = opModes.keyAt(opCountNum); - final int mode = opModes.valueAt(opCountNum); - out.startTag(null, "op"); - out.attributeInt(null, "n", op); - out.attributeInt(null, "m", mode); - out.endTag(null, "op"); - } - out.endTag(null, "uid"); - } - } - - if (allOps != null) { - String lastPkg = null; - for (int i=0; i<allOps.size(); i++) { - AppOpsManager.PackageOps pkg = allOps.get(i); - if (!Objects.equals(pkg.getPackageName(), lastPkg)) { - if (lastPkg != null) { - out.endTag(null, "pkg"); - } - lastPkg = pkg.getPackageName(); - if (lastPkg != null) { - out.startTag(null, "pkg"); - out.attribute(null, "n", lastPkg); - } - } - out.startTag(null, "uid"); - out.attributeInt(null, "n", pkg.getUid()); - List<AppOpsManager.OpEntry> ops = pkg.getOps(); - for (int j=0; j<ops.size(); j++) { - AppOpsManager.OpEntry op = ops.get(j); - out.startTag(null, "op"); - out.attributeInt(null, "n", op.getOp()); - if (op.getMode() != AppOpsManager.opToDefaultMode(op.getOp())) { - out.attributeInt(null, "m", op.getMode()); - } - - for (String attributionTag : op.getAttributedOpEntries().keySet()) { - final AttributedOpEntry attribution = - op.getAttributedOpEntries().get(attributionTag); - - final ArraySet<Long> keys = attribution.collectKeys(); - - final int keyCount = keys.size(); - for (int k = 0; k < keyCount; k++) { - final long key = keys.valueAt(k); - - final int uidState = AppOpsManager.extractUidStateFromKey(key); - final int flags = AppOpsManager.extractFlagsFromKey(key); - - final long accessTime = attribution.getLastAccessTime(uidState, - uidState, flags); - final long rejectTime = attribution.getLastRejectTime(uidState, - uidState, flags); - final long accessDuration = attribution.getLastDuration( - uidState, uidState, flags); - // Proxy information for rejections is not backed up - final OpEventProxyInfo proxy = attribution.getLastProxyInfo( - uidState, uidState, flags); - - if (accessTime <= 0 && rejectTime <= 0 && accessDuration <= 0 - && proxy == null) { - continue; - } - - String proxyPkg = null; - String proxyAttributionTag = null; - int proxyUid = Process.INVALID_UID; - if (proxy != null) { - proxyPkg = proxy.getPackageName(); - proxyAttributionTag = proxy.getAttributionTag(); - proxyUid = proxy.getUid(); - } - - out.startTag(null, "st"); - if (attributionTag != null) { - out.attribute(null, "id", attributionTag); - } - out.attributeLong(null, "n", key); - if (accessTime > 0) { - out.attributeLong(null, "t", accessTime); - } - if (rejectTime > 0) { - out.attributeLong(null, "r", rejectTime); - } - if (accessDuration > 0) { - out.attributeLong(null, "d", accessDuration); - } - if (proxyPkg != null) { - out.attribute(null, "pp", proxyPkg); - } - if (proxyAttributionTag != null) { - out.attribute(null, "pc", proxyAttributionTag); - } - if (proxyUid >= 0) { - out.attributeInt(null, "pu", proxyUid); - } - out.endTag(null, "st"); - } - } - - out.endTag(null, "op"); - } - out.endTag(null, "uid"); - } - if (lastPkg != null) { - out.endTag(null, "pkg"); - } - } - - out.endTag(null, "app-ops"); - out.endDocument(); - mFile.finishWrite(stream); - } catch (IOException e) { - Slog.w(TAG, "Failed to write state, restoring backup.", e); - mFile.failWrite(stream); - } - } - mHistoricalRegistry.writeAndClearDiscreteHistory(); - } - static class Shell extends ShellCommand { final IAppOpsService mInterface; final AppOpsService mInternal; @@ -4309,7 +1178,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch int mode; int packageUid; int nonpackageUid; - final static Binder sBinder = new Binder(); IBinder mToken; boolean targetsUid; @@ -4330,7 +1198,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch dumpCommandHelp(pw); } - static private int strOpToOp(String op, PrintWriter err) { + static int strOpToOp(String op, PrintWriter err) { try { return AppOpsManager.strOpToOp(op); } catch (IllegalArgumentException e) { @@ -4527,6 +1395,24 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch pw.println(" not specified, the current user is assumed."); } + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + mAppOpsService.dump(fd, pw, args); + + pw.println(); + if (mCheckOpsDelegateDispatcher.mPolicy != null + && mCheckOpsDelegateDispatcher.mPolicy instanceof AppOpsPolicy) { + AppOpsPolicy policy = (AppOpsPolicy) mCheckOpsDelegateDispatcher.mPolicy; + policy.dumpTags(pw); + } else { + pw.println(" AppOps policy not set."); + } + + if (mAudioRestrictionManager.hasActiveRestrictions()) { + pw.println(); + mAudioRestrictionManager.dump(pw); + } + } static int onShellCommand(Shell shell, String cmd) { if (cmd == null) { return shell.handleDefaultCommands(cmd); @@ -4730,14 +1616,12 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch return 0; } case "write-settings": { - shell.mInternal.enforceManageAppOpsModes(Binder.getCallingPid(), + shell.mInternal.mAppOpsService + .enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), -1); final long token = Binder.clearCallingIdentity(); try { - synchronized (shell.mInternal) { - shell.mInternal.mHandler.removeCallbacks(shell.mInternal.mWriteRunner); - } - shell.mInternal.writeState(); + shell.mInternal.mAppOpsService.writeState(); pw.println("Current settings written."); } finally { Binder.restoreCallingIdentity(token); @@ -4745,11 +1629,12 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch return 0; } case "read-settings": { - shell.mInternal.enforceManageAppOpsModes(Binder.getCallingPid(), - Binder.getCallingUid(), -1); + shell.mInternal.mAppOpsService + .enforceManageAppOpsModes(Binder.getCallingPid(), + Binder.getCallingUid(), -1); final long token = Binder.clearCallingIdentity(); try { - shell.mInternal.readState(); + shell.mInternal.mAppOpsService.readState(); pw.println("Last settings read."); } finally { Binder.restoreCallingIdentity(token); @@ -4795,877 +1680,70 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch return -1; } - private void dumpHelp(PrintWriter pw) { - pw.println("AppOps service (appops) dump options:"); - pw.println(" -h"); - pw.println(" Print this help text."); - pw.println(" --op [OP]"); - pw.println(" Limit output to data associated with the given app op code."); - pw.println(" --mode [MODE]"); - pw.println(" Limit output to data associated with the given app op mode."); - pw.println(" --package [PACKAGE]"); - pw.println(" Limit output to data associated with the given package name."); - pw.println(" --attributionTag [attributionTag]"); - pw.println(" Limit output to data associated with the given attribution tag."); - pw.println(" --include-discrete [n]"); - pw.println(" Include discrete ops limited to n per dimension. Use zero for no limit."); - pw.println(" --watchers"); - pw.println(" Only output the watcher sections."); - pw.println(" --history"); - pw.println(" Only output history."); - pw.println(" --uid-state-changes"); - pw.println(" Include logs about uid state changes."); - } - - private void dumpStatesLocked(@NonNull PrintWriter pw, @Nullable String filterAttributionTag, - @HistoricalOpsRequestFilter int filter, long nowElapsed, @NonNull Op op, long now, - @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix) { - final int numAttributions = op.mAttributions.size(); - for (int i = 0; i < numAttributions; i++) { - if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0 && !Objects.equals( - op.mAttributions.keyAt(i), filterAttributionTag)) { - continue; - } - - pw.print(prefix + op.mAttributions.keyAt(i) + "=[\n"); - dumpStatesLocked(pw, nowElapsed, op, op.mAttributions.keyAt(i), now, sdf, date, - prefix + " "); - pw.print(prefix + "]\n"); - } - } - - private void dumpStatesLocked(@NonNull PrintWriter pw, long nowElapsed, @NonNull Op op, - @Nullable String attributionTag, long now, @NonNull SimpleDateFormat sdf, - @NonNull Date date, @NonNull String prefix) { - - final AttributedOpEntry entry = op.createSingleAttributionEntryLocked( - attributionTag).getAttributedOpEntries().get(attributionTag); - - final ArraySet<Long> keys = entry.collectKeys(); - - final int keyCount = keys.size(); - for (int k = 0; k < keyCount; k++) { - final long key = keys.valueAt(k); - - final int uidState = AppOpsManager.extractUidStateFromKey(key); - final int flags = AppOpsManager.extractFlagsFromKey(key); - - final long accessTime = entry.getLastAccessTime(uidState, uidState, flags); - final long rejectTime = entry.getLastRejectTime(uidState, uidState, flags); - final long accessDuration = entry.getLastDuration(uidState, uidState, flags); - final OpEventProxyInfo proxy = entry.getLastProxyInfo(uidState, uidState, flags); - - String proxyPkg = null; - String proxyAttributionTag = null; - int proxyUid = Process.INVALID_UID; - if (proxy != null) { - proxyPkg = proxy.getPackageName(); - proxyAttributionTag = proxy.getAttributionTag(); - proxyUid = proxy.getUid(); - } - - if (accessTime > 0) { - pw.print(prefix); - pw.print("Access: "); - pw.print(AppOpsManager.keyToString(key)); - pw.print(" "); - date.setTime(accessTime); - pw.print(sdf.format(date)); - pw.print(" ("); - TimeUtils.formatDuration(accessTime - now, pw); - pw.print(")"); - if (accessDuration > 0) { - pw.print(" duration="); - TimeUtils.formatDuration(accessDuration, pw); - } - if (proxyUid >= 0) { - pw.print(" proxy["); - pw.print("uid="); - pw.print(proxyUid); - pw.print(", pkg="); - pw.print(proxyPkg); - pw.print(", attributionTag="); - pw.print(proxyAttributionTag); - pw.print("]"); - } - pw.println(); - } - - if (rejectTime > 0) { - pw.print(prefix); - pw.print("Reject: "); - pw.print(AppOpsManager.keyToString(key)); - date.setTime(rejectTime); - pw.print(sdf.format(date)); - pw.print(" ("); - TimeUtils.formatDuration(rejectTime - now, pw); - pw.print(")"); - if (proxyUid >= 0) { - pw.print(" proxy["); - pw.print("uid="); - pw.print(proxyUid); - pw.print(", pkg="); - pw.print(proxyPkg); - pw.print(", attributionTag="); - pw.print(proxyAttributionTag); - pw.print("]"); - } - pw.println(); - } - } - - final AttributedOp attributedOp = op.mAttributions.get(attributionTag); - if (attributedOp.isRunning()) { - long earliestElapsedTime = Long.MAX_VALUE; - long maxNumStarts = 0; - int numInProgressEvents = attributedOp.mInProgressEvents.size(); - for (int i = 0; i < numInProgressEvents; i++) { - AttributedOp.InProgressStartOpEvent event = - attributedOp.mInProgressEvents.valueAt(i); - - earliestElapsedTime = Math.min(earliestElapsedTime, event.getStartElapsedTime()); - maxNumStarts = Math.max(maxNumStarts, event.mNumUnfinishedStarts); - } - - pw.print(prefix + "Running start at: "); - TimeUtils.formatDuration(nowElapsed - earliestElapsedTime, pw); - pw.println(); - - if (maxNumStarts > 1) { - pw.print(prefix + "startNesting="); - pw.println(maxNumStarts); - } - } - } - - @NeverCompile // Avoid size overhead of debugging code. - @Override - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return; - - int dumpOp = OP_NONE; - String dumpPackage = null; - String dumpAttributionTag = null; - int dumpUid = Process.INVALID_UID; - int dumpMode = -1; - boolean dumpWatchers = false; - // TODO ntmyren: Remove the dumpHistory and dumpFilter - boolean dumpHistory = false; - boolean includeDiscreteOps = false; - boolean dumpUidStateChangeLogs = false; - int nDiscreteOps = 10; - @HistoricalOpsRequestFilter int dumpFilter = 0; - boolean dumpAll = false; - - if (args != null) { - for (int i = 0; i < args.length; i++) { - String arg = args[i]; - if ("-h".equals(arg)) { - dumpHelp(pw); - return; - } else if ("-a".equals(arg)) { - // dump all data - dumpAll = true; - } else if ("--op".equals(arg)) { - i++; - if (i >= args.length) { - pw.println("No argument for --op option"); - return; - } - dumpOp = Shell.strOpToOp(args[i], pw); - dumpFilter |= FILTER_BY_OP_NAMES; - if (dumpOp < 0) { - return; - } - } else if ("--package".equals(arg)) { - i++; - if (i >= args.length) { - pw.println("No argument for --package option"); - return; - } - dumpPackage = args[i]; - dumpFilter |= FILTER_BY_PACKAGE_NAME; - try { - dumpUid = AppGlobals.getPackageManager().getPackageUid(dumpPackage, - PackageManager.MATCH_KNOWN_PACKAGES | PackageManager.MATCH_INSTANT, - 0); - } catch (RemoteException e) { - } - if (dumpUid < 0) { - pw.println("Unknown package: " + dumpPackage); - return; - } - dumpUid = UserHandle.getAppId(dumpUid); - dumpFilter |= FILTER_BY_UID; - } else if ("--attributionTag".equals(arg)) { - i++; - if (i >= args.length) { - pw.println("No argument for --attributionTag option"); - return; - } - dumpAttributionTag = args[i]; - dumpFilter |= FILTER_BY_ATTRIBUTION_TAG; - } else if ("--mode".equals(arg)) { - i++; - if (i >= args.length) { - pw.println("No argument for --mode option"); - return; - } - dumpMode = Shell.strModeToMode(args[i], pw); - if (dumpMode < 0) { - return; - } - } else if ("--watchers".equals(arg)) { - dumpWatchers = true; - } else if ("--include-discrete".equals(arg)) { - i++; - if (i >= args.length) { - pw.println("No argument for --include-discrete option"); - return; - } - try { - nDiscreteOps = Integer.valueOf(args[i]); - } catch (NumberFormatException e) { - pw.println("Wrong parameter: " + args[i]); - return; - } - includeDiscreteOps = true; - } else if ("--history".equals(arg)) { - dumpHistory = true; - } else if (arg.length() > 0 && arg.charAt(0) == '-') { - pw.println("Unknown option: " + arg); - return; - } else if ("--uid-state-changes".equals(arg)) { - dumpUidStateChangeLogs = true; - } else { - pw.println("Unknown command: " + arg); - return; - } - } - } - - final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); - final Date date = new Date(); - synchronized (this) { - pw.println("Current AppOps Service state:"); - if (!dumpHistory && !dumpWatchers) { - mConstants.dump(pw); - } - pw.println(); - final long now = System.currentTimeMillis(); - final long nowElapsed = SystemClock.elapsedRealtime(); - final long nowUptime = SystemClock.uptimeMillis(); - boolean needSep = false; - if (dumpFilter == 0 && dumpMode < 0 && mProfileOwners != null && !dumpWatchers - && !dumpHistory) { - pw.println(" Profile owners:"); - for (int poi = 0; poi < mProfileOwners.size(); poi++) { - pw.print(" User #"); - pw.print(mProfileOwners.keyAt(poi)); - pw.print(": "); - UserHandle.formatUid(pw, mProfileOwners.valueAt(poi)); - pw.println(); - } - pw.println(); - } - - if (!dumpHistory) { - needSep |= mAppOpsServiceInterface.dumpListeners(dumpOp, dumpUid, dumpPackage, pw); - } - - if (mModeWatchers.size() > 0 && dumpOp < 0 && !dumpHistory) { - boolean printedHeader = false; - for (int i = 0; i < mModeWatchers.size(); i++) { - final ModeCallback cb = mModeWatchers.valueAt(i); - if (dumpPackage != null - && dumpUid != UserHandle.getAppId(cb.getWatchingUid())) { - continue; - } - needSep = true; - if (!printedHeader) { - pw.println(" All op mode watchers:"); - printedHeader = true; - } - pw.print(" "); - pw.print(Integer.toHexString(System.identityHashCode(mModeWatchers.keyAt(i)))); - pw.print(": "); pw.println(cb); - } - } - if (mActiveWatchers.size() > 0 && dumpMode < 0) { - needSep = true; - boolean printedHeader = false; - for (int watcherNum = 0; watcherNum < mActiveWatchers.size(); watcherNum++) { - final SparseArray<ActiveCallback> activeWatchers = - mActiveWatchers.valueAt(watcherNum); - if (activeWatchers.size() <= 0) { - continue; - } - final ActiveCallback cb = activeWatchers.valueAt(0); - if (dumpOp >= 0 && activeWatchers.indexOfKey(dumpOp) < 0) { - continue; - } - if (dumpPackage != null - && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) { - continue; - } - if (!printedHeader) { - pw.println(" All op active watchers:"); - printedHeader = true; - } - pw.print(" "); - pw.print(Integer.toHexString(System.identityHashCode( - mActiveWatchers.keyAt(watcherNum)))); - pw.println(" ->"); - pw.print(" ["); - final int opCount = activeWatchers.size(); - for (int opNum = 0; opNum < opCount; opNum++) { - if (opNum > 0) { - pw.print(' '); - } - pw.print(AppOpsManager.opToName(activeWatchers.keyAt(opNum))); - if (opNum < opCount - 1) { - pw.print(','); - } - } - pw.println("]"); - pw.print(" "); - pw.println(cb); - } - } - if (mStartedWatchers.size() > 0 && dumpMode < 0) { - needSep = true; - boolean printedHeader = false; - - final int watchersSize = mStartedWatchers.size(); - for (int watcherNum = 0; watcherNum < watchersSize; watcherNum++) { - final SparseArray<StartedCallback> startedWatchers = - mStartedWatchers.valueAt(watcherNum); - if (startedWatchers.size() <= 0) { - continue; - } - - final StartedCallback cb = startedWatchers.valueAt(0); - if (dumpOp >= 0 && startedWatchers.indexOfKey(dumpOp) < 0) { - continue; - } - - if (dumpPackage != null - && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) { - continue; - } - - if (!printedHeader) { - pw.println(" All op started watchers:"); - printedHeader = true; - } - - pw.print(" "); - pw.print(Integer.toHexString(System.identityHashCode( - mStartedWatchers.keyAt(watcherNum)))); - pw.println(" ->"); - - pw.print(" ["); - final int opCount = startedWatchers.size(); - for (int opNum = 0; opNum < opCount; opNum++) { - if (opNum > 0) { - pw.print(' '); - } - - pw.print(AppOpsManager.opToName(startedWatchers.keyAt(opNum))); - if (opNum < opCount - 1) { - pw.print(','); - } - } - pw.println("]"); - - pw.print(" "); - pw.println(cb); - } - } - if (mNotedWatchers.size() > 0 && dumpMode < 0) { - needSep = true; - boolean printedHeader = false; - for (int watcherNum = 0; watcherNum < mNotedWatchers.size(); watcherNum++) { - final SparseArray<NotedCallback> notedWatchers = - mNotedWatchers.valueAt(watcherNum); - if (notedWatchers.size() <= 0) { - continue; - } - final NotedCallback cb = notedWatchers.valueAt(0); - if (dumpOp >= 0 && notedWatchers.indexOfKey(dumpOp) < 0) { - continue; - } - if (dumpPackage != null - && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) { - continue; - } - if (!printedHeader) { - pw.println(" All op noted watchers:"); - printedHeader = true; - } - pw.print(" "); - pw.print(Integer.toHexString(System.identityHashCode( - mNotedWatchers.keyAt(watcherNum)))); - pw.println(" ->"); - pw.print(" ["); - final int opCount = notedWatchers.size(); - for (int opNum = 0; opNum < opCount; opNum++) { - if (opNum > 0) { - pw.print(' '); - } - pw.print(AppOpsManager.opToName(notedWatchers.keyAt(opNum))); - if (opNum < opCount - 1) { - pw.print(','); - } - } - pw.println("]"); - pw.print(" "); - pw.println(cb); - } - } - if (mAudioRestrictionManager.hasActiveRestrictions() && dumpOp < 0 - && dumpPackage != null && dumpMode < 0 && !dumpWatchers) { - needSep = mAudioRestrictionManager.dump(pw) || needSep; - } - if (needSep) { - pw.println(); - } - for (int i=0; i<mUidStates.size(); i++) { - UidState uidState = mUidStates.valueAt(i); - final SparseIntArray opModes = uidState.getNonDefaultUidModes(); - final ArrayMap<String, Ops> pkgOps = uidState.pkgOps; - - if (dumpWatchers || dumpHistory) { - continue; - } - if (dumpOp >= 0 || dumpPackage != null || dumpMode >= 0) { - boolean hasOp = dumpOp < 0 || (opModes != null - && opModes.indexOfKey(dumpOp) >= 0); - boolean hasPackage = dumpPackage == null || dumpUid == mUidStates.keyAt(i); - boolean hasMode = dumpMode < 0; - if (!hasMode && opModes != null) { - for (int opi = 0; !hasMode && opi < opModes.size(); opi++) { - if (opModes.valueAt(opi) == dumpMode) { - hasMode = true; - } - } - } - if (pkgOps != null) { - for (int pkgi = 0; - (!hasOp || !hasPackage || !hasMode) && pkgi < pkgOps.size(); - pkgi++) { - Ops ops = pkgOps.valueAt(pkgi); - if (!hasOp && ops != null && ops.indexOfKey(dumpOp) >= 0) { - hasOp = true; - } - if (!hasMode) { - for (int opi = 0; !hasMode && opi < ops.size(); opi++) { - if (ops.valueAt(opi).getMode() == dumpMode) { - hasMode = true; - } - } - } - if (!hasPackage && dumpPackage.equals(ops.packageName)) { - hasPackage = true; - } - } - } - if (uidState.foregroundOps != null && !hasOp) { - if (uidState.foregroundOps.indexOfKey(dumpOp) > 0) { - hasOp = true; - } - } - if (!hasOp || !hasPackage || !hasMode) { - continue; - } - } - - pw.print(" Uid "); UserHandle.formatUid(pw, uidState.uid); pw.println(":"); - uidState.dump(pw, nowElapsed); - if (uidState.foregroundOps != null && (dumpMode < 0 - || dumpMode == AppOpsManager.MODE_FOREGROUND)) { - pw.println(" foregroundOps:"); - for (int j = 0; j < uidState.foregroundOps.size(); j++) { - if (dumpOp >= 0 && dumpOp != uidState.foregroundOps.keyAt(j)) { - continue; - } - pw.print(" "); - pw.print(AppOpsManager.opToName(uidState.foregroundOps.keyAt(j))); - pw.print(": "); - pw.println(uidState.foregroundOps.valueAt(j) ? "WATCHER" : "SILENT"); - } - pw.print(" hasForegroundWatchers="); - pw.println(uidState.hasForegroundWatchers); - } - needSep = true; - - if (opModes != null) { - final int opModeCount = opModes.size(); - for (int j = 0; j < opModeCount; j++) { - final int code = opModes.keyAt(j); - final int mode = opModes.valueAt(j); - if (dumpOp >= 0 && dumpOp != code) { - continue; - } - if (dumpMode >= 0 && dumpMode != mode) { - continue; - } - pw.print(" "); pw.print(AppOpsManager.opToName(code)); - pw.print(": mode="); pw.println(AppOpsManager.modeToName(mode)); - } - } - - if (pkgOps == null) { - continue; - } - - for (int pkgi = 0; pkgi < pkgOps.size(); pkgi++) { - final Ops ops = pkgOps.valueAt(pkgi); - if (dumpPackage != null && !dumpPackage.equals(ops.packageName)) { - continue; - } - boolean printedPackage = false; - for (int j=0; j<ops.size(); j++) { - final Op op = ops.valueAt(j); - final int opCode = op.op; - if (dumpOp >= 0 && dumpOp != opCode) { - continue; - } - if (dumpMode >= 0 && dumpMode != op.getMode()) { - continue; - } - if (!printedPackage) { - pw.print(" Package "); pw.print(ops.packageName); pw.println(":"); - printedPackage = true; - } - pw.print(" "); pw.print(AppOpsManager.opToName(opCode)); - pw.print(" ("); pw.print(AppOpsManager.modeToName(op.getMode())); - final int switchOp = AppOpsManager.opToSwitch(opCode); - if (switchOp != opCode) { - pw.print(" / switch "); - pw.print(AppOpsManager.opToName(switchOp)); - final Op switchObj = ops.get(switchOp); - int mode = switchObj == null - ? AppOpsManager.opToDefaultMode(switchOp) : switchObj.getMode(); - pw.print("="); pw.print(AppOpsManager.modeToName(mode)); - } - pw.println("): "); - dumpStatesLocked(pw, dumpAttributionTag, dumpFilter, nowElapsed, op, now, - sdf, date, " "); - } - } - } - if (needSep) { - pw.println(); - } - - boolean showUserRestrictions = !(dumpMode < 0 && !dumpWatchers && !dumpHistory); - mAppOpsRestrictions.dumpRestrictions(pw, dumpOp, dumpPackage, showUserRestrictions); - - if (!dumpHistory && !dumpWatchers) { - pw.println(); - if (mCheckOpsDelegateDispatcher.mPolicy != null - && mCheckOpsDelegateDispatcher.mPolicy instanceof AppOpsPolicy) { - AppOpsPolicy policy = (AppOpsPolicy) mCheckOpsDelegateDispatcher.mPolicy; - policy.dumpTags(pw); - } else { - pw.println(" AppOps policy not set."); - } - } - - if (dumpAll || dumpUidStateChangeLogs) { - pw.println(); - pw.println("Uid State Changes Event Log:"); - getUidStateTracker().dumpEvents(pw); - } - } - - // Must not hold the appops lock - if (dumpHistory && !dumpWatchers) { - mHistoricalRegistry.dump(" ", pw, dumpUid, dumpPackage, dumpAttributionTag, dumpOp, - dumpFilter); - } - if (includeDiscreteOps) { - pw.println("Discrete accesses: "); - mHistoricalRegistry.dumpDiscreteData(pw, dumpUid, dumpPackage, dumpAttributionTag, - dumpFilter, dumpOp, sdf, date, " ", nDiscreteOps); - } - } - @Override public void setUserRestrictions(Bundle restrictions, IBinder token, int userHandle) { - checkSystemUid("setUserRestrictions"); - Objects.requireNonNull(restrictions); - Objects.requireNonNull(token); - for (int i = 0; i < AppOpsManager._NUM_OP; i++) { - String restriction = AppOpsManager.opToRestriction(i); - if (restriction != null) { - setUserRestrictionNoCheck(i, restrictions.getBoolean(restriction, false), token, - userHandle, null); - } - } + mAppOpsService.setUserRestrictions(restrictions, token, userHandle); } @Override public void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle, PackageTagsList excludedPackageTags) { - if (Binder.getCallingPid() != Process.myPid()) { - mContext.enforcePermission(Manifest.permission.MANAGE_APP_OPS_RESTRICTIONS, - Binder.getCallingPid(), Binder.getCallingUid(), null); - } - if (userHandle != UserHandle.getCallingUserId()) { - if (mContext.checkCallingOrSelfPermission(Manifest.permission - .INTERACT_ACROSS_USERS_FULL) != PackageManager.PERMISSION_GRANTED - && mContext.checkCallingOrSelfPermission(Manifest.permission - .INTERACT_ACROSS_USERS) != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Need INTERACT_ACROSS_USERS_FULL or" - + " INTERACT_ACROSS_USERS to interact cross user "); - } - } - verifyIncomingOp(code); - Objects.requireNonNull(token); - setUserRestrictionNoCheck(code, restricted, token, userHandle, excludedPackageTags); - } - - private void setUserRestrictionNoCheck(int code, boolean restricted, IBinder token, - int userHandle, PackageTagsList excludedPackageTags) { - synchronized (AppOpsService.this) { - ClientUserRestrictionState restrictionState = mOpUserRestrictions.get(token); - - if (restrictionState == null) { - try { - restrictionState = new ClientUserRestrictionState(token); - } catch (RemoteException e) { - return; - } - mOpUserRestrictions.put(token, restrictionState); - } - - if (restrictionState.setRestriction(code, restricted, excludedPackageTags, - userHandle)) { - mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsService::notifyWatchersOfChange, this, code, UID_ANY)); - mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsService::updateStartedOpModeForUser, this, code, restricted, - userHandle)); - } - - if (restrictionState.isDefault()) { - mOpUserRestrictions.remove(token); - restrictionState.destroy(); - } - } - } - - private void updateStartedOpModeForUser(int code, boolean restricted, int userId) { - synchronized (AppOpsService.this) { - int numUids = mUidStates.size(); - for (int uidNum = 0; uidNum < numUids; uidNum++) { - int uid = mUidStates.keyAt(uidNum); - if (userId != UserHandle.USER_ALL && UserHandle.getUserId(uid) != userId) { - continue; - } - updateStartedOpModeForUidLocked(code, restricted, uid); - } - } - } - - private void updateStartedOpModeForUidLocked(int code, boolean restricted, int uid) { - UidState uidState = mUidStates.get(uid); - if (uidState == null || uidState.pkgOps == null) { - return; - } - - int numPkgOps = uidState.pkgOps.size(); - for (int pkgNum = 0; pkgNum < numPkgOps; pkgNum++) { - Ops ops = uidState.pkgOps.valueAt(pkgNum); - Op op = ops != null ? ops.get(code) : null; - if (op == null || (op.getMode() != MODE_ALLOWED && op.getMode() != MODE_FOREGROUND)) { - continue; - } - int numAttrTags = op.mAttributions.size(); - for (int attrNum = 0; attrNum < numAttrTags; attrNum++) { - AttributedOp attrOp = op.mAttributions.valueAt(attrNum); - if (restricted && attrOp.isRunning()) { - attrOp.pause(); - } else if (attrOp.isPaused()) { - attrOp.resume(); - } - } - } - } - - private void notifyWatchersOfChange(int code, int uid) { - final ArraySet<OnOpModeChangedListener> modeChangedListenerSet; - synchronized (this) { - modeChangedListenerSet = mAppOpsServiceInterface.getOpModeChangedListeners(code); - if (modeChangedListenerSet == null) { - return; - } - } - - notifyOpChanged(modeChangedListenerSet, code, uid, null); + mAppOpsService.setUserRestriction(code, restricted, token, userHandle, + excludedPackageTags); } @Override public void removeUser(int userHandle) throws RemoteException { - checkSystemUid("removeUser"); - synchronized (AppOpsService.this) { - final int tokenCount = mOpUserRestrictions.size(); - for (int i = tokenCount - 1; i >= 0; i--) { - ClientUserRestrictionState opRestrictions = mOpUserRestrictions.valueAt(i); - opRestrictions.removeUser(userHandle); - } - removeUidsForUserLocked(userHandle); - } + mAppOpsService.removeUser(userHandle); } @Override public boolean isOperationActive(int code, int uid, String packageName) { - if (Binder.getCallingUid() != uid) { - if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS) - != PackageManager.PERMISSION_GRANTED) { - return false; - } - } - verifyIncomingOp(code); - if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) { - return false; - } - - final String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName); - if (resolvedPackageName == null) { - return false; - } - // TODO moltmann: Allow to check for attribution op activeness - synchronized (AppOpsService.this) { - Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null, false); - if (pkgOps == null) { - return false; - } - - Op op = pkgOps.get(code); - if (op == null) { - return false; - } - - return op.isRunning(); - } + return mAppOpsService.isOperationActive(code, uid, packageName); } @Override public boolean isProxying(int op, @NonNull String proxyPackageName, @NonNull String proxyAttributionTag, int proxiedUid, @NonNull String proxiedPackageName) { - Objects.requireNonNull(proxyPackageName); - Objects.requireNonNull(proxiedPackageName); - final long callingUid = Binder.getCallingUid(); - final long identity = Binder.clearCallingIdentity(); - try { - final List<AppOpsManager.PackageOps> packageOps = getOpsForPackage(proxiedUid, - proxiedPackageName, new int[] {op}); - if (packageOps == null || packageOps.isEmpty()) { - return false; - } - final List<OpEntry> opEntries = packageOps.get(0).getOps(); - if (opEntries.isEmpty()) { - return false; - } - final OpEntry opEntry = opEntries.get(0); - if (!opEntry.isRunning()) { - return false; - } - final OpEventProxyInfo proxyInfo = opEntry.getLastProxyInfo( - OP_FLAG_TRUSTED_PROXIED | AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED); - return proxyInfo != null && callingUid == proxyInfo.getUid() - && proxyPackageName.equals(proxyInfo.getPackageName()) - && Objects.equals(proxyAttributionTag, proxyInfo.getAttributionTag()); - } finally { - Binder.restoreCallingIdentity(identity); - } + return mAppOpsService.isProxying(op, proxyPackageName, proxyAttributionTag, + proxiedUid, proxiedPackageName); } @Override public void resetPackageOpsNoHistory(@NonNull String packageName) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS, - "resetPackageOpsNoHistory"); - synchronized (AppOpsService.this) { - final int uid = mPackageManagerInternal.getPackageUid(packageName, 0, - UserHandle.getCallingUserId()); - if (uid == Process.INVALID_UID) { - return; - } - UidState uidState = mUidStates.get(uid); - if (uidState == null || uidState.pkgOps == null) { - return; - } - Ops removedOps = uidState.pkgOps.remove(packageName); - mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid)); - if (removedOps != null) { - scheduleFastWriteLocked(); - } - } + mAppOpsService.resetPackageOpsNoHistory(packageName); } @Override public void setHistoryParameters(@AppOpsManager.HistoricalMode int mode, long baseSnapshotInterval, int compressionStep) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS, - "setHistoryParameters"); - // Must not hold the appops lock - mHistoricalRegistry.setHistoryParameters(mode, baseSnapshotInterval, compressionStep); + mAppOpsService.setHistoryParameters(mode, baseSnapshotInterval, compressionStep); } @Override public void offsetHistory(long offsetMillis) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS, - "offsetHistory"); - // Must not hold the appops lock - mHistoricalRegistry.offsetHistory(offsetMillis); - mHistoricalRegistry.offsetDiscreteHistory(offsetMillis); + mAppOpsService.offsetHistory(offsetMillis); } @Override public void addHistoricalOps(HistoricalOps ops) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS, - "addHistoricalOps"); - // Must not hold the appops lock - mHistoricalRegistry.addHistoricalOps(ops); + mAppOpsService.addHistoricalOps(ops); } @Override public void resetHistoryParameters() { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS, - "resetHistoryParameters"); - // Must not hold the appops lock - mHistoricalRegistry.resetHistoryParameters(); + mAppOpsService.resetHistoryParameters(); } @Override public void clearHistory() { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS, - "clearHistory"); - // Must not hold the appops lock - mHistoricalRegistry.clearAllHistory(); + mAppOpsService.clearHistory(); } @Override public void rebootHistory(long offlineDurationMillis) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS, - "rebootHistory"); - - Preconditions.checkArgument(offlineDurationMillis >= 0); - - // Must not hold the appops lock - mHistoricalRegistry.shutdown(); - - if (offlineDurationMillis > 0) { - SystemClock.sleep(offlineDurationMillis); - } - - mHistoricalRegistry = new HistoricalRegistry(mHistoricalRegistry); - mHistoricalRegistry.systemReady(mContext.getContentResolver()); - mHistoricalRegistry.persistPendingHistory(); + mAppOpsService.rebootHistory(offlineDurationMillis); } /** @@ -5920,24 +1998,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch return false; } - @GuardedBy("this") - private void removeUidsForUserLocked(int userHandle) { - for (int i = mUidStates.size() - 1; i >= 0; --i) { - final int uid = mUidStates.keyAt(i); - if (UserHandle.getUserId(uid) == userHandle) { - mUidStates.valueAt(i).clear(); - mUidStates.removeAt(i); - } - } - } - - private void checkSystemUid(String function) { - int uid = Binder.getCallingUid(); - if (uid != Process.SYSTEM_UID) { - throw new SecurityException(function + " must by called by the system"); - } - } - private static int resolveUid(String packageName) { if (packageName == null) { return Process.INVALID_UID; @@ -5958,184 +2018,43 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch return Process.INVALID_UID; } - private static String[] getPackagesForUid(int uid) { - String[] packageNames = null; - - // Very early during boot the package manager is not yet or not yet fully started. At this - // time there are no packages yet. - if (AppGlobals.getPackageManager() != null) { - try { - packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid); - } catch (RemoteException e) { - /* ignore - local call */ - } - } - if (packageNames == null) { - return EmptyArray.STRING; - } - return packageNames; - } - - private final class ClientUserRestrictionState implements DeathRecipient { - private final IBinder token; - - ClientUserRestrictionState(IBinder token) - throws RemoteException { - token.linkToDeath(this, 0); - this.token = token; - } - - public boolean setRestriction(int code, boolean restricted, - PackageTagsList excludedPackageTags, int userId) { - return mAppOpsRestrictions.setUserRestriction(token, userId, code, - restricted, excludedPackageTags); - } - - public boolean hasRestriction(int code, String packageName, String attributionTag, - int userId, boolean isCheckOp) { - return mAppOpsRestrictions.getUserRestriction(token, userId, code, packageName, - attributionTag, isCheckOp); - } - - public void removeUser(int userId) { - mAppOpsRestrictions.clearUserRestrictions(token, userId); - } - - public boolean isDefault() { - return !mAppOpsRestrictions.hasUserRestrictions(token); - } - - @Override - public void binderDied() { - synchronized (AppOpsService.this) { - mAppOpsRestrictions.clearUserRestrictions(token); - mOpUserRestrictions.remove(token); - destroy(); - } - } - - public void destroy() { - token.unlinkToDeath(this, 0); - } - } - - private final class ClientGlobalRestrictionState implements DeathRecipient { - final IBinder mToken; - - ClientGlobalRestrictionState(IBinder token) - throws RemoteException { - token.linkToDeath(this, 0); - this.mToken = token; - } - - boolean setRestriction(int code, boolean restricted) { - return mAppOpsRestrictions.setGlobalRestriction(mToken, code, restricted); - } - - boolean hasRestriction(int code) { - return mAppOpsRestrictions.getGlobalRestriction(mToken, code); - } - - boolean isDefault() { - return !mAppOpsRestrictions.hasGlobalRestrictions(mToken); - } - - @Override - public void binderDied() { - mAppOpsRestrictions.clearGlobalRestrictions(mToken); - mOpGlobalRestrictions.remove(mToken); - destroy(); - } - - void destroy() { - mToken.unlinkToDeath(this, 0); - } - } - private final class AppOpsManagerInternalImpl extends AppOpsManagerInternal { @Override public void setDeviceAndProfileOwners(SparseIntArray owners) { - synchronized (AppOpsService.this) { - mProfileOwners = owners; - } + AppOpsService.this.mAppOpsService.setDeviceAndProfileOwners(owners); } @Override public void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible) { - AppOpsService.this.updateAppWidgetVisibility(uidPackageNames, visible); + AppOpsService.this.mAppOpsService + .updateAppWidgetVisibility(uidPackageNames, visible); } @Override public void setUidModeFromPermissionPolicy(int code, int uid, int mode, @Nullable IAppOpsCallback callback) { - setUidMode(code, uid, mode, callback); + AppOpsService.this.mAppOpsService.setUidMode(code, uid, mode, callback); } @Override public void setModeFromPermissionPolicy(int code, int uid, @NonNull String packageName, int mode, @Nullable IAppOpsCallback callback) { - setMode(code, uid, packageName, mode, callback); + AppOpsService.this.mAppOpsService + .setMode(code, uid, packageName, mode, callback); } @Override public void setGlobalRestriction(int code, boolean restricted, IBinder token) { - if (Binder.getCallingPid() != Process.myPid()) { - // TODO instead of this enforcement put in AppOpsManagerInternal - throw new SecurityException("Only the system can set global restrictions"); - } - - synchronized (AppOpsService.this) { - ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.get(token); - - if (restrictionState == null) { - try { - restrictionState = new ClientGlobalRestrictionState(token); - } catch (RemoteException e) { - return; - } - mOpGlobalRestrictions.put(token, restrictionState); - } - - if (restrictionState.setRestriction(code, restricted)) { - mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsService::notifyWatchersOfChange, AppOpsService.this, code, - UID_ANY)); - mHandler.sendMessage(PooledLambda.obtainMessage( - AppOpsService::updateStartedOpModeForUser, AppOpsService.this, - code, restricted, UserHandle.USER_ALL)); - } - - if (restrictionState.isDefault()) { - mOpGlobalRestrictions.remove(token); - restrictionState.destroy(); - } - } + AppOpsService.this.mAppOpsService + .setGlobalRestriction(code, restricted, token); } @Override public int getOpRestrictionCount(int code, UserHandle user, String pkg, String attributionTag) { - int number = 0; - synchronized (AppOpsService.this) { - int numRestrictions = mOpUserRestrictions.size(); - for (int i = 0; i < numRestrictions; i++) { - if (mOpUserRestrictions.valueAt(i) - .hasRestriction(code, pkg, attributionTag, user.getIdentifier(), - false)) { - number++; - } - } - - numRestrictions = mOpGlobalRestrictions.size(); - for (int i = 0; i < numRestrictions; i++) { - if (mOpGlobalRestrictions.valueAt(i).hasRestriction(code)) { - number++; - } - } - } - - return number; + return AppOpsService.this.mAppOpsService + .getOpRestrictionCount(code, user, pkg, attributionTag); } } @@ -6431,7 +2350,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch attributionFlags, attributionChainId, AppOpsService.this::startOperationImpl); } - public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code, + public SyncNotedAppOp startProxyOperation(IBinder clientId, int code, @NonNull AttributionSource attributionSource, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags, @@ -6461,7 +2380,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch proxyAttributionFlags, proxiedAttributionFlags, attributionChainId); } - private SyncNotedAppOp startDelegateProxyOperationImpl(@NonNull IBinder clientId, int code, + private SyncNotedAppOp startDelegateProxyOperationImpl(IBinder clientId, int code, @NonNull AttributionSource attributionSource, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags, @@ -6495,7 +2414,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch AppOpsService.this::finishOperationImpl); } - public void finishProxyOperation(@NonNull IBinder clientId, int code, + public void finishProxyOperation(IBinder clientId, int code, @NonNull AttributionSource attributionSource, boolean skipProxyOperation) { if (mPolicy != null) { if (mCheckOpsDelegate != null) { @@ -6513,7 +2432,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch } } - private Void finishDelegateProxyOperationImpl(@NonNull IBinder clientId, int code, + private Void finishDelegateProxyOperationImpl(IBinder clientId, int code, @NonNull AttributionSource attributionSource, boolean skipProxyOperation) { mCheckOpsDelegate.finishProxyOperation(clientId, code, attributionSource, skipProxyOperation, AppOpsService.this::finishProxyOperationImpl); diff --git a/services/core/java/com/android/server/appop/AppOpsServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsServiceImpl.java new file mode 100644 index 000000000000..70f3bcc64ef0 --- /dev/null +++ b/services/core/java/com/android/server/appop/AppOpsServiceImpl.java @@ -0,0 +1,4679 @@ +/* + * Copyright (C) 2012 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.appop; + +import static android.app.AppOpsManager.AttributedOpEntry; +import static android.app.AppOpsManager.AttributionFlags; +import static android.app.AppOpsManager.CALL_BACK_ON_SWITCHED_OP; +import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG; +import static android.app.AppOpsManager.FILTER_BY_OP_NAMES; +import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME; +import static android.app.AppOpsManager.FILTER_BY_UID; +import static android.app.AppOpsManager.HISTORY_FLAG_GET_ATTRIBUTION_CHAINS; +import static android.app.AppOpsManager.HistoricalOps; +import static android.app.AppOpsManager.HistoricalOpsRequestFilter; +import static android.app.AppOpsManager.KEY_BG_STATE_SETTLE_TIME; +import static android.app.AppOpsManager.KEY_FG_SERVICE_STATE_SETTLE_TIME; +import static android.app.AppOpsManager.KEY_TOP_STATE_SETTLE_TIME; +import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.app.AppOpsManager.MODE_DEFAULT; +import static android.app.AppOpsManager.MODE_ERRORED; +import static android.app.AppOpsManager.MODE_FOREGROUND; +import static android.app.AppOpsManager.MODE_IGNORED; +import static android.app.AppOpsManager.Mode; +import static android.app.AppOpsManager.OP_CAMERA; +import static android.app.AppOpsManager.OP_FLAGS_ALL; +import static android.app.AppOpsManager.OP_FLAG_SELF; +import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED; +import static android.app.AppOpsManager.OP_NONE; +import static android.app.AppOpsManager.OP_PLAY_AUDIO; +import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO; +import static android.app.AppOpsManager.OP_RECORD_AUDIO; +import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD; +import static android.app.AppOpsManager.OP_VIBRATE; +import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED; +import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED; +import static android.app.AppOpsManager.OpEntry; +import static android.app.AppOpsManager.OpEventProxyInfo; +import static android.app.AppOpsManager.OpFlags; +import static android.app.AppOpsManager.RestrictionBypass; +import static android.app.AppOpsManager.SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE; +import static android.app.AppOpsManager._NUM_OP; +import static android.app.AppOpsManager.extractFlagsFromKey; +import static android.app.AppOpsManager.extractUidStateFromKey; +import static android.app.AppOpsManager.modeToName; +import static android.app.AppOpsManager.opAllowSystemBypassRestriction; +import static android.app.AppOpsManager.opRestrictsRead; +import static android.app.AppOpsManager.opToName; +import static android.content.Intent.ACTION_PACKAGE_REMOVED; +import static android.content.Intent.EXTRA_REPLACING; + +import static com.android.server.appop.AppOpsServiceImpl.ModeCallback.ALL_OPS; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.app.ActivityManager; +import android.app.ActivityManagerInternal; +import android.app.AppGlobals; +import android.app.AppOpsManager; +import android.app.admin.DevicePolicyManagerInternal; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; +import android.content.pm.PermissionInfo; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Binder; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerExecutor; +import android.os.IBinder; +import android.os.IBinder.DeathRecipient; +import android.os.PackageTagsList; +import android.os.Process; +import android.os.RemoteCallback; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.os.UserHandle; +import android.os.storage.StorageManagerInternal; +import android.permission.PermissionManager; +import android.provider.Settings; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.AtomicFile; +import android.util.KeyValueListParser; +import android.util.Slog; +import android.util.SparseArray; +import android.util.SparseBooleanArray; +import android.util.SparseIntArray; +import android.util.TimeUtils; +import android.util.Xml; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.app.IAppOpsActiveCallback; +import com.android.internal.app.IAppOpsCallback; +import com.android.internal.app.IAppOpsNotedCallback; +import com.android.internal.app.IAppOpsStartedCallback; +import com.android.internal.compat.IPlatformCompat; +import com.android.internal.os.Clock; +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.DumpUtils; +import com.android.internal.util.Preconditions; +import com.android.internal.util.XmlUtils; +import com.android.internal.util.function.pooled.PooledLambda; +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; +import com.android.server.LocalServices; +import com.android.server.LockGuard; +import com.android.server.SystemServerInitThreadPool; +import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.component.ParsedAttribution; + +import dalvik.annotation.optimization.NeverCompile; + +import libcore.util.EmptyArray; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +class AppOpsServiceImpl implements AppOpsServiceInterface { + static final String TAG = "AppOps"; + static final boolean DEBUG = false; + + private static final int NO_VERSION = -1; + /** + * Increment by one every time and add the corresponding upgrade logic in + * {@link #upgradeLocked(int)} below. The first version was 1 + */ + private static final int CURRENT_VERSION = 1; + + // Write at most every 30 minutes. + static final long WRITE_DELAY = DEBUG ? 1000 : 30 * 60 * 1000; + + // Constant meaning that any UID should be matched when dispatching callbacks + private static final int UID_ANY = -2; + + private static final int[] OPS_RESTRICTED_ON_SUSPEND = { + OP_PLAY_AUDIO, + OP_RECORD_AUDIO, + OP_CAMERA, + OP_VIBRATE, + }; + private static final int MAX_UNUSED_POOLED_OBJECTS = 3; + + final Context mContext; + final AtomicFile mFile; + final Handler mHandler; + + /** + * Pool for {@link AttributedOp.OpEventProxyInfoPool} to avoid to constantly reallocate new + * objects + */ + @GuardedBy("this") + final AttributedOp.OpEventProxyInfoPool mOpEventProxyInfoPool = + new AttributedOp.OpEventProxyInfoPool(MAX_UNUSED_POOLED_OBJECTS); + + /** + * Pool for {@link AttributedOp.InProgressStartOpEventPool} to avoid to constantly reallocate + * new objects + */ + @GuardedBy("this") + final AttributedOp.InProgressStartOpEventPool mInProgressStartOpEventPool = + new AttributedOp.InProgressStartOpEventPool(mOpEventProxyInfoPool, + MAX_UNUSED_POOLED_OBJECTS); + @Nullable + private final DevicePolicyManagerInternal dpmi = + LocalServices.getService(DevicePolicyManagerInternal.class); + + private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface( + ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); + + boolean mWriteScheduled; + boolean mFastWriteScheduled; + final Runnable mWriteRunner = new Runnable() { + public void run() { + synchronized (AppOpsServiceImpl.this) { + mWriteScheduled = false; + mFastWriteScheduled = false; + AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... params) { + writeState(); + return null; + } + }; + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null); + } + } + }; + + @GuardedBy("this") + @VisibleForTesting + final SparseArray<UidState> mUidStates = new SparseArray<>(); + + volatile @NonNull HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this); + + /* + * These are app op restrictions imposed per user from various parties. + */ + private final ArrayMap<IBinder, ClientUserRestrictionState> mOpUserRestrictions = + new ArrayMap<>(); + + /* + * These are app op restrictions imposed globally from various parties within the system. + */ + private final ArrayMap<IBinder, ClientGlobalRestrictionState> mOpGlobalRestrictions = + new ArrayMap<>(); + + SparseIntArray mProfileOwners; + + /** + * Reverse lookup for {@link AppOpsManager#opToSwitch(int)}. Initialized once and never + * changed + */ + private final SparseArray<int[]> mSwitchedOps = new SparseArray<>(); + + /** + * Package Manager internal. Access via {@link #getPackageManagerInternal()} + */ + private @Nullable PackageManagerInternal mPackageManagerInternal; + + /** + * Interface for app-op modes. + */ + @VisibleForTesting + AppOpsCheckingServiceInterface mAppOpsServiceInterface; + + /** + * Interface for app-op restrictions. + */ + @VisibleForTesting + AppOpsRestrictions mAppOpsRestrictions; + + private AppOpsUidStateTracker mUidStateTracker; + + /** + * Hands the definition of foreground and uid states + */ + @GuardedBy("this") + public AppOpsUidStateTracker getUidStateTracker() { + if (mUidStateTracker == null) { + mUidStateTracker = new AppOpsUidStateTrackerImpl( + LocalServices.getService(ActivityManagerInternal.class), + mHandler, + r -> { + synchronized (AppOpsServiceImpl.this) { + r.run(); + } + }, + Clock.SYSTEM_CLOCK, mConstants); + + mUidStateTracker.addUidStateChangedCallback(new HandlerExecutor(mHandler), + this::onUidStateChanged); + } + return mUidStateTracker; + } + + /** + * All times are in milliseconds. These constants are kept synchronized with the system + * global Settings. Any access to this class or its fields should be done while + * holding the AppOpsService lock. + */ + final class Constants extends ContentObserver { + + /** + * How long we want for a drop in uid state from top to settle before applying it. + * + * @see Settings.Global#APP_OPS_CONSTANTS + * @see AppOpsManager#KEY_TOP_STATE_SETTLE_TIME + */ + public long TOP_STATE_SETTLE_TIME; + + /** + * How long we want for a drop in uid state from foreground to settle before applying it. + * + * @see Settings.Global#APP_OPS_CONSTANTS + * @see AppOpsManager#KEY_FG_SERVICE_STATE_SETTLE_TIME + */ + public long FG_SERVICE_STATE_SETTLE_TIME; + + /** + * How long we want for a drop in uid state from background to settle before applying it. + * + * @see Settings.Global#APP_OPS_CONSTANTS + * @see AppOpsManager#KEY_BG_STATE_SETTLE_TIME + */ + public long BG_STATE_SETTLE_TIME; + + private final KeyValueListParser mParser = new KeyValueListParser(','); + private ContentResolver mResolver; + + Constants(Handler handler) { + super(handler); + updateConstants(); + } + + public void startMonitoring(ContentResolver resolver) { + mResolver = resolver; + mResolver.registerContentObserver( + Settings.Global.getUriFor(Settings.Global.APP_OPS_CONSTANTS), + false, this); + updateConstants(); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + updateConstants(); + } + + private void updateConstants() { + String value = mResolver != null ? Settings.Global.getString(mResolver, + Settings.Global.APP_OPS_CONSTANTS) : ""; + + synchronized (AppOpsServiceImpl.this) { + try { + mParser.setString(value); + } catch (IllegalArgumentException e) { + // Failed to parse the settings string, log this and move on + // with defaults. + Slog.e(TAG, "Bad app ops settings", e); + } + TOP_STATE_SETTLE_TIME = mParser.getDurationMillis( + KEY_TOP_STATE_SETTLE_TIME, 5 * 1000L); + FG_SERVICE_STATE_SETTLE_TIME = mParser.getDurationMillis( + KEY_FG_SERVICE_STATE_SETTLE_TIME, 5 * 1000L); + BG_STATE_SETTLE_TIME = mParser.getDurationMillis( + KEY_BG_STATE_SETTLE_TIME, 1 * 1000L); + } + } + + void dump(PrintWriter pw) { + pw.println(" Settings:"); + + pw.print(" "); + pw.print(KEY_TOP_STATE_SETTLE_TIME); + pw.print("="); + TimeUtils.formatDuration(TOP_STATE_SETTLE_TIME, pw); + pw.println(); + pw.print(" "); + pw.print(KEY_FG_SERVICE_STATE_SETTLE_TIME); + pw.print("="); + TimeUtils.formatDuration(FG_SERVICE_STATE_SETTLE_TIME, pw); + pw.println(); + pw.print(" "); + pw.print(KEY_BG_STATE_SETTLE_TIME); + pw.print("="); + TimeUtils.formatDuration(BG_STATE_SETTLE_TIME, pw); + pw.println(); + } + } + + @VisibleForTesting + final Constants mConstants; + + @VisibleForTesting + final class UidState { + public final int uid; + + public ArrayMap<String, Ops> pkgOps; + + // true indicates there is an interested observer, false there isn't but it has such an op + //TODO: Move foregroundOps and hasForegroundWatchers into the AppOpsServiceInterface. + public SparseBooleanArray foregroundOps; + public boolean hasForegroundWatchers; + + public UidState(int uid) { + this.uid = uid; + } + + public void clear() { + mAppOpsServiceInterface.removeUid(uid); + if (pkgOps != null) { + for (String packageName : pkgOps.keySet()) { + mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid)); + } + } + pkgOps = null; + } + + public boolean isDefault() { + boolean areAllPackageModesDefault = true; + if (pkgOps != null) { + for (String packageName : pkgOps.keySet()) { + if (!mAppOpsServiceInterface.arePackageModesDefault(packageName, + UserHandle.getUserId(uid))) { + areAllPackageModesDefault = false; + break; + } + } + } + return (pkgOps == null || pkgOps.isEmpty()) + && mAppOpsServiceInterface.areUidModesDefault(uid) + && areAllPackageModesDefault; + } + + // Functions for uid mode access and manipulation. + public SparseIntArray getNonDefaultUidModes() { + return mAppOpsServiceInterface.getNonDefaultUidModes(uid); + } + + public int getUidMode(int op) { + return mAppOpsServiceInterface.getUidMode(uid, op); + } + + public boolean setUidMode(int op, int mode) { + return mAppOpsServiceInterface.setUidMode(uid, op, mode); + } + + @SuppressWarnings("GuardedBy") + int evalMode(int op, int mode) { + return getUidStateTracker().evalMode(uid, op, mode); + } + + public void evalForegroundOps() { + foregroundOps = null; + foregroundOps = mAppOpsServiceInterface.evalForegroundUidOps(uid, foregroundOps); + if (pkgOps != null) { + for (int i = pkgOps.size() - 1; i >= 0; i--) { + foregroundOps = mAppOpsServiceInterface + .evalForegroundPackageOps(pkgOps.valueAt(i).packageName, + foregroundOps, + UserHandle.getUserId(uid)); + } + } + hasForegroundWatchers = false; + if (foregroundOps != null) { + for (int i = 0; i < foregroundOps.size(); i++) { + if (foregroundOps.valueAt(i)) { + hasForegroundWatchers = true; + break; + } + } + } + } + + @SuppressWarnings("GuardedBy") + public int getState() { + return getUidStateTracker().getUidState(uid); + } + + @SuppressWarnings("GuardedBy") + public void dump(PrintWriter pw, long nowElapsed) { + getUidStateTracker().dumpUidState(pw, uid, nowElapsed); + } + } + + static final class Ops extends SparseArray<Op> { + final String packageName; + final UidState uidState; + + /** + * The restriction properties of the package. If {@code null} it could not have been read + * yet and has to be refreshed. + */ + @Nullable RestrictionBypass bypass; + + /** Lazily populated cache of attributionTags of this package */ + final @NonNull ArraySet<String> knownAttributionTags = new ArraySet<>(); + + /** + * Lazily populated cache of <b>valid</b> attributionTags of this package, a set smaller + * than or equal to {@link #knownAttributionTags}. + */ + final @NonNull ArraySet<String> validAttributionTags = new ArraySet<>(); + + Ops(String _packageName, UidState _uidState) { + packageName = _packageName; + uidState = _uidState; + } + } + + /** Returned from {@link #verifyAndGetBypass(int, String, String, String)}. */ + private static final class PackageVerificationResult { + + final RestrictionBypass bypass; + final boolean isAttributionTagValid; + + PackageVerificationResult(RestrictionBypass bypass, boolean isAttributionTagValid) { + this.bypass = bypass; + this.isAttributionTagValid = isAttributionTagValid; + } + } + + final class Op { + int op; + int uid; + final UidState uidState; + final @NonNull String packageName; + + /** attributionTag -> AttributedOp */ + final ArrayMap<String, AttributedOp> mAttributions = new ArrayMap<>(1); + + Op(UidState uidState, String packageName, int op, int uid) { + this.op = op; + this.uid = uid; + this.uidState = uidState; + this.packageName = packageName; + } + + @Mode int getMode() { + return mAppOpsServiceInterface.getPackageMode(packageName, this.op, + UserHandle.getUserId(this.uid)); + } + + void setMode(@Mode int mode) { + mAppOpsServiceInterface.setPackageMode(packageName, this.op, mode, + UserHandle.getUserId(this.uid)); + } + + void removeAttributionsWithNoTime() { + for (int i = mAttributions.size() - 1; i >= 0; i--) { + if (!mAttributions.valueAt(i).hasAnyTime()) { + mAttributions.removeAt(i); + } + } + } + + private @NonNull AttributedOp getOrCreateAttribution(@NonNull Op parent, + @Nullable String attributionTag) { + AttributedOp attributedOp; + + attributedOp = mAttributions.get(attributionTag); + if (attributedOp == null) { + attributedOp = new AttributedOp(AppOpsServiceImpl.this, attributionTag, + parent); + mAttributions.put(attributionTag, attributedOp); + } + + return attributedOp; + } + + @NonNull + OpEntry createEntryLocked() { + final int numAttributions = mAttributions.size(); + + final ArrayMap<String, AppOpsManager.AttributedOpEntry> attributionEntries = + new ArrayMap<>(numAttributions); + for (int i = 0; i < numAttributions; i++) { + attributionEntries.put(mAttributions.keyAt(i), + mAttributions.valueAt(i).createAttributedOpEntryLocked()); + } + + return new OpEntry(op, getMode(), attributionEntries); + } + + @NonNull + OpEntry createSingleAttributionEntryLocked(@Nullable String attributionTag) { + final int numAttributions = mAttributions.size(); + + final ArrayMap<String, AttributedOpEntry> attributionEntries = new ArrayMap<>(1); + for (int i = 0; i < numAttributions; i++) { + if (Objects.equals(mAttributions.keyAt(i), attributionTag)) { + attributionEntries.put(mAttributions.keyAt(i), + mAttributions.valueAt(i).createAttributedOpEntryLocked()); + break; + } + } + + return new OpEntry(op, getMode(), attributionEntries); + } + + boolean isRunning() { + final int numAttributions = mAttributions.size(); + for (int i = 0; i < numAttributions; i++) { + if (mAttributions.valueAt(i).isRunning()) { + return true; + } + } + + return false; + } + } + + final ArrayMap<IBinder, ModeCallback> mModeWatchers = new ArrayMap<>(); + final ArrayMap<IBinder, SparseArray<ActiveCallback>> mActiveWatchers = new ArrayMap<>(); + final ArrayMap<IBinder, SparseArray<StartedCallback>> mStartedWatchers = new ArrayMap<>(); + final ArrayMap<IBinder, SparseArray<NotedCallback>> mNotedWatchers = new ArrayMap<>(); + + final class ModeCallback extends OnOpModeChangedListener implements DeathRecipient { + /** If mWatchedOpCode==ALL_OPS notify for ops affected by the switch-op */ + public static final int ALL_OPS = -2; + + // Need to keep this only because stopWatchingMode needs an IAppOpsCallback. + // Otherwise we can just use the IBinder object. + private final IAppOpsCallback mCallback; + + ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int watchedOpCode, + int callingUid, int callingPid) { + super(watchingUid, flags, watchedOpCode, callingUid, callingPid); + this.mCallback = callback; + try { + mCallback.asBinder().linkToDeath(this, 0); + } catch (RemoteException e) { + /*ignored*/ + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(128); + sb.append("ModeCallback{"); + sb.append(Integer.toHexString(System.identityHashCode(this))); + sb.append(" watchinguid="); + UserHandle.formatUid(sb, getWatchingUid()); + sb.append(" flags=0x"); + sb.append(Integer.toHexString(getFlags())); + switch (getWatchedOpCode()) { + case OP_NONE: + break; + case ALL_OPS: + sb.append(" op=(all)"); + break; + default: + sb.append(" op="); + sb.append(opToName(getWatchedOpCode())); + break; + } + sb.append(" from uid="); + UserHandle.formatUid(sb, getCallingUid()); + sb.append(" pid="); + sb.append(getCallingPid()); + sb.append('}'); + return sb.toString(); + } + + void unlinkToDeath() { + mCallback.asBinder().unlinkToDeath(this, 0); + } + + @Override + public void binderDied() { + stopWatchingMode(mCallback); + } + + @Override + public void onOpModeChanged(int op, int uid, String packageName) throws RemoteException { + mCallback.opChanged(op, uid, packageName); + } + } + + final class ActiveCallback implements DeathRecipient { + final IAppOpsActiveCallback mCallback; + final int mWatchingUid; + final int mCallingUid; + final int mCallingPid; + + ActiveCallback(IAppOpsActiveCallback callback, int watchingUid, int callingUid, + int callingPid) { + mCallback = callback; + mWatchingUid = watchingUid; + mCallingUid = callingUid; + mCallingPid = callingPid; + try { + mCallback.asBinder().linkToDeath(this, 0); + } catch (RemoteException e) { + /*ignored*/ + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(128); + sb.append("ActiveCallback{"); + sb.append(Integer.toHexString(System.identityHashCode(this))); + sb.append(" watchinguid="); + UserHandle.formatUid(sb, mWatchingUid); + sb.append(" from uid="); + UserHandle.formatUid(sb, mCallingUid); + sb.append(" pid="); + sb.append(mCallingPid); + sb.append('}'); + return sb.toString(); + } + + void destroy() { + mCallback.asBinder().unlinkToDeath(this, 0); + } + + @Override + public void binderDied() { + stopWatchingActive(mCallback); + } + } + + final class StartedCallback implements DeathRecipient { + final IAppOpsStartedCallback mCallback; + final int mWatchingUid; + final int mCallingUid; + final int mCallingPid; + + StartedCallback(IAppOpsStartedCallback callback, int watchingUid, int callingUid, + int callingPid) { + mCallback = callback; + mWatchingUid = watchingUid; + mCallingUid = callingUid; + mCallingPid = callingPid; + try { + mCallback.asBinder().linkToDeath(this, 0); + } catch (RemoteException e) { + /*ignored*/ + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(128); + sb.append("StartedCallback{"); + sb.append(Integer.toHexString(System.identityHashCode(this))); + sb.append(" watchinguid="); + UserHandle.formatUid(sb, mWatchingUid); + sb.append(" from uid="); + UserHandle.formatUid(sb, mCallingUid); + sb.append(" pid="); + sb.append(mCallingPid); + sb.append('}'); + return sb.toString(); + } + + void destroy() { + mCallback.asBinder().unlinkToDeath(this, 0); + } + + @Override + public void binderDied() { + stopWatchingStarted(mCallback); + } + } + + final class NotedCallback implements DeathRecipient { + final IAppOpsNotedCallback mCallback; + final int mWatchingUid; + final int mCallingUid; + final int mCallingPid; + + NotedCallback(IAppOpsNotedCallback callback, int watchingUid, int callingUid, + int callingPid) { + mCallback = callback; + mWatchingUid = watchingUid; + mCallingUid = callingUid; + mCallingPid = callingPid; + try { + mCallback.asBinder().linkToDeath(this, 0); + } catch (RemoteException e) { + /*ignored*/ + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(128); + sb.append("NotedCallback{"); + sb.append(Integer.toHexString(System.identityHashCode(this))); + sb.append(" watchinguid="); + UserHandle.formatUid(sb, mWatchingUid); + sb.append(" from uid="); + UserHandle.formatUid(sb, mCallingUid); + sb.append(" pid="); + sb.append(mCallingPid); + sb.append('}'); + return sb.toString(); + } + + void destroy() { + mCallback.asBinder().unlinkToDeath(this, 0); + } + + @Override + public void binderDied() { + stopWatchingNoted(mCallback); + } + } + + /** + * Call {@link AttributedOp#onClientDeath attributedOp.onClientDeath(clientId)}. + */ + static void onClientDeath(@NonNull AttributedOp attributedOp, + @NonNull IBinder clientId) { + attributedOp.onClientDeath(clientId); + } + + AppOpsServiceImpl(File storagePath, Handler handler, Context context) { + mContext = context; + + for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) { + int switchCode = AppOpsManager.opToSwitch(switchedCode); + mSwitchedOps.put(switchCode, + ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode)); + } + mAppOpsServiceInterface = + new AppOpsCheckingServiceImpl(this, this, handler, context, mSwitchedOps); + mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler, + mAppOpsServiceInterface); + + LockGuard.installLock(this, LockGuard.INDEX_APP_OPS); + mFile = new AtomicFile(storagePath, "appops"); + + mHandler = handler; + mConstants = new Constants(mHandler); + readState(); + } + + /** + * Handler for work when packages are removed or updated + */ + private BroadcastReceiver mOnPackageUpdatedReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + String pkgName = intent.getData().getEncodedSchemeSpecificPart(); + int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID); + + if (action.equals(ACTION_PACKAGE_REMOVED) && !intent.hasExtra(EXTRA_REPLACING)) { + synchronized (AppOpsServiceImpl.this) { + UidState uidState = mUidStates.get(uid); + if (uidState == null || uidState.pkgOps == null) { + return; + } + mAppOpsServiceInterface.removePackage(pkgName, UserHandle.getUserId(uid)); + Ops removedOps = uidState.pkgOps.remove(pkgName); + if (removedOps != null) { + scheduleFastWriteLocked(); + } + } + } else if (action.equals(Intent.ACTION_PACKAGE_REPLACED)) { + AndroidPackage pkg = getPackageManagerInternal().getPackage(pkgName); + if (pkg == null) { + return; + } + + ArrayMap<String, String> dstAttributionTags = new ArrayMap<>(); + ArraySet<String> attributionTags = new ArraySet<>(); + attributionTags.add(null); + if (pkg.getAttributions() != null) { + int numAttributions = pkg.getAttributions().size(); + for (int attributionNum = 0; attributionNum < numAttributions; + attributionNum++) { + ParsedAttribution attribution = pkg.getAttributions().get(attributionNum); + attributionTags.add(attribution.getTag()); + + int numInheritFrom = attribution.getInheritFrom().size(); + for (int inheritFromNum = 0; inheritFromNum < numInheritFrom; + inheritFromNum++) { + dstAttributionTags.put(attribution.getInheritFrom().get(inheritFromNum), + attribution.getTag()); + } + } + } + + synchronized (AppOpsServiceImpl.this) { + UidState uidState = mUidStates.get(uid); + if (uidState == null || uidState.pkgOps == null) { + return; + } + + Ops ops = uidState.pkgOps.get(pkgName); + if (ops == null) { + return; + } + + // Reset cached package properties to re-initialize when needed + ops.bypass = null; + ops.knownAttributionTags.clear(); + + // Merge data collected for removed attributions into their successor + // attributions + int numOps = ops.size(); + for (int opNum = 0; opNum < numOps; opNum++) { + Op op = ops.valueAt(opNum); + + int numAttributions = op.mAttributions.size(); + for (int attributionNum = numAttributions - 1; attributionNum >= 0; + attributionNum--) { + String attributionTag = op.mAttributions.keyAt(attributionNum); + + if (attributionTags.contains(attributionTag)) { + // attribution still exist after upgrade + continue; + } + + String newAttributionTag = dstAttributionTags.get(attributionTag); + + AttributedOp newAttributedOp = op.getOrCreateAttribution(op, + newAttributionTag); + newAttributedOp.add(op.mAttributions.valueAt(attributionNum)); + op.mAttributions.removeAt(attributionNum); + + scheduleFastWriteLocked(); + } + } + } + } + } + }; + + @Override + public void systemReady() { + mConstants.startMonitoring(mContext.getContentResolver()); + mHistoricalRegistry.systemReady(mContext.getContentResolver()); + + IntentFilter packageUpdateFilter = new IntentFilter(); + packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REPLACED); + packageUpdateFilter.addDataScheme("package"); + + mContext.registerReceiverAsUser(mOnPackageUpdatedReceiver, UserHandle.ALL, + packageUpdateFilter, null, null); + + synchronized (this) { + for (int uidNum = mUidStates.size() - 1; uidNum >= 0; uidNum--) { + int uid = mUidStates.keyAt(uidNum); + UidState uidState = mUidStates.valueAt(uidNum); + + String[] pkgsInUid = getPackagesForUid(uidState.uid); + if (ArrayUtils.isEmpty(pkgsInUid)) { + uidState.clear(); + mUidStates.removeAt(uidNum); + scheduleFastWriteLocked(); + continue; + } + + ArrayMap<String, Ops> pkgs = uidState.pkgOps; + if (pkgs == null) { + continue; + } + + int numPkgs = pkgs.size(); + for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) { + String pkg = pkgs.keyAt(pkgNum); + + String action; + if (!ArrayUtils.contains(pkgsInUid, pkg)) { + action = Intent.ACTION_PACKAGE_REMOVED; + } else { + action = Intent.ACTION_PACKAGE_REPLACED; + } + + SystemServerInitThreadPool.submit( + () -> mOnPackageUpdatedReceiver.onReceive(mContext, new Intent(action) + .setData(Uri.fromParts("package", pkg, null)) + .putExtra(Intent.EXTRA_UID, uid)), + "Update app-ops uidState in case package " + pkg + " changed"); + } + } + } + + final IntentFilter packageSuspendFilter = new IntentFilter(); + packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED); + packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED); + mContext.registerReceiverAsUser(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final int[] changedUids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST); + final String[] changedPkgs = intent.getStringArrayExtra( + Intent.EXTRA_CHANGED_PACKAGE_LIST); + for (int code : OPS_RESTRICTED_ON_SUSPEND) { + ArraySet<OnOpModeChangedListener> onModeChangedListeners; + synchronized (AppOpsServiceImpl.this) { + onModeChangedListeners = + mAppOpsServiceInterface.getOpModeChangedListeners(code); + if (onModeChangedListeners == null) { + continue; + } + } + for (int i = 0; i < changedUids.length; i++) { + final int changedUid = changedUids[i]; + final String changedPkg = changedPkgs[i]; + // We trust packagemanager to insert matching uid and packageNames in the + // extras + notifyOpChanged(onModeChangedListeners, code, changedUid, changedPkg); + } + } + } + }, UserHandle.ALL, packageSuspendFilter, null, null); + } + + @Override + public void packageRemoved(int uid, String packageName) { + synchronized (this) { + UidState uidState = mUidStates.get(uid); + if (uidState == null) { + return; + } + + Ops removedOps = null; + + // Remove any package state if such. + if (uidState.pkgOps != null) { + removedOps = uidState.pkgOps.remove(packageName); + mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid)); + } + + // If we just nuked the last package state check if the UID is valid. + if (removedOps != null && uidState.pkgOps.isEmpty() + && getPackagesForUid(uid).length <= 0) { + uidState.clear(); + mUidStates.remove(uid); + } + + if (removedOps != null) { + scheduleFastWriteLocked(); + + final int numOps = removedOps.size(); + for (int opNum = 0; opNum < numOps; opNum++) { + final Op op = removedOps.valueAt(opNum); + + final int numAttributions = op.mAttributions.size(); + for (int attributionNum = 0; attributionNum < numAttributions; + attributionNum++) { + AttributedOp attributedOp = op.mAttributions.valueAt(attributionNum); + + while (attributedOp.isRunning()) { + attributedOp.finished(attributedOp.mInProgressEvents.keyAt(0)); + } + while (attributedOp.isPaused()) { + attributedOp.finished(attributedOp.mPausedInProgressEvents.keyAt(0)); + } + } + } + } + } + + mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::clearHistory, + mHistoricalRegistry, uid, packageName)); + } + + @Override + public void uidRemoved(int uid) { + synchronized (this) { + if (mUidStates.indexOfKey(uid) >= 0) { + mUidStates.get(uid).clear(); + mUidStates.remove(uid); + scheduleFastWriteLocked(); + } + } + } + + // The callback method from ForegroundPolicyInterface + private void onUidStateChanged(int uid, int state, boolean foregroundModeMayChange) { + synchronized (this) { + UidState uidState = getUidStateLocked(uid, true); + + if (uidState != null && foregroundModeMayChange && uidState.hasForegroundWatchers) { + for (int fgi = uidState.foregroundOps.size() - 1; fgi >= 0; fgi--) { + if (!uidState.foregroundOps.valueAt(fgi)) { + continue; + } + final int code = uidState.foregroundOps.keyAt(fgi); + + if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code) + && uidState.getUidMode(code) == AppOpsManager.MODE_FOREGROUND) { + mHandler.sendMessage(PooledLambda.obtainMessage( + AppOpsServiceImpl::notifyOpChangedForAllPkgsInUid, + this, code, uidState.uid, true, null)); + } else if (uidState.pkgOps != null) { + final ArraySet<OnOpModeChangedListener> listenerSet = + mAppOpsServiceInterface.getOpModeChangedListeners(code); + if (listenerSet != null) { + for (int cbi = listenerSet.size() - 1; cbi >= 0; cbi--) { + final OnOpModeChangedListener listener = listenerSet.valueAt(cbi); + if ((listener.getFlags() + & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0 + || !listener.isWatchingUid(uidState.uid)) { + continue; + } + for (int pkgi = uidState.pkgOps.size() - 1; pkgi >= 0; pkgi--) { + final Op op = uidState.pkgOps.valueAt(pkgi).get(code); + if (op == null) { + continue; + } + if (op.getMode() == AppOpsManager.MODE_FOREGROUND) { + mHandler.sendMessage(PooledLambda.obtainMessage( + AppOpsServiceImpl::notifyOpChanged, + this, listenerSet.valueAt(cbi), code, uidState.uid, + uidState.pkgOps.keyAt(pkgi))); + } + } + } + } + } + } + } + + if (uidState != null && uidState.pkgOps != null) { + int numPkgs = uidState.pkgOps.size(); + for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) { + Ops ops = uidState.pkgOps.valueAt(pkgNum); + + int numOps = ops.size(); + for (int opNum = 0; opNum < numOps; opNum++) { + Op op = ops.valueAt(opNum); + + int numAttributions = op.mAttributions.size(); + for (int attributionNum = 0; attributionNum < numAttributions; + attributionNum++) { + AttributedOp attributedOp = op.mAttributions.valueAt( + attributionNum); + + attributedOp.onUidStateChanged(state); + } + } + } + } + } + } + + /** + * Notify the proc state or capability has changed for a certain UID. + */ + @Override + public void updateUidProcState(int uid, int procState, + @ActivityManager.ProcessCapability int capability) { + synchronized (this) { + getUidStateTracker().updateUidProcState(uid, procState, capability); + if (!mUidStates.contains(uid)) { + UidState uidState = new UidState(uid); + mUidStates.put(uid, uidState); + onUidStateChanged(uid, + AppOpsUidStateTracker.processStateToUidState(procState), false); + } + } + } + + @Override + public void shutdown() { + Slog.w(TAG, "Writing app ops before shutdown..."); + boolean doWrite = false; + synchronized (this) { + if (mWriteScheduled) { + mWriteScheduled = false; + mFastWriteScheduled = false; + mHandler.removeCallbacks(mWriteRunner); + doWrite = true; + } + } + if (doWrite) { + writeState(); + } + + mHistoricalRegistry.shutdown(); + } + + private ArrayList<AppOpsManager.OpEntry> collectOps(Ops pkgOps, int[] ops) { + ArrayList<AppOpsManager.OpEntry> resOps = null; + if (ops == null) { + resOps = new ArrayList<>(); + for (int j = 0; j < pkgOps.size(); j++) { + Op curOp = pkgOps.valueAt(j); + resOps.add(getOpEntryForResult(curOp)); + } + } else { + for (int j = 0; j < ops.length; j++) { + Op curOp = pkgOps.get(ops[j]); + if (curOp != null) { + if (resOps == null) { + resOps = new ArrayList<>(); + } + resOps.add(getOpEntryForResult(curOp)); + } + } + } + return resOps; + } + + @Nullable + private ArrayList<AppOpsManager.OpEntry> collectUidOps(@NonNull UidState uidState, + @Nullable int[] ops) { + final SparseIntArray opModes = uidState.getNonDefaultUidModes(); + if (opModes == null) { + return null; + } + + int opModeCount = opModes.size(); + if (opModeCount == 0) { + return null; + } + ArrayList<AppOpsManager.OpEntry> resOps = null; + if (ops == null) { + resOps = new ArrayList<>(); + for (int i = 0; i < opModeCount; i++) { + int code = opModes.keyAt(i); + resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap())); + } + } else { + for (int j = 0; j < ops.length; j++) { + int code = ops[j]; + if (opModes.indexOfKey(code) >= 0) { + if (resOps == null) { + resOps = new ArrayList<>(); + } + resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap())); + } + } + } + return resOps; + } + + private static @NonNull OpEntry getOpEntryForResult(@NonNull Op op) { + return op.createEntryLocked(); + } + + @Override + public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) { + final int callingUid = Binder.getCallingUid(); + final boolean hasAllPackageAccess = mContext.checkPermission( + Manifest.permission.GET_APP_OPS_STATS, Binder.getCallingPid(), + Binder.getCallingUid(), null) == PackageManager.PERMISSION_GRANTED; + ArrayList<AppOpsManager.PackageOps> res = null; + synchronized (this) { + final int uidStateCount = mUidStates.size(); + for (int i = 0; i < uidStateCount; i++) { + UidState uidState = mUidStates.valueAt(i); + if (uidState.pkgOps == null || uidState.pkgOps.isEmpty()) { + continue; + } + ArrayMap<String, Ops> packages = uidState.pkgOps; + final int packageCount = packages.size(); + for (int j = 0; j < packageCount; j++) { + Ops pkgOps = packages.valueAt(j); + ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops); + if (resOps != null) { + if (res == null) { + res = new ArrayList<>(); + } + AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps( + pkgOps.packageName, pkgOps.uidState.uid, resOps); + // Caller can always see their packages and with a permission all. + if (hasAllPackageAccess || callingUid == pkgOps.uidState.uid) { + res.add(resPackage); + } + } + } + } + } + return res; + } + + @Override + public List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName, + int[] ops) { + enforceGetAppOpsStatsPermissionIfNeeded(uid, packageName); + String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName); + if (resolvedPackageName == null) { + return Collections.emptyList(); + } + synchronized (this) { + Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null, + /* edit */ false); + if (pkgOps == null) { + return null; + } + ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops); + if (resOps == null) { + return null; + } + ArrayList<AppOpsManager.PackageOps> res = new ArrayList<>(); + AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps( + pkgOps.packageName, pkgOps.uidState.uid, resOps); + res.add(resPackage); + return res; + } + } + + private void enforceGetAppOpsStatsPermissionIfNeeded(int uid, String packageName) { + final int callingUid = Binder.getCallingUid(); + // We get to access everything + if (callingUid == Process.myPid()) { + return; + } + // Apps can access their own data + if (uid == callingUid && packageName != null + && checkPackage(uid, packageName) == MODE_ALLOWED) { + return; + } + // Otherwise, you need a permission... + mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS, + Binder.getCallingPid(), callingUid, null); + } + + /** + * Verify that historical appop request arguments are valid. + */ + private void ensureHistoricalOpRequestIsValid(int uid, String packageName, + String attributionTag, List<String> opNames, int filter, long beginTimeMillis, + long endTimeMillis, int flags) { + if ((filter & FILTER_BY_UID) != 0) { + Preconditions.checkArgument(uid != Process.INVALID_UID); + } else { + Preconditions.checkArgument(uid == Process.INVALID_UID); + } + + if ((filter & FILTER_BY_PACKAGE_NAME) != 0) { + Objects.requireNonNull(packageName); + } else { + Preconditions.checkArgument(packageName == null); + } + + if ((filter & FILTER_BY_ATTRIBUTION_TAG) == 0) { + Preconditions.checkArgument(attributionTag == null); + } + + if ((filter & FILTER_BY_OP_NAMES) != 0) { + Objects.requireNonNull(opNames); + } else { + Preconditions.checkArgument(opNames == null); + } + + Preconditions.checkFlagsArgument(filter, + FILTER_BY_UID | FILTER_BY_PACKAGE_NAME | FILTER_BY_ATTRIBUTION_TAG + | FILTER_BY_OP_NAMES); + Preconditions.checkArgumentNonnegative(beginTimeMillis); + Preconditions.checkArgument(endTimeMillis > beginTimeMillis); + Preconditions.checkFlagsArgument(flags, OP_FLAGS_ALL); + } + + @Override + public void getHistoricalOps(int uid, String packageName, String attributionTag, + List<String> opNames, int dataType, int filter, long beginTimeMillis, + long endTimeMillis, int flags, RemoteCallback callback) { + PackageManager pm = mContext.getPackageManager(); + + ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter, + beginTimeMillis, endTimeMillis, flags); + Objects.requireNonNull(callback, "callback cannot be null"); + ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class); + boolean isSelfRequest = (filter & FILTER_BY_UID) != 0 && uid == Binder.getCallingUid(); + if (!isSelfRequest) { + boolean isCallerInstrumented = + ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID; + boolean isCallerSystem = Binder.getCallingPid() == Process.myPid(); + boolean isCallerPermissionController; + try { + isCallerPermissionController = pm.getPackageUidAsUser( + mContext.getPackageManager().getPermissionControllerPackageName(), 0, + UserHandle.getUserId(Binder.getCallingUid())) + == Binder.getCallingUid(); + } catch (PackageManager.NameNotFoundException doesNotHappen) { + return; + } + + boolean doesCallerHavePermission = mContext.checkPermission( + android.Manifest.permission.GET_HISTORICAL_APP_OPS_STATS, + Binder.getCallingPid(), Binder.getCallingUid()) + == PackageManager.PERMISSION_GRANTED; + + if (!isCallerSystem && !isCallerInstrumented && !isCallerPermissionController + && !doesCallerHavePermission) { + mHandler.post(() -> callback.sendResult(new Bundle())); + return; + } + + mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS, + Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps"); + } + + final String[] opNamesArray = (opNames != null) + ? opNames.toArray(new String[opNames.size()]) : null; + + Set<String> attributionChainExemptPackages = null; + if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) { + attributionChainExemptPackages = + PermissionManager.getIndicatorExemptedPackages(mContext); + } + + final String[] chainExemptPkgArray = attributionChainExemptPackages != null + ? attributionChainExemptPackages.toArray( + new String[attributionChainExemptPackages.size()]) : null; + + // Must not hold the appops lock + mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOps, + mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType, + filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray, + callback).recycleOnUse()); + } + + @Override + public void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag, + List<String> opNames, int dataType, int filter, long beginTimeMillis, + long endTimeMillis, int flags, RemoteCallback callback) { + ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter, + beginTimeMillis, endTimeMillis, flags); + Objects.requireNonNull(callback, "callback cannot be null"); + + mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS, + Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps"); + + final String[] opNamesArray = (opNames != null) + ? opNames.toArray(new String[opNames.size()]) : null; + + Set<String> attributionChainExemptPackages = null; + if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) { + attributionChainExemptPackages = + PermissionManager.getIndicatorExemptedPackages(mContext); + } + + final String[] chainExemptPkgArray = attributionChainExemptPackages != null + ? attributionChainExemptPackages.toArray( + new String[attributionChainExemptPackages.size()]) : null; + + // Must not hold the appops lock + mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOpsFromDiskRaw, + mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType, + filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray, + callback).recycleOnUse()); + } + + @Override + public void reloadNonHistoricalState() { + mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS, + Binder.getCallingPid(), Binder.getCallingUid(), "reloadNonHistoricalState"); + writeState(); + readState(); + } + + @Override + public List<AppOpsManager.PackageOps> getUidOps(int uid, int[] ops) { + mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS, + Binder.getCallingPid(), Binder.getCallingUid(), null); + synchronized (this) { + UidState uidState = getUidStateLocked(uid, false); + if (uidState == null) { + return null; + } + ArrayList<AppOpsManager.OpEntry> resOps = collectUidOps(uidState, ops); + if (resOps == null) { + return null; + } + ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>(); + AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps( + null, uidState.uid, resOps); + res.add(resPackage); + return res; + } + } + + private void pruneOpLocked(Op op, int uid, String packageName) { + op.removeAttributionsWithNoTime(); + + if (op.mAttributions.isEmpty()) { + Ops ops = getOpsLocked(uid, packageName, null, false, null, /* edit */ false); + if (ops != null) { + ops.remove(op.op); + op.setMode(AppOpsManager.opToDefaultMode(op.op)); + if (ops.size() <= 0) { + UidState uidState = ops.uidState; + ArrayMap<String, Ops> pkgOps = uidState.pkgOps; + if (pkgOps != null) { + pkgOps.remove(ops.packageName); + mAppOpsServiceInterface.removePackage(ops.packageName, + UserHandle.getUserId(uidState.uid)); + if (pkgOps.isEmpty()) { + uidState.pkgOps = null; + } + if (uidState.isDefault()) { + uidState.clear(); + mUidStates.remove(uid); + } + } + } + } + } + } + + @Override + public void enforceManageAppOpsModes(int callingPid, int callingUid, int targetUid) { + if (callingPid == Process.myPid()) { + return; + } + final int callingUser = UserHandle.getUserId(callingUid); + synchronized (this) { + if (mProfileOwners != null && mProfileOwners.get(callingUser, -1) == callingUid) { + if (targetUid >= 0 && callingUser == UserHandle.getUserId(targetUid)) { + // Profile owners are allowed to change modes but only for apps + // within their user. + return; + } + } + } + mContext.enforcePermission(android.Manifest.permission.MANAGE_APP_OPS_MODES, + Binder.getCallingPid(), Binder.getCallingUid(), null); + } + + @Override + public void setUidMode(int code, int uid, int mode, + @Nullable IAppOpsCallback permissionPolicyCallback) { + if (DEBUG) { + Slog.i(TAG, "uid " + uid + " OP_" + opToName(code) + " := " + modeToName(mode) + + " by uid " + Binder.getCallingUid()); + } + + enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid); + verifyIncomingOp(code); + code = AppOpsManager.opToSwitch(code); + + if (permissionPolicyCallback == null) { + updatePermissionRevokedCompat(uid, code, mode); + } + + int previousMode; + synchronized (this) { + final int defaultMode = AppOpsManager.opToDefaultMode(code); + + UidState uidState = getUidStateLocked(uid, false); + if (uidState == null) { + if (mode == defaultMode) { + return; + } + uidState = new UidState(uid); + mUidStates.put(uid, uidState); + } + if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) { + previousMode = uidState.getUidMode(code); + } else { + // doesn't look right but is legacy behavior. + previousMode = MODE_DEFAULT; + } + + if (!uidState.setUidMode(code, mode)) { + return; + } + uidState.evalForegroundOps(); + if (mode != MODE_ERRORED && mode != previousMode) { + updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid); + } + } + + notifyOpChangedForAllPkgsInUid(code, uid, false, permissionPolicyCallback); + notifyOpChangedSync(code, uid, null, mode, previousMode); + } + + /** + * Notify that an op changed for all packages in an uid. + * + * @param code The op that changed + * @param uid The uid the op was changed for + * @param onlyForeground Only notify watchers that watch for foreground changes + */ + private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground, + @Nullable IAppOpsCallback callbackToIgnore) { + ModeCallback listenerToIgnore = callbackToIgnore != null + ? mModeWatchers.get(callbackToIgnore.asBinder()) : null; + mAppOpsServiceInterface.notifyOpChangedForAllPkgsInUid(code, uid, onlyForeground, + listenerToIgnore); + } + + private void updatePermissionRevokedCompat(int uid, int switchCode, int mode) { + PackageManager packageManager = mContext.getPackageManager(); + if (packageManager == null) { + // This can only happen during early boot. At this time the permission state and appop + // state are in sync + return; + } + + String[] packageNames = packageManager.getPackagesForUid(uid); + if (ArrayUtils.isEmpty(packageNames)) { + return; + } + String packageName = packageNames[0]; + + int[] ops = mSwitchedOps.get(switchCode); + for (int code : ops) { + String permissionName = AppOpsManager.opToPermission(code); + if (permissionName == null) { + continue; + } + + if (packageManager.checkPermission(permissionName, packageName) + != PackageManager.PERMISSION_GRANTED) { + continue; + } + + PermissionInfo permissionInfo; + try { + permissionInfo = packageManager.getPermissionInfo(permissionName, 0); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + continue; + } + + if (!permissionInfo.isRuntime()) { + continue; + } + + boolean supportsRuntimePermissions = getPackageManagerInternal() + .getUidTargetSdkVersion(uid) >= Build.VERSION_CODES.M; + + UserHandle user = UserHandle.getUserHandleForUid(uid); + boolean isRevokedCompat; + if (permissionInfo.backgroundPermission != null) { + if (packageManager.checkPermission(permissionInfo.backgroundPermission, packageName) + == PackageManager.PERMISSION_GRANTED) { + boolean isBackgroundRevokedCompat = mode != AppOpsManager.MODE_ALLOWED; + + if (isBackgroundRevokedCompat && supportsRuntimePermissions) { + Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime" + + " permission state, this is discouraged and you should revoke the" + + " runtime permission instead: uid=" + uid + ", switchCode=" + + switchCode + ", mode=" + mode + ", permission=" + + permissionInfo.backgroundPermission); + } + + final long identity = Binder.clearCallingIdentity(); + try { + packageManager.updatePermissionFlags(permissionInfo.backgroundPermission, + packageName, PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, + isBackgroundRevokedCompat + ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED + && mode != AppOpsManager.MODE_FOREGROUND; + } else { + isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED; + } + + if (isRevokedCompat && supportsRuntimePermissions) { + Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime" + + " permission state, this is discouraged and you should revoke the" + + " runtime permission instead: uid=" + uid + ", switchCode=" + + switchCode + ", mode=" + mode + ", permission=" + permissionName); + } + + final long identity = Binder.clearCallingIdentity(); + try { + packageManager.updatePermissionFlags(permissionName, packageName, + PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, isRevokedCompat + ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + private void notifyOpChangedSync(int code, int uid, @NonNull String packageName, int mode, + int previousMode) { + final StorageManagerInternal storageManagerInternal = + LocalServices.getService(StorageManagerInternal.class); + if (storageManagerInternal != null) { + storageManagerInternal.onAppOpsChanged(code, uid, packageName, mode, previousMode); + } + } + + @Override + public void setMode(int code, int uid, @NonNull String packageName, int mode, + @Nullable IAppOpsCallback permissionPolicyCallback) { + enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid); + verifyIncomingOp(code); + if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) { + return; + } + + ArraySet<OnOpModeChangedListener> repCbs = null; + code = AppOpsManager.opToSwitch(code); + + PackageVerificationResult pvr; + try { + pvr = verifyAndGetBypass(uid, packageName, null); + } catch (SecurityException e) { + Slog.e(TAG, "Cannot setMode", e); + return; + } + + int previousMode = MODE_DEFAULT; + synchronized (this) { + UidState uidState = getUidStateLocked(uid, false); + Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ true); + if (op != null) { + if (op.getMode() != mode) { + previousMode = op.getMode(); + op.setMode(mode); + + if (uidState != null) { + uidState.evalForegroundOps(); + } + ArraySet<OnOpModeChangedListener> cbs = + mAppOpsServiceInterface.getOpModeChangedListeners(code); + if (cbs != null) { + if (repCbs == null) { + repCbs = new ArraySet<>(); + } + repCbs.addAll(cbs); + } + cbs = mAppOpsServiceInterface.getPackageModeChangedListeners(packageName); + if (cbs != null) { + if (repCbs == null) { + repCbs = new ArraySet<>(); + } + repCbs.addAll(cbs); + } + if (repCbs != null && permissionPolicyCallback != null) { + repCbs.remove(mModeWatchers.get(permissionPolicyCallback.asBinder())); + } + if (mode == AppOpsManager.opToDefaultMode(op.op)) { + // If going into the default mode, prune this op + // if there is nothing else interesting in it. + pruneOpLocked(op, uid, packageName); + } + scheduleFastWriteLocked(); + if (mode != MODE_ERRORED) { + updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid); + } + } + } + } + if (repCbs != null) { + mHandler.sendMessage(PooledLambda.obtainMessage( + AppOpsServiceImpl::notifyOpChanged, + this, repCbs, code, uid, packageName)); + } + + notifyOpChangedSync(code, uid, packageName, mode, previousMode); + } + + private void notifyOpChanged(ArraySet<OnOpModeChangedListener> callbacks, int code, + int uid, String packageName) { + for (int i = 0; i < callbacks.size(); i++) { + final OnOpModeChangedListener callback = callbacks.valueAt(i); + notifyOpChanged(callback, code, uid, packageName); + } + } + + private void notifyOpChanged(OnOpModeChangedListener callback, int code, + int uid, String packageName) { + mAppOpsServiceInterface.notifyOpChanged(callback, code, uid, packageName); + } + + private static ArrayList<ChangeRec> addChange(ArrayList<ChangeRec> reports, + int op, int uid, String packageName, int previousMode) { + boolean duplicate = false; + if (reports == null) { + reports = new ArrayList<>(); + } else { + final int reportCount = reports.size(); + for (int j = 0; j < reportCount; j++) { + ChangeRec report = reports.get(j); + if (report.op == op && report.pkg.equals(packageName)) { + duplicate = true; + break; + } + } + } + if (!duplicate) { + reports.add(new ChangeRec(op, uid, packageName, previousMode)); + } + + return reports; + } + + private static HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> addCallbacks( + HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks, + int op, int uid, String packageName, int previousMode, + ArraySet<OnOpModeChangedListener> cbs) { + if (cbs == null) { + return callbacks; + } + if (callbacks == null) { + callbacks = new HashMap<>(); + } + final int N = cbs.size(); + for (int i=0; i<N; i++) { + OnOpModeChangedListener cb = cbs.valueAt(i); + ArrayList<ChangeRec> reports = callbacks.get(cb); + ArrayList<ChangeRec> changed = addChange(reports, op, uid, packageName, previousMode); + if (changed != reports) { + callbacks.put(cb, changed); + } + } + return callbacks; + } + + static final class ChangeRec { + final int op; + final int uid; + final String pkg; + final int previous_mode; + + ChangeRec(int _op, int _uid, String _pkg, int _previous_mode) { + op = _op; + uid = _uid; + pkg = _pkg; + previous_mode = _previous_mode; + } + } + + @Override + public void resetAllModes(int reqUserId, String reqPackageName) { + final int callingPid = Binder.getCallingPid(); + final int callingUid = Binder.getCallingUid(); + reqUserId = ActivityManager.handleIncomingUser(callingPid, callingUid, reqUserId, + true, true, "resetAllModes", null); + + int reqUid = -1; + if (reqPackageName != null) { + try { + reqUid = AppGlobals.getPackageManager().getPackageUid( + reqPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, reqUserId); + } catch (RemoteException e) { + /* ignore - local call */ + } + } + + enforceManageAppOpsModes(callingPid, callingUid, reqUid); + + HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks = null; + ArrayList<ChangeRec> allChanges = new ArrayList<>(); + synchronized (this) { + boolean changed = false; + for (int i = mUidStates.size() - 1; i >= 0; i--) { + UidState uidState = mUidStates.valueAt(i); + + SparseIntArray opModes = uidState.getNonDefaultUidModes(); + if (opModes != null && (uidState.uid == reqUid || reqUid == -1)) { + final int uidOpCount = opModes.size(); + for (int j = uidOpCount - 1; j >= 0; j--) { + final int code = opModes.keyAt(j); + if (AppOpsManager.opAllowsReset(code)) { + int previousMode = opModes.valueAt(j); + uidState.setUidMode(code, AppOpsManager.opToDefaultMode(code)); + for (String packageName : getPackagesForUid(uidState.uid)) { + callbacks = addCallbacks(callbacks, code, uidState.uid, + packageName, previousMode, + mAppOpsServiceInterface.getOpModeChangedListeners(code)); + callbacks = addCallbacks(callbacks, code, uidState.uid, + packageName, previousMode, mAppOpsServiceInterface + .getPackageModeChangedListeners(packageName)); + + allChanges = addChange(allChanges, code, uidState.uid, + packageName, previousMode); + } + } + } + } + + if (uidState.pkgOps == null) { + continue; + } + + if (reqUserId != UserHandle.USER_ALL + && reqUserId != UserHandle.getUserId(uidState.uid)) { + // Skip any ops for a different user + continue; + } + + Map<String, Ops> packages = uidState.pkgOps; + Iterator<Map.Entry<String, Ops>> it = packages.entrySet().iterator(); + boolean uidChanged = false; + while (it.hasNext()) { + Map.Entry<String, Ops> ent = it.next(); + String packageName = ent.getKey(); + if (reqPackageName != null && !reqPackageName.equals(packageName)) { + // Skip any ops for a different package + continue; + } + Ops pkgOps = ent.getValue(); + for (int j=pkgOps.size()-1; j>=0; j--) { + Op curOp = pkgOps.valueAt(j); + if (shouldDeferResetOpToDpm(curOp.op)) { + deferResetOpToDpm(curOp.op, reqPackageName, reqUserId); + continue; + } + if (AppOpsManager.opAllowsReset(curOp.op) + && curOp.getMode() != AppOpsManager.opToDefaultMode(curOp.op)) { + int previousMode = curOp.getMode(); + curOp.setMode(AppOpsManager.opToDefaultMode(curOp.op)); + changed = true; + uidChanged = true; + final int uid = curOp.uidState.uid; + callbacks = addCallbacks(callbacks, curOp.op, uid, packageName, + previousMode, + mAppOpsServiceInterface.getOpModeChangedListeners(curOp.op)); + callbacks = addCallbacks(callbacks, curOp.op, uid, packageName, + previousMode, mAppOpsServiceInterface + .getPackageModeChangedListeners(packageName)); + + allChanges = addChange(allChanges, curOp.op, uid, packageName, + previousMode); + curOp.removeAttributionsWithNoTime(); + if (curOp.mAttributions.isEmpty()) { + pkgOps.removeAt(j); + } + } + } + if (pkgOps.size() == 0) { + it.remove(); + mAppOpsServiceInterface.removePackage(packageName, + UserHandle.getUserId(uidState.uid)); + } + } + if (uidState.isDefault()) { + uidState.clear(); + mUidStates.remove(uidState.uid); + } + if (uidChanged) { + uidState.evalForegroundOps(); + } + } + + if (changed) { + scheduleFastWriteLocked(); + } + } + if (callbacks != null) { + for (Map.Entry<OnOpModeChangedListener, ArrayList<ChangeRec>> ent + : callbacks.entrySet()) { + OnOpModeChangedListener cb = ent.getKey(); + ArrayList<ChangeRec> reports = ent.getValue(); + for (int i=0; i<reports.size(); i++) { + ChangeRec rep = reports.get(i); + mHandler.sendMessage(PooledLambda.obtainMessage( + AppOpsServiceImpl::notifyOpChanged, + this, cb, rep.op, rep.uid, rep.pkg)); + } + } + } + + int numChanges = allChanges.size(); + for (int i = 0; i < numChanges; i++) { + ChangeRec change = allChanges.get(i); + notifyOpChangedSync(change.op, change.uid, change.pkg, + AppOpsManager.opToDefaultMode(change.op), change.previous_mode); + } + } + + private boolean shouldDeferResetOpToDpm(int op) { + // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission + // pre-grants to a role-based mechanism or another general-purpose mechanism. + return dpmi != null && dpmi.supportsResetOp(op); + } + + /** Assumes {@link #shouldDeferResetOpToDpm(int)} is true. */ + private void deferResetOpToDpm(int op, String packageName, @UserIdInt int userId) { + // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission + // pre-grants to a role-based mechanism or another general-purpose mechanism. + dpmi.resetOp(op, packageName, userId); + } + + private void evalAllForegroundOpsLocked() { + for (int uidi = mUidStates.size() - 1; uidi >= 0; uidi--) { + final UidState uidState = mUidStates.valueAt(uidi); + if (uidState.foregroundOps != null) { + uidState.evalForegroundOps(); + } + } + } + + @Override + public void startWatchingModeWithFlags(int op, String packageName, int flags, + IAppOpsCallback callback) { + int watchedUid = -1; + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + // TODO: should have a privileged permission to protect this. + // Also, if the caller has requested WATCH_FOREGROUND_CHANGES, should we require + // the USAGE_STATS permission since this can provide information about when an + // app is in the foreground? + Preconditions.checkArgumentInRange(op, AppOpsManager.OP_NONE, + AppOpsManager._NUM_OP - 1, "Invalid op code: " + op); + if (callback == null) { + return; + } + final boolean mayWatchPackageName = packageName != null + && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(callingUid)); + synchronized (this) { + int switchOp = (op != AppOpsManager.OP_NONE) ? AppOpsManager.opToSwitch(op) : op; + + int notifiedOps; + if ((flags & CALL_BACK_ON_SWITCHED_OP) == 0) { + if (op == OP_NONE) { + notifiedOps = ALL_OPS; + } else { + notifiedOps = op; + } + } else { + notifiedOps = switchOp; + } + + ModeCallback cb = mModeWatchers.get(callback.asBinder()); + if (cb == null) { + cb = new ModeCallback(callback, watchedUid, flags, notifiedOps, callingUid, + callingPid); + mModeWatchers.put(callback.asBinder(), cb); + } + if (switchOp != AppOpsManager.OP_NONE) { + mAppOpsServiceInterface.startWatchingOpModeChanged(cb, switchOp); + } + if (mayWatchPackageName) { + mAppOpsServiceInterface.startWatchingPackageModeChanged(cb, packageName); + } + evalAllForegroundOpsLocked(); + } + } + + @Override + public void stopWatchingMode(IAppOpsCallback callback) { + if (callback == null) { + return; + } + synchronized (this) { + ModeCallback cb = mModeWatchers.remove(callback.asBinder()); + if (cb != null) { + cb.unlinkToDeath(); + mAppOpsServiceInterface.removeListener(cb); + } + + evalAllForegroundOpsLocked(); + } + } + + @Override + public int checkOperation(int code, int uid, String packageName, + @Nullable String attributionTag, boolean raw) { + verifyIncomingOp(code); + if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) { + return AppOpsManager.opToDefaultMode(code); + } + + String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName); + if (resolvedPackageName == null) { + return AppOpsManager.MODE_IGNORED; + } + return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag, raw); + } + + /** + * Get the mode of an app-op. + * + * @param code The code of the op + * @param uid The uid of the package the op belongs to + * @param packageName The package the op belongs to + * @param raw If the raw state of eval-ed state should be checked. + * @return The mode of the op + */ + private @Mode int checkOperationUnchecked(int code, int uid, @NonNull String packageName, + @Nullable String attributionTag, boolean raw) { + PackageVerificationResult pvr; + try { + pvr = verifyAndGetBypass(uid, packageName, null); + } catch (SecurityException e) { + Slog.e(TAG, "checkOperation", e); + return AppOpsManager.opToDefaultMode(code); + } + + if (isOpRestrictedDueToSuspend(code, packageName, uid)) { + return AppOpsManager.MODE_IGNORED; + } + synchronized (this) { + if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, true)) { + return AppOpsManager.MODE_IGNORED; + } + code = AppOpsManager.opToSwitch(code); + UidState uidState = getUidStateLocked(uid, false); + if (uidState != null + && uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) { + final int rawMode = uidState.getUidMode(code); + return raw ? rawMode : uidState.evalMode(code, rawMode); + } + Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ false); + if (op == null) { + return AppOpsManager.opToDefaultMode(code); + } + return raw ? op.getMode() : op.uidState.evalMode(op.op, op.getMode()); + } + } + + @Override + public int checkPackage(int uid, String packageName) { + Objects.requireNonNull(packageName); + try { + verifyAndGetBypass(uid, packageName, null); + // When the caller is the system, it's possible that the packageName is the special + // one (e.g., "root") which isn't actually existed. + if (resolveUid(packageName) == uid + || (isPackageExisted(packageName) + && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(uid)))) { + return AppOpsManager.MODE_ALLOWED; + } + return AppOpsManager.MODE_ERRORED; + } catch (SecurityException ignored) { + return AppOpsManager.MODE_ERRORED; + } + } + + private boolean isPackageExisted(String packageName) { + return getPackageManagerInternal().getPackageStateInternal(packageName) != null; + } + + /** + * This method will check with PackageManager to determine if the package provided should + * be visible to the {@link Binder#getCallingUid()}. + * + * NOTE: This must not be called while synchronized on {@code this} to avoid dead locks + */ + private boolean filterAppAccessUnlocked(String packageName, int userId) { + final int callingUid = Binder.getCallingUid(); + return LocalServices.getService(PackageManagerInternal.class) + .filterAppAccess(packageName, callingUid, userId); + } + + @Override + public int noteOperation(int code, int uid, @Nullable String packageName, + @Nullable String attributionTag, @Nullable String message) { + verifyIncomingUid(uid); + verifyIncomingOp(code); + if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) { + return AppOpsManager.MODE_ERRORED; + } + + String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName); + if (resolvedPackageName == null) { + return AppOpsManager.MODE_IGNORED; + } + return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag, + Process.INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF); + } + + @Override + public int noteOperationUnchecked(int code, int uid, @NonNull String packageName, + @Nullable String attributionTag, int proxyUid, String proxyPackageName, + @Nullable String proxyAttributionTag, @OpFlags int flags) { + PackageVerificationResult pvr; + try { + pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName); + if (!pvr.isAttributionTagValid) { + attributionTag = null; + } + } catch (SecurityException e) { + Slog.e(TAG, "noteOperation", e); + return AppOpsManager.MODE_ERRORED; + } + + synchronized (this) { + final Ops ops = getOpsLocked(uid, packageName, attributionTag, + pvr.isAttributionTagValid, pvr.bypass, /* edit */ true); + if (ops == null) { + scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags, + AppOpsManager.MODE_IGNORED); + if (DEBUG) { + Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid + + " package " + packageName + "flags: " + + AppOpsManager.flagsToString(flags)); + } + return AppOpsManager.MODE_ERRORED; + } + final Op op = getOpLocked(ops, code, uid, true); + final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag); + if (attributedOp.isRunning()) { + Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code " + + code + " startTime of in progress event=" + + attributedOp.mInProgressEvents.valueAt(0).getStartTime()); + } + + final int switchCode = AppOpsManager.opToSwitch(code); + final UidState uidState = ops.uidState; + if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, false)) { + attributedOp.rejected(uidState.getState(), flags); + scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags, + AppOpsManager.MODE_IGNORED); + return AppOpsManager.MODE_IGNORED; + } + // If there is a non-default per UID policy (we set UID op mode only if + // non-default) it takes over, otherwise use the per package policy. + if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) { + final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode)); + if (uidMode != AppOpsManager.MODE_ALLOWED) { + if (DEBUG) { + Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code " + + switchCode + " (" + code + ") uid " + uid + " package " + + packageName + " flags: " + AppOpsManager.flagsToString(flags)); + } + attributedOp.rejected(uidState.getState(), flags); + scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags, + uidMode); + return uidMode; + } + } else { + final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true) + : op; + final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode()); + if (mode != AppOpsManager.MODE_ALLOWED) { + if (DEBUG) { + Slog.d(TAG, "noteOperation: reject #" + mode + " for code " + + switchCode + " (" + code + ") uid " + uid + " package " + + packageName + " flags: " + AppOpsManager.flagsToString(flags)); + } + attributedOp.rejected(uidState.getState(), flags); + scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags, + mode); + return mode; + } + } + if (DEBUG) { + Slog.d(TAG, + "noteOperation: allowing code " + code + " uid " + uid + " package " + + packageName + (attributionTag == null ? "" + : "." + attributionTag) + " flags: " + + AppOpsManager.flagsToString(flags)); + } + scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags, + AppOpsManager.MODE_ALLOWED); + attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag, + uidState.getState(), + flags); + + return AppOpsManager.MODE_ALLOWED; + } + } + + @Override + public boolean isAttributionTagValid(int uid, @NonNull String packageName, + @Nullable String attributionTag, + @Nullable String proxyPackageName) { + try { + return verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName) + .isAttributionTagValid; + } catch (SecurityException ignored) { + // We don't want to throw, this exception will be handled in the (c/n/s)Operation calls + // when they need the bypass object. + return false; + } + } + + // TODO moltmann: Allow watching for attribution ops + @Override + public void startWatchingActive(int[] ops, IAppOpsActiveCallback callback) { + int watchedUid = Process.INVALID_UID; + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS) + != PackageManager.PERMISSION_GRANTED) { + watchedUid = callingUid; + } + if (ops != null) { + Preconditions.checkArrayElementsInRange(ops, 0, + AppOpsManager._NUM_OP - 1, "Invalid op code in: " + Arrays.toString(ops)); + } + if (callback == null) { + return; + } + synchronized (this) { + SparseArray<ActiveCallback> callbacks = mActiveWatchers.get(callback.asBinder()); + if (callbacks == null) { + callbacks = new SparseArray<>(); + mActiveWatchers.put(callback.asBinder(), callbacks); + } + final ActiveCallback activeCallback = new ActiveCallback(callback, watchedUid, + callingUid, callingPid); + for (int op : ops) { + callbacks.put(op, activeCallback); + } + } + } + + @Override + public void stopWatchingActive(IAppOpsActiveCallback callback) { + if (callback == null) { + return; + } + synchronized (this) { + final SparseArray<ActiveCallback> activeCallbacks = + mActiveWatchers.remove(callback.asBinder()); + if (activeCallbacks == null) { + return; + } + final int callbackCount = activeCallbacks.size(); + for (int i = 0; i < callbackCount; i++) { + activeCallbacks.valueAt(i).destroy(); + } + } + } + + @Override + public void startWatchingStarted(int[] ops, @NonNull IAppOpsStartedCallback callback) { + int watchedUid = Process.INVALID_UID; + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS) + != PackageManager.PERMISSION_GRANTED) { + watchedUid = callingUid; + } + + Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty"); + Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1, + "Invalid op code in: " + Arrays.toString(ops)); + Objects.requireNonNull(callback, "Callback cannot be null"); + + synchronized (this) { + SparseArray<StartedCallback> callbacks = mStartedWatchers.get(callback.asBinder()); + if (callbacks == null) { + callbacks = new SparseArray<>(); + mStartedWatchers.put(callback.asBinder(), callbacks); + } + + final StartedCallback startedCallback = new StartedCallback(callback, watchedUid, + callingUid, callingPid); + for (int op : ops) { + callbacks.put(op, startedCallback); + } + } + } + + @Override + public void stopWatchingStarted(IAppOpsStartedCallback callback) { + Objects.requireNonNull(callback, "Callback cannot be null"); + + synchronized (this) { + final SparseArray<StartedCallback> startedCallbacks = + mStartedWatchers.remove(callback.asBinder()); + if (startedCallbacks == null) { + return; + } + + final int callbackCount = startedCallbacks.size(); + for (int i = 0; i < callbackCount; i++) { + startedCallbacks.valueAt(i).destroy(); + } + } + } + + @Override + public void startWatchingNoted(@NonNull int[] ops, @NonNull IAppOpsNotedCallback callback) { + int watchedUid = Process.INVALID_UID; + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS) + != PackageManager.PERMISSION_GRANTED) { + watchedUid = callingUid; + } + Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty"); + Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1, + "Invalid op code in: " + Arrays.toString(ops)); + Objects.requireNonNull(callback, "Callback cannot be null"); + synchronized (this) { + SparseArray<NotedCallback> callbacks = mNotedWatchers.get(callback.asBinder()); + if (callbacks == null) { + callbacks = new SparseArray<>(); + mNotedWatchers.put(callback.asBinder(), callbacks); + } + final NotedCallback notedCallback = new NotedCallback(callback, watchedUid, + callingUid, callingPid); + for (int op : ops) { + callbacks.put(op, notedCallback); + } + } + } + + @Override + public void stopWatchingNoted(IAppOpsNotedCallback callback) { + Objects.requireNonNull(callback, "Callback cannot be null"); + synchronized (this) { + final SparseArray<NotedCallback> notedCallbacks = + mNotedWatchers.remove(callback.asBinder()); + if (notedCallbacks == null) { + return; + } + final int callbackCount = notedCallbacks.size(); + for (int i = 0; i < callbackCount; i++) { + notedCallbacks.valueAt(i).destroy(); + } + } + } + + @Override + public int startOperation(@NonNull IBinder clientId, int code, int uid, + @Nullable String packageName, @Nullable String attributionTag, + boolean startIfModeDefault, @NonNull String message, + @AttributionFlags int attributionFlags, int attributionChainId) { + verifyIncomingUid(uid); + verifyIncomingOp(code); + if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) { + return AppOpsManager.MODE_ERRORED; + } + + String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName); + if (resolvedPackageName == null) { + return AppOpsManager.MODE_IGNORED; + } + + // As a special case for OP_RECORD_AUDIO_HOTWORD, which we use only for attribution + // purposes and not as a check, also make sure that the caller is allowed to access + // the data gated by OP_RECORD_AUDIO. + // + // TODO: Revert this change before Android 12. + if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO) { + int result = checkOperation(OP_RECORD_AUDIO, uid, packageName, null, false); + if (result != AppOpsManager.MODE_ALLOWED) { + return result; + } + } + return startOperationUnchecked(clientId, code, uid, packageName, attributionTag, + Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault, + attributionFlags, attributionChainId, /*dryRun*/ false); + } + + private boolean shouldStartForMode(int mode, boolean startIfModeDefault) { + return (mode == MODE_ALLOWED || (mode == MODE_DEFAULT && startIfModeDefault)); + } + + @Override + public int startOperationUnchecked(IBinder clientId, int code, int uid, + @NonNull String packageName, @Nullable String attributionTag, int proxyUid, + String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags, + boolean startIfModeDefault, @AttributionFlags int attributionFlags, + int attributionChainId, boolean dryRun) { + PackageVerificationResult pvr; + try { + pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName); + if (!pvr.isAttributionTagValid) { + attributionTag = null; + } + } catch (SecurityException e) { + Slog.e(TAG, "startOperation", e); + return AppOpsManager.MODE_ERRORED; + } + + boolean isRestricted; + int startType = START_TYPE_FAILED; + synchronized (this) { + final Ops ops = getOpsLocked(uid, packageName, attributionTag, + pvr.isAttributionTagValid, pvr.bypass, /* edit */ true); + if (ops == null) { + if (!dryRun) { + scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, + flags, AppOpsManager.MODE_IGNORED, startType, attributionFlags, + attributionChainId); + } + if (DEBUG) { + Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid + + " package " + packageName + " flags: " + + AppOpsManager.flagsToString(flags)); + } + return AppOpsManager.MODE_ERRORED; + } + final Op op = getOpLocked(ops, code, uid, true); + final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag); + final UidState uidState = ops.uidState; + isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, + false); + final int switchCode = AppOpsManager.opToSwitch(code); + // If there is a non-default per UID policy (we set UID op mode only if + // non-default) it takes over, otherwise use the per package policy. + if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) { + final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode)); + if (!shouldStartForMode(uidMode, startIfModeDefault)) { + if (DEBUG) { + Slog.d(TAG, "startOperation: uid reject #" + uidMode + " for code " + + switchCode + " (" + code + ") uid " + uid + " package " + + packageName + " flags: " + AppOpsManager.flagsToString(flags)); + } + if (!dryRun) { + attributedOp.rejected(uidState.getState(), flags); + scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, + flags, uidMode, startType, attributionFlags, attributionChainId); + } + return uidMode; + } + } else { + final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true) + : op; + final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode()); + if (!shouldStartForMode(mode, startIfModeDefault)) { + if (DEBUG) { + Slog.d(TAG, "startOperation: reject #" + mode + " for code " + + switchCode + " (" + code + ") uid " + uid + " package " + + packageName + " flags: " + AppOpsManager.flagsToString(flags)); + } + if (!dryRun) { + attributedOp.rejected(uidState.getState(), flags); + scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, + flags, mode, startType, attributionFlags, attributionChainId); + } + return mode; + } + } + if (DEBUG) { + Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid + + " package " + packageName + " restricted: " + isRestricted + + " flags: " + AppOpsManager.flagsToString(flags)); + } + if (!dryRun) { + try { + if (isRestricted) { + attributedOp.createPaused(clientId, proxyUid, proxyPackageName, + proxyAttributionTag, uidState.getState(), flags, + attributionFlags, attributionChainId); + } else { + attributedOp.started(clientId, proxyUid, proxyPackageName, + proxyAttributionTag, uidState.getState(), flags, + attributionFlags, attributionChainId); + startType = START_TYPE_STARTED; + } + } catch (RemoteException e) { + throw new RuntimeException(e); + } + scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags, + isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags, + attributionChainId); + } + } + + // Possible bug? The raw mode could have been MODE_DEFAULT to reach here. + return isRestricted ? MODE_IGNORED : MODE_ALLOWED; + } + + @Override + public void finishOperation(IBinder clientId, int code, int uid, String packageName, + String attributionTag) { + verifyIncomingUid(uid); + verifyIncomingOp(code); + if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) { + return; + } + + String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName); + if (resolvedPackageName == null) { + return; + } + + finishOperationUnchecked(clientId, code, uid, resolvedPackageName, attributionTag); + } + + @Override + public void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName, + String attributionTag) { + PackageVerificationResult pvr; + try { + pvr = verifyAndGetBypass(uid, packageName, attributionTag); + if (!pvr.isAttributionTagValid) { + attributionTag = null; + } + } catch (SecurityException e) { + Slog.e(TAG, "Cannot finishOperation", e); + return; + } + + synchronized (this) { + Op op = getOpLocked(code, uid, packageName, attributionTag, pvr.isAttributionTagValid, + pvr.bypass, /* edit */ true); + if (op == null) { + Slog.e(TAG, "Operation not found: uid=" + uid + " pkg=" + packageName + "(" + + attributionTag + ") op=" + AppOpsManager.opToName(code)); + return; + } + final AttributedOp attributedOp = op.mAttributions.get(attributionTag); + if (attributedOp == null) { + Slog.e(TAG, "Attribution not found: uid=" + uid + " pkg=" + packageName + "(" + + attributionTag + ") op=" + AppOpsManager.opToName(code)); + return; + } + + if (attributedOp.isRunning() || attributedOp.isPaused()) { + attributedOp.finished(clientId); + } else { + Slog.e(TAG, "Operation not started: uid=" + uid + " pkg=" + packageName + "(" + + attributionTag + ") op=" + AppOpsManager.opToName(code)); + } + } + } + + void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull String packageName, + @Nullable String attributionTag, boolean active, + @AttributionFlags int attributionFlags, int attributionChainId) { + ArraySet<ActiveCallback> dispatchedCallbacks = null; + final int callbackListCount = mActiveWatchers.size(); + for (int i = 0; i < callbackListCount; i++) { + final SparseArray<ActiveCallback> callbacks = mActiveWatchers.valueAt(i); + ActiveCallback callback = callbacks.get(code); + if (callback != null) { + if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) { + continue; + } + if (dispatchedCallbacks == null) { + dispatchedCallbacks = new ArraySet<>(); + } + dispatchedCallbacks.add(callback); + } + } + if (dispatchedCallbacks == null) { + return; + } + mHandler.sendMessage(PooledLambda.obtainMessage( + AppOpsServiceImpl::notifyOpActiveChanged, + this, dispatchedCallbacks, code, uid, packageName, attributionTag, active, + attributionFlags, attributionChainId)); + } + + private void notifyOpActiveChanged(ArraySet<ActiveCallback> callbacks, + int code, int uid, @NonNull String packageName, @Nullable String attributionTag, + boolean active, @AttributionFlags int attributionFlags, int attributionChainId) { + // There are features watching for mode changes such as window manager + // and location manager which are in our process. The callbacks in these + // features may require permissions our remote caller does not have. + final long identity = Binder.clearCallingIdentity(); + try { + final int callbackCount = callbacks.size(); + for (int i = 0; i < callbackCount; i++) { + final ActiveCallback callback = callbacks.valueAt(i); + try { + if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) { + continue; + } + callback.mCallback.opActiveChanged(code, uid, packageName, attributionTag, + active, attributionFlags, attributionChainId); + } catch (RemoteException e) { + /* do nothing */ + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName, + String attributionTag, @OpFlags int flags, @Mode int result, + @AppOpsManager.OnOpStartedListener.StartedType int startedType, + @AttributionFlags int attributionFlags, int attributionChainId) { + ArraySet<StartedCallback> dispatchedCallbacks = null; + final int callbackListCount = mStartedWatchers.size(); + for (int i = 0; i < callbackListCount; i++) { + final SparseArray<StartedCallback> callbacks = mStartedWatchers.valueAt(i); + + StartedCallback callback = callbacks.get(code); + if (callback != null) { + if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) { + continue; + } + + if (dispatchedCallbacks == null) { + dispatchedCallbacks = new ArraySet<>(); + } + dispatchedCallbacks.add(callback); + } + } + + if (dispatchedCallbacks == null) { + return; + } + + mHandler.sendMessage(PooledLambda.obtainMessage( + AppOpsServiceImpl::notifyOpStarted, + this, dispatchedCallbacks, code, uid, pkgName, attributionTag, flags, + result, startedType, attributionFlags, attributionChainId)); + } + + private void notifyOpStarted(ArraySet<StartedCallback> callbacks, + int code, int uid, String packageName, String attributionTag, @OpFlags int flags, + @Mode int result, @AppOpsManager.OnOpStartedListener.StartedType int startedType, + @AttributionFlags int attributionFlags, int attributionChainId) { + final long identity = Binder.clearCallingIdentity(); + try { + final int callbackCount = callbacks.size(); + for (int i = 0; i < callbackCount; i++) { + final StartedCallback callback = callbacks.valueAt(i); + try { + if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) { + continue; + } + callback.mCallback.opStarted(code, uid, packageName, attributionTag, flags, + result, startedType, attributionFlags, attributionChainId); + } catch (RemoteException e) { + /* do nothing */ + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + private void scheduleOpNotedIfNeededLocked(int code, int uid, String packageName, + String attributionTag, @OpFlags int flags, @Mode int result) { + ArraySet<NotedCallback> dispatchedCallbacks = null; + final int callbackListCount = mNotedWatchers.size(); + for (int i = 0; i < callbackListCount; i++) { + final SparseArray<NotedCallback> callbacks = mNotedWatchers.valueAt(i); + final NotedCallback callback = callbacks.get(code); + if (callback != null) { + if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) { + continue; + } + if (dispatchedCallbacks == null) { + dispatchedCallbacks = new ArraySet<>(); + } + dispatchedCallbacks.add(callback); + } + } + if (dispatchedCallbacks == null) { + return; + } + mHandler.sendMessage(PooledLambda.obtainMessage( + AppOpsServiceImpl::notifyOpChecked, + this, dispatchedCallbacks, code, uid, packageName, attributionTag, flags, + result)); + } + + private void notifyOpChecked(ArraySet<NotedCallback> callbacks, + int code, int uid, String packageName, String attributionTag, @OpFlags int flags, + @Mode int result) { + // There are features watching for checks in our process. The callbacks in + // these features may require permissions our remote caller does not have. + final long identity = Binder.clearCallingIdentity(); + try { + final int callbackCount = callbacks.size(); + for (int i = 0; i < callbackCount; i++) { + final NotedCallback callback = callbacks.valueAt(i); + try { + if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) { + continue; + } + callback.mCallback.opNoted(code, uid, packageName, attributionTag, flags, + result); + } catch (RemoteException e) { + /* do nothing */ + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + private void verifyIncomingUid(int uid) { + if (uid == Binder.getCallingUid()) { + return; + } + if (Binder.getCallingPid() == Process.myPid()) { + return; + } + mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS, + Binder.getCallingPid(), Binder.getCallingUid(), null); + } + + private boolean shouldIgnoreCallback(int op, int watcherPid, int watcherUid) { + // If it's a restricted read op, ignore it if watcher doesn't have manage ops permission, + // as watcher should not use this to signal if the value is changed. + return opRestrictsRead(op) && mContext.checkPermission(Manifest.permission.MANAGE_APPOPS, + watcherPid, watcherUid) != PackageManager.PERMISSION_GRANTED; + } + + private void verifyIncomingOp(int op) { + if (op >= 0 && op < AppOpsManager._NUM_OP) { + // Enforce manage appops permission if it's a restricted read op. + if (opRestrictsRead(op)) { + mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS, + Binder.getCallingPid(), Binder.getCallingUid(), "verifyIncomingOp"); + } + return; + } + throw new IllegalArgumentException("Bad operation #" + op); + } + + private boolean isIncomingPackageValid(@Nullable String packageName, @UserIdInt int userId) { + final int callingUid = Binder.getCallingUid(); + // Handle the special UIDs that don't have actual packages (audioserver, cameraserver, etc). + if (packageName == null || isSpecialPackage(callingUid, packageName)) { + return true; + } + + // If the package doesn't exist, #verifyAndGetBypass would throw a SecurityException in + // the end. Although that exception would be caught and return, we could make it return + // early. + if (!isPackageExisted(packageName)) { + return false; + } + + if (getPackageManagerInternal().filterAppAccess(packageName, callingUid, userId)) { + Slog.w(TAG, packageName + " not found from " + callingUid); + return false; + } + + return true; + } + + private boolean isSpecialPackage(int callingUid, @Nullable String packageName) { + final String resolvedPackage = AppOpsManager.resolvePackageName(callingUid, packageName); + return callingUid == Process.SYSTEM_UID + || resolveUid(resolvedPackage) != Process.INVALID_UID; + } + + private @Nullable UidState getUidStateLocked(int uid, boolean edit) { + UidState uidState = mUidStates.get(uid); + if (uidState == null) { + if (!edit) { + return null; + } + uidState = new UidState(uid); + mUidStates.put(uid, uidState); + } + + return uidState; + } + + @Override + public void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible) { + synchronized (this) { + getUidStateTracker().updateAppWidgetVisibility(uidPackageNames, visible); + } + } + + /** + * @return {@link PackageManagerInternal} + */ + private @NonNull PackageManagerInternal getPackageManagerInternal() { + if (mPackageManagerInternal == null) { + mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); + } + + return mPackageManagerInternal; + } + + @Override + public void verifyPackage(int uid, String packageName) { + verifyAndGetBypass(uid, packageName, null); + } + + /** + * Create a restriction description matching the properties of the package. + * + * @param pkg The package to create the restriction description for + * @return The restriction matching the package + */ + private RestrictionBypass getBypassforPackage(@NonNull AndroidPackage pkg) { + return new RestrictionBypass(pkg.getUid() == Process.SYSTEM_UID, pkg.isPrivileged(), + mContext.checkPermission(android.Manifest.permission + .EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS, -1, pkg.getUid()) + == PackageManager.PERMISSION_GRANTED); + } + + /** + * @see #verifyAndGetBypass(int, String, String, String) + */ + private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName, + @Nullable String attributionTag) { + return verifyAndGetBypass(uid, packageName, attributionTag, null); + } + + /** + * Verify that package belongs to uid and return the {@link RestrictionBypass bypass + * description} for the package, along with a boolean indicating whether the attribution tag is + * valid. + * + * @param uid The uid the package belongs to + * @param packageName The package the might belong to the uid + * @param attributionTag attribution tag or {@code null} if no need to verify + * @param proxyPackageName The proxy package, from which the attribution tag is to be pulled + * @return PackageVerificationResult containing {@link RestrictionBypass} and whether the + * attribution tag is valid + */ + private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName, + @Nullable String attributionTag, @Nullable String proxyPackageName) { + if (uid == Process.ROOT_UID) { + // For backwards compatibility, don't check package name for root UID. + return new PackageVerificationResult(null, + /* isAttributionTagValid */ true); + } + if (Process.isSdkSandboxUid(uid)) { + // SDK sandbox processes run in their own UID range, but their associated + // UID for checks should always be the UID of the package implementing SDK sandbox + // service. + // TODO: We will need to modify the callers of this function instead, so + // modifications and checks against the app ops state are done with the + // correct UID. + try { + final PackageManager pm = mContext.getPackageManager(); + final String supplementalPackageName = pm.getSdkSandboxPackageName(); + if (Objects.equals(packageName, supplementalPackageName)) { + uid = pm.getPackageUidAsUser(supplementalPackageName, + PackageManager.PackageInfoFlags.of(0), UserHandle.getUserId(uid)); + } + } catch (PackageManager.NameNotFoundException e) { + // Shouldn't happen for the supplemental package + e.printStackTrace(); + } + } + + + // Do not check if uid/packageName/attributionTag is already known. + synchronized (this) { + UidState uidState = mUidStates.get(uid); + if (uidState != null && uidState.pkgOps != null) { + Ops ops = uidState.pkgOps.get(packageName); + + if (ops != null && (attributionTag == null || ops.knownAttributionTags.contains( + attributionTag)) && ops.bypass != null) { + return new PackageVerificationResult(ops.bypass, + ops.validAttributionTags.contains(attributionTag)); + } + } + } + + int callingUid = Binder.getCallingUid(); + + // Allow any attribution tag for resolvable uids + int pkgUid; + if (Objects.equals(packageName, "com.android.shell")) { + // Special case for the shell which is a package but should be able + // to bypass app attribution tag restrictions. + pkgUid = Process.SHELL_UID; + } else { + pkgUid = resolveUid(packageName); + } + if (pkgUid != Process.INVALID_UID) { + if (pkgUid != UserHandle.getAppId(uid)) { + Slog.e(TAG, "Bad call made by uid " + callingUid + ". " + + "Package \"" + packageName + "\" does not belong to uid " + uid + "."); + String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not"; + throw new SecurityException("Specified package \"" + packageName + "\" under uid " + + UserHandle.getAppId(uid) + otherUidMessage); + } + return new PackageVerificationResult(RestrictionBypass.UNRESTRICTED, + /* isAttributionTagValid */ true); + } + + int userId = UserHandle.getUserId(uid); + RestrictionBypass bypass = null; + boolean isAttributionTagValid = false; + + final long ident = Binder.clearCallingIdentity(); + try { + PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class); + AndroidPackage pkg = pmInt.getPackage(packageName); + if (pkg != null) { + isAttributionTagValid = isAttributionInPackage(pkg, attributionTag); + pkgUid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.getUid())); + bypass = getBypassforPackage(pkg); + } + if (!isAttributionTagValid) { + AndroidPackage proxyPkg = proxyPackageName != null + ? pmInt.getPackage(proxyPackageName) : null; + // Re-check in proxy. + isAttributionTagValid = isAttributionInPackage(proxyPkg, attributionTag); + String msg; + if (pkg != null && isAttributionTagValid) { + msg = "attributionTag " + attributionTag + " declared in manifest of the proxy" + + " package " + proxyPackageName + ", this is not advised"; + } else if (pkg != null) { + msg = "attributionTag " + attributionTag + " not declared in manifest of " + + packageName; + } else { + msg = "package " + packageName + " not found, can't check for " + + "attributionTag " + attributionTag; + } + + try { + if (!mPlatformCompat.isChangeEnabledByPackageName( + SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE, packageName, + userId) || !mPlatformCompat.isChangeEnabledByUid( + SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE, + callingUid)) { + // Do not override tags if overriding is not enabled for this package + isAttributionTagValid = true; + } + Slog.e(TAG, msg); + } catch (RemoteException neverHappens) { + } + } + } finally { + Binder.restoreCallingIdentity(ident); + } + + if (pkgUid != uid) { + Slog.e(TAG, "Bad call made by uid " + callingUid + ". " + + "Package \"" + packageName + "\" does not belong to uid " + uid + "."); + String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not"; + throw new SecurityException("Specified package \"" + packageName + "\" under uid " + uid + + otherUidMessage); + } + + return new PackageVerificationResult(bypass, isAttributionTagValid); + } + + private boolean isAttributionInPackage(@Nullable AndroidPackage pkg, + @Nullable String attributionTag) { + if (pkg == null) { + return false; + } else if (attributionTag == null) { + return true; + } + if (pkg.getAttributions() != null) { + int numAttributions = pkg.getAttributions().size(); + for (int i = 0; i < numAttributions; i++) { + if (pkg.getAttributions().get(i).getTag().equals(attributionTag)) { + return true; + } + } + } + + return false; + } + + /** + * Get (and potentially create) ops. + * + * @param uid The uid the package belongs to + * @param packageName The name of the package + * @param attributionTag attribution tag + * @param isAttributionTagValid whether the given attribution tag is valid + * @param bypass When to bypass certain op restrictions (can be null if edit + * == false) + * @param edit If an ops does not exist, create the ops? + * @return The ops + */ + private Ops getOpsLocked(int uid, String packageName, @Nullable String attributionTag, + boolean isAttributionTagValid, @Nullable RestrictionBypass bypass, boolean edit) { + UidState uidState = getUidStateLocked(uid, edit); + if (uidState == null) { + return null; + } + + if (uidState.pkgOps == null) { + if (!edit) { + return null; + } + uidState.pkgOps = new ArrayMap<>(); + } + + Ops ops = uidState.pkgOps.get(packageName); + if (ops == null) { + if (!edit) { + return null; + } + ops = new Ops(packageName, uidState); + uidState.pkgOps.put(packageName, ops); + } + + if (edit) { + if (bypass != null) { + ops.bypass = bypass; + } + + if (attributionTag != null) { + ops.knownAttributionTags.add(attributionTag); + if (isAttributionTagValid) { + ops.validAttributionTags.add(attributionTag); + } else { + ops.validAttributionTags.remove(attributionTag); + } + } + } + + return ops; + } + + @Override + public void scheduleWriteLocked() { + if (!mWriteScheduled) { + mWriteScheduled = true; + mHandler.postDelayed(mWriteRunner, WRITE_DELAY); + } + } + + @Override + public void scheduleFastWriteLocked() { + if (!mFastWriteScheduled) { + mWriteScheduled = true; + mFastWriteScheduled = true; + mHandler.removeCallbacks(mWriteRunner); + mHandler.postDelayed(mWriteRunner, 10 * 1000); + } + } + + /** + * Get the state of an op for a uid. + * + * @param code The code of the op + * @param uid The uid the of the package + * @param packageName The package name for which to get the state for + * @param attributionTag The attribution tag + * @param isAttributionTagValid Whether the given attribution tag is valid + * @param bypass When to bypass certain op restrictions (can be null if edit + * == false) + * @param edit Iff {@code true} create the {@link Op} object if not yet created + * @return The {@link Op state} of the op + */ + private @Nullable Op getOpLocked(int code, int uid, @NonNull String packageName, + @Nullable String attributionTag, boolean isAttributionTagValid, + @Nullable RestrictionBypass bypass, boolean edit) { + Ops ops = getOpsLocked(uid, packageName, attributionTag, isAttributionTagValid, bypass, + edit); + if (ops == null) { + return null; + } + return getOpLocked(ops, code, uid, edit); + } + + private Op getOpLocked(Ops ops, int code, int uid, boolean edit) { + Op op = ops.get(code); + if (op == null) { + if (!edit) { + return null; + } + op = new Op(ops.uidState, ops.packageName, code, uid); + ops.put(code, op); + } + if (edit) { + scheduleWriteLocked(); + } + return op; + } + + private boolean isOpRestrictedDueToSuspend(int code, String packageName, int uid) { + if (!ArrayUtils.contains(OPS_RESTRICTED_ON_SUSPEND, code)) { + return false; + } + final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class); + return pmi.isPackageSuspended(packageName, UserHandle.getUserId(uid)); + } + + private boolean isOpRestrictedLocked(int uid, int code, String packageName, + String attributionTag, @Nullable RestrictionBypass appBypass, boolean isCheckOp) { + int restrictionSetCount = mOpGlobalRestrictions.size(); + + for (int i = 0; i < restrictionSetCount; i++) { + ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.valueAt(i); + if (restrictionState.hasRestriction(code)) { + return true; + } + } + + int userHandle = UserHandle.getUserId(uid); + restrictionSetCount = mOpUserRestrictions.size(); + + for (int i = 0; i < restrictionSetCount; i++) { + // For each client, check that the given op is not restricted, or that the given + // package is exempt from the restriction. + ClientUserRestrictionState restrictionState = mOpUserRestrictions.valueAt(i); + if (restrictionState.hasRestriction(code, packageName, attributionTag, userHandle, + isCheckOp)) { + RestrictionBypass opBypass = opAllowSystemBypassRestriction(code); + if (opBypass != null) { + // If we are the system, bypass user restrictions for certain codes + synchronized (this) { + if (opBypass.isSystemUid && appBypass != null && appBypass.isSystemUid) { + return false; + } + if (opBypass.isPrivileged && appBypass != null && appBypass.isPrivileged) { + return false; + } + if (opBypass.isRecordAudioRestrictionExcept && appBypass != null + && appBypass.isRecordAudioRestrictionExcept) { + return false; + } + } + } + return true; + } + } + return false; + } + + @Override + public void readState() { + int oldVersion = NO_VERSION; + synchronized (mFile) { + synchronized (this) { + FileInputStream stream; + try { + stream = mFile.openRead(); + } catch (FileNotFoundException e) { + Slog.i(TAG, "No existing app ops " + mFile.getBaseFile() + "; starting empty"); + return; + } + boolean success = false; + mUidStates.clear(); + mAppOpsServiceInterface.clearAllModes(); + try { + TypedXmlPullParser parser = Xml.resolvePullParser(stream); + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + // Parse next until we reach the start or end + } + + if (type != XmlPullParser.START_TAG) { + throw new IllegalStateException("no start tag found"); + } + + oldVersion = parser.getAttributeInt(null, "v", NO_VERSION); + + int outerDepth = parser.getDepth(); + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + + String tagName = parser.getName(); + if (tagName.equals("pkg")) { + readPackage(parser); + } else if (tagName.equals("uid")) { + readUidOps(parser); + } else { + Slog.w(TAG, "Unknown element under <app-ops>: " + + parser.getName()); + XmlUtils.skipCurrentTag(parser); + } + } + success = true; + } catch (IllegalStateException e) { + Slog.w(TAG, "Failed parsing " + e); + } catch (NullPointerException e) { + Slog.w(TAG, "Failed parsing " + e); + } catch (NumberFormatException e) { + Slog.w(TAG, "Failed parsing " + e); + } catch (XmlPullParserException e) { + Slog.w(TAG, "Failed parsing " + e); + } catch (IOException e) { + Slog.w(TAG, "Failed parsing " + e); + } catch (IndexOutOfBoundsException e) { + Slog.w(TAG, "Failed parsing " + e); + } finally { + if (!success) { + mUidStates.clear(); + mAppOpsServiceInterface.clearAllModes(); + } + try { + stream.close(); + } catch (IOException e) { + } + } + } + } + synchronized (this) { + upgradeLocked(oldVersion); + } + } + + private void upgradeRunAnyInBackgroundLocked() { + for (int i = 0; i < mUidStates.size(); i++) { + final UidState uidState = mUidStates.valueAt(i); + if (uidState == null) { + continue; + } + SparseIntArray opModes = uidState.getNonDefaultUidModes(); + if (opModes != null) { + final int idx = opModes.indexOfKey(AppOpsManager.OP_RUN_IN_BACKGROUND); + if (idx >= 0) { + uidState.setUidMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, + opModes.valueAt(idx)); + } + } + if (uidState.pkgOps == null) { + continue; + } + boolean changed = false; + for (int j = 0; j < uidState.pkgOps.size(); j++) { + Ops ops = uidState.pkgOps.valueAt(j); + if (ops != null) { + final Op op = ops.get(AppOpsManager.OP_RUN_IN_BACKGROUND); + if (op != null && op.getMode() != AppOpsManager.opToDefaultMode(op.op)) { + final Op copy = new Op(op.uidState, op.packageName, + AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uidState.uid); + copy.setMode(op.getMode()); + ops.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, copy); + changed = true; + } + } + } + if (changed) { + uidState.evalForegroundOps(); + } + } + } + + private void upgradeLocked(int oldVersion) { + if (oldVersion >= CURRENT_VERSION) { + return; + } + Slog.d(TAG, "Upgrading app-ops xml from version " + oldVersion + " to " + CURRENT_VERSION); + switch (oldVersion) { + case NO_VERSION: + upgradeRunAnyInBackgroundLocked(); + // fall through + case 1: + // for future upgrades + } + scheduleFastWriteLocked(); + } + + private void readUidOps(TypedXmlPullParser parser) throws NumberFormatException, + XmlPullParserException, IOException { + final int uid = parser.getAttributeInt(null, "n"); + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + + String tagName = parser.getName(); + if (tagName.equals("op")) { + final int code = parser.getAttributeInt(null, "n"); + final int mode = parser.getAttributeInt(null, "m"); + setUidMode(code, uid, mode, null); + } else { + Slog.w(TAG, "Unknown element under <uid-ops>: " + + parser.getName()); + XmlUtils.skipCurrentTag(parser); + } + } + } + + private void readPackage(TypedXmlPullParser parser) + throws NumberFormatException, XmlPullParserException, IOException { + String pkgName = parser.getAttributeValue(null, "n"); + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + + String tagName = parser.getName(); + if (tagName.equals("uid")) { + readUid(parser, pkgName); + } else { + Slog.w(TAG, "Unknown element under <pkg>: " + + parser.getName()); + XmlUtils.skipCurrentTag(parser); + } + } + } + + private void readUid(TypedXmlPullParser parser, String pkgName) + throws NumberFormatException, XmlPullParserException, IOException { + int uid = parser.getAttributeInt(null, "n"); + final UidState uidState = getUidStateLocked(uid, true); + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + String tagName = parser.getName(); + if (tagName.equals("op")) { + readOp(parser, uidState, pkgName); + } else { + Slog.w(TAG, "Unknown element under <pkg>: " + + parser.getName()); + XmlUtils.skipCurrentTag(parser); + } + } + uidState.evalForegroundOps(); + } + + private void readAttributionOp(TypedXmlPullParser parser, @NonNull Op parent, + @Nullable String attribution) + throws NumberFormatException, IOException, XmlPullParserException { + final AttributedOp attributedOp = parent.getOrCreateAttribution(parent, attribution); + + final long key = parser.getAttributeLong(null, "n"); + final int uidState = extractUidStateFromKey(key); + final int opFlags = extractFlagsFromKey(key); + + final long accessTime = parser.getAttributeLong(null, "t", 0); + final long rejectTime = parser.getAttributeLong(null, "r", 0); + final long accessDuration = parser.getAttributeLong(null, "d", -1); + final String proxyPkg = XmlUtils.readStringAttribute(parser, "pp"); + final int proxyUid = parser.getAttributeInt(null, "pu", Process.INVALID_UID); + final String proxyAttributionTag = XmlUtils.readStringAttribute(parser, "pc"); + + if (accessTime > 0) { + attributedOp.accessed(accessTime, accessDuration, proxyUid, proxyPkg, + proxyAttributionTag, uidState, opFlags); + } + if (rejectTime > 0) { + attributedOp.rejected(rejectTime, uidState, opFlags); + } + } + + private void readOp(TypedXmlPullParser parser, + @NonNull UidState uidState, @NonNull String pkgName) + throws NumberFormatException, XmlPullParserException, IOException { + int opCode = parser.getAttributeInt(null, "n"); + Op op = new Op(uidState, pkgName, opCode, uidState.uid); + + final int mode = parser.getAttributeInt(null, "m", AppOpsManager.opToDefaultMode(op.op)); + op.setMode(mode); + + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + String tagName = parser.getName(); + if (tagName.equals("st")) { + readAttributionOp(parser, op, XmlUtils.readStringAttribute(parser, "id")); + } else { + Slog.w(TAG, "Unknown element under <op>: " + + parser.getName()); + XmlUtils.skipCurrentTag(parser); + } + } + + if (uidState.pkgOps == null) { + uidState.pkgOps = new ArrayMap<>(); + } + Ops ops = uidState.pkgOps.get(pkgName); + if (ops == null) { + ops = new Ops(pkgName, uidState); + uidState.pkgOps.put(pkgName, ops); + } + ops.put(op.op, op); + } + + @Override + public void writeState() { + synchronized (mFile) { + FileOutputStream stream; + try { + stream = mFile.startWrite(); + } catch (IOException e) { + Slog.w(TAG, "Failed to write state: " + e); + return; + } + + List<AppOpsManager.PackageOps> allOps = getPackagesForOps(null); + + try { + TypedXmlSerializer out = Xml.resolveSerializer(stream); + out.startDocument(null, true); + out.startTag(null, "app-ops"); + out.attributeInt(null, "v", CURRENT_VERSION); + + SparseArray<SparseIntArray> uidStatesClone; + synchronized (this) { + uidStatesClone = new SparseArray<>(mUidStates.size()); + + final int uidStateCount = mUidStates.size(); + for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) { + UidState uidState = mUidStates.valueAt(uidStateNum); + int uid = mUidStates.keyAt(uidStateNum); + + SparseIntArray opModes = uidState.getNonDefaultUidModes(); + if (opModes != null && opModes.size() > 0) { + uidStatesClone.put(uid, opModes); + } + } + } + + final int uidStateCount = uidStatesClone.size(); + for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) { + SparseIntArray opModes = uidStatesClone.valueAt(uidStateNum); + if (opModes != null && opModes.size() > 0) { + out.startTag(null, "uid"); + out.attributeInt(null, "n", uidStatesClone.keyAt(uidStateNum)); + final int opCount = opModes.size(); + for (int opCountNum = 0; opCountNum < opCount; opCountNum++) { + final int op = opModes.keyAt(opCountNum); + final int mode = opModes.valueAt(opCountNum); + out.startTag(null, "op"); + out.attributeInt(null, "n", op); + out.attributeInt(null, "m", mode); + out.endTag(null, "op"); + } + out.endTag(null, "uid"); + } + } + + if (allOps != null) { + String lastPkg = null; + for (int i = 0; i < allOps.size(); i++) { + AppOpsManager.PackageOps pkg = allOps.get(i); + if (!Objects.equals(pkg.getPackageName(), lastPkg)) { + if (lastPkg != null) { + out.endTag(null, "pkg"); + } + lastPkg = pkg.getPackageName(); + if (lastPkg != null) { + out.startTag(null, "pkg"); + out.attribute(null, "n", lastPkg); + } + } + out.startTag(null, "uid"); + out.attributeInt(null, "n", pkg.getUid()); + List<AppOpsManager.OpEntry> ops = pkg.getOps(); + for (int j = 0; j < ops.size(); j++) { + AppOpsManager.OpEntry op = ops.get(j); + out.startTag(null, "op"); + out.attributeInt(null, "n", op.getOp()); + if (op.getMode() != AppOpsManager.opToDefaultMode(op.getOp())) { + out.attributeInt(null, "m", op.getMode()); + } + + for (String attributionTag : op.getAttributedOpEntries().keySet()) { + final AttributedOpEntry attribution = + op.getAttributedOpEntries().get(attributionTag); + + final ArraySet<Long> keys = attribution.collectKeys(); + + final int keyCount = keys.size(); + for (int k = 0; k < keyCount; k++) { + final long key = keys.valueAt(k); + + final int uidState = AppOpsManager.extractUidStateFromKey(key); + final int flags = AppOpsManager.extractFlagsFromKey(key); + + final long accessTime = attribution.getLastAccessTime(uidState, + uidState, flags); + final long rejectTime = attribution.getLastRejectTime(uidState, + uidState, flags); + final long accessDuration = attribution.getLastDuration( + uidState, uidState, flags); + // Proxy information for rejections is not backed up + final OpEventProxyInfo proxy = attribution.getLastProxyInfo( + uidState, uidState, flags); + + if (accessTime <= 0 && rejectTime <= 0 && accessDuration <= 0 + && proxy == null) { + continue; + } + + String proxyPkg = null; + String proxyAttributionTag = null; + int proxyUid = Process.INVALID_UID; + if (proxy != null) { + proxyPkg = proxy.getPackageName(); + proxyAttributionTag = proxy.getAttributionTag(); + proxyUid = proxy.getUid(); + } + + out.startTag(null, "st"); + if (attributionTag != null) { + out.attribute(null, "id", attributionTag); + } + out.attributeLong(null, "n", key); + if (accessTime > 0) { + out.attributeLong(null, "t", accessTime); + } + if (rejectTime > 0) { + out.attributeLong(null, "r", rejectTime); + } + if (accessDuration > 0) { + out.attributeLong(null, "d", accessDuration); + } + if (proxyPkg != null) { + out.attribute(null, "pp", proxyPkg); + } + if (proxyAttributionTag != null) { + out.attribute(null, "pc", proxyAttributionTag); + } + if (proxyUid >= 0) { + out.attributeInt(null, "pu", proxyUid); + } + out.endTag(null, "st"); + } + } + + out.endTag(null, "op"); + } + out.endTag(null, "uid"); + } + if (lastPkg != null) { + out.endTag(null, "pkg"); + } + } + + out.endTag(null, "app-ops"); + out.endDocument(); + mFile.finishWrite(stream); + } catch (IOException e) { + Slog.w(TAG, "Failed to write state, restoring backup.", e); + mFile.failWrite(stream); + } + } + mHistoricalRegistry.writeAndClearDiscreteHistory(); + } + + private void dumpHelp(PrintWriter pw) { + pw.println("AppOps service (appops) dump options:"); + pw.println(" -h"); + pw.println(" Print this help text."); + pw.println(" --op [OP]"); + pw.println(" Limit output to data associated with the given app op code."); + pw.println(" --mode [MODE]"); + pw.println(" Limit output to data associated with the given app op mode."); + pw.println(" --package [PACKAGE]"); + pw.println(" Limit output to data associated with the given package name."); + pw.println(" --attributionTag [attributionTag]"); + pw.println(" Limit output to data associated with the given attribution tag."); + pw.println(" --include-discrete [n]"); + pw.println(" Include discrete ops limited to n per dimension. Use zero for no limit."); + pw.println(" --watchers"); + pw.println(" Only output the watcher sections."); + pw.println(" --history"); + pw.println(" Only output history."); + pw.println(" --uid-state-changes"); + pw.println(" Include logs about uid state changes."); + } + + private void dumpStatesLocked(@NonNull PrintWriter pw, @Nullable String filterAttributionTag, + @HistoricalOpsRequestFilter int filter, long nowElapsed, @NonNull Op op, long now, + @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix) { + final int numAttributions = op.mAttributions.size(); + for (int i = 0; i < numAttributions; i++) { + if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0 && !Objects.equals( + op.mAttributions.keyAt(i), filterAttributionTag)) { + continue; + } + + pw.print(prefix + op.mAttributions.keyAt(i) + "=[\n"); + dumpStatesLocked(pw, nowElapsed, op, op.mAttributions.keyAt(i), now, sdf, date, + prefix + " "); + pw.print(prefix + "]\n"); + } + } + + private void dumpStatesLocked(@NonNull PrintWriter pw, long nowElapsed, @NonNull Op op, + @Nullable String attributionTag, long now, @NonNull SimpleDateFormat sdf, + @NonNull Date date, @NonNull String prefix) { + + final AttributedOpEntry entry = op.createSingleAttributionEntryLocked( + attributionTag).getAttributedOpEntries().get(attributionTag); + + final ArraySet<Long> keys = entry.collectKeys(); + + final int keyCount = keys.size(); + for (int k = 0; k < keyCount; k++) { + final long key = keys.valueAt(k); + + final int uidState = AppOpsManager.extractUidStateFromKey(key); + final int flags = AppOpsManager.extractFlagsFromKey(key); + + final long accessTime = entry.getLastAccessTime(uidState, uidState, flags); + final long rejectTime = entry.getLastRejectTime(uidState, uidState, flags); + final long accessDuration = entry.getLastDuration(uidState, uidState, flags); + final OpEventProxyInfo proxy = entry.getLastProxyInfo(uidState, uidState, flags); + + String proxyPkg = null; + String proxyAttributionTag = null; + int proxyUid = Process.INVALID_UID; + if (proxy != null) { + proxyPkg = proxy.getPackageName(); + proxyAttributionTag = proxy.getAttributionTag(); + proxyUid = proxy.getUid(); + } + + if (accessTime > 0) { + pw.print(prefix); + pw.print("Access: "); + pw.print(AppOpsManager.keyToString(key)); + pw.print(" "); + date.setTime(accessTime); + pw.print(sdf.format(date)); + pw.print(" ("); + TimeUtils.formatDuration(accessTime - now, pw); + pw.print(")"); + if (accessDuration > 0) { + pw.print(" duration="); + TimeUtils.formatDuration(accessDuration, pw); + } + if (proxyUid >= 0) { + pw.print(" proxy["); + pw.print("uid="); + pw.print(proxyUid); + pw.print(", pkg="); + pw.print(proxyPkg); + pw.print(", attributionTag="); + pw.print(proxyAttributionTag); + pw.print("]"); + } + pw.println(); + } + + if (rejectTime > 0) { + pw.print(prefix); + pw.print("Reject: "); + pw.print(AppOpsManager.keyToString(key)); + date.setTime(rejectTime); + pw.print(sdf.format(date)); + pw.print(" ("); + TimeUtils.formatDuration(rejectTime - now, pw); + pw.print(")"); + if (proxyUid >= 0) { + pw.print(" proxy["); + pw.print("uid="); + pw.print(proxyUid); + pw.print(", pkg="); + pw.print(proxyPkg); + pw.print(", attributionTag="); + pw.print(proxyAttributionTag); + pw.print("]"); + } + pw.println(); + } + } + + final AttributedOp attributedOp = op.mAttributions.get(attributionTag); + if (attributedOp.isRunning()) { + long earliestElapsedTime = Long.MAX_VALUE; + long maxNumStarts = 0; + int numInProgressEvents = attributedOp.mInProgressEvents.size(); + for (int i = 0; i < numInProgressEvents; i++) { + AttributedOp.InProgressStartOpEvent event = + attributedOp.mInProgressEvents.valueAt(i); + + earliestElapsedTime = Math.min(earliestElapsedTime, event.getStartElapsedTime()); + maxNumStarts = Math.max(maxNumStarts, event.mNumUnfinishedStarts); + } + + pw.print(prefix + "Running start at: "); + TimeUtils.formatDuration(nowElapsed - earliestElapsedTime, pw); + pw.println(); + + if (maxNumStarts > 1) { + pw.print(prefix + "startNesting="); + pw.println(maxNumStarts); + } + } + } + + @NeverCompile // Avoid size overhead of debugging code. + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return; + + int dumpOp = OP_NONE; + String dumpPackage = null; + String dumpAttributionTag = null; + int dumpUid = Process.INVALID_UID; + int dumpMode = -1; + boolean dumpWatchers = false; + // TODO ntmyren: Remove the dumpHistory and dumpFilter + boolean dumpHistory = false; + boolean includeDiscreteOps = false; + boolean dumpUidStateChangeLogs = false; + int nDiscreteOps = 10; + @HistoricalOpsRequestFilter int dumpFilter = 0; + boolean dumpAll = false; + + if (args != null) { + for (int i = 0; i < args.length; i++) { + String arg = args[i]; + if ("-h".equals(arg)) { + dumpHelp(pw); + return; + } else if ("-a".equals(arg)) { + // dump all data + dumpAll = true; + } else if ("--op".equals(arg)) { + i++; + if (i >= args.length) { + pw.println("No argument for --op option"); + return; + } + dumpOp = AppOpsService.Shell.strOpToOp(args[i], pw); + dumpFilter |= FILTER_BY_OP_NAMES; + if (dumpOp < 0) { + return; + } + } else if ("--package".equals(arg)) { + i++; + if (i >= args.length) { + pw.println("No argument for --package option"); + return; + } + dumpPackage = args[i]; + dumpFilter |= FILTER_BY_PACKAGE_NAME; + try { + dumpUid = AppGlobals.getPackageManager().getPackageUid(dumpPackage, + PackageManager.MATCH_KNOWN_PACKAGES | PackageManager.MATCH_INSTANT, + 0); + } catch (RemoteException e) { + } + if (dumpUid < 0) { + pw.println("Unknown package: " + dumpPackage); + return; + } + dumpUid = UserHandle.getAppId(dumpUid); + dumpFilter |= FILTER_BY_UID; + } else if ("--attributionTag".equals(arg)) { + i++; + if (i >= args.length) { + pw.println("No argument for --attributionTag option"); + return; + } + dumpAttributionTag = args[i]; + dumpFilter |= FILTER_BY_ATTRIBUTION_TAG; + } else if ("--mode".equals(arg)) { + i++; + if (i >= args.length) { + pw.println("No argument for --mode option"); + return; + } + dumpMode = AppOpsService.Shell.strModeToMode(args[i], pw); + if (dumpMode < 0) { + return; + } + } else if ("--watchers".equals(arg)) { + dumpWatchers = true; + } else if ("--include-discrete".equals(arg)) { + i++; + if (i >= args.length) { + pw.println("No argument for --include-discrete option"); + return; + } + try { + nDiscreteOps = Integer.valueOf(args[i]); + } catch (NumberFormatException e) { + pw.println("Wrong parameter: " + args[i]); + return; + } + includeDiscreteOps = true; + } else if ("--history".equals(arg)) { + dumpHistory = true; + } else if (arg.length() > 0 && arg.charAt(0) == '-') { + pw.println("Unknown option: " + arg); + return; + } else if ("--uid-state-changes".equals(arg)) { + dumpUidStateChangeLogs = true; + } else { + pw.println("Unknown command: " + arg); + return; + } + } + } + + final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + final Date date = new Date(); + synchronized (this) { + pw.println("Current AppOps Service state:"); + if (!dumpHistory && !dumpWatchers) { + mConstants.dump(pw); + } + pw.println(); + final long now = System.currentTimeMillis(); + final long nowElapsed = SystemClock.elapsedRealtime(); + boolean needSep = false; + if (dumpFilter == 0 && dumpMode < 0 && mProfileOwners != null && !dumpWatchers + && !dumpHistory) { + pw.println(" Profile owners:"); + for (int poi = 0; poi < mProfileOwners.size(); poi++) { + pw.print(" User #"); + pw.print(mProfileOwners.keyAt(poi)); + pw.print(": "); + UserHandle.formatUid(pw, mProfileOwners.valueAt(poi)); + pw.println(); + } + pw.println(); + } + + if (!dumpHistory) { + needSep |= mAppOpsServiceInterface.dumpListeners(dumpOp, dumpUid, dumpPackage, pw); + } + + if (mModeWatchers.size() > 0 && dumpOp < 0 && !dumpHistory) { + boolean printedHeader = false; + for (int i = 0; i < mModeWatchers.size(); i++) { + final ModeCallback cb = mModeWatchers.valueAt(i); + if (dumpPackage != null + && dumpUid != UserHandle.getAppId(cb.getWatchingUid())) { + continue; + } + needSep = true; + if (!printedHeader) { + pw.println(" All op mode watchers:"); + printedHeader = true; + } + pw.print(" "); + pw.print(Integer.toHexString(System.identityHashCode(mModeWatchers.keyAt(i)))); + pw.print(": "); + pw.println(cb); + } + } + if (mActiveWatchers.size() > 0 && dumpMode < 0) { + needSep = true; + boolean printedHeader = false; + for (int watcherNum = 0; watcherNum < mActiveWatchers.size(); watcherNum++) { + final SparseArray<ActiveCallback> activeWatchers = + mActiveWatchers.valueAt(watcherNum); + if (activeWatchers.size() <= 0) { + continue; + } + final ActiveCallback cb = activeWatchers.valueAt(0); + if (dumpOp >= 0 && activeWatchers.indexOfKey(dumpOp) < 0) { + continue; + } + if (dumpPackage != null + && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) { + continue; + } + if (!printedHeader) { + pw.println(" All op active watchers:"); + printedHeader = true; + } + pw.print(" "); + pw.print(Integer.toHexString(System.identityHashCode( + mActiveWatchers.keyAt(watcherNum)))); + pw.println(" ->"); + pw.print(" ["); + final int opCount = activeWatchers.size(); + for (int opNum = 0; opNum < opCount; opNum++) { + if (opNum > 0) { + pw.print(' '); + } + pw.print(AppOpsManager.opToName(activeWatchers.keyAt(opNum))); + if (opNum < opCount - 1) { + pw.print(','); + } + } + pw.println("]"); + pw.print(" "); + pw.println(cb); + } + } + if (mStartedWatchers.size() > 0 && dumpMode < 0) { + needSep = true; + boolean printedHeader = false; + + final int watchersSize = mStartedWatchers.size(); + for (int watcherNum = 0; watcherNum < watchersSize; watcherNum++) { + final SparseArray<StartedCallback> startedWatchers = + mStartedWatchers.valueAt(watcherNum); + if (startedWatchers.size() <= 0) { + continue; + } + + final StartedCallback cb = startedWatchers.valueAt(0); + if (dumpOp >= 0 && startedWatchers.indexOfKey(dumpOp) < 0) { + continue; + } + + if (dumpPackage != null + && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) { + continue; + } + + if (!printedHeader) { + pw.println(" All op started watchers:"); + printedHeader = true; + } + + pw.print(" "); + pw.print(Integer.toHexString(System.identityHashCode( + mStartedWatchers.keyAt(watcherNum)))); + pw.println(" ->"); + + pw.print(" ["); + final int opCount = startedWatchers.size(); + for (int opNum = 0; opNum < opCount; opNum++) { + if (opNum > 0) { + pw.print(' '); + } + + pw.print(AppOpsManager.opToName(startedWatchers.keyAt(opNum))); + if (opNum < opCount - 1) { + pw.print(','); + } + } + pw.println("]"); + + pw.print(" "); + pw.println(cb); + } + } + if (mNotedWatchers.size() > 0 && dumpMode < 0) { + needSep = true; + boolean printedHeader = false; + for (int watcherNum = 0; watcherNum < mNotedWatchers.size(); watcherNum++) { + final SparseArray<NotedCallback> notedWatchers = + mNotedWatchers.valueAt(watcherNum); + if (notedWatchers.size() <= 0) { + continue; + } + final NotedCallback cb = notedWatchers.valueAt(0); + if (dumpOp >= 0 && notedWatchers.indexOfKey(dumpOp) < 0) { + continue; + } + if (dumpPackage != null + && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) { + continue; + } + if (!printedHeader) { + pw.println(" All op noted watchers:"); + printedHeader = true; + } + pw.print(" "); + pw.print(Integer.toHexString(System.identityHashCode( + mNotedWatchers.keyAt(watcherNum)))); + pw.println(" ->"); + pw.print(" ["); + final int opCount = notedWatchers.size(); + for (int opNum = 0; opNum < opCount; opNum++) { + if (opNum > 0) { + pw.print(' '); + } + pw.print(AppOpsManager.opToName(notedWatchers.keyAt(opNum))); + if (opNum < opCount - 1) { + pw.print(','); + } + } + pw.println("]"); + pw.print(" "); + pw.println(cb); + } + } + if (needSep) { + pw.println(); + } + for (int i = 0; i < mUidStates.size(); i++) { + UidState uidState = mUidStates.valueAt(i); + final SparseIntArray opModes = uidState.getNonDefaultUidModes(); + final ArrayMap<String, Ops> pkgOps = uidState.pkgOps; + + if (dumpWatchers || dumpHistory) { + continue; + } + if (dumpOp >= 0 || dumpPackage != null || dumpMode >= 0) { + boolean hasOp = dumpOp < 0 || (opModes != null + && opModes.indexOfKey(dumpOp) >= 0); + boolean hasPackage = dumpPackage == null || dumpUid == mUidStates.keyAt(i); + boolean hasMode = dumpMode < 0; + if (!hasMode && opModes != null) { + for (int opi = 0; !hasMode && opi < opModes.size(); opi++) { + if (opModes.valueAt(opi) == dumpMode) { + hasMode = true; + } + } + } + if (pkgOps != null) { + for (int pkgi = 0; + (!hasOp || !hasPackage || !hasMode) && pkgi < pkgOps.size(); + pkgi++) { + Ops ops = pkgOps.valueAt(pkgi); + if (!hasOp && ops != null && ops.indexOfKey(dumpOp) >= 0) { + hasOp = true; + } + if (!hasMode) { + for (int opi = 0; !hasMode && opi < ops.size(); opi++) { + if (ops.valueAt(opi).getMode() == dumpMode) { + hasMode = true; + } + } + } + if (!hasPackage && dumpPackage.equals(ops.packageName)) { + hasPackage = true; + } + } + } + if (uidState.foregroundOps != null && !hasOp) { + if (uidState.foregroundOps.indexOfKey(dumpOp) > 0) { + hasOp = true; + } + } + if (!hasOp || !hasPackage || !hasMode) { + continue; + } + } + + pw.print(" Uid "); + UserHandle.formatUid(pw, uidState.uid); + pw.println(":"); + uidState.dump(pw, nowElapsed); + if (uidState.foregroundOps != null && (dumpMode < 0 + || dumpMode == AppOpsManager.MODE_FOREGROUND)) { + pw.println(" foregroundOps:"); + for (int j = 0; j < uidState.foregroundOps.size(); j++) { + if (dumpOp >= 0 && dumpOp != uidState.foregroundOps.keyAt(j)) { + continue; + } + pw.print(" "); + pw.print(AppOpsManager.opToName(uidState.foregroundOps.keyAt(j))); + pw.print(": "); + pw.println(uidState.foregroundOps.valueAt(j) ? "WATCHER" : "SILENT"); + } + pw.print(" hasForegroundWatchers="); + pw.println(uidState.hasForegroundWatchers); + } + needSep = true; + + if (opModes != null) { + final int opModeCount = opModes.size(); + for (int j = 0; j < opModeCount; j++) { + final int code = opModes.keyAt(j); + final int mode = opModes.valueAt(j); + if (dumpOp >= 0 && dumpOp != code) { + continue; + } + if (dumpMode >= 0 && dumpMode != mode) { + continue; + } + pw.print(" "); + pw.print(AppOpsManager.opToName(code)); + pw.print(": mode="); + pw.println(AppOpsManager.modeToName(mode)); + } + } + + if (pkgOps == null) { + continue; + } + + for (int pkgi = 0; pkgi < pkgOps.size(); pkgi++) { + final Ops ops = pkgOps.valueAt(pkgi); + if (dumpPackage != null && !dumpPackage.equals(ops.packageName)) { + continue; + } + boolean printedPackage = false; + for (int j = 0; j < ops.size(); j++) { + final Op op = ops.valueAt(j); + final int opCode = op.op; + if (dumpOp >= 0 && dumpOp != opCode) { + continue; + } + if (dumpMode >= 0 && dumpMode != op.getMode()) { + continue; + } + if (!printedPackage) { + pw.print(" Package "); + pw.print(ops.packageName); + pw.println(":"); + printedPackage = true; + } + pw.print(" "); + pw.print(AppOpsManager.opToName(opCode)); + pw.print(" ("); + pw.print(AppOpsManager.modeToName(op.getMode())); + final int switchOp = AppOpsManager.opToSwitch(opCode); + if (switchOp != opCode) { + pw.print(" / switch "); + pw.print(AppOpsManager.opToName(switchOp)); + final Op switchObj = ops.get(switchOp); + int mode = switchObj == null + ? AppOpsManager.opToDefaultMode(switchOp) : switchObj.getMode(); + pw.print("="); + pw.print(AppOpsManager.modeToName(mode)); + } + pw.println("): "); + dumpStatesLocked(pw, dumpAttributionTag, dumpFilter, nowElapsed, op, now, + sdf, date, " "); + } + } + } + if (needSep) { + pw.println(); + } + + boolean showUserRestrictions = !(dumpMode < 0 && !dumpWatchers && !dumpHistory); + mAppOpsRestrictions.dumpRestrictions(pw, dumpOp, dumpPackage, showUserRestrictions); + + if (dumpAll || dumpUidStateChangeLogs) { + pw.println(); + pw.println("Uid State Changes Event Log:"); + getUidStateTracker().dumpEvents(pw); + } + } + + // Must not hold the appops lock + if (dumpHistory && !dumpWatchers) { + mHistoricalRegistry.dump(" ", pw, dumpUid, dumpPackage, dumpAttributionTag, dumpOp, + dumpFilter); + } + if (includeDiscreteOps) { + pw.println("Discrete accesses: "); + mHistoricalRegistry.dumpDiscreteData(pw, dumpUid, dumpPackage, dumpAttributionTag, + dumpFilter, dumpOp, sdf, date, " ", nDiscreteOps); + } + } + + @Override + public void setUserRestrictions(Bundle restrictions, IBinder token, int userHandle) { + checkSystemUid("setUserRestrictions"); + Objects.requireNonNull(restrictions); + Objects.requireNonNull(token); + for (int i = 0; i < AppOpsManager._NUM_OP; i++) { + String restriction = AppOpsManager.opToRestriction(i); + if (restriction != null) { + setUserRestrictionNoCheck(i, restrictions.getBoolean(restriction, false), token, + userHandle, null); + } + } + } + + @Override + public void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle, + PackageTagsList excludedPackageTags) { + if (Binder.getCallingPid() != Process.myPid()) { + mContext.enforcePermission(Manifest.permission.MANAGE_APP_OPS_RESTRICTIONS, + Binder.getCallingPid(), Binder.getCallingUid(), null); + } + if (userHandle != UserHandle.getCallingUserId()) { + if (mContext.checkCallingOrSelfPermission(Manifest.permission + .INTERACT_ACROSS_USERS_FULL) != PackageManager.PERMISSION_GRANTED + && mContext.checkCallingOrSelfPermission(Manifest.permission + .INTERACT_ACROSS_USERS) != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Need INTERACT_ACROSS_USERS_FULL or" + + " INTERACT_ACROSS_USERS to interact cross user "); + } + } + verifyIncomingOp(code); + Objects.requireNonNull(token); + setUserRestrictionNoCheck(code, restricted, token, userHandle, excludedPackageTags); + } + + private void setUserRestrictionNoCheck(int code, boolean restricted, IBinder token, + int userHandle, PackageTagsList excludedPackageTags) { + synchronized (AppOpsServiceImpl.this) { + ClientUserRestrictionState restrictionState = mOpUserRestrictions.get(token); + + if (restrictionState == null) { + try { + restrictionState = new ClientUserRestrictionState(token); + } catch (RemoteException e) { + return; + } + mOpUserRestrictions.put(token, restrictionState); + } + + if (restrictionState.setRestriction(code, restricted, excludedPackageTags, + userHandle)) { + mHandler.sendMessage(PooledLambda.obtainMessage( + AppOpsServiceImpl::notifyWatchersOfChange, this, code, UID_ANY)); + mHandler.sendMessage(PooledLambda.obtainMessage( + AppOpsServiceImpl::updateStartedOpModeForUser, this, code, + restricted, userHandle)); + } + + if (restrictionState.isDefault()) { + mOpUserRestrictions.remove(token); + restrictionState.destroy(); + } + } + } + + @Override + public void setGlobalRestriction(int code, boolean restricted, IBinder token) { + if (Binder.getCallingPid() != Process.myPid()) { + throw new SecurityException("Only the system can set global restrictions"); + } + + synchronized (this) { + ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.get(token); + + if (restrictionState == null) { + try { + restrictionState = new ClientGlobalRestrictionState(token); + } catch (RemoteException e) { + return; + } + mOpGlobalRestrictions.put(token, restrictionState); + } + + if (restrictionState.setRestriction(code, restricted)) { + mHandler.sendMessage(PooledLambda.obtainMessage( + AppOpsServiceImpl::notifyWatchersOfChange, this, code, UID_ANY)); + mHandler.sendMessage(PooledLambda.obtainMessage( + AppOpsServiceImpl::updateStartedOpModeForUser, this, code, + restricted, UserHandle.USER_ALL)); + } + + if (restrictionState.isDefault()) { + mOpGlobalRestrictions.remove(token); + restrictionState.destroy(); + } + } + } + + @Override + public int getOpRestrictionCount(int code, UserHandle user, String pkg, + String attributionTag) { + int number = 0; + synchronized (this) { + int numRestrictions = mOpUserRestrictions.size(); + for (int i = 0; i < numRestrictions; i++) { + if (mOpUserRestrictions.valueAt(i) + .hasRestriction(code, pkg, attributionTag, user.getIdentifier(), + false)) { + number++; + } + } + + numRestrictions = mOpGlobalRestrictions.size(); + for (int i = 0; i < numRestrictions; i++) { + if (mOpGlobalRestrictions.valueAt(i).hasRestriction(code)) { + number++; + } + } + } + + return number; + } + + private void updateStartedOpModeForUser(int code, boolean restricted, int userId) { + synchronized (AppOpsServiceImpl.this) { + int numUids = mUidStates.size(); + for (int uidNum = 0; uidNum < numUids; uidNum++) { + int uid = mUidStates.keyAt(uidNum); + if (userId != UserHandle.USER_ALL && UserHandle.getUserId(uid) != userId) { + continue; + } + updateStartedOpModeForUidLocked(code, restricted, uid); + } + } + } + + private void updateStartedOpModeForUidLocked(int code, boolean restricted, int uid) { + UidState uidState = mUidStates.get(uid); + if (uidState == null || uidState.pkgOps == null) { + return; + } + + int numPkgOps = uidState.pkgOps.size(); + for (int pkgNum = 0; pkgNum < numPkgOps; pkgNum++) { + Ops ops = uidState.pkgOps.valueAt(pkgNum); + Op op = ops != null ? ops.get(code) : null; + if (op == null || (op.getMode() != MODE_ALLOWED && op.getMode() != MODE_FOREGROUND)) { + continue; + } + int numAttrTags = op.mAttributions.size(); + for (int attrNum = 0; attrNum < numAttrTags; attrNum++) { + AttributedOp attrOp = op.mAttributions.valueAt(attrNum); + if (restricted && attrOp.isRunning()) { + attrOp.pause(); + } else if (attrOp.isPaused()) { + attrOp.resume(); + } + } + } + } + + @Override + public void notifyWatchersOfChange(int code, int uid) { + final ArraySet<OnOpModeChangedListener> modeChangedListenerSet; + synchronized (this) { + modeChangedListenerSet = mAppOpsServiceInterface.getOpModeChangedListeners(code); + if (modeChangedListenerSet == null) { + return; + } + } + + notifyOpChanged(modeChangedListenerSet, code, uid, null); + } + + @Override + public void removeUser(int userHandle) throws RemoteException { + checkSystemUid("removeUser"); + synchronized (AppOpsServiceImpl.this) { + final int tokenCount = mOpUserRestrictions.size(); + for (int i = tokenCount - 1; i >= 0; i--) { + ClientUserRestrictionState opRestrictions = mOpUserRestrictions.valueAt(i); + opRestrictions.removeUser(userHandle); + } + removeUidsForUserLocked(userHandle); + } + } + + @Override + public boolean isOperationActive(int code, int uid, String packageName) { + if (Binder.getCallingUid() != uid) { + if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS) + != PackageManager.PERMISSION_GRANTED) { + return false; + } + } + verifyIncomingOp(code); + if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) { + return false; + } + + final String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName); + if (resolvedPackageName == null) { + return false; + } + // TODO moltmann: Allow to check for attribution op activeness + synchronized (AppOpsServiceImpl.this) { + Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null, false); + if (pkgOps == null) { + return false; + } + + Op op = pkgOps.get(code); + if (op == null) { + return false; + } + + return op.isRunning(); + } + } + + @Override + public boolean isProxying(int op, @NonNull String proxyPackageName, + @NonNull String proxyAttributionTag, int proxiedUid, + @NonNull String proxiedPackageName) { + Objects.requireNonNull(proxyPackageName); + Objects.requireNonNull(proxiedPackageName); + final long callingUid = Binder.getCallingUid(); + final long identity = Binder.clearCallingIdentity(); + try { + final List<AppOpsManager.PackageOps> packageOps = getOpsForPackage(proxiedUid, + proxiedPackageName, new int[]{op}); + if (packageOps == null || packageOps.isEmpty()) { + return false; + } + final List<OpEntry> opEntries = packageOps.get(0).getOps(); + if (opEntries.isEmpty()) { + return false; + } + final OpEntry opEntry = opEntries.get(0); + if (!opEntry.isRunning()) { + return false; + } + final OpEventProxyInfo proxyInfo = opEntry.getLastProxyInfo( + OP_FLAG_TRUSTED_PROXIED | AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED); + return proxyInfo != null && callingUid == proxyInfo.getUid() + && proxyPackageName.equals(proxyInfo.getPackageName()) + && Objects.equals(proxyAttributionTag, proxyInfo.getAttributionTag()); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void resetPackageOpsNoHistory(@NonNull String packageName) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS, + "resetPackageOpsNoHistory"); + synchronized (AppOpsServiceImpl.this) { + final int uid = mPackageManagerInternal.getPackageUid(packageName, 0, + UserHandle.getCallingUserId()); + if (uid == Process.INVALID_UID) { + return; + } + UidState uidState = mUidStates.get(uid); + if (uidState == null || uidState.pkgOps == null) { + return; + } + Ops removedOps = uidState.pkgOps.remove(packageName); + mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid)); + if (removedOps != null) { + scheduleFastWriteLocked(); + } + } + } + + @Override + public void setHistoryParameters(@AppOpsManager.HistoricalMode int mode, + long baseSnapshotInterval, int compressionStep) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS, + "setHistoryParameters"); + // Must not hold the appops lock + mHistoricalRegistry.setHistoryParameters(mode, baseSnapshotInterval, compressionStep); + } + + @Override + public void offsetHistory(long offsetMillis) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS, + "offsetHistory"); + // Must not hold the appops lock + mHistoricalRegistry.offsetHistory(offsetMillis); + mHistoricalRegistry.offsetDiscreteHistory(offsetMillis); + } + + @Override + public void addHistoricalOps(HistoricalOps ops) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS, + "addHistoricalOps"); + // Must not hold the appops lock + mHistoricalRegistry.addHistoricalOps(ops); + } + + @Override + public void resetHistoryParameters() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS, + "resetHistoryParameters"); + // Must not hold the appops lock + mHistoricalRegistry.resetHistoryParameters(); + } + + @Override + public void clearHistory() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS, + "clearHistory"); + // Must not hold the appops lock + mHistoricalRegistry.clearAllHistory(); + } + + @Override + public void rebootHistory(long offlineDurationMillis) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS, + "rebootHistory"); + + Preconditions.checkArgument(offlineDurationMillis >= 0); + + // Must not hold the appops lock + mHistoricalRegistry.shutdown(); + + if (offlineDurationMillis > 0) { + SystemClock.sleep(offlineDurationMillis); + } + + mHistoricalRegistry = new HistoricalRegistry(mHistoricalRegistry); + mHistoricalRegistry.systemReady(mContext.getContentResolver()); + mHistoricalRegistry.persistPendingHistory(); + } + + @GuardedBy("this") + private void removeUidsForUserLocked(int userHandle) { + for (int i = mUidStates.size() - 1; i >= 0; --i) { + final int uid = mUidStates.keyAt(i); + if (UserHandle.getUserId(uid) == userHandle) { + mUidStates.valueAt(i).clear(); + mUidStates.removeAt(i); + } + } + } + + private void checkSystemUid(String function) { + int uid = Binder.getCallingUid(); + if (uid != Process.SYSTEM_UID) { + throw new SecurityException(function + " must by called by the system"); + } + } + + private static int resolveUid(String packageName) { + if (packageName == null) { + return Process.INVALID_UID; + } + switch (packageName) { + case "root": + return Process.ROOT_UID; + case "shell": + case "dumpstate": + return Process.SHELL_UID; + case "media": + return Process.MEDIA_UID; + case "audioserver": + return Process.AUDIOSERVER_UID; + case "cameraserver": + return Process.CAMERASERVER_UID; + } + return Process.INVALID_UID; + } + + private static String[] getPackagesForUid(int uid) { + String[] packageNames = null; + + // Very early during boot the package manager is not yet or not yet fully started. At this + // time there are no packages yet. + if (AppGlobals.getPackageManager() != null) { + try { + packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid); + } catch (RemoteException e) { + /* ignore - local call */ + } + } + if (packageNames == null) { + return EmptyArray.STRING; + } + return packageNames; + } + + private final class ClientUserRestrictionState implements DeathRecipient { + private final IBinder mToken; + + ClientUserRestrictionState(IBinder token) + throws RemoteException { + token.linkToDeath(this, 0); + this.mToken = token; + } + + public boolean setRestriction(int code, boolean restricted, + PackageTagsList excludedPackageTags, int userId) { + return mAppOpsRestrictions.setUserRestriction(mToken, userId, code, + restricted, excludedPackageTags); + } + + public boolean hasRestriction(int code, String packageName, String attributionTag, + int userId, boolean isCheckOp) { + return mAppOpsRestrictions.getUserRestriction(mToken, userId, code, packageName, + attributionTag, isCheckOp); + } + + public void removeUser(int userId) { + mAppOpsRestrictions.clearUserRestrictions(mToken, userId); + } + + public boolean isDefault() { + return !mAppOpsRestrictions.hasUserRestrictions(mToken); + } + + @Override + public void binderDied() { + synchronized (AppOpsServiceImpl.this) { + mAppOpsRestrictions.clearUserRestrictions(mToken); + mOpUserRestrictions.remove(mToken); + destroy(); + } + } + + public void destroy() { + mToken.unlinkToDeath(this, 0); + } + } + + private final class ClientGlobalRestrictionState implements DeathRecipient { + final IBinder mToken; + + ClientGlobalRestrictionState(IBinder token) + throws RemoteException { + token.linkToDeath(this, 0); + this.mToken = token; + } + + boolean setRestriction(int code, boolean restricted) { + return mAppOpsRestrictions.setGlobalRestriction(mToken, code, restricted); + } + + boolean hasRestriction(int code) { + return mAppOpsRestrictions.getGlobalRestriction(mToken, code); + } + + boolean isDefault() { + return !mAppOpsRestrictions.hasGlobalRestrictions(mToken); + } + + @Override + public void binderDied() { + mAppOpsRestrictions.clearGlobalRestrictions(mToken); + mOpGlobalRestrictions.remove(mToken); + destroy(); + } + + void destroy() { + mToken.unlinkToDeath(this, 0); + } + } + + @Override + public void setDeviceAndProfileOwners(SparseIntArray owners) { + synchronized (this) { + mProfileOwners = owners; + } + } +} diff --git a/services/core/java/com/android/server/appop/AppOpsServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsServiceInterface.java index 18f659e4c62a..8420fcbd346f 100644 --- a/services/core/java/com/android/server/appop/AppOpsServiceInterface.java +++ b/services/core/java/com/android/server/appop/AppOpsServiceInterface.java @@ -13,197 +13,482 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.android.server.appop; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UserIdInt; -import android.app.AppOpsManager.Mode; -import android.util.ArraySet; -import android.util.SparseBooleanArray; +import android.app.ActivityManager; +import android.app.AppOpsManager; +import android.content.AttributionSource; +import android.os.Bundle; +import android.os.IBinder; +import android.os.PackageTagsList; +import android.os.RemoteCallback; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.SparseArray; import android.util.SparseIntArray; +import com.android.internal.app.IAppOpsActiveCallback; +import com.android.internal.app.IAppOpsCallback; +import com.android.internal.app.IAppOpsNotedCallback; +import com.android.internal.app.IAppOpsStartedCallback; + +import dalvik.annotation.optimization.NeverCompile; + +import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.List; /** - * Interface for accessing and modifying modes for app-ops i.e. package and uid modes. - * This interface also includes functions for added and removing op mode watchers. - * In the future this interface will also include op restrictions. + * */ -public interface AppOpsServiceInterface { +public interface AppOpsServiceInterface extends PersistenceScheduler { + + /** + * + */ + void systemReady(); + + /** + * + */ + void shutdown(); + + /** + * + * @param uid + * @param packageName + */ + void verifyPackage(int uid, String packageName); + + /** + * + * @param op + * @param packageName + * @param flags + * @param callback + */ + void startWatchingModeWithFlags(int op, String packageName, int flags, + IAppOpsCallback callback); + + /** + * + * @param callback + */ + void stopWatchingMode(IAppOpsCallback callback); + + /** + * + * @param ops + * @param callback + */ + void startWatchingActive(int[] ops, IAppOpsActiveCallback callback); + + /** + * + * @param callback + */ + void stopWatchingActive(IAppOpsActiveCallback callback); + + /** + * + * @param ops + * @param callback + */ + void startWatchingStarted(int[] ops, @NonNull IAppOpsStartedCallback callback); + /** - * Returns a copy of non-default app-ops with op as keys and their modes as values for a uid. - * Returns an empty SparseIntArray if nothing is set. - * @param uid for which we need the app-ops and their modes. + * + * @param callback */ - SparseIntArray getNonDefaultUidModes(int uid); + void stopWatchingStarted(IAppOpsStartedCallback callback); /** - * Returns the app-op mode for a particular app-op of a uid. - * Returns default op mode if the op mode for particular uid and op is not set. - * @param uid user id for which we need the mode. - * @param op app-op for which we need the mode. - * @return mode of the app-op. + * + * @param ops + * @param callback */ - int getUidMode(int uid, int op); + void startWatchingNoted(@NonNull int[] ops, @NonNull IAppOpsNotedCallback callback); /** - * Set the app-op mode for a particular uid and op. - * The mode is not set if the mode is the same as the default mode for the op. - * @param uid user id for which we want to set the mode. - * @param op app-op for which we want to set the mode. - * @param mode mode for the app-op. - * @return true if op mode is changed. + * + * @param callback */ - boolean setUidMode(int uid, int op, @Mode int mode); + void stopWatchingNoted(IAppOpsNotedCallback callback); /** - * Gets the app-op mode for a particular package. - * Returns default op mode if the op mode for the particular package is not set. - * @param packageName package name for which we need the op mode. - * @param op app-op for which we need the mode. - * @param userId user id associated with the package. - * @return the mode of the app-op. + * @param clientId + * @param code + * @param uid + * @param packageName + * @param attributionTag + * @param startIfModeDefault + * @param message + * @param attributionFlags + * @param attributionChainId + * @return */ - int getPackageMode(@NonNull String packageName, int op, @UserIdInt int userId); + int startOperation(@NonNull IBinder clientId, int code, int uid, + @Nullable String packageName, @Nullable String attributionTag, + boolean startIfModeDefault, @NonNull String message, + @AppOpsManager.AttributionFlags int attributionFlags, + int attributionChainId); + + + int startOperationUnchecked(IBinder clientId, int code, int uid, @NonNull String packageName, + @Nullable String attributionTag, int proxyUid, String proxyPackageName, + @Nullable String proxyAttributionTag, @AppOpsManager.OpFlags int flags, + boolean startIfModeDefault, @AppOpsManager.AttributionFlags int attributionFlags, + int attributionChainId, boolean dryRun); /** - * Sets the app-op mode for a particular package. - * @param packageName package name for which we need to set the op mode. - * @param op app-op for which we need to set the mode. - * @param mode the mode of the app-op. - * @param userId user id associated with the package. * + * @param clientId + * @param code + * @param uid + * @param packageName + * @param attributionTag */ - void setPackageMode(@NonNull String packageName, int op, @Mode int mode, @UserIdInt int userId); + void finishOperation(IBinder clientId, int code, int uid, String packageName, + String attributionTag); /** - * Stop tracking any app-op modes for a package. - * @param packageName Name of the package for which we want to remove all mode tracking. - * @param userId user id associated with the package. + * + * @param clientId + * @param code + * @param uid + * @param packageName + * @param attributionTag */ - boolean removePackage(@NonNull String packageName, @UserIdInt int userId); + void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName, + String attributionTag); /** - * Stop tracking any app-op modes for this uid. - * @param uid user id for which we want to remove all tracking. + * + * @param uidPackageNames + * @param visible */ - void removeUid(int uid); + void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible); /** - * Returns true if all uid modes for this uid are - * in default state. - * @param uid user id + * */ - boolean areUidModesDefault(int uid); + void readState(); /** - * Returns true if all package modes for this package name are - * in default state. - * @param packageName package name. - * @param userId user id associated with the package. + * */ - boolean arePackageModesDefault(String packageName, @UserIdInt int userId); + void writeState(); /** - * Stop tracking app-op modes for all uid and packages. + * + * @param uid + * @param packageName */ - void clearAllModes(); + void packageRemoved(int uid, String packageName); /** - * Registers changedListener to listen to op's mode change. - * @param changedListener the listener that must be trigger on the op's mode change. - * @param op op representing the app-op whose mode change needs to be listened to. + * + * @param uid */ - void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener, int op); + void uidRemoved(int uid); /** - * Registers changedListener to listen to package's app-op's mode change. - * @param changedListener the listener that must be trigger on the mode change. - * @param packageName of the package whose app-op's mode change needs to be listened to. + * + * @param uid + * @param procState + * @param capability */ - void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener, - @NonNull String packageName); + void updateUidProcState(int uid, int procState, + @ActivityManager.ProcessCapability int capability); /** - * Stop the changedListener from triggering on any mode change. - * @param changedListener the listener that needs to be removed. + * + * @param ops + * @return */ - void removeListener(@NonNull OnOpModeChangedListener changedListener); + List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops); /** - * Temporary API which will be removed once we can safely untangle the methods that use this. - * Returns a set of OnOpModeChangedListener that are listening for op's mode changes. - * @param op app-op whose mode change is being listened to. + * + * @param uid + * @param packageName + * @param ops + * @return */ - ArraySet<OnOpModeChangedListener> getOpModeChangedListeners(int op); + List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName, + int[] ops); /** - * Temporary API which will be removed once we can safely untangle the methods that use this. - * Returns a set of OnOpModeChangedListener that are listening for package's op's mode changes. - * @param packageName of package whose app-op's mode change is being listened to. + * + * @param uid + * @param packageName + * @param attributionTag + * @param opNames + * @param dataType + * @param filter + * @param beginTimeMillis + * @param endTimeMillis + * @param flags + * @param callback */ - ArraySet<OnOpModeChangedListener> getPackageModeChangedListeners(@NonNull String packageName); + void getHistoricalOps(int uid, String packageName, String attributionTag, + List<String> opNames, int dataType, int filter, long beginTimeMillis, + long endTimeMillis, int flags, RemoteCallback callback); /** - * Temporary API which will be removed once we can safely untangle the methods that use this. - * Notify that the app-op's mode is changed by triggering the change listener. - * @param op App-op whose mode has changed - * @param uid user id associated with the app-op (or, if UID_ANY, notifies all users) + * + * @param uid + * @param packageName + * @param attributionTag + * @param opNames + * @param dataType + * @param filter + * @param beginTimeMillis + * @param endTimeMillis + * @param flags + * @param callback */ - void notifyWatchersOfChange(int op, int uid); + void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag, + List<String> opNames, int dataType, int filter, long beginTimeMillis, + long endTimeMillis, int flags, RemoteCallback callback); /** - * Temporary API which will be removed once we can safely untangle the methods that use this. - * Notify that the app-op's mode is changed by triggering the change listener. - * @param changedListener the change listener. - * @param op App-op whose mode has changed - * @param uid user id associated with the app-op - * @param packageName package name that is associated with the app-op + * */ - void notifyOpChanged(@NonNull OnOpModeChangedListener changedListener, int op, int uid, - @Nullable String packageName); + void reloadNonHistoricalState(); /** - * Temporary API which will be removed once we can safely untangle the methods that use this. - * Notify that the app-op's mode is changed to all packages associated with the uid by - * triggering the appropriate change listener. - * @param op App-op whose mode has changed - * @param uid user id associated with the app-op - * @param onlyForeground true if only watchers that - * @param callbackToIgnore callback that should be ignored. + * + * @param uid + * @param ops + * @return */ - void notifyOpChangedForAllPkgsInUid(int op, int uid, boolean onlyForeground, - @Nullable OnOpModeChangedListener callbackToIgnore); + List<AppOpsManager.PackageOps> getUidOps(int uid, int[] ops); /** - * TODO: Move hasForegroundWatchers and foregroundOps into this. - * Go over the list of app-ops for the uid and mark app-ops with MODE_FOREGROUND in - * foregroundOps. - * @param uid for which the app-op's mode needs to be marked. - * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true. - * @return foregroundOps. + * + * @param owners */ - SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps); + void setDeviceAndProfileOwners(SparseIntArray owners); + // used in audio restriction calls, might just copy the logic to avoid having this call. /** - * Go over the list of app-ops for the package name and mark app-ops with MODE_FOREGROUND in - * foregroundOps. - * @param packageName for which the app-op's mode needs to be marked. - * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true. - * @param userId user id associated with the package. - * @return foregroundOps. + * + * @param callingPid + * @param callingUid + * @param targetUid + */ + void enforceManageAppOpsModes(int callingPid, int callingUid, int targetUid); + + /** + * + * @param code + * @param uid + * @param mode + * @param permissionPolicyCallback + */ + void setUidMode(int code, int uid, int mode, + @Nullable IAppOpsCallback permissionPolicyCallback); + + /** + * + * @param code + * @param uid + * @param packageName + * @param mode + * @param permissionPolicyCallback */ - SparseBooleanArray evalForegroundPackageOps(String packageName, - SparseBooleanArray foregroundOps, @UserIdInt int userId); + void setMode(int code, int uid, @NonNull String packageName, int mode, + @Nullable IAppOpsCallback permissionPolicyCallback); /** - * Dump op mode and package mode listeners and their details. - * @param dumpOp if -1 then op mode listeners for all app-ops are dumped. If it's set to an - * app-op, only the watchers for that app-op are dumped. - * @param dumpUid uid for which we want to dump op mode watchers. - * @param dumpPackage if not null and if dumpOp is -1, dumps watchers for the package name. - * @param printWriter writer to dump to. + * + * @param reqUserId + * @param reqPackageName + */ + void resetAllModes(int reqUserId, String reqPackageName); + + /** + * + * @param code + * @param uid + * @param packageName + * @param attributionTag + * @param raw + * @return + */ + int checkOperation(int code, int uid, String packageName, + @Nullable String attributionTag, boolean raw); + + /** + * + * @param uid + * @param packageName + * @return + */ + int checkPackage(int uid, String packageName); + + /** + * + * @param code + * @param uid + * @param packageName + * @param attributionTag + * @param message + * @return + */ + int noteOperation(int code, int uid, @Nullable String packageName, + @Nullable String attributionTag, @Nullable String message); + + /** + * + * @param code + * @param uid + * @param packageName + * @param attributionTag + * @param proxyUid + * @param proxyPackageName + * @param proxyAttributionTag + * @param flags + * @return + */ + @AppOpsManager.Mode + int noteOperationUnchecked(int code, int uid, @NonNull String packageName, + @Nullable String attributionTag, int proxyUid, String proxyPackageName, + @Nullable String proxyAttributionTag, @AppOpsManager.OpFlags int flags); + + boolean isAttributionTagValid(int uid, @NonNull String packageName, + @Nullable String attributionTag, @Nullable String proxyPackageName); + + /** + * + * @param fd + * @param pw + * @param args + */ + @NeverCompile + // Avoid size overhead of debugging code. + void dump(FileDescriptor fd, PrintWriter pw, String[] args); + + /** + * + * @param restrictions + * @param token + * @param userHandle + */ + void setUserRestrictions(Bundle restrictions, IBinder token, int userHandle); + + /** + * + * @param code + * @param restricted + * @param token + * @param userHandle + * @param excludedPackageTags + */ + void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle, + PackageTagsList excludedPackageTags); + + /** + * + * @param code + * @param restricted + * @param token + */ + void setGlobalRestriction(int code, boolean restricted, IBinder token); + + /** + * + * @param code + * @param user + * @param pkg + * @param attributionTag + * @return + */ + int getOpRestrictionCount(int code, UserHandle user, String pkg, + String attributionTag); + + /** + * + * @param code + * @param uid + */ + // added to interface for audio restriction stuff + void notifyWatchersOfChange(int code, int uid); + + /** + * + * @param userHandle + * @throws RemoteException + */ + void removeUser(int userHandle) throws RemoteException; + + /** + * + * @param code + * @param uid + * @param packageName + * @return + */ + boolean isOperationActive(int code, int uid, String packageName); + + /** + * + * @param op + * @param proxyPackageName + * @param proxyAttributionTag + * @param proxiedUid + * @param proxiedPackageName + * @return + */ + // TODO this one might not need to be in the interface + boolean isProxying(int op, @NonNull String proxyPackageName, + @NonNull String proxyAttributionTag, int proxiedUid, + @NonNull String proxiedPackageName); + + /** + * + * @param packageName + */ + void resetPackageOpsNoHistory(@NonNull String packageName); + + /** + * + * @param mode + * @param baseSnapshotInterval + * @param compressionStep + */ + void setHistoryParameters(@AppOpsManager.HistoricalMode int mode, + long baseSnapshotInterval, int compressionStep); + + /** + * + * @param offsetMillis + */ + void offsetHistory(long offsetMillis); + + /** + * + * @param ops + */ + void addHistoricalOps(AppOpsManager.HistoricalOps ops); + + /** + * + */ + void resetHistoryParameters(); + + /** + * + */ + void clearHistory(); + + /** + * + * @param offlineDurationMillis */ - boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, PrintWriter printWriter); + void rebootHistory(long offlineDurationMillis); } diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java index 5114bd59f084..c1434e4d9f4d 100644 --- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java +++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java @@ -59,7 +59,7 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { private final DelayableExecutor mExecutor; private final Clock mClock; private ActivityManagerInternal mActivityManagerInternal; - private AppOpsService.Constants mConstants; + private AppOpsServiceImpl.Constants mConstants; private SparseIntArray mUidStates = new SparseIntArray(); private SparseIntArray mPendingUidStates = new SparseIntArray(); @@ -85,7 +85,7 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { AppOpsUidStateTrackerImpl(ActivityManagerInternal activityManagerInternal, Handler handler, Executor lockingExecutor, Clock clock, - AppOpsService.Constants constants) { + AppOpsServiceImpl.Constants constants) { this(activityManagerInternal, new DelayableExecutor() { @Override @@ -102,7 +102,7 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { @VisibleForTesting AppOpsUidStateTrackerImpl(ActivityManagerInternal activityManagerInternal, - DelayableExecutor executor, Clock clock, AppOpsService.Constants constants, + DelayableExecutor executor, Clock clock, AppOpsServiceImpl.Constants constants, Thread executorThread) { mActivityManagerInternal = activityManagerInternal; mExecutor = executor; diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java index dcc36bcf6149..797026908619 100644 --- a/services/core/java/com/android/server/appop/AttributedOp.java +++ b/services/core/java/com/android/server/appop/AttributedOp.java @@ -40,9 +40,9 @@ import java.util.List; import java.util.NoSuchElementException; final class AttributedOp { - private final @NonNull AppOpsService mAppOpsService; + private final @NonNull AppOpsServiceImpl mAppOpsService; public final @Nullable String tag; - public final @NonNull AppOpsService.Op parent; + public final @NonNull AppOpsServiceImpl.Op parent; /** * Last successful accesses (noteOp + finished startOp) for each uidState/opFlag combination @@ -80,8 +80,8 @@ final class AttributedOp { // @GuardedBy("mAppOpsService") @Nullable ArrayMap<IBinder, InProgressStartOpEvent> mPausedInProgressEvents; - AttributedOp(@NonNull AppOpsService appOpsService, @Nullable String tag, - @NonNull AppOpsService.Op parent) { + AttributedOp(@NonNull AppOpsServiceImpl appOpsService, @Nullable String tag, + @NonNull AppOpsServiceImpl.Op parent) { mAppOpsService = appOpsService; this.tag = tag; this.parent = parent; @@ -131,8 +131,8 @@ final class AttributedOp { AppOpsManager.OpEventProxyInfo proxyInfo = null; if (proxyUid != Process.INVALID_UID) { - proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName, - proxyAttributionTag); + proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid, + proxyPackageName, proxyAttributionTag); } AppOpsManager.NoteOpEvent existingEvent = mAccessEvents.get(key); @@ -238,7 +238,7 @@ final class AttributedOp { if (event == null) { event = mAppOpsService.mInProgressStartOpEventPool.acquire(startTime, SystemClock.elapsedRealtime(), clientId, tag, - PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId), + PooledLambda.obtainRunnable(AppOpsServiceImpl::onClientDeath, this, clientId), proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags, attributionFlags, attributionChainId); events.put(clientId, event); @@ -251,9 +251,9 @@ final class AttributedOp { event.mNumUnfinishedStarts++; if (isStarted) { - mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, - parent.packageName, tag, uidState, flags, startTime, attributionFlags, - attributionChainId); + mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, + parent.uid, parent.packageName, tag, uidState, flags, startTime, + attributionFlags, attributionChainId); } } @@ -309,8 +309,8 @@ final class AttributedOp { mAccessEvents.put(makeKey(event.getUidState(), event.getFlags()), finishedEvent); - mAppOpsService.mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid, - parent.packageName, tag, event.getUidState(), + mAppOpsService.mHistoricalRegistry.increaseOpAccessDuration(parent.op, + parent.uid, parent.packageName, tag, event.getUidState(), event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(), event.getAttributionFlags(), event.getAttributionChainId()); @@ -334,13 +334,13 @@ final class AttributedOp { @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService private void finishPossiblyPaused(@NonNull IBinder clientId, boolean isPausing) { if (!isPaused()) { - Slog.wtf(AppOpsService.TAG, "No ops running or paused"); + Slog.wtf(AppOpsServiceImpl.TAG, "No ops running or paused"); return; } int indexOfToken = mPausedInProgressEvents.indexOfKey(clientId); if (indexOfToken < 0) { - Slog.wtf(AppOpsService.TAG, "No op running or paused for the client"); + Slog.wtf(AppOpsServiceImpl.TAG, "No op running or paused for the client"); return; } else if (isPausing) { // already paused @@ -416,9 +416,9 @@ final class AttributedOp { mInProgressEvents.put(event.getClientId(), event); event.setStartElapsedTime(SystemClock.elapsedRealtime()); event.setStartTime(startTime); - mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, - parent.packageName, tag, event.getUidState(), event.getFlags(), startTime, - event.getAttributionFlags(), event.getAttributionChainId()); + mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, + parent.uid, parent.packageName, tag, event.getUidState(), event.getFlags(), + startTime, event.getAttributionFlags(), event.getAttributionChainId()); if (shouldSendActive) { mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, parent.packageName, tag, true, event.getAttributionFlags(), @@ -503,8 +503,8 @@ final class AttributedOp { newEvent.mNumUnfinishedStarts += numPreviousUnfinishedStarts - 1; } } catch (RemoteException e) { - if (AppOpsService.DEBUG) { - Slog.e(AppOpsService.TAG, + if (AppOpsServiceImpl.DEBUG) { + Slog.e(AppOpsServiceImpl.TAG, "Cannot switch to new uidState " + newState); } } @@ -555,8 +555,8 @@ final class AttributedOp { ArrayMap<IBinder, InProgressStartOpEvent> ignoredEvents = opToAdd.isRunning() ? opToAdd.mInProgressEvents : opToAdd.mPausedInProgressEvents; - Slog.w(AppOpsService.TAG, "Ignoring " + ignoredEvents.size() + " app-ops, running: " - + opToAdd.isRunning()); + Slog.w(AppOpsServiceImpl.TAG, "Ignoring " + ignoredEvents.size() + + " app-ops, running: " + opToAdd.isRunning()); int numInProgressEvents = ignoredEvents.size(); for (int i = 0; i < numInProgressEvents; i++) { @@ -668,16 +668,22 @@ final class AttributedOp { /** * Create a new {@link InProgressStartOpEvent}. * - * @param startTime The time {@link #startOperation} was called - * @param startElapsedTime The elapsed time when {@link #startOperation} was called - * @param clientId The client id of the caller of {@link #startOperation} + * @param startTime The time {@link AppOpCheckingServiceInterface#startOperation} + * was called + * @param startElapsedTime The elapsed time whe + * {@link AppOpCheckingServiceInterface#startOperation} was called + * @param clientId The client id of the caller of + * {@link AppOpCheckingServiceInterface#startOperation} * @param attributionTag The attribution tag for the operation. * @param onDeath The code to execute on client death - * @param uidState The uidstate of the app {@link #startOperation} was called for + * @param uidState The uidstate of the app + * {@link AppOpCheckingServiceInterface#startOperation} was called + * for * @param attributionFlags the attribution flags for this operation. * @param attributionChainId the unique id of the attribution chain this op is a part of. - * @param proxy The proxy information, if {@link #startProxyOperation} was - * called + * @param proxy The proxy information, if + * {@link AppOpCheckingServiceInterface#startProxyOperation} was + * called * @param flags The trusted/nontrusted/self flags. * @throws RemoteException If the client is dying */ @@ -718,15 +724,21 @@ final class AttributedOp { /** * Reinit existing object with new state. * - * @param startTime The time {@link #startOperation} was called - * @param startElapsedTime The elapsed time when {@link #startOperation} was called - * @param clientId The client id of the caller of {@link #startOperation} + * @param startTime The time {@link AppOpCheckingServiceInterface#startOperation} + * was called + * @param startElapsedTime The elapsed time when + * {@link AppOpCheckingServiceInterface#startOperation} was called + * @param clientId The client id of the caller of + * {@link AppOpCheckingServiceInterface#startOperation} * @param attributionTag The attribution tag for this operation. * @param onDeath The code to execute on client death - * @param uidState The uidstate of the app {@link #startOperation} was called for + * @param uidState The uidstate of the app + * {@link AppOpCheckingServiceInterface#startOperation} was called + * for * @param flags The flags relating to the proxy - * @param proxy The proxy information, if {@link #startProxyOperation} - * was called + * @param proxy The proxy information, if + * {@link AppOpCheckingServiceInterface#startProxyOperation was + * called * @param attributionFlags the attribution flags for this operation. * @param attributionChainId the unique id of the attribution chain this op is a part of. * @param proxyPool The pool to release diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index ae929c4dc5a1..8aa898edff9a 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -5863,6 +5863,9 @@ public class AudioService extends IAudioService.Stub }; private boolean isValidCommunicationDevice(AudioDeviceInfo device) { + if (!device.isSink()) { + return false; + } for (int type : VALID_COMMUNICATION_DEVICE_TYPES) { if (device.getType() == type) { return true; @@ -5897,7 +5900,11 @@ public class AudioService extends IAudioService.Stub throw new IllegalArgumentException("invalid portID " + portId); } if (!isValidCommunicationDevice(device)) { - throw new IllegalArgumentException("invalid device type " + device.getType()); + if (!device.isSink()) { + throw new IllegalArgumentException("device must have sink role"); + } else { + throw new IllegalArgumentException("invalid device type: " + device.getType()); + } } } final String eventSource = new StringBuilder() @@ -7092,9 +7099,10 @@ public class AudioService extends IAudioService.Stub private @AudioManager.DeviceVolumeBehavior int getDeviceVolumeBehaviorInt(@NonNull AudioDeviceAttributes device) { - // translate Java device type to native device type (for the devices masks for full / fixed) - final int audioSystemDeviceOut = AudioDeviceInfo.convertDeviceTypeToInternalDevice( - device.getType()); + // Get the internal type set by the AudioDeviceAttributes constructor which is always more + // exact (avoids double conversions) than a conversion from SDK type via + // AudioDeviceInfo.convertDeviceTypeToInternalDevice() + final int audioSystemDeviceOut = device.getInternalType(); int setDeviceVolumeBehavior = retrieveStoredDeviceVolumeBehavior(audioSystemDeviceOut); if (setDeviceVolumeBehavior != AudioManager.DEVICE_VOLUME_BEHAVIOR_UNSET) { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java index 1c57151ed7bd..229393def27f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java @@ -421,13 +421,6 @@ public class FingerprintService extends SystemService { return -1; } - if (!Utils.isUserEncryptedOrLockdown(mLockPatternUtils, userId)) { - // If this happens, something in KeyguardUpdateMonitor is wrong. This should only - // ever be invoked when the user is encrypted or lockdown. - Slog.e(TAG, "detectFingerprint invoked when user is not encrypted or lockdown"); - return -1; - } - final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for detectFingerprint"); diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java index b882c47b71d0..e16ca0bbe1e1 100644 --- a/services/core/java/com/android/server/camera/CameraServiceProxy.java +++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java @@ -551,6 +551,15 @@ public class CameraServiceProxy extends SystemService lensFacing, ignoreResizableAndSdkCheck); } + /** + * Placeholder method to fetch the system state for autoframing. + * TODO: b/260617354 + */ + @Override + public int getAutoframingOverride(String packageName) { + return CaptureRequest.CONTROL_AUTOFRAMING_OFF; + } + @Override public void pingForUserUpdate() { if (Binder.getCallingUid() != Process.CAMERASERVER_UID) { diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java index 84dfe860bc84..a0cbd7fb60a0 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java +++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java @@ -158,6 +158,19 @@ final class DisplayDeviceInfo { public static final int FLAG_TOUCH_FEEDBACK_DISABLED = 1 << 16; /** + * Flag: Indicates that the display maintains its own focus and touch mode. + * + * This flag is similar to {@link com.android.internal.R.bool.config_perDisplayFocusEnabled} in + * behavior, but only applies to the specific display instead of system-wide to all displays. + * + * Note: The display must be trusted in order to have its own focus. + * + * @see #FLAG_TRUSTED + * @hide + */ + public static final int FLAG_OWN_FOCUS = 1 << 17; + + /** * Touch attachment: Display does not receive touch. */ public static final int TOUCH_NONE = 0; @@ -584,9 +597,30 @@ final class DisplayDeviceInfo { if ((flags & FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD) != 0) { msg.append(", FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD"); } + if ((flags & FLAG_DESTROY_CONTENT_ON_REMOVAL) != 0) { + msg.append(", FLAG_DESTROY_CONTENT_ON_REMOVAL"); + } if ((flags & FLAG_MASK_DISPLAY_CUTOUT) != 0) { msg.append(", FLAG_MASK_DISPLAY_CUTOUT"); } + if ((flags & FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0) { + msg.append(", FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS"); + } + if ((flags & FLAG_TRUSTED) != 0) { + msg.append(", FLAG_TRUSTED"); + } + if ((flags & FLAG_OWN_DISPLAY_GROUP) != 0) { + msg.append(", FLAG_OWN_DISPLAY_GROUP"); + } + if ((flags & FLAG_ALWAYS_UNLOCKED) != 0) { + msg.append(", FLAG_ALWAYS_UNLOCKED"); + } + if ((flags & FLAG_TOUCH_FEEDBACK_DISABLED) != 0) { + msg.append(", FLAG_TOUCH_FEEDBACK_DISABLED"); + } + if ((flags & FLAG_OWN_FOCUS) != 0) { + msg.append(", FLAG_OWN_FOCUS"); + } return msg.toString(); } } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 05cd67f2f808..c5cb08db934f 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -105,7 +105,6 @@ import android.os.UserHandle; import android.os.UserManager; import android.provider.DeviceConfig; import android.provider.Settings; -import android.sysprop.DisplayProperties; import android.text.TextUtils; import android.util.ArraySet; import android.util.EventLog; @@ -451,8 +450,6 @@ public final class DisplayManagerService extends SystemService { } }; - private final boolean mAllowNonNativeRefreshRateOverride; - private final BrightnessSynchronizer mBrightnessSynchronizer; /** @@ -506,7 +503,6 @@ public final class DisplayManagerService extends SystemService { ColorSpace[] colorSpaces = SurfaceControl.getCompositionColorSpaces(); mWideColorSpace = colorSpaces[1]; mOverlayProperties = SurfaceControl.getOverlaySupport(); - mAllowNonNativeRefreshRateOverride = mInjector.getAllowNonNativeRefreshRateOverride(); mSystemReady = false; } @@ -930,24 +926,20 @@ public final class DisplayManagerService extends SystemService { } } - if (mAllowNonNativeRefreshRateOverride) { - overriddenInfo.refreshRateOverride = frameRateHz; - if (!CompatChanges.isChangeEnabled(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE, - callingUid)) { - overriddenInfo.supportedModes = Arrays.copyOf(info.supportedModes, - info.supportedModes.length + 1); - overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1] = - new Display.Mode(Display.DISPLAY_MODE_ID_FOR_FRAME_RATE_OVERRIDE, - currentMode.getPhysicalWidth(), currentMode.getPhysicalHeight(), - overriddenInfo.refreshRateOverride); - overriddenInfo.modeId = - overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1] - .getModeId(); - } - return overriddenInfo; + overriddenInfo.refreshRateOverride = frameRateHz; + if (!CompatChanges.isChangeEnabled(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE, + callingUid)) { + overriddenInfo.supportedModes = Arrays.copyOf(info.supportedModes, + info.supportedModes.length + 1); + overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1] = + new Display.Mode(Display.DISPLAY_MODE_ID_FOR_FRAME_RATE_OVERRIDE, + currentMode.getPhysicalWidth(), currentMode.getPhysicalHeight(), + overriddenInfo.refreshRateOverride); + overriddenInfo.modeId = + overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1] + .getModeId(); } - - return info; + return overriddenInfo; } private DisplayInfo getDisplayInfoInternal(int displayId, int callingUid) { @@ -2602,11 +2594,6 @@ public final class DisplayManagerService extends SystemService { long getDefaultDisplayDelayTimeout() { return WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT; } - - boolean getAllowNonNativeRefreshRateOverride() { - return DisplayProperties - .debug_allow_non_native_refresh_rate_override().orElse(true); - } } @VisibleForTesting diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index dedc56ac9b41..26ac528e519c 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -384,6 +384,9 @@ final class LogicalDisplay { if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_TOUCH_FEEDBACK_DISABLED) != 0) { mBaseDisplayInfo.flags |= Display.FLAG_TOUCH_FEEDBACK_DISABLED; } + if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_OWN_FOCUS) != 0) { + mBaseDisplayInfo.flags |= Display.FLAG_OWN_FOCUS; + } Rect maskingInsets = getMaskingInsets(deviceInfo); int maskedWidth = deviceInfo.width - maskingInsets.left - maskingInsets.right; int maskedHeight = deviceInfo.height - maskingInsets.top - maskingInsets.bottom; diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index 20b82c3e3c97..a23a073ad447 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -21,6 +21,7 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP; +import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT; @@ -495,13 +496,25 @@ public class VirtualDisplayAdapter extends DisplayAdapter { if ((mFlags & VIRTUAL_DISPLAY_FLAG_TRUSTED) != 0) { mInfo.flags |= FLAG_TRUSTED; } - if ((mFlags & VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED) != 0 - && (mInfo.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP) != 0) { - mInfo.flags |= FLAG_ALWAYS_UNLOCKED; + if ((mFlags & VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED) != 0) { + if ((mFlags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) != 0) { + mInfo.flags |= FLAG_ALWAYS_UNLOCKED; + } else { + Slog.w(TAG, "Ignoring VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED as it " + + "requires VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP."); + } } if ((mFlags & VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED) != 0) { mInfo.flags |= FLAG_TOUCH_FEEDBACK_DISABLED; } + if ((mFlags & VIRTUAL_DISPLAY_FLAG_OWN_FOCUS) != 0) { + if ((mFlags & VIRTUAL_DISPLAY_FLAG_TRUSTED) != 0) { + mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_FOCUS; + } else { + Slog.w(TAG, "Ignoring VIRTUAL_DISPLAY_FLAG_OWN_FOCUS as it requires " + + "VIRTUAL_DISPLAY_FLAG_TRUSTED."); + } + } mInfo.type = Display.TYPE_VIRTUAL; mInfo.touch = ((mFlags & VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH) == 0) ? diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java index 6dbb362db030..4d67311db150 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java @@ -42,12 +42,15 @@ import android.view.inputmethod.InputMethod; import android.view.inputmethod.InputMethodInfo; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.inputmethod.IInputMethod; import com.android.internal.inputmethod.InputBindResult; import com.android.internal.inputmethod.UnbindReason; import com.android.server.EventLogTags; import com.android.server.wm.WindowManagerInternal; +import java.util.concurrent.CountDownLatch; + /** * A controller managing the state of the input method binding. */ @@ -77,19 +80,26 @@ final class InputMethodBindingController { @GuardedBy("ImfLock.class") private boolean mVisibleBound; @GuardedBy("ImfLock.class") private boolean mSupportsStylusHw; + @Nullable private CountDownLatch mLatchForTesting; + /** * Binding flags for establishing connection to the {@link InputMethodService}. */ - private static final int IME_CONNECTION_BIND_FLAGS = + @VisibleForTesting + static final int IME_CONNECTION_BIND_FLAGS = Context.BIND_AUTO_CREATE | Context.BIND_NOT_VISIBLE | Context.BIND_NOT_FOREGROUND | Context.BIND_IMPORTANT_BACKGROUND | Context.BIND_SCHEDULE_LIKE_TOP_APP; + + private final int mImeConnectionBindFlags; + /** * Binding flags used only while the {@link InputMethodService} is showing window. */ - private static final int IME_VISIBLE_BIND_FLAGS = + @VisibleForTesting + static final int IME_VISIBLE_BIND_FLAGS = Context.BIND_AUTO_CREATE | Context.BIND_TREAT_LIKE_ACTIVITY | Context.BIND_FOREGROUND_SERVICE @@ -97,12 +107,19 @@ final class InputMethodBindingController { | Context.BIND_SHOWING_UI; InputMethodBindingController(@NonNull InputMethodManagerService service) { + this(service, IME_CONNECTION_BIND_FLAGS, null /* latchForTesting */); + } + + InputMethodBindingController(@NonNull InputMethodManagerService service, + int imeConnectionBindFlags, CountDownLatch latchForTesting) { mService = service; mContext = mService.mContext; mMethodMap = mService.mMethodMap; mSettings = mService.mSettings; mPackageManagerInternal = mService.mPackageManagerInternal; mWindowManagerInternal = mService.mWindowManagerInternal; + mImeConnectionBindFlags = imeConnectionBindFlags; + mLatchForTesting = latchForTesting; } /** @@ -242,7 +259,7 @@ final class InputMethodBindingController { @Override public void onBindingDied(ComponentName name) { synchronized (ImfLock.class) { mService.invalidateAutofillSessionLocked(); - if (mVisibleBound) { + if (isVisibleBound()) { unbindVisibleConnection(); } } @@ -291,6 +308,10 @@ final class InputMethodBindingController { mService.scheduleResetStylusHandwriting(); } Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + + if (mLatchForTesting != null) { + mLatchForTesting.countDown(); // Notify the finish to tests + } } @GuardedBy("ImfLock.class") @@ -338,15 +359,15 @@ final class InputMethodBindingController { @GuardedBy("ImfLock.class") void unbindCurrentMethod() { - if (mVisibleBound) { + if (isVisibleBound()) { unbindVisibleConnection(); } - if (mHasConnection) { + if (hasConnection()) { unbindMainConnection(); } - if (mCurToken != null) { + if (getCurToken() != null) { removeCurrentToken(); mService.resetSystemUiLocked(); } @@ -448,17 +469,17 @@ final class InputMethodBindingController { @GuardedBy("ImfLock.class") private boolean bindCurrentInputMethodService(ServiceConnection conn, int flags) { - if (mCurIntent == null || conn == null) { + if (getCurIntent() == null || conn == null) { Slog.e(TAG, "--- bind failed: service = " + mCurIntent + ", conn = " + conn); return false; } - return mContext.bindServiceAsUser(mCurIntent, conn, flags, + return mContext.bindServiceAsUser(getCurIntent(), conn, flags, new UserHandle(mSettings.getCurrentUserId())); } @GuardedBy("ImfLock.class") private boolean bindCurrentInputMethodServiceMainConnection() { - mHasConnection = bindCurrentInputMethodService(mMainConnection, IME_CONNECTION_BIND_FLAGS); + mHasConnection = bindCurrentInputMethodService(mMainConnection, mImeConnectionBindFlags); return mHasConnection; } @@ -472,7 +493,7 @@ final class InputMethodBindingController { void setCurrentMethodVisible() { if (mCurMethod != null) { if (DEBUG) Slog.d(TAG, "setCurrentMethodVisible: mCurToken=" + mCurToken); - if (mHasConnection && !mVisibleBound) { + if (hasConnection() && !isVisibleBound()) { mVisibleBound = bindCurrentInputMethodService(mVisibleConnection, IME_VISIBLE_BIND_FLAGS); } @@ -480,7 +501,7 @@ final class InputMethodBindingController { } // No IME is currently connected. Reestablish the main connection. - if (!mHasConnection) { + if (!hasConnection()) { if (DEBUG) { Slog.d(TAG, "Cannot show input: no IME bound. Rebinding."); } @@ -512,7 +533,7 @@ final class InputMethodBindingController { */ @GuardedBy("ImfLock.class") void setCurrentMethodNotVisible() { - if (mVisibleBound) { + if (isVisibleBound()) { unbindVisibleConnection(); } } diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index 8d247f6a89e3..3ce51c3d1412 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -28,6 +28,7 @@ import static android.location.LocationManager.GPS_PROVIDER; import static android.location.LocationManager.NETWORK_PROVIDER; import static android.location.LocationRequest.LOW_POWER_EXCEPTIONS; import static android.location.provider.LocationProviderBase.ACTION_FUSED_PROVIDER; +import static android.location.provider.LocationProviderBase.ACTION_GNSS_PROVIDER; import static android.location.provider.LocationProviderBase.ACTION_NETWORK_PROVIDER; import static com.android.server.location.LocationPermissions.PERMISSION_COARSE; @@ -439,9 +440,24 @@ public class LocationManagerService extends ILocationManager.Stub implements mGnssManagerService = new GnssManagerService(mContext, mInjector, gnssNative); mGnssManagerService.onSystemReady(); + boolean useGnssHardwareProvider = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_useGnssHardwareProvider); + AbstractLocationProvider gnssProvider = null; + if (!useGnssHardwareProvider) { + gnssProvider = ProxyLocationProvider.create( + mContext, + GPS_PROVIDER, + ACTION_GNSS_PROVIDER, + com.android.internal.R.bool.config_useGnssHardwareProvider, + com.android.internal.R.string.config_gnssLocationProviderPackageName); + } + if (gnssProvider == null) { + gnssProvider = mGnssManagerService.getGnssLocationProvider(); + } + LocationProviderManager gnssManager = new LocationProviderManager(mContext, mInjector, GPS_PROVIDER, mPassiveManager); - addLocationProviderManager(gnssManager, mGnssManagerService.getGnssLocationProvider()); + addLocationProviderManager(gnssManager, gnssProvider); } // bind to geocoder provider diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java index 6f6b1c910ff0..282ad574a0ed 100644 --- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java @@ -81,6 +81,7 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; +import android.os.UserManager; import android.os.WorkSource; import android.os.WorkSource.WorkChain; import android.provider.Settings; @@ -930,9 +931,15 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } private void updateEnabled() { - // Generally follow location setting for current user - boolean enabled = mContext.getSystemService(LocationManager.class) - .isLocationEnabledForUser(UserHandle.CURRENT); + boolean enabled = false; + + // Generally follow location setting for visible users + LocationManager locationManager = mContext.getSystemService(LocationManager.class); + Set<UserHandle> visibleUserHandles = + mContext.getSystemService(UserManager.class).getVisibleUsers(); + for (UserHandle visibleUserHandle : visibleUserHandles) { + enabled |= locationManager.isLocationEnabledForUser(visibleUserHandle); + } // .. but enable anyway, if there's an active bypass request (e.g. ELS or ADAS) enabled |= (mProviderRequest != null diff --git a/services/core/java/com/android/server/pm/AppStateHelper.java b/services/core/java/com/android/server/pm/AppStateHelper.java new file mode 100644 index 000000000000..9ea350f265b6 --- /dev/null +++ b/services/core/java/com/android/server/pm/AppStateHelper.java @@ -0,0 +1,172 @@ +/* + * 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.pm; + +import android.app.ActivityManager; +import android.app.ActivityManager.RunningAppProcessInfo; +import android.content.Context; +import android.media.IAudioService; +import android.os.ServiceManager; +import android.text.TextUtils; +import android.util.ArraySet; + +import com.android.internal.util.ArrayUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * A helper class to provide queries for app states concerning gentle-update. + */ +public class AppStateHelper { + private final Context mContext; + + public AppStateHelper(Context context) { + mContext = context; + } + + /** + * True if the package is loaded into the process. + */ + private static boolean isPackageLoaded(RunningAppProcessInfo info, String packageName) { + return ArrayUtils.contains(info.pkgList, packageName) + || ArrayUtils.contains(info.pkgDeps, packageName); + } + + /** + * Returns the importance of the given package. + */ + private int getImportance(String packageName) { + var am = mContext.getSystemService(ActivityManager.class); + return am.getPackageImportance(packageName); + } + + /** + * True if the app owns the audio focus. + */ + private boolean hasAudioFocus(String packageName) { + var audioService = IAudioService.Stub.asInterface( + ServiceManager.getService(Context.AUDIO_SERVICE)); + try { + var focusInfos = audioService.getFocusStack(); + int size = focusInfos.size(); + var audioFocusPackage = (size > 0) ? focusInfos.get(size - 1).getPackageName() : null; + return TextUtils.equals(packageName, audioFocusPackage); + } catch (Exception ignore) { + } + return false; + } + + /** + * True if the app is in the foreground. + */ + private boolean isAppForeground(String packageName) { + return getImportance(packageName) <= RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE; + } + + /** + * True if the app is currently at the top of the screen that the user is interacting with. + */ + public boolean isAppTopVisible(String packageName) { + return getImportance(packageName) <= RunningAppProcessInfo.IMPORTANCE_FOREGROUND; + } + + /** + * True if the app is playing/recording audio. + */ + private boolean hasActiveAudio(String packageName) { + // TODO(b/235306967): also check recording + return hasAudioFocus(packageName); + } + + /** + * True if the app is sending or receiving network data. + */ + private boolean hasActiveNetwork(String packageName) { + // To be implemented + return false; + } + + /** + * True if any app is interacting with the user. + */ + public boolean hasInteractingApp(List<String> packageNames) { + for (var packageName : packageNames) { + if (hasActiveAudio(packageName) + || hasActiveNetwork(packageName) + || isAppTopVisible(packageName)) { + return true; + } + } + return false; + } + + /** + * True if any app is in the foreground. + */ + public boolean hasForegroundApp(List<String> packageNames) { + for (var packageName : packageNames) { + if (isAppForeground(packageName)) { + return true; + } + } + return false; + } + + /** + * True if any app is top visible. + */ + public boolean hasTopVisibleApp(List<String> packageNames) { + for (var packageName : packageNames) { + if (isAppTopVisible(packageName)) { + return true; + } + } + return false; + } + + /** + * True if there is an ongoing phone call. + */ + public boolean isInCall() { + // To be implemented + return false; + } + + /** + * Returns a list of packages which depend on {@code packageNames}. These are the packages + * that will be affected when updating {@code packageNames} and should participate in + * the evaluation of install constraints. + * + * TODO(b/235306967): Also include bounded services as dependency. + */ + public List<String> getDependencyPackages(List<String> packageNames) { + var results = new ArraySet<String>(); + var am = mContext.getSystemService(ActivityManager.class); + for (var info : am.getRunningAppProcesses()) { + for (var packageName : packageNames) { + if (!isPackageLoaded(info, packageName)) { + continue; + } + for (var pkg : info.pkgList) { + results.add(pkg); + } + } + } + return new ArrayList<>(results); + } +} diff --git a/services/core/java/com/android/server/pm/GentleUpdateHelper.java b/services/core/java/com/android/server/pm/GentleUpdateHelper.java new file mode 100644 index 000000000000..247ac9095bff --- /dev/null +++ b/services/core/java/com/android/server/pm/GentleUpdateHelper.java @@ -0,0 +1,171 @@ +/* + * 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.pm; + +import android.annotation.WorkerThread; +import android.app.ActivityThread; +import android.app.job.JobInfo; +import android.app.job.JobParameters; +import android.app.job.JobScheduler; +import android.app.job.JobService; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageInstaller.InstallConstraints; +import android.content.pm.PackageInstaller.InstallConstraintsResult; +import android.os.Handler; +import android.os.Looper; +import android.util.Slog; + +import java.util.ArrayDeque; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +/** + * A helper class to coordinate install flow for sessions with install constraints. + * These sessions will be pending and wait until the constraints are satisfied to + * resume installation. + */ +public class GentleUpdateHelper { + private static final String TAG = "GentleUpdateHelper"; + private static final int JOB_ID = 235306967; // bug id + // The timeout used to determine whether the device is idle or not. + private static final long PENDING_CHECK_MILLIS = TimeUnit.SECONDS.toMillis(10); + + /** + * A wrapper class used by JobScheduler to schedule jobs. + */ + public static class Service extends JobService { + @Override + public boolean onStartJob(JobParameters params) { + try { + var pis = (PackageInstallerService) ActivityThread.getPackageManager() + .getPackageInstaller(); + var helper = pis.getGentleUpdateHelper(); + helper.mHandler.post(helper::runIdleJob); + } catch (Exception e) { + Slog.e(TAG, "Failed to get PackageInstallerService", e); + } + return false; + } + + @Override + public boolean onStopJob(JobParameters params) { + return false; + } + } + + private static class PendingInstallConstraintsCheck { + public final List<String> packageNames; + public final InstallConstraints constraints; + public final CompletableFuture<InstallConstraintsResult> future; + PendingInstallConstraintsCheck(List<String> packageNames, + InstallConstraints constraints, + CompletableFuture<InstallConstraintsResult> future) { + this.packageNames = packageNames; + this.constraints = constraints; + this.future = future; + } + } + + private final Context mContext; + private final Handler mHandler; + private final AppStateHelper mAppStateHelper; + // Worker thread only + private final ArrayDeque<PendingInstallConstraintsCheck> mPendingChecks = new ArrayDeque<>(); + private boolean mHasPendingIdleJob; + + GentleUpdateHelper(Context context, Looper looper, AppStateHelper appStateHelper) { + mContext = context; + mHandler = new Handler(looper); + mAppStateHelper = appStateHelper; + } + + /** + * Checks if install constraints are satisfied for the given packages. + */ + CompletableFuture<InstallConstraintsResult> checkInstallConstraints( + List<String> packageNames, InstallConstraints constraints) { + var future = new CompletableFuture<InstallConstraintsResult>(); + mHandler.post(() -> { + var pendingCheck = new PendingInstallConstraintsCheck( + packageNames, constraints, future); + if (constraints.isRequireDeviceIdle()) { + mPendingChecks.add(pendingCheck); + // JobScheduler doesn't provide queries about whether the device is idle. + // We schedule 2 tasks to determine device idle. If the idle job is executed + // before the delayed runnable, we know the device is idle. + // Note #processPendingCheck will be no-op for the task executed later. + scheduleIdleJob(); + mHandler.postDelayed(() -> processPendingCheck(pendingCheck, false), + PENDING_CHECK_MILLIS); + } else { + processPendingCheck(pendingCheck, false); + } + }); + return future; + } + + @WorkerThread + private void scheduleIdleJob() { + if (mHasPendingIdleJob) { + // No need to schedule the job again + return; + } + mHasPendingIdleJob = true; + var componentName = new ComponentName( + mContext.getPackageName(), GentleUpdateHelper.Service.class.getName()); + var jobInfo = new JobInfo.Builder(JOB_ID, componentName) + .setRequiresDeviceIdle(true) + .build(); + var jobScheduler = mContext.getSystemService(JobScheduler.class); + jobScheduler.schedule(jobInfo); + } + + @WorkerThread + private void runIdleJob() { + mHasPendingIdleJob = false; + processPendingChecksInIdle(); + } + + @WorkerThread + private void processPendingCheck(PendingInstallConstraintsCheck pendingCheck, boolean isIdle) { + var future = pendingCheck.future; + if (future.isDone()) { + return; + } + var constraints = pendingCheck.constraints; + var packageNames = mAppStateHelper.getDependencyPackages(pendingCheck.packageNames); + var constraintsSatisfied = (!constraints.isRequireDeviceIdle() || isIdle) + && (!constraints.isRequireAppNotForeground() + || !mAppStateHelper.hasForegroundApp(packageNames)) + && (!constraints.isRequireAppNotInteracting() + || !mAppStateHelper.hasInteractingApp(packageNames)) + && (!constraints.isRequireAppNotTopVisible() + || !mAppStateHelper.hasTopVisibleApp(packageNames)) + && (!constraints.isRequireNotInCall() + || !mAppStateHelper.isInCall()); + future.complete(new InstallConstraintsResult((constraintsSatisfied))); + } + + @WorkerThread + private void processPendingChecksInIdle() { + while (!mPendingChecks.isEmpty()) { + processPendingCheck(mPendingChecks.remove(), true); + } + } +} diff --git a/services/core/java/com/android/server/pm/InstallArgs.java b/services/core/java/com/android/server/pm/InstallArgs.java index a94a4e2a70be..ced547c35899 100644 --- a/services/core/java/com/android/server/pm/InstallArgs.java +++ b/services/core/java/com/android/server/pm/InstallArgs.java @@ -59,6 +59,7 @@ final class InstallArgs { final boolean mForceQueryableOverride; final int mDataLoaderType; final int mPackageSource; + final boolean mKeepApplicationEnabledSetting; // The list of instruction sets supported by this app. This is currently // only used during the rmdex() phase to clean up resources. We can get rid of this @@ -72,7 +73,8 @@ final class InstallArgs { List<String> allowlistedRestrictedPermissions, int autoRevokePermissionsMode, String traceMethod, int traceCookie, SigningDetails signingDetails, int installReason, int installScenario, - boolean forceQueryableOverride, int dataLoaderType, int packageSource) { + boolean forceQueryableOverride, int dataLoaderType, int packageSource, + boolean keepApplicationEnabledSetting) { mOriginInfo = originInfo; mMoveInfo = moveInfo; mInstallFlags = installFlags; @@ -93,6 +95,7 @@ final class InstallArgs { mForceQueryableOverride = forceQueryableOverride; mDataLoaderType = dataLoaderType; mPackageSource = packageSource; + mKeepApplicationEnabledSetting = keepApplicationEnabledSetting; } /** @@ -104,7 +107,7 @@ final class InstallArgs { null, null, instructionSets, null, null, null, MODE_DEFAULT, null, 0, SigningDetails.UNKNOWN, PackageManager.INSTALL_REASON_UNKNOWN, PackageManager.INSTALL_SCENARIO_DEFAULT, false, DataLoaderType.NONE, - PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED); + PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, false); mCodeFile = (codePath != null) ? new File(codePath) : null; } diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index e6ed0ad071ba..283640d7613a 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -20,6 +20,7 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS; import static android.content.pm.PackageManager.INSTALL_FAILED_BAD_PERMISSION_GROUP; +import static android.content.pm.PackageManager.INSTALL_FAILED_DEPRECATED_SDK_VERSION; import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE; import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION; import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION_GROUP; @@ -136,6 +137,7 @@ import android.os.incremental.IncrementalManager; import android.os.incremental.IncrementalStorage; import android.os.storage.StorageManager; import android.os.storage.VolumeInfo; +import android.provider.DeviceConfig; import android.stats.storage.StorageEnums; import android.system.ErrnoException; import android.system.Os; @@ -1017,6 +1019,28 @@ final class InstallPackageHelper { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } + // If the minimum installable SDK version enforcement is enabled, block the install + // of apps using a lower target SDK version than required. This helps improve security + // and privacy as malware can target older SDK versions to avoid enforcement of new API + // behavior. + if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE, + "MinInstallableTargetSdk__install_block_enabled", + false)) { + int minInstallableTargetSdk = + DeviceConfig.getInt(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE, + "MinInstallableTargetSdk__min_installable_target_sdk", + 0); + if (parsedPackage.getTargetSdkVersion() < minInstallableTargetSdk) { + Slog.w(TAG, "App " + parsedPackage.getPackageName() + + " targets deprecated sdk version"); + throw new PrepareFailure(INSTALL_FAILED_DEPRECATED_SDK_VERSION, + "App package must target at least version " + + minInstallableTargetSdk); + } + } else { + Slog.i(TAG, "Minimum installable target sdk enforcement not enabled"); + } + // Instant apps have several additional install-time checks. if (instantApp) { if (parsedPackage.getTargetSdkVersion() < Build.VERSION_CODES.O) { @@ -2020,7 +2044,8 @@ final class InstallPackageHelper { Slog.d(TAG, "Implicitly enabling system package on upgrade: " + pkgName); } // Enable system package for requested users - if (installedForUsers != null) { + if (installedForUsers != null + && !installRequest.isKeepApplicationEnabledSetting()) { for (int origUserId : installedForUsers) { if (userId == UserHandle.USER_ALL || userId == origUserId) { ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, @@ -2070,16 +2095,22 @@ final class InstallPackageHelper { if (userId != UserHandle.USER_ALL) { // It's implied that when a user requests installation, they want the app to - // be installed and enabled. + // be installed and enabled. The caller, however, can explicitly specify to + // keep the existing enabled state. ps.setInstalled(true, userId); - ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, installerPackageName); + if (!installRequest.isKeepApplicationEnabledSetting()) { + ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, + installerPackageName); + } } else if (allUsers != null) { // The caller explicitly specified INSTALL_ALL_USERS flag. // Thus, updating the settings to install the app for all users. for (int currentUserId : allUsers) { ps.setInstalled(true, currentUserId); - ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, - installerPackageName); + if (!installRequest.isKeepApplicationEnabledSetting()) { + ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, currentUserId, + installerPackageName); + } } } diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java index 71571dc4f306..5974a9cbab67 100644 --- a/services/core/java/com/android/server/pm/InstallRequest.java +++ b/services/core/java/com/android/server/pm/InstallRequest.java @@ -128,7 +128,8 @@ final class InstallRequest { params.mAutoRevokePermissionsMode, params.mTraceMethod, params.mTraceCookie, params.mSigningDetails, params.mInstallReason, params.mInstallScenario, params.mForceQueryableOverride, - params.mDataLoaderType, params.mPackageSource); + params.mDataLoaderType, params.mPackageSource, + params.mKeepApplicationEnabledSetting); mPackageMetrics = new PackageMetrics(this); mIsInstallInherit = params.mIsInherit; mSessionId = params.mSessionId; @@ -498,6 +499,10 @@ final class InstallRequest { return mScanResult.mChangedAbiCodePath; } + public boolean isKeepApplicationEnabledSetting() { + return mInstallArgs == null ? false : mInstallArgs.mKeepApplicationEnabledSetting; + } + public boolean isForceQueryableOverride() { return mInstallArgs != null && mInstallArgs.mForceQueryableOverride; } diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java index 69ced1b39585..2b6398af348e 100644 --- a/services/core/java/com/android/server/pm/InstallingSession.java +++ b/services/core/java/com/android/server/pm/InstallingSession.java @@ -98,6 +98,7 @@ class InstallingSession { final boolean mIsInherit; final int mSessionId; final int mRequireUserAction; + final boolean mKeepApplicationEnabledSetting; // For move install InstallingSession(OriginInfo originInfo, MoveInfo moveInfo, IPackageInstallObserver2 observer, @@ -130,6 +131,7 @@ class InstallingSession { mIsInherit = false; mSessionId = -1; mRequireUserAction = USER_ACTION_UNSPECIFIED; + mKeepApplicationEnabledSetting = false; } InstallingSession(int sessionId, File stagedDir, IPackageInstallObserver2 observer, @@ -163,6 +165,7 @@ class InstallingSession { mIsInherit = sessionParams.mode == MODE_INHERIT_EXISTING; mSessionId = sessionId; mRequireUserAction = sessionParams.requireUserAction; + mKeepApplicationEnabledSetting = sessionParams.keepApplicationEnabledSetting; } @Override diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 653a882b3447..409d3524c312 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -44,6 +44,7 @@ import android.content.pm.IPackageInstallerCallback; import android.content.pm.IPackageInstallerSession; import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; +import android.content.pm.PackageInstaller.InstallConstraints; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageInstaller.SessionParams; import android.content.pm.PackageItemInfo; @@ -54,12 +55,14 @@ import android.graphics.Bitmap; import android.net.Uri; import android.os.Binder; import android.os.Build; +import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.Process; +import android.os.RemoteCallback; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SELinux; @@ -88,6 +91,7 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.util.ImageUtils; import com.android.internal.util.IndentingPrintWriter; +import com.android.internal.util.Preconditions; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import com.android.server.IoThread; @@ -186,6 +190,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements private final InternalCallback mInternalCallback = new InternalCallback(); private final PackageSessionVerifier mSessionVerifier; + private final GentleUpdateHelper mGentleUpdateHelper; /** * Used for generating session IDs. Since this is created at boot time, @@ -272,6 +277,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements mStagingManager = new StagingManager(context); mSessionVerifier = new PackageSessionVerifier(context, mPm, mApexManager, apexParserSupplier, mInstallThread.getLooper()); + mGentleUpdateHelper = new GentleUpdateHelper( + context, mInstallThread.getLooper(), new AppStateHelper(context)); LocalServices.getService(SystemServiceManager.class).startService( new Lifecycle(context, this)); @@ -1233,6 +1240,33 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } @Override + public void checkInstallConstraints(String installerPackageName, List<String> packageNames, + InstallConstraints constraints, RemoteCallback callback) { + Preconditions.checkArgument(packageNames != null); + Preconditions.checkArgument(constraints != null); + Preconditions.checkArgument(callback != null); + + final var snapshot = mPm.snapshotComputer(); + final int callingUid = Binder.getCallingUid(); + if (!isCalledBySystemOrShell(callingUid)) { + for (var packageName : packageNames) { + var ps = snapshot.getPackageStateInternal(packageName); + if (ps == null || !TextUtils.equals( + ps.getInstallSource().mInstallerPackageName, installerPackageName)) { + throw new SecurityException("Caller has no access to package " + packageName); + } + } + } + + var future = mGentleUpdateHelper.checkInstallConstraints(packageNames, constraints); + future.thenAccept(result -> { + var b = new Bundle(); + b.putParcelable("result", result); + callback.sendResult(b); + }); + } + + @Override public void registerCallback(IPackageInstallerCallback callback, int userId) { final Computer snapshot = mPm.snapshotComputer(); snapshot.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, @@ -1265,6 +1299,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } @Override + public GentleUpdateHelper getGentleUpdateHelper() { + return mGentleUpdateHelper; + } + + @Override public void bypassNextStagedInstallerCheck(boolean value) { if (!isCalledBySystemOrShell(Binder.getCallingUid())) { throw new SecurityException("Caller not allowed to bypass staged installer check"); diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 2ee12bf97823..3983acfe9d2d 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -269,6 +269,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private static final String ATTR_SIGNATURE = "signature"; private static final String ATTR_CHECKSUM_KIND = "checksumKind"; private static final String ATTR_CHECKSUM_VALUE = "checksumValue"; + private static final String ATTR_KEEP_APPLICATION_ENABLED_SETTING = + "keepApplicationEnabledSetting"; private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill"; private static final int[] EMPTY_CHILD_SESSION_ARRAY = EmptyArray.INT; @@ -1098,6 +1100,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { info.requireUserAction = params.requireUserAction; info.installerUid = mInstallerUid; info.packageSource = params.packageSource; + info.keepApplicationEnabledSetting = params.keepApplicationEnabledSetting; } return info; } @@ -4310,6 +4313,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mPreapprovalRequested.set(true); } + @Override + public boolean isKeepApplicationEnabledSetting() { + return params.keepApplicationEnabledSetting; + } + void setSessionReady() { synchronized (mLock) { // Do not allow destroyed/failed session to change state @@ -4691,6 +4699,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { writeStringAttribute(out, ATTR_ABI_OVERRIDE, params.abiOverride); writeStringAttribute(out, ATTR_VOLUME_UUID, params.volumeUuid); out.attributeInt(null, ATTR_INSTALL_REASON, params.installReason); + writeBooleanAttribute(out, ATTR_KEEP_APPLICATION_ENABLED_SETTING, + params.keepApplicationEnabledSetting); final boolean isDataLoader = params.dataLoaderParams != null; writeBooleanAttribute(out, ATTR_IS_DATALOADER, isDataLoader); @@ -4852,6 +4862,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { params.volumeUuid = readStringAttribute(in, ATTR_VOLUME_UUID); params.installReason = in.getAttributeInt(null, ATTR_INSTALL_REASON); params.packageSource = in.getAttributeInt(null, ATTR_PACKAGE_SOURCE); + params.keepApplicationEnabledSetting = in.getAttributeBoolean(null, + ATTR_KEEP_APPLICATION_ENABLED_SETTING, false); if (in.getAttributeBoolean(null, ATTR_IS_DATALOADER, false)) { params.dataLoaderParams = new DataLoaderParams( diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index cc1306dbc2b3..e1efc612224c 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -3234,6 +3234,9 @@ class PackageManagerShellCommand extends ShellCommand { case "--skip-verification": sessionParams.installFlags |= PackageManager.INSTALL_DISABLE_VERIFICATION; break; + case "--skip-enable": + sessionParams.setKeepApplicationEnabledSetting(); + break; default: throw new IllegalArgumentException("Unknown option " + opt); } diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java index 3dcf9260f829..81f1a987c0a4 100644 --- a/services/core/java/com/android/server/pm/PackageMetrics.java +++ b/services/core/java/com/android/server/pm/PackageMetrics.java @@ -16,6 +16,8 @@ package com.android.server.pm; +import static android.os.Process.INVALID_UID; + import android.annotation.IntDef; import android.content.pm.PackageManager; import android.content.pm.parsing.ApkLiteParseUtils; @@ -209,4 +211,35 @@ final class PackageMetrics { deleteFlags, PackageManager.DELETE_SUCCEEDED, info.mIsRemovedPackageSystemUpdate, !info.mRemovedForAllUsers); } + + public static void onVerificationFailed(VerifyingSession verifyingSession) { + FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_INSTALLATION_SESSION_REPORTED, + verifyingSession.getSessionId() /* session_id */, + null /* package_name */, + INVALID_UID /* uid */, + null /* user_ids */, + null /* user_types */, + null /* original_user_ids */, + null /* original_user_types */, + verifyingSession.getRet() /* public_return_code */, + 0 /* internal_error_code */, + 0 /* apks_size_bytes */, + 0 /* version_code */, + null /* install_steps */, + null /* step_duration_millis */, + 0 /* total_duration_millis */, + 0 /* install_flags */, + verifyingSession.getInstallerPackageUid() /* installer_package_uid */, + INVALID_UID /* original_installer_package_uid */, + verifyingSession.getDataLoaderType() /* data_loader_type */, + verifyingSession.getUserActionRequiredType() /* user_action_required_type */, + verifyingSession.isInstant() /* is_instant */, + false /* is_replace */, + false /* is_system */, + verifyingSession.isInherit() /* is_inherit */, + false /* is_installing_existing_as_user */, + false /* is_move_install */, + verifyingSession.isStaged() /* is_staged */ + ); + } } diff --git a/services/core/java/com/android/server/pm/PackageSessionProvider.java b/services/core/java/com/android/server/pm/PackageSessionProvider.java index ad5cf1341b2a..79b88b38364d 100644 --- a/services/core/java/com/android/server/pm/PackageSessionProvider.java +++ b/services/core/java/com/android/server/pm/PackageSessionProvider.java @@ -29,4 +29,9 @@ public interface PackageSessionProvider { PackageInstallerSession getSession(int sessionId); PackageSessionVerifier getSessionVerifier(); + + /** + * Get the GentleUpdateHelper instance. + */ + GentleUpdateHelper getGentleUpdateHelper(); } diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java index 1027f4c03127..df132a97418a 100644 --- a/services/core/java/com/android/server/pm/UserManagerInternal.java +++ b/services/core/java/com/android/server/pm/UserManagerInternal.java @@ -441,4 +441,12 @@ public abstract class UserManagerInternal { /** Return the integer types of the given user IDs. Only used for reporting metrics to statsd. */ public abstract int[] getUserTypesForStatsd(@UserIdInt int[] userIds); + + /** + * Returns the user id of the main user, or {@link android.os.UserHandle#USER_NULL} if there is + * no main user. + * + * @see UserManager#isMainUser() + */ + public abstract @UserIdInt int getMainUserId(); } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 88e12fa9a604..02a57b288a06 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -901,6 +901,25 @@ public class UserManagerService extends IUserManager.Stub { return null; } + @Override + public @UserIdInt int getMainUserId() { + checkQueryOrCreateUsersPermission("get main user id"); + return getMainUserIdUnchecked(); + } + + private @UserIdInt int getMainUserIdUnchecked() { + synchronized (mUsersLock) { + final int userSize = mUsers.size(); + for (int i = 0; i < userSize; i++) { + final UserInfo user = mUsers.valueAt(i).info; + if (user.isMain() && !mRemovingUserIds.get(user.id)) { + return user.id; + } + } + } + return UserHandle.USER_NULL; + } + public @NonNull List<UserInfo> getUsers(boolean excludeDying) { return getUsers(/*excludePartial= */ true, excludeDying, /* excludePreCreated= */ true); @@ -3783,10 +3802,10 @@ public class UserManagerService extends IUserManager.Stub { private UserInfo getEarliestCreatedFullUser() { final List<UserInfo> users = getUsersInternal(true, true, true); UserInfo earliestUser = users.get(0); - long earliestCreationTime = earliestUser.creationTime; + long earliestCreationTime = Long.MAX_VALUE; for (int i = 0; i < users.size(); i++) { final UserInfo info = users.get(i); - if (info.isFull() && info.isAdmin() && info.creationTime > 0 + if (info.isFull() && info.isAdmin() && info.creationTime >= 0 && info.creationTime < earliestCreationTime) { earliestCreationTime = info.creationTime; earliestUser = info; @@ -6898,6 +6917,12 @@ public class UserManagerService extends IUserManager.Stub { } return userTypes; } + + @Override + public @UserIdInt int getMainUserId() { + return getMainUserIdUnchecked(); + } + } // class LocalService diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java index 2650b2394cc5..878855a89fb1 100644 --- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java +++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java @@ -575,9 +575,7 @@ public final class UserVisibilityMediator implements Dumpable { ipw.println(mCurrentUserId); ipw.print("Visible users: "); - // TODO: merge 2 lines below if/when IntArray implements toString()... - IntArray visibleUsers = getVisibleUsers(); - ipw.println(java.util.Arrays.toString(visibleUsers.toArray())); + ipw.println(getVisibleUsers()); dumpSparseIntArray(ipw, mStartedProfileGroupIds, "started user / profile group", "u", "pg"); diff --git a/services/core/java/com/android/server/pm/VerificationUtils.java b/services/core/java/com/android/server/pm/VerificationUtils.java index e1026b419ff0..30f2132ce1f1 100644 --- a/services/core/java/com/android/server/pm/VerificationUtils.java +++ b/services/core/java/com/android/server/pm/VerificationUtils.java @@ -112,7 +112,7 @@ final class VerificationUtils { VerificationUtils.broadcastPackageVerified(verificationId, originUri, verificationCode, null, - verifyingSession.mDataLoaderType, verifyingSession.getUser(), + verifyingSession.getDataLoaderType(), verifyingSession.getUser(), pms.mContext); if (state.isInstallAllowed()) { diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java index 6160519f4e77..a54f52619f35 100644 --- a/services/core/java/com/android/server/pm/VerifyingSession.java +++ b/services/core/java/com/android/server/pm/VerifyingSession.java @@ -19,6 +19,7 @@ package com.android.server.pm; import static android.content.Intent.EXTRA_LONG_VERSION_CODE; import static android.content.Intent.EXTRA_PACKAGE_NAME; import static android.content.Intent.EXTRA_VERSION_CODE; +import static android.content.pm.PackageInstaller.SessionParams.MODE_INHERIT_EXISTING; import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID; import static android.content.pm.PackageManager.INSTALL_SUCCEEDED; import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING; @@ -114,30 +115,32 @@ final class VerifyingSession { final OriginInfo mOriginInfo; final IPackageInstallObserver2 mObserver; - final int mInstallFlags; + private final int mInstallFlags; @NonNull - final InstallSource mInstallSource; - final String mPackageAbiOverride; - final VerificationInfo mVerificationInfo; - final SigningDetails mSigningDetails; + private final InstallSource mInstallSource; + private final String mPackageAbiOverride; + private final VerificationInfo mVerificationInfo; + private final SigningDetails mSigningDetails; @Nullable MultiPackageVerifyingSession mParentVerifyingSession; - final long mRequiredInstalledVersionCode; - final int mDataLoaderType; - final int mSessionId; - final boolean mUserActionRequired; - + private final long mRequiredInstalledVersionCode; + private final int mDataLoaderType; + private final int mSessionId; + private final boolean mUserActionRequired; + private final int mUserActionRequiredType; private boolean mWaitForVerificationToComplete; private boolean mWaitForIntegrityVerificationToComplete; private boolean mWaitForEnableRollbackToComplete; private int mRet = PackageManager.INSTALL_SUCCEEDED; private String mErrorMessage = null; + private final boolean mIsInherit; + private final boolean mIsStaged; - final PackageLite mPackageLite; + private final PackageLite mPackageLite; private final UserHandle mUser; @NonNull - final PackageManagerService mPm; - final InstallPackageHelper mInstallPackageHelper; + private final PackageManagerService mPm; + private final InstallPackageHelper mInstallPackageHelper; VerifyingSession(UserHandle user, File stagedDir, IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams, InstallSource installSource, @@ -164,6 +167,9 @@ final class VerifyingSession { mSessionId = sessionId; mPackageLite = lite; mUserActionRequired = userActionRequired; + mUserActionRequiredType = sessionParams.requireUserAction; + mIsInherit = sessionParams.mode == MODE_INHERIT_EXISTING; + mIsStaged = sessionParams.isStaged; } @Override @@ -186,7 +192,7 @@ final class VerifyingSession { // Perform package verification and enable rollback (unless we are simply moving the // package). if (!mOriginInfo.mExisting) { - if ((mInstallFlags & PackageManager.INSTALL_APEX) == 0) { + if (!isApex()) { // TODO(b/182426975): treat APEX as APK when APK verification is concerned sendApkVerificationRequest(pkgLite); } @@ -674,10 +680,9 @@ final class VerifyingSession { } final int installerUid = mVerificationInfo == null ? -1 : mVerificationInfo.mInstallerUid; - final int installFlags = mInstallFlags; // Check if installing from ADB - if ((installFlags & PackageManager.INSTALL_FROM_ADB) != 0) { + if ((mInstallFlags & PackageManager.INSTALL_FROM_ADB) != 0) { boolean requestedDisableVerification = (mInstallFlags & PackageManager.INSTALL_DISABLE_VERIFICATION) != 0; return isAdbVerificationEnabled(pkgInfoLite, userId, requestedDisableVerification); @@ -685,8 +690,7 @@ final class VerifyingSession { // only when not installed from ADB, skip verification for instant apps when // the installer and verifier are the same. - if ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0 - && mPm.mInstantAppInstallerActivity != null) { + if (isInstant() && mPm.mInstantAppInstallerActivity != null) { String installerPackage = mPm.mInstantAppInstallerActivity.packageName; for (String requiredVerifierPackage : requiredVerifierPackages) { if (installerPackage.equals(requiredVerifierPackage)) { @@ -818,6 +822,9 @@ final class VerifyingSession { return; } sendVerificationCompleteNotification(); + if (mRet != INSTALL_SUCCEEDED) { + PackageMetrics.onVerificationFailed(this); + } } private void sendVerificationCompleteNotification() { @@ -865,4 +872,28 @@ final class VerifyingSession { public UserHandle getUser() { return mUser; } + public int getSessionId() { + return mSessionId; + } + public int getDataLoaderType() { + return mDataLoaderType; + } + public int getUserActionRequiredType() { + return mUserActionRequiredType; + } + public boolean isInstant() { + return (mInstallFlags & PackageManager.INSTALL_INSTANT_APP) != 0; + } + public boolean isInherit() { + return mIsInherit; + } + public int getInstallerPackageUid() { + return mInstallSource.mInstallerPackageUid; + } + public boolean isApex() { + return (mInstallFlags & PackageManager.INSTALL_APEX) != 0; + } + public boolean isStaged() { + return mIsStaged; + } } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 3aa333aacdb6..e9c93eef6c54 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -4208,11 +4208,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { wakeUpFromWakeKey(event); } - if ((result & ACTION_PASS_TO_USER) != 0) { + if ((result & ACTION_PASS_TO_USER) != 0 && !mPerDisplayFocusEnabled + && displayId != INVALID_DISPLAY && displayId != mTopFocusedDisplayId) { // If the key event is targeted to a specific display, then the user is interacting with - // that display. Therefore, give focus to the display that the user is interacting with. - if (!mPerDisplayFocusEnabled - && displayId != INVALID_DISPLAY && displayId != mTopFocusedDisplayId) { + // that display. Therefore, give focus to the display that the user is interacting with, + // unless that display maintains its own focus. + Display display = mDisplayManager.getDisplay(displayId); + if ((display.getFlags() & Display.FLAG_OWN_FOCUS) == 0) { // An event is targeting a non-focused display. Move the display to top so that // it can become the focused display to interact with the user. // This should be done asynchronously, once the focus logic is fully moved to input diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 9281f4bf75b0..1ea0988893ad 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -2204,6 +2204,15 @@ public final class PowerManagerService extends SystemService if (sQuiescent) { mDirty |= DIRTY_QUIESCENT; } + PowerGroup defaultGroup = mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP); + if (defaultGroup.getWakefulnessLocked() == WAKEFULNESS_DOZING) { + // Workaround for b/187231320 where the AOD can get stuck in a "half on / + // half off" state when a non-default-group VirtualDisplay causes the global + // wakefulness to change to awake, even though the default display is + // dozing. We set sandman summoned to restart dreaming to get it unstuck. + // TODO(b/255688811) - fix this so that AOD never gets interrupted at all. + defaultGroup.setSandmanSummonedLocked(true); + } break; case WAKEFULNESS_ASLEEP: diff --git a/services/core/java/com/android/server/sensors/SensorManagerInternal.java b/services/core/java/com/android/server/sensors/SensorManagerInternal.java index fbb6644934f1..f17e5e73ceb0 100644 --- a/services/core/java/com/android/server/sensors/SensorManagerInternal.java +++ b/services/core/java/com/android/server/sensors/SensorManagerInternal.java @@ -43,6 +43,43 @@ public abstract class SensorManagerInternal { public abstract void removeProximityActiveListener(@NonNull ProximityActiveListener listener); /** + * Creates a sensor that is registered at runtime by the system with the sensor service. + * + * The runtime sensors created here are different from the + * <a href="https://source.android.com/docs/core/interaction/sensors/sensors-hal2#dynamic-sensors"> + * dynamic sensor support in the HAL</a>. These sensors have no HAL dependency and correspond to + * sensors that belong to an external (virtual) device. + * + * @param deviceId The identifier of the device this sensor is associated with. + * @param type The generic type of the sensor. + * @param name The name of the sensor. + * @param vendor The vendor string of the sensor. + * @param callback The callback to get notified when the sensor listeners have changed. + * @return The sensor handle. + */ + public abstract int createRuntimeSensor(int deviceId, int type, @NonNull String name, + @NonNull String vendor, @NonNull RuntimeSensorStateChangeCallback callback); + + /** + * Unregisters the sensor with the given handle from the framework. + */ + public abstract void removeRuntimeSensor(int handle); + + /** + * Sends an event for the runtime sensor with the given handle to the framework. + * + * Only relevant for sending runtime sensor events. @see #createRuntimeSensor. + * + * @param handle The sensor handle. + * @param type The type of the sensor. + * @param timestampNanos When the event occurred. + * @param values The values of the event. + * @return Whether the event injection was successful. + */ + public abstract boolean sendSensorEvent(int handle, int type, long timestampNanos, + @NonNull float[] values); + + /** * Listener for proximity sensor state changes. */ public interface ProximityActiveListener { @@ -52,4 +89,17 @@ public abstract class SensorManagerInternal { */ void onProximityActive(boolean isActive); } + + /** + * Callback for runtime sensor state changes. Only relevant to sensors created via + * {@link #createRuntimeSensor}, i.e. the dynamic sensors created via the dynamic sensor HAL are + * not covered. + */ + public interface RuntimeSensorStateChangeCallback { + /** + * Invoked when the listeners of the runtime sensor have changed. + */ + void onStateChanged(boolean enabled, int samplingPeriodMicros, + int batchReportLatencyMicros); + } } diff --git a/services/core/java/com/android/server/sensors/SensorService.java b/services/core/java/com/android/server/sensors/SensorService.java index 8fe2d52f7160..d8e3bddd6432 100644 --- a/services/core/java/com/android/server/sensors/SensorService.java +++ b/services/core/java/com/android/server/sensors/SensorService.java @@ -29,7 +29,9 @@ import com.android.server.SystemServerInitThreadPool; import com.android.server.SystemService; import com.android.server.utils.TimingsTraceAndSlog; +import java.util.HashSet; import java.util.Objects; +import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.Future; @@ -40,6 +42,8 @@ public class SensorService extends SystemService { private final ArrayMap<ProximityActiveListener, ProximityListenerProxy> mProximityListeners = new ArrayMap<>(); @GuardedBy("mLock") + private final Set<Integer> mRuntimeSensorHandles = new HashSet<>(); + @GuardedBy("mLock") private Future<?> mSensorServiceStart; @GuardedBy("mLock") private long mPtr; @@ -51,6 +55,12 @@ public class SensorService extends SystemService { private static native void registerProximityActiveListenerNative(long ptr); private static native void unregisterProximityActiveListenerNative(long ptr); + private static native int registerRuntimeSensorNative(long ptr, int deviceId, int type, + String name, String vendor, + SensorManagerInternal.RuntimeSensorStateChangeCallback callback); + private static native void unregisterRuntimeSensorNative(long ptr, int handle); + private static native boolean sendRuntimeSensorEventNative(long ptr, int handle, int type, + long timestampNanos, float[] values); public SensorService(Context ctx) { super(ctx); @@ -85,6 +95,38 @@ public class SensorService extends SystemService { class LocalService extends SensorManagerInternal { @Override + public int createRuntimeSensor(int deviceId, int type, @NonNull String name, + @NonNull String vendor, @NonNull RuntimeSensorStateChangeCallback callback) { + synchronized (mLock) { + int handle = registerRuntimeSensorNative(mPtr, deviceId, type, name, vendor, + callback); + mRuntimeSensorHandles.add(handle); + return handle; + } + } + + @Override + public void removeRuntimeSensor(int handle) { + synchronized (mLock) { + if (mRuntimeSensorHandles.contains(handle)) { + mRuntimeSensorHandles.remove(handle); + unregisterRuntimeSensorNative(mPtr, handle); + } + } + } + + @Override + public boolean sendSensorEvent(int handle, int type, long timestampNanos, + @NonNull float[] values) { + synchronized (mLock) { + if (!mRuntimeSensorHandles.contains(handle)) { + return false; + } + return sendRuntimeSensorEventNative(mPtr, handle, type, timestampNanos, values); + } + } + + @Override public void addProximityActiveListener(@NonNull Executor executor, @NonNull ProximityActiveListener listener) { Objects.requireNonNull(executor, "executor must not be null"); diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java index f8c1c9269ff3..10cd5d1a0669 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java @@ -91,7 +91,7 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub deviceActivityMonitor.addListener(new DeviceActivityMonitor.Listener() { @Override public void onFlightComplete() { - timeZoneDetectorStrategy.enableTelephonyTimeZoneFallback(); + timeZoneDetectorStrategy.enableTelephonyTimeZoneFallback("onFlightComplete()"); } }); @@ -402,9 +402,9 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub * Sends a signal to enable telephony fallback. Provided for command-line access for use * during tests. This is not exposed as a binder API. */ - void enableTelephonyFallback() { + void enableTelephonyFallback(@NonNull String reason) { enforceManageTimeZoneDetectorPermission(); - mTimeZoneDetectorStrategy.enableTelephonyTimeZoneFallback(); + mTimeZoneDetectorStrategy.enableTelephonyTimeZoneFallback(reason); } /** diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java index 69274dba7825..ab68e834d337 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java @@ -189,7 +189,7 @@ class TimeZoneDetectorShellCommand extends ShellCommand { } private int runEnableTelephonyFallback() { - mInterface.enableTelephonyFallback(); + mInterface.enableTelephonyFallback("Command line"); return 0; } diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java index 5768a6bfa0dc..37e67c921634 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java @@ -35,13 +35,13 @@ import android.util.IndentingPrintWriter; * <p>Devices can have zero, one or two automatic time zone detection algorithms available at any * point in time. * - * <p>The two automatic detection algorithms supported are "telephony" and "geolocation". Algorithm + * <p>The two automatic detection algorithms supported are "telephony" and "location". Algorithm * availability and use depends on several factors: * <ul> * <li>Telephony is only available on devices with a telephony stack. - * <li>Geolocation is also optional and configured at image creation time. When enabled on a - * device, its availability depends on the current user's settings, so switching between users can - * change the automatic algorithm used by the device.</li> + * <li>Location is also optional and configured at image creation time. When enabled on a device, + * its availability depends on the current user's settings, so switching between users can change + * the automatic detection algorithm used by the device.</li> * </ul> * * <p>If there are no automatic time zone detections algorithms available then the user can usually @@ -56,14 +56,14 @@ import android.util.IndentingPrintWriter; * slotIndexes must have an empty suggestion submitted in order to "withdraw" their previous * suggestion otherwise it will remain in use. * - * <p>Geolocation detection is dependent on the current user and their settings. The device retains - * at most one geolocation suggestion. Generally, use of a device's location is dependent on the - * user's "location toggle", but even when that is enabled the user may choose to enable / disable - * the use of geolocation for device time zone detection. If the current user changes to one that - * does not have geolocation detection enabled, or the user turns off geolocation detection, then - * the strategy discards the latest geolocation suggestion. Devices that lose a location fix must - * have an empty suggestion submitted in order to "withdraw" their previous suggestion otherwise it - * will remain in use. + * <p>Location-based detection is dependent on the current user and their settings. The device + * retains at most one geolocation suggestion. Generally, use of a device's location is dependent on + * the user's "location toggle", but even when that is enabled the user may choose to enable / + * disable the use of location for device time zone detection. If the current user changes to one + * that does not have location-based detection enabled, or the user turns off the location-based + * detection, then the strategy will be sent an event that clears the latest suggestion. Devices + * that lose their location fix must have an empty suggestion submitted in order to "withdraw" their + * previous suggestion otherwise it will remain in use. * * <p>The strategy uses only one algorithm at a time and does not attempt consensus even when * more than one is available on a device. This "use only one" behavior is deliberate as different @@ -72,25 +72,27 @@ import android.util.IndentingPrintWriter; * users enter areas without the necessary signals. Ultimately, with no perfect algorithm available, * the user is left to choose which algorithm works best for their circumstances. * - * <p>When geolocation detection is supported and enabled, in certain circumstances, such as during - * international travel, it makes sense to prioritize speed of detection via telephony (when - * available) Vs waiting for the geolocation algorithm to reach certainty. Geolocation detection can - * sometimes be slow to get a location fix and can require network connectivity (which cannot be - * assumed when users are travelling) for server-assisted location detection or time zone lookup. - * Therefore, as a restricted form of prioritization between geolocation and telephony algorithms, - * the strategy provides "telephony fallback" behavior, which can be set to "supported" via device - * config. Fallback mode is toggled on at runtime via {@link #enableTelephonyTimeZoneFallback()} in - * response to signals outside of the scope of this class. Telephony fallback allows the use of - * telephony suggestions to help with faster detection but only until geolocation detection - * provides a concrete, "certain" suggestion. After geolocation has made the first certain - * suggestion, telephony fallback is disabled until the next call to {@link - * #enableTelephonyTimeZoneFallback()}. + * <p>When the location detection algorithm is supported and enabled, in certain circumstances, such + * as during international travel, it makes sense to prioritize speed of detection via telephony + * (when available) Vs waiting for the location-based detection algorithm to reach certainty. + * Location-based detection can sometimes be slow to get a location fix and can require network + * connectivity (which cannot be assumed when users are travelling) for server-assisted location + * detection or time zone lookup. Therefore, as a restricted form of prioritization between location + * and telephony algorithms, the strategy provides "telephony fallback mode" behavior, which can be + * set to "supported" via device config. Fallback mode is entered at runtime in response to signals + * from outside of the strategy, e.g. from a call to {@link + * #enableTelephonyTimeZoneFallback(String)}, or from information in the latest {@link + * LocationAlgorithmEvent}. For telephony fallback mode to actually use a telephony suggestion, the + * location algorithm <em>must</em> report it is uncertain. Telephony fallback allows the use of + * telephony suggestions to help with faster detection but only until the location algorithm + * provides a concrete, "certain" suggestion. After the location algorithm has made a certain + * suggestion, telephony fallback mode is disabled. * * <p>Threading: * * <p>Implementations of this class must be thread-safe as calls calls like {@link * #generateMetricsState()} and {@link #dump(IndentingPrintWriter, String[])} may be called on - * differents thread concurrently with other operations. + * different threads concurrently with other operations. * * @hide */ @@ -181,11 +183,11 @@ public interface TimeZoneDetectorStrategy extends Dumpable { void suggestTelephonyTimeZone(@NonNull TelephonyTimeZoneSuggestion suggestion); /** - * Tells the strategy that it can fall back to telephony detection while geolocation detection - * remains uncertain. {@link #handleLocationAlgorithmEvent(LocationAlgorithmEvent)} can - * disable it again. See {@link TimeZoneDetectorStrategy} for details. + * Tells the strategy that it can fall back to telephony detection while the location detection + * algorithm remains uncertain. {@link #handleLocationAlgorithmEvent(LocationAlgorithmEvent)} + * can disable it again. See {@link TimeZoneDetectorStrategy} for details. */ - void enableTelephonyTimeZoneFallback(); + void enableTelephonyTimeZoneFallback(@NonNull String reason); /** Generates a state snapshot for metrics. */ @NonNull @@ -194,6 +196,6 @@ public interface TimeZoneDetectorStrategy extends Dumpable { /** Returns {@code true} if the device supports telephony time zone detection. */ boolean isTelephonyTimeZoneDetectionSupported(); - /** Returns {@code true} if the device supports geolocation time zone detection. */ + /** Returns {@code true} if the device supports location-based time zone detection. */ boolean isGeoTimeZoneDetectionSupported(); } diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java index fa811efcfec0..e0e3565e1b0b 100644 --- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java +++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java @@ -63,12 +63,8 @@ import java.util.Objects; public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrategy { /** - * Used by {@link TimeZoneDetectorStrategyImpl} to interact with device configuration / settings - * / system properties. It can be faked for testing. - * - * <p>Note: Because the settings / system properties-derived values can currently be modified - * independently and from different threads (and processes!), their use is prone to race - * conditions. + * Used by {@link TimeZoneDetectorStrategyImpl} to interact with device state besides that + * available from {@link #mServiceConfigAccessor}. It can be faked for testing. */ @VisibleForTesting public interface Environment { @@ -234,7 +230,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat * allows). * * <p>This field is only actually used when telephony time zone fallback is supported, but the - * value is maintained even when it isn't supported as it can be turned on at any time via + * value is maintained even when it isn't supported as support can be turned on at any time via * server flags. The elapsed realtime when the mode last changed is used to help ordering * between fallback mode switches and suggestions. * @@ -421,10 +417,15 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat notifyStateChangeListenersAsynchronously(); } - // Update the mTelephonyTimeZoneFallbackEnabled state if needed: a certain suggestion - // will usually disable telephony fallback mode if it is currently enabled. - // TODO(b/236624675)Some provider status codes can be used to enable telephony fallback. - disableTelephonyFallbackIfNeeded(); + // Manage telephony fallback state. + if (event.getAlgorithmStatus().couldEnableTelephonyFallback()) { + // An event may trigger entry into telephony fallback mode if the status + // indicates the location algorithm cannot work and is likely to stay not working. + enableTelephonyTimeZoneFallback("handleLocationAlgorithmEvent(), event=" + event); + } else { + // A certain suggestion will exit telephony fallback mode. + disableTelephonyFallbackIfNeeded(); + } // Now perform auto time zone detection. The new event may be used to modify the time zone // setting. @@ -497,38 +498,41 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat } @Override - public synchronized void enableTelephonyTimeZoneFallback() { - // Only do any work if fallback is currently not enabled. + public synchronized void enableTelephonyTimeZoneFallback(@NonNull String reason) { + // Only do any work to enter fallback mode if fallback is currently not already enabled. if (!mTelephonyTimeZoneFallbackEnabled.getValue()) { ConfigurationInternal currentUserConfig = mCurrentConfigurationInternal; final boolean fallbackEnabled = true; mTelephonyTimeZoneFallbackEnabled = new TimestampedValue<>( mEnvironment.elapsedRealtimeMillis(), fallbackEnabled); - String logMsg = "enableTelephonyTimeZoneFallbackMode: " - + " currentUserConfig=" + currentUserConfig - + ", mTelephonyTimeZoneFallbackEnabled=" - + mTelephonyTimeZoneFallbackEnabled; + String logMsg = "enableTelephonyTimeZoneFallback: " + + " reason=" + reason + + ", currentUserConfig=" + currentUserConfig + + ", mTelephonyTimeZoneFallbackEnabled=" + mTelephonyTimeZoneFallbackEnabled; logTimeZoneDebugInfo(logMsg); // mTelephonyTimeZoneFallbackEnabled and mLatestLocationAlgorithmEvent interact. - // If the latest event contains a "certain" geolocation suggestion, then the telephony - // fallback value needs to be considered after changing it. + // If the latest location algorithm event contains a "certain" geolocation suggestion, + // then the telephony fallback mode needs to be (re)considered after changing it. + // // With the way that the mTelephonyTimeZoneFallbackEnabled time is currently chosen // above, and the fact that geolocation suggestions should never have a time in the - // future, the following call will be a no-op, and telephony fallback will remain - // enabled. This comment / call is left as a reminder that it is possible for there to - // be a current, "certain" geolocation suggestion when this signal arrives and it is - // intentional that fallback stays enabled in this case. The choice to do this - // is mostly for symmetry WRT the case where fallback is enabled and an old "certain" - // geolocation is received; that would also leave telephony fallback enabled. - // This choice means that telephony fallback will remain enabled until a new "certain" - // geolocation suggestion is received. If, instead, the next geolocation is "uncertain", - // then telephony fallback will occur. + // future, the following call will usually be a no-op, and telephony fallback mode will + // remain enabled. This comment / call is left as a reminder that it is possible in some + // cases for there to be a current, "certain" geolocation suggestion when an attempt is + // made to enable telephony fallback mode and it is intentional that fallback mode stays + // enabled in this case. The choice to do this is mostly for symmetry WRT the case where + // fallback is enabled and then an old "certain" geolocation suggestion is received; + // that would also leave telephony fallback mode enabled. + // + // This choice means that telephony fallback mode remains enabled if there is an + // existing "certain" suggestion until a new "certain" geolocation suggestion is + // received. If, instead, the next geolocation suggestion is "uncertain", then telephony + // fallback, i.e. the use of a telephony suggestion, will actually occur. disableTelephonyFallbackIfNeeded(); if (currentUserConfig.isTelephonyFallbackSupported()) { - String reason = "enableTelephonyTimeZoneFallbackMode"; doAutoTimeZoneDetection(currentUserConfig, reason); } } diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java index 0b1f6b9ba285..f971db9b5f0e 100644 --- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java +++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java @@ -107,6 +107,7 @@ public class TrustAgentWrapper { // Trust state private boolean mTrusted; private boolean mWaitingForTrustableDowngrade = false; + private boolean mWithinSecurityLockdownWindow = false; private boolean mTrustable; private CharSequence mMessage; private boolean mDisplayTrustGrantedMessage; @@ -160,6 +161,7 @@ public class TrustAgentWrapper { mDisplayTrustGrantedMessage = (flags & FLAG_GRANT_TRUST_DISPLAY_MESSAGE) != 0; if ((flags & FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) != 0) { mWaitingForTrustableDowngrade = true; + setSecurityWindowTimer(); } else { mWaitingForTrustableDowngrade = false; } @@ -452,6 +454,9 @@ public class TrustAgentWrapper { if (mBound) { scheduleRestart(); } + if (mWithinSecurityLockdownWindow) { + mTrustManagerService.lockUser(mUserId); + } // mTrustDisabledByDpm maintains state } }; @@ -673,6 +678,22 @@ public class TrustAgentWrapper { } } + private void setSecurityWindowTimer() { + mWithinSecurityLockdownWindow = true; + long expiration = SystemClock.elapsedRealtime() + (15 * 1000); // timer for 15 seconds + mAlarmManager.setExact( + AlarmManager.ELAPSED_REALTIME_WAKEUP, + expiration, + TAG, + new AlarmManager.OnAlarmListener() { + @Override + public void onAlarm() { + mWithinSecurityLockdownWindow = false; + } + }, + Handler.getMain()); + } + public boolean isManagingTrust() { return mManagingTrust && !mTrustDisabledByDpm; } @@ -691,7 +712,6 @@ public class TrustAgentWrapper { public void destroy() { mHandler.removeMessages(MSG_RESTART_TIMEOUT); - if (!mBound) { return; } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index f74956b7c846..5d084616bfea 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -1559,8 +1559,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub try { mReply.sendResult(null); } catch (RemoteException e) { - Binder.restoreCallingIdentity(ident); Slog.d(TAG, "failed to send callback!", e); + } finally { + Binder.restoreCallingIdentity(ident); } t.traceEnd(); mReply = null; diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index 14d6d7bdfb7d..798e73906761 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -241,6 +241,7 @@ class BackNavigationController { // We have another Activity in the same currentTask to go to backType = BackNavigationInfo.TYPE_CROSS_ACTIVITY; removedWindowContainer = currentActivity; + prevTask = prevActivity.getTask(); } else if (currentTask.returnsToHomeRootTask()) { // Our Task should bring back to home removedWindowContainer = currentTask; @@ -608,26 +609,23 @@ class BackNavigationController { // reset leash after animation finished. leashes.add(screenshotSurface); } - } else if (prevTask != null) { - prevActivity = prevTask.getTopNonFinishingActivity(); - if (prevActivity != null) { - // Make previous task show from behind by marking its top activity as visible - // and launch-behind to bump its visibility for the duration of the back gesture. - setLaunchBehind(prevActivity); - - final SurfaceControl leash = prevActivity.makeAnimationLeash() - .setName("BackPreview Leash for " + prevActivity) - .setHidden(false) - .build(); - prevActivity.reparentSurfaceControl(startedTransaction, leash); - behindAppTarget = createRemoteAnimationTargetLocked( - prevTask, leash, MODE_OPENING); - - // reset leash after animation finished. - leashes.add(leash); - prevActivity.reparentSurfaceControl(finishedTransaction, - prevActivity.getParentSurfaceControl()); - } + } else if (prevTask != null && prevActivity != null) { + // Make previous task show from behind by marking its top activity as visible + // and launch-behind to bump its visibility for the duration of the back gesture. + setLaunchBehind(prevActivity); + + final SurfaceControl leash = prevActivity.makeAnimationLeash() + .setName("BackPreview Leash for " + prevActivity) + .setHidden(false) + .build(); + prevActivity.reparentSurfaceControl(startedTransaction, leash); + behindAppTarget = createRemoteAnimationTargetLocked( + prevTask, leash, MODE_OPENING); + + // reset leash after animation finished. + leashes.add(leash); + prevActivity.reparentSurfaceControl(finishedTransaction, + prevActivity.getParentSurfaceControl()); } if (mShowWallpaper) { diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 0119e4df9578..9c920f53808b 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -3380,7 +3380,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } } mWmService.mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN); - controller.mTransitionMetricsReporter.associate(t, + controller.mTransitionMetricsReporter.associate(t.getToken(), startTime -> mWmService.mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN)); startAsyncRotation(false /* shouldDebounce */); } @@ -3683,7 +3683,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp * @return The focused window or null if there isn't any or no need to seek. */ WindowState findFocusedWindowIfNeeded(int topFocusedDisplayId) { - return (mWmService.mPerDisplayFocusEnabled || topFocusedDisplayId == INVALID_DISPLAY) + return (hasOwnFocus() || topFocusedDisplayId == INVALID_DISPLAY) ? findFocusedWindow() : null; } @@ -6315,6 +6315,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } /** + * @return whether this display maintains its own focus and touch mode. + */ + boolean hasOwnFocus() { + return mWmService.mPerDisplayFocusEnabled + || (mDisplayInfo.flags & Display.FLAG_OWN_FOCUS) != 0; + } + + /** * @return whether the keyguard is occluded on this display */ boolean isKeyguardOccluded() { diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 7860b1530e92..3e1105b7cb51 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -270,7 +270,7 @@ final class InputMonitor { InputConfigAdapter.getMask()); final boolean focusable = w.canReceiveKeys() - && (mService.mPerDisplayFocusEnabled || mDisplayContent.isOnTop()); + && (mDisplayContent.hasOwnFocus() || mDisplayContent.isOnTop()); inputWindowHandle.setFocusable(focusable); final boolean hasWallpaper = mDisplayContent.mWallpaperController.isWallpaperTarget(w) diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 2dbccae6dde6..bb4c482118a1 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -87,10 +87,6 @@ final class LetterboxUiController { private final LetterboxConfiguration mLetterboxConfiguration; private final ActivityRecord mActivityRecord; - // Taskbar expanded height. Used to determine whether to crop an app window to display rounded - // corners above the taskbar. - private final float mExpandedTaskBarHeight; - private boolean mShowWallpaperForLetterboxBackground; @Nullable @@ -102,8 +98,6 @@ final class LetterboxUiController { // is created in its constructor. It shouldn't be used in this constructor but it's safe // to use it after since controller is only used in ActivityRecord. mActivityRecord = activityRecord; - mExpandedTaskBarHeight = - getResources().getDimensionPixelSize(R.dimen.taskbar_frame_height); } /** Cleans up {@link Letterbox} if it exists.*/ @@ -285,14 +279,17 @@ final class LetterboxUiController { } float getSplitScreenAspectRatio() { + // Getting the same aspect ratio that apps get in split screen. + final DisplayContent displayContent = mActivityRecord.getDisplayContent(); + if (displayContent == null) { + return getDefaultMinAspectRatioForUnresizableApps(); + } int dividerWindowWidth = getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_thickness); int dividerInsets = getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_insets); int dividerSize = dividerWindowWidth - dividerInsets * 2; - - // Getting the same aspect ratio that apps get in split screen. - Rect bounds = new Rect(mActivityRecord.getDisplayContent().getBounds()); + final Rect bounds = new Rect(displayContent.getBounds()); if (bounds.width() >= bounds.height()) { bounds.inset(/* dx */ dividerSize / 2, /* dy */ 0); bounds.right = bounds.centerX(); @@ -555,7 +552,6 @@ final class LetterboxUiController { final InsetsSource taskbarInsetsSource = getTaskbarInsetsSource(mainWindow); return taskbarInsetsSource != null - && taskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight && taskbarInsetsSource.isVisible(); } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index b277804ed230..9cb13e420184 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -91,6 +91,7 @@ import com.android.server.inputmethod.InputMethodManagerInternal; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -100,7 +101,7 @@ import java.util.function.Predicate; * Represents a logical transition. * @see TransitionController */ -class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListener { +class Transition implements BLASTSyncEngine.TransactionReadyListener { private static final String TAG = "Transition"; private static final String TRACE_NAME_PLAY_TRANSITION = "PlayTransition"; @@ -151,6 +152,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe private @TransitionFlags int mFlags; private final TransitionController mController; private final BLASTSyncEngine mSyncEngine; + private final Token mToken; private RemoteTransition mRemoteTransition = null; /** Only use for clean-up after binder death! */ @@ -213,10 +215,26 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe mFlags = flags; mController = controller; mSyncEngine = syncEngine; + mToken = new Token(this); controller.mTransitionTracer.logState(this); } + @Nullable + static Transition fromBinder(@NonNull IBinder token) { + try { + return ((Token) token).mTransition.get(); + } catch (ClassCastException e) { + Slog.w(TAG, "Invalid transition token: " + token, e); + return null; + } + } + + @NonNull + IBinder getToken() { + return mToken; + } + void addFlag(int flag) { mFlags |= flag; } @@ -726,6 +744,11 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION, System.identityHashCode(this)); } + // Close the transactions now. They were originally copied to Shell in case we needed to + // apply them due to a remote failure. Since we don't need to apply them anymore, free them + // immediately. + if (mStartTransaction != null) mStartTransaction.close(); + if (mFinishTransaction != null) mFinishTransaction.close(); mStartTransaction = mFinishTransaction = null; if (mState < STATE_PLAYING) { throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId); @@ -867,6 +890,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe mController.mAtm.mWindowManager.updateRotation(false /* alwaysSendConfiguration */, false /* forceRelayout */); } + cleanUpInternal(); } void abort() { @@ -909,6 +933,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe dc.getPendingTransaction().merge(transaction); mSyncId = -1; mOverrideOptions = null; + cleanUpInternal(); return; } @@ -1026,7 +1051,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Calling onTransitionReady: %s", info); mController.getTransitionPlayer().onTransitionReady( - this, info, transaction, mFinishTransaction); + mToken, info, transaction, mFinishTransaction); + // Since we created root-leash but no longer reference it from core, release it now + info.releaseAnimSurfaces(); if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) { Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION, System.identityHashCode(this)); @@ -1059,7 +1086,17 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe if (mFinishTransaction != null) { mFinishTransaction.apply(); } - mController.finishTransition(this); + mController.finishTransition(mToken); + } + + private void cleanUpInternal() { + // Clean-up any native references. + for (int i = 0; i < mChanges.size(); ++i) { + final ChangeInfo ci = mChanges.valueAt(i); + if (ci.mSnapshot != null) { + ci.mSnapshot.release(); + } + } } /** @see RecentsAnimationController#attachNavigationBarToApp */ @@ -1815,10 +1852,6 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe return isCollecting() && mSyncId >= 0; } - static Transition fromBinder(IBinder binder) { - return (Transition) binder; - } - @VisibleForTesting static class ChangeInfo { private static final int FLAG_NONE = 0; @@ -2325,4 +2358,18 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe } } } + + private static class Token extends Binder { + final WeakReference<Transition> mTransition; + + Token(Transition transition) { + mTransition = new WeakReference<>(transition); + } + + @Override + public String toString() { + return "Token{" + Integer.toHexString(System.identityHashCode(this)) + " " + + mTransition.get() + "}"; + } + } } diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 25df5112e395..99527b1454c1 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -458,8 +458,9 @@ class TransitionController { info = new ActivityManager.RunningTaskInfo(); startTask.fillTaskInfo(info); } - mTransitionPlayer.requestStartTransition(transition, new TransitionRequestInfo( - transition.mType, info, remoteTransition, displayChange)); + mTransitionPlayer.requestStartTransition(transition.getToken(), + new TransitionRequestInfo(transition.mType, info, remoteTransition, + displayChange)); transition.setRemoteTransition(remoteTransition); } catch (RemoteException e) { Slog.e(TAG, "Error requesting transition", e); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 6032f874dd75..f6f825f42add 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -3847,6 +3847,11 @@ public class WindowManagerService extends IWindowManager.Stub || displayContent.isInTouchMode() == inTouch)) { return; } + final boolean displayHasOwnTouchMode = + displayContent != null && displayContent.hasOwnFocus(); + if (displayHasOwnTouchMode && displayContent.isInTouchMode() == inTouch) { + return; + } final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); final boolean hasPermission = @@ -3855,17 +3860,17 @@ public class WindowManagerService extends IWindowManager.Stub /* printlog= */ false); final long token = Binder.clearCallingIdentity(); try { - // If perDisplayFocusEnabled is set, then just update the display pointed by - // displayId - if (perDisplayFocusEnabled) { + // If perDisplayFocusEnabled is set or the display maintains its own touch mode, + // then just update the display pointed by displayId + if (perDisplayFocusEnabled || displayHasOwnTouchMode) { if (mInputManager.setInTouchMode(inTouch, pid, uid, hasPermission, displayId)) { displayContent.setInTouchMode(inTouch); } - } else { // Otherwise update all displays + } else { // Otherwise update all displays that do not maintain their own touch mode final int displayCount = mRoot.mChildren.size(); for (int i = 0; i < displayCount; ++i) { DisplayContent dc = mRoot.mChildren.get(i); - if (dc.isInTouchMode() == inTouch) { + if (dc.isInTouchMode() == inTouch || dc.hasOwnFocus()) { continue; } if (mInputManager.setInTouchMode(inTouch, pid, uid, hasPermission, @@ -8935,14 +8940,14 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public List<DisplayInfo> getPossibleDisplayInfo(int displayId, String packageName) { + public List<DisplayInfo> getPossibleDisplayInfo(int displayId) { final int callingUid = Binder.getCallingUid(); final long origId = Binder.clearCallingIdentity(); try { synchronized (mGlobalLock) { - if (packageName == null || !isRecentsComponent(packageName, callingUid)) { - Slog.e(TAG, "Unable to verify uid for package " + packageName - + " for getPossibleMaximumWindowMetrics"); + if (!mAtmService.isCallerRecents(callingUid)) { + Slog.e(TAG, "Unable to verify uid for getPossibleDisplayInfo" + + " on uid " + callingUid); return new ArrayList<>(); } @@ -8960,31 +8965,6 @@ public class WindowManagerService extends IWindowManager.Stub return mPossibleDisplayInfoMapper.getPossibleDisplayInfos(displayId); } - /** - * Returns {@code true} when the calling package is the recents component. - */ - boolean isRecentsComponent(@NonNull String callingPackageName, int callingUid) { - String recentsPackage; - try { - String recentsComponent = mContext.getResources().getString( - R.string.config_recentsComponentName); - if (recentsComponent == null) { - return false; - } - recentsPackage = ComponentName.unflattenFromString(recentsComponent).getPackageName(); - } catch (Resources.NotFoundException e) { - Slog.e(TAG, "Unable to verify if recents component", e); - return false; - } - try { - return callingUid == mContext.getPackageManager().getPackageUid(callingPackageName, 0) - && callingPackageName.equals(recentsPackage); - } catch (PackageManager.NameNotFoundException e) { - Slog.e(TAG, "Unable to verify if recents component", e); - return false; - } - } - void grantEmbeddedWindowFocus(Session session, IBinder focusToken, boolean grantFocus) { synchronized (mGlobalLock) { final EmbeddedWindowController.EmbeddedWindow embeddedWindow = diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 4c35178d1b34..aa1cf563da0b 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -306,7 +306,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub nextTransition.setAllReady(); } }); - return nextTransition; + return nextTransition.getToken(); } transition = mTransitionController.createTransition(type); } @@ -315,7 +315,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub if (needsSetReady) { transition.setAllReady(); } - return transition; + return transition.getToken(); } } finally { Binder.restoreCallingIdentity(ident); diff --git a/services/core/jni/com_android_server_sensor_SensorService.cpp b/services/core/jni/com_android_server_sensor_SensorService.cpp index 63b7dfbc2a3b..10d8b42c2979 100644 --- a/services/core/jni/com_android_server_sensor_SensorService.cpp +++ b/services/core/jni/com_android_server_sensor_SensorService.cpp @@ -22,6 +22,7 @@ #include <cutils/properties.h> #include <jni.h> #include <sensorservice/SensorService.h> +#include <string.h> #include <utils/Log.h> #include <utils/misc.h> @@ -30,10 +31,14 @@ #define PROXIMITY_ACTIVE_CLASS \ "com/android/server/sensors/SensorManagerInternal$ProximityActiveListener" +#define RUNTIME_SENSOR_CALLBACK_CLASS \ + "com/android/server/sensors/SensorManagerInternal$RuntimeSensorStateChangeCallback" + namespace android { static JavaVM* sJvm = nullptr; static jmethodID sMethodIdOnProximityActive; +static jmethodID sMethodIdOnStateChanged; class NativeSensorService { public: @@ -41,6 +46,11 @@ public: void registerProximityActiveListener(); void unregisterProximityActiveListener(); + jint registerRuntimeSensor(JNIEnv* env, jint deviceId, jint type, jstring name, jstring vendor, + jobject callback); + void unregisterRuntimeSensor(jint handle); + jboolean sendRuntimeSensorEvent(JNIEnv* env, jint handle, jint type, jlong timestamp, + jfloatArray values); private: sp<SensorService> mService; @@ -56,6 +66,18 @@ private: jobject mListener; }; sp<ProximityActiveListenerDelegate> mProximityActiveListenerDelegate; + + class RuntimeSensorCallbackDelegate : public SensorService::RuntimeSensorStateChangeCallback { + public: + RuntimeSensorCallbackDelegate(JNIEnv* env, jobject callback); + ~RuntimeSensorCallbackDelegate(); + + void onStateChanged(bool enabled, int64_t samplingPeriodNs, + int64_t batchReportLatencyNs) override; + + private: + jobject mCallback; + }; }; NativeSensorService::NativeSensorService(JNIEnv* env, jobject listener) @@ -85,6 +107,109 @@ void NativeSensorService::unregisterProximityActiveListener() { mService->removeProximityActiveListener(mProximityActiveListenerDelegate); } +jint NativeSensorService::registerRuntimeSensor(JNIEnv* env, jint deviceId, jint type, jstring name, + jstring vendor, jobject callback) { + if (mService == nullptr) { + ALOGD("Dropping registerRuntimeSensor, sensor service not available."); + return -1; + } + + sensor_t sensor{ + .name = env->GetStringUTFChars(name, 0), + .vendor = env->GetStringUTFChars(vendor, 0), + .version = sizeof(sensor_t), + .type = type, + }; + + sp<RuntimeSensorCallbackDelegate> callbackDelegate( + new RuntimeSensorCallbackDelegate(env, callback)); + return mService->registerRuntimeSensor(sensor, deviceId, callbackDelegate); +} + +void NativeSensorService::unregisterRuntimeSensor(jint handle) { + if (mService == nullptr) { + ALOGD("Dropping unregisterProximityActiveListener, sensor service not available."); + return; + } + + mService->unregisterRuntimeSensor(handle); +} + +jboolean NativeSensorService::sendRuntimeSensorEvent(JNIEnv* env, jint handle, jint type, + jlong timestamp, jfloatArray values) { + if (mService == nullptr) { + ALOGD("Dropping sendRuntimeSensorEvent, sensor service not available."); + return false; + } + if (values == nullptr) { + ALOGD("Dropping sendRuntimeSensorEvent, no values."); + return false; + } + + sensors_event_t event{ + .version = sizeof(sensors_event_t), + .timestamp = timestamp, + .sensor = handle, + .type = type, + }; + + int valuesLength = env->GetArrayLength(values); + jfloat* sensorValues = env->GetFloatArrayElements(values, nullptr); + + switch (type) { + case SENSOR_TYPE_ACCELEROMETER: + case SENSOR_TYPE_MAGNETIC_FIELD: + case SENSOR_TYPE_ORIENTATION: + case SENSOR_TYPE_GYROSCOPE: + case SENSOR_TYPE_GRAVITY: + case SENSOR_TYPE_LINEAR_ACCELERATION: { + if (valuesLength != 3) { + ALOGD("Dropping sendRuntimeSensorEvent, wrong number of values."); + return false; + } + event.acceleration.x = sensorValues[0]; + event.acceleration.y = sensorValues[1]; + event.acceleration.z = sensorValues[2]; + break; + } + case SENSOR_TYPE_DEVICE_ORIENTATION: + case SENSOR_TYPE_LIGHT: + case SENSOR_TYPE_PRESSURE: + case SENSOR_TYPE_TEMPERATURE: + case SENSOR_TYPE_PROXIMITY: + case SENSOR_TYPE_RELATIVE_HUMIDITY: + case SENSOR_TYPE_AMBIENT_TEMPERATURE: + case SENSOR_TYPE_SIGNIFICANT_MOTION: + case SENSOR_TYPE_STEP_DETECTOR: + case SENSOR_TYPE_TILT_DETECTOR: + case SENSOR_TYPE_WAKE_GESTURE: + case SENSOR_TYPE_GLANCE_GESTURE: + case SENSOR_TYPE_PICK_UP_GESTURE: + case SENSOR_TYPE_WRIST_TILT_GESTURE: + case SENSOR_TYPE_STATIONARY_DETECT: + case SENSOR_TYPE_MOTION_DETECT: + case SENSOR_TYPE_HEART_BEAT: + case SENSOR_TYPE_LOW_LATENCY_OFFBODY_DETECT: { + if (valuesLength != 1) { + ALOGD("Dropping sendRuntimeSensorEvent, wrong number of values."); + return false; + } + event.data[0] = sensorValues[0]; + break; + } + default: { + if (valuesLength > 16) { + ALOGD("Dropping sendRuntimeSensorEvent, number of values exceeds the maximum."); + return false; + } + memcpy(event.data, sensorValues, valuesLength * sizeof(float)); + } + } + + status_t err = mService->sendRuntimeSensorEvent(event); + return err == OK; +} + NativeSensorService::ProximityActiveListenerDelegate::ProximityActiveListenerDelegate( JNIEnv* env, jobject listener) : mListener(env->NewGlobalRef(listener)) {} @@ -98,6 +223,22 @@ void NativeSensorService::ProximityActiveListenerDelegate::onProximityActive(boo jniEnv->CallVoidMethod(mListener, sMethodIdOnProximityActive, static_cast<jboolean>(isActive)); } +NativeSensorService::RuntimeSensorCallbackDelegate::RuntimeSensorCallbackDelegate(JNIEnv* env, + jobject callback) + : mCallback(env->NewGlobalRef(callback)) {} + +NativeSensorService::RuntimeSensorCallbackDelegate::~RuntimeSensorCallbackDelegate() { + AndroidRuntime::getJNIEnv()->DeleteGlobalRef(mCallback); +} + +void NativeSensorService::RuntimeSensorCallbackDelegate::onStateChanged( + bool enabled, int64_t samplingPeriodNs, int64_t batchReportLatencyNs) { + auto jniEnv = GetOrAttachJNIEnvironment(sJvm); + jniEnv->CallVoidMethod(mCallback, sMethodIdOnStateChanged, static_cast<jboolean>(enabled), + static_cast<jint>(ns2us(samplingPeriodNs)), + static_cast<jint>(ns2us(batchReportLatencyNs))); +} + static jlong startSensorServiceNative(JNIEnv* env, jclass, jobject listener) { NativeSensorService* service = new NativeSensorService(env, listener); return reinterpret_cast<jlong>(service); @@ -113,26 +254,46 @@ static void unregisterProximityActiveListenerNative(JNIEnv* env, jclass, jlong p service->unregisterProximityActiveListener(); } -static const JNINativeMethod methods[] = { - { - "startSensorServiceNative", "(L" PROXIMITY_ACTIVE_CLASS ";)J", - reinterpret_cast<void*>(startSensorServiceNative) - }, - { - "registerProximityActiveListenerNative", "(J)V", - reinterpret_cast<void*>(registerProximityActiveListenerNative) - }, - { - "unregisterProximityActiveListenerNative", "(J)V", - reinterpret_cast<void*>(unregisterProximityActiveListenerNative) - }, +static jint registerRuntimeSensorNative(JNIEnv* env, jclass, jlong ptr, jint deviceId, jint type, + jstring name, jstring vendor, jobject callback) { + auto* service = reinterpret_cast<NativeSensorService*>(ptr); + return service->registerRuntimeSensor(env, deviceId, type, name, vendor, callback); +} + +static void unregisterRuntimeSensorNative(JNIEnv* env, jclass, jlong ptr, jint handle) { + auto* service = reinterpret_cast<NativeSensorService*>(ptr); + service->unregisterRuntimeSensor(handle); +} + +static jboolean sendRuntimeSensorEventNative(JNIEnv* env, jclass, jlong ptr, jint handle, jint type, + jlong timestamp, jfloatArray values) { + auto* service = reinterpret_cast<NativeSensorService*>(ptr); + return service->sendRuntimeSensorEvent(env, handle, type, timestamp, values); +} +static const JNINativeMethod methods[] = { + {"startSensorServiceNative", "(L" PROXIMITY_ACTIVE_CLASS ";)J", + reinterpret_cast<void*>(startSensorServiceNative)}, + {"registerProximityActiveListenerNative", "(J)V", + reinterpret_cast<void*>(registerProximityActiveListenerNative)}, + {"unregisterProximityActiveListenerNative", "(J)V", + reinterpret_cast<void*>(unregisterProximityActiveListenerNative)}, + {"registerRuntimeSensorNative", + "(JIILjava/lang/String;Ljava/lang/String;L" RUNTIME_SENSOR_CALLBACK_CLASS ";)I", + reinterpret_cast<void*>(registerRuntimeSensorNative)}, + {"unregisterRuntimeSensorNative", "(JI)V", + reinterpret_cast<void*>(unregisterRuntimeSensorNative)}, + {"sendRuntimeSensorEventNative", "(JIIJ[F)Z", + reinterpret_cast<void*>(sendRuntimeSensorEventNative)}, }; int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env) { sJvm = vm; jclass listenerClass = FindClassOrDie(env, PROXIMITY_ACTIVE_CLASS); sMethodIdOnProximityActive = GetMethodIDOrDie(env, listenerClass, "onProximityActive", "(Z)V"); + jclass runtimeSensorCallbackClass = FindClassOrDie(env, RUNTIME_SENSOR_CALLBACK_CLASS); + sMethodIdOnStateChanged = + GetMethodIDOrDie(env, runtimeSensorCallbackClass, "onStateChanged", "(ZII)V"); return jniRegisterNativeMethods(env, "com/android/server/sensors/SensorService", methods, NELEM(methods)); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java index 8a8485ad5771..9cb7533e43ac 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java @@ -16,24 +16,35 @@ package com.android.server.devicepolicy; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.Log; + import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.util.Objects; final class BooleanPolicySerializer extends PolicySerializer<Boolean> { @Override - void saveToXml(TypedXmlSerializer serializer, String attributeName, Boolean value) + void saveToXml(TypedXmlSerializer serializer, String attributeName, @NonNull Boolean value) throws IOException { + Objects.requireNonNull(value); serializer.attributeBoolean(/* namespace= */ null, attributeName, value); } + @Nullable @Override - Boolean readFromXml(TypedXmlPullParser parser, String attributeName) - throws XmlPullParserException { - return parser.getAttributeBoolean(/* namespace= */ null, attributeName); + Boolean readFromXml(TypedXmlPullParser parser, String attributeName) { + try { + return parser.getAttributeBoolean(/* namespace= */ null, attributeName); + } catch (XmlPullParserException e) { + Log.e(DevicePolicyEngine.TAG, "Error parsing Boolean policy value", e); + return null; + } } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 61d93c7ce655..775e3d833403 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -81,6 +81,7 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; +import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED; import static android.app.admin.DevicePolicyManager.PERSONAL_APPS_NOT_SUSPENDED; import static android.app.admin.DevicePolicyManager.PERSONAL_APPS_SUSPENDED_EXPLICITLY; import static android.app.admin.DevicePolicyManager.PERSONAL_APPS_SUSPENDED_PROFILE_TIMEOUT; @@ -140,6 +141,7 @@ import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE; import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK; import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; +import static android.provider.DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER; import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER; import static android.provider.Settings.Secure.MANAGED_PROVISIONING_DPC_DOWNLOADED; import static android.provider.Settings.Secure.USER_SETUP_COMPLETE; @@ -309,6 +311,7 @@ import android.permission.PermissionControllerManager; import android.provider.CalendarContract; import android.provider.ContactsContract.QuickContact; import android.provider.ContactsInternal; +import android.provider.DeviceConfig; import android.provider.Settings; import android.provider.Settings.Global; import android.provider.Telephony; @@ -712,6 +715,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { + "management app's authentication policy"; private static final String NOT_SYSTEM_CALLER_MSG = "Only the system can %s"; + private static final String ENABLE_COEXISTENCE_FLAG = "enable_coexistence"; + private static final boolean DEFAULT_ENABLE_COEXISTENCE_FLAG = false; + + /** + * For apps targeting U+ + * Enable multiple admins to coexist on the same device. + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + static final long ENABLE_COEXISTENCE_CHANGE = 260560985L; + final Context mContext; final Injector mInjector; final PolicyPathProvider mPathProvider; @@ -795,6 +809,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private final DeviceManagementResourcesProvider mDeviceManagementResourcesProvider; private final DevicePolicyManagementRoleObserver mDevicePolicyManagementRoleObserver; + private final DevicePolicyEngine mDevicePolicyEngine; + private static final boolean ENABLE_LOCK_GUARD = true; /** @@ -1864,6 +1880,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mUserData = new SparseArray<>(); mOwners = makeOwners(injector, pathProvider); + mDevicePolicyEngine = new DevicePolicyEngine(mContext); + if (!mHasFeature) { // Skip the rest of the initialization mSetupContentObserver = null; @@ -1908,6 +1926,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mUserManagerInternal.addUserLifecycleListener(new UserLifecycleListener()); mDeviceManagementResourcesProvider.load(); + if (isCoexistenceFlagEnabled()) { + mDevicePolicyEngine.load(); + } // The binder caches are not enabled until the first invalidation. invalidateBinderCaches(); @@ -7951,8 +7972,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller)); - mInjector.binderWithCleanCallingIdentity(() -> - mInjector.settingsGlobalPutInt(Global.AUTO_TIME_ZONE, enabled ? 1 : 0)); + if (isCoexistenceEnabled(caller)) { + mDevicePolicyEngine.setGlobalPolicy( + PolicyDefinition.AUTO_TIMEZONE, + // TODO(b/260573124): add correct enforcing admin when permission changes are + // merged. + EnforcingAdmin.createEnterpriseEnforcingAdmin(caller.getComponentName()), + enabled); + } else { + mInjector.binderWithCleanCallingIdentity(() -> + mInjector.settingsGlobalPutInt(Global.AUTO_TIME_ZONE, enabled ? 1 : 0)); + } DevicePolicyEventLogger .createEvent(DevicePolicyEnums.SET_AUTO_TIME_ZONE) @@ -12245,8 +12275,38 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { synchronized (getLockObject()) { enforceCanCallLockTaskLocked(caller); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOCK_TASK_PACKAGES); - final int userHandle = caller.getUserId(); - setLockTaskPackagesLocked(userHandle, new ArrayList<>(Arrays.asList(packages))); + } + + if (isCoexistenceEnabled(caller)) { + EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(who); + if (packages.length == 0) { + mDevicePolicyEngine.removeLocalPolicy( + PolicyDefinition.LOCK_TASK, + admin, + caller.getUserId()); + } else { + LockTaskPolicy currentPolicy = mDevicePolicyEngine.getLocalPolicy( + PolicyDefinition.LOCK_TASK, + caller.getUserId()).getPoliciesSetByAdmins().get(admin); + LockTaskPolicy policy; + if (currentPolicy == null) { + policy = new LockTaskPolicy(Set.of(packages)); + } else { + policy = currentPolicy.clone(); + policy.setPackages(Set.of(packages)); + } + + mDevicePolicyEngine.setLocalPolicy( + PolicyDefinition.LOCK_TASK, + EnforcingAdmin.createEnterpriseEnforcingAdmin(who), + policy, + caller.getUserId()); + } + } else { + synchronized (getLockObject()) { + final int userHandle = caller.getUserId(); + setLockTaskPackagesLocked(userHandle, new ArrayList<>(Arrays.asList(packages))); + } } } @@ -12267,8 +12327,21 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { synchronized (getLockObject()) { enforceCanCallLockTaskLocked(caller); - final List<String> packages = getUserData(userHandle).mLockTaskPackages; - return packages.toArray(new String[packages.size()]); + } + + if (isCoexistenceEnabled(caller)) { + LockTaskPolicy policy = mDevicePolicyEngine.getLocalPolicy( + PolicyDefinition.LOCK_TASK, userHandle).getCurrentResolvedPolicy(); + if (policy == null) { + return new String[0]; + } else { + return policy.getPackages().toArray(new String[policy.getPackages().size()]); + } + } else { + synchronized (getLockObject()) { + final List<String> packages = getUserData(userHandle).mLockTaskPackages; + return packages.toArray(new String[packages.size()]); + } } } @@ -12284,8 +12357,19 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } final int userId = mInjector.userHandleGetCallingUserId(); - synchronized (getLockObject()) { - return getUserData(userId).mLockTaskPackages.contains(pkg); + // TODO(b/260560985): This is not the right check, as the flag could be enabled but there + // could be an admin that hasn't targeted U. + if (isCoexistenceFlagEnabled()) { + LockTaskPolicy policy = mDevicePolicyEngine.getLocalPolicy( + PolicyDefinition.LOCK_TASK, userId).getCurrentResolvedPolicy(); + if (policy == null) { + return false; + } + return policy.getPackages().contains(pkg); + } else { + synchronized (getLockObject()) { + return getUserData(userId).mLockTaskPackages.contains(pkg); + } } } @@ -12308,7 +12392,28 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { enforceCanCallLockTaskLocked(caller); enforceCanSetLockTaskFeaturesOnFinancedDevice(caller, flags); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOCK_TASK_FEATURES); - setLockTaskFeaturesLocked(userHandle, flags); + } + if (isCoexistenceEnabled(caller)) { + EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(who); + LockTaskPolicy currentPolicy = mDevicePolicyEngine.getLocalPolicy( + PolicyDefinition.LOCK_TASK, + caller.getUserId()).getPoliciesSetByAdmins().get(admin); + if (currentPolicy == null) { + throw new IllegalArgumentException("Can't set a lock task flags without setting " + + "lock task packages first."); + } + LockTaskPolicy policy = currentPolicy.clone(); + policy.setFlags(flags); + + mDevicePolicyEngine.setLocalPolicy( + PolicyDefinition.LOCK_TASK, + EnforcingAdmin.createEnterpriseEnforcingAdmin(who), + policy, + caller.getUserId()); + } else { + synchronized (getLockObject()) { + setLockTaskFeaturesLocked(userHandle, flags); + } } } @@ -12326,7 +12431,21 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final int userHandle = caller.getUserId(); synchronized (getLockObject()) { enforceCanCallLockTaskLocked(caller); - return getUserData(userHandle).mLockTaskFeatures; + } + + if (isCoexistenceEnabled(caller)) { + LockTaskPolicy policy = mDevicePolicyEngine.getLocalPolicy( + PolicyDefinition.LOCK_TASK, userHandle).getCurrentResolvedPolicy(); + if (policy == null) { + // We default on the power button menu, in order to be consistent with pre-P + // behaviour. + return DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS; + } + return policy.getFlags(); + } else { + synchronized (getLockObject()) { + return getUserData(userHandle).mLockTaskFeatures; + } } } @@ -13905,6 +14024,20 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (isFinancedDeviceOwner(caller)) { enforcePermissionGrantStateOnFinancedDevice(packageName, permission); } + } + if (isCoexistenceEnabled(caller)) { + mDevicePolicyEngine.setLocalPolicy( + PolicyDefinition.PERMISSION_GRANT(packageName, permission), + // TODO(b/260573124): Add correct enforcing admin when permission changes are + // merged, and don't forget to handle delegates! Enterprise admins assume + // component name isn't null. + EnforcingAdmin.createEnterpriseEnforcingAdmin(caller.getComponentName()), + grantState, + caller.getUserId()); + // TODO: update javadoc to reflect that callback no longer return success/failure + callback.sendResult(Bundle.EMPTY); + } else { + synchronized (getLockObject()) { long ident = mInjector.binderClearCallingIdentity(); try { boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId()) @@ -13921,14 +14054,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { callback.sendResult(null); return; } - if (grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED + if (grantState == PERMISSION_GRANT_STATE_GRANTED || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) { AdminPermissionControlParams permissionParams = - new AdminPermissionControlParams(packageName, permission, grantState, + new AdminPermissionControlParams(packageName, permission, + grantState, canAdminGrantSensorsPermissionsForUser(caller.getUserId())); mInjector.getPermissionControllerManager(caller.getUserHandle()) - .setRuntimePermissionGrantStateByDeviceAdmin(caller.getPackageName(), + .setRuntimePermissionGrantStateByDeviceAdmin( + caller.getPackageName(), permissionParams, mContext.getMainExecutor(), (permissionWasSet) -> { if (isPostQAdmin && !permissionWasSet) { @@ -13947,13 +14082,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { callback.sendResult(Bundle.EMPTY); }); - } - } catch (SecurityException e) { - Slogf.e(LOG_TAG, "Could not set permission grant state", e); + } + } catch (SecurityException e) { + Slogf.e(LOG_TAG, "Could not set permission grant state", e); - callback.sendResult(null); - } finally { - mInjector.binderRestoreCallingIdentity(ident); + callback.sendResult(null); + } finally { + mInjector.binderRestoreCallingIdentity(ident); + } } } } @@ -19017,4 +19153,18 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return result; }); } + + // TODO(b/260560985): properly gate coexistence changes + private boolean isCoexistenceEnabled(CallerIdentity caller) { + return isCoexistenceFlagEnabled() + && mInjector.isChangeEnabled( + ENABLE_COEXISTENCE_CHANGE, caller.getPackageName(), caller.getUserId()); + } + + private boolean isCoexistenceFlagEnabled() { + return DeviceConfig.getBoolean( + NAMESPACE_DEVICE_POLICY_MANAGER, + ENABLE_COEXISTENCE_FLAG, + DEFAULT_ENABLE_COEXISTENCE_FLAG); + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java index 3152f0ba1f26..d5949dda8b30 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java @@ -16,24 +16,35 @@ package com.android.server.devicepolicy; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.Log; + import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.util.Objects; final class IntegerPolicySerializer extends PolicySerializer<Integer> { @Override - void saveToXml(TypedXmlSerializer serializer, String attributeName, Integer value) + void saveToXml(TypedXmlSerializer serializer, String attributeName, @NonNull Integer value) throws IOException { + Objects.requireNonNull(value); serializer.attributeInt(/* namespace= */ null, attributeName, value); } + @Nullable @Override - Integer readFromXml(TypedXmlPullParser parser, String attributeName) - throws XmlPullParserException { - return parser.getAttributeInt(/* namespace= */ null, attributeName); + Integer readFromXml(TypedXmlPullParser parser, String attributeName) { + try { + return parser.getAttributeInt(/* namespace= */ null, attributeName); + } catch (XmlPullParserException e) { + Log.e(DevicePolicyEngine.TAG, "Error parsing Integer policy value", e); + return null; + } } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicy.java b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicy.java index 9360fd790ab1..d3e8de488e0b 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicy.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicy.java @@ -16,7 +16,10 @@ package com.android.server.devicepolicy; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.admin.DevicePolicyManager; +import android.util.Log; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; @@ -24,19 +27,16 @@ import com.android.modules.utils.TypedXmlSerializer; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.util.HashSet; import java.util.Objects; import java.util.Set; final class LockTaskPolicy { - private Set<String> mPackages; - private int mFlags; + static final int DEFAULT_LOCK_TASK_FLAG = DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS; + private Set<String> mPackages = new HashSet<>(); + private int mFlags = DEFAULT_LOCK_TASK_FLAG; - LockTaskPolicy(@Nullable Set<String> packages, int flags) { - mPackages = packages; - mFlags = flags; - } - - @Nullable + @NonNull Set<String> getPackages() { return mPackages; } @@ -45,8 +45,20 @@ final class LockTaskPolicy { return mFlags; } - void setPackages(Set<String> packages) { - mPackages = packages; + LockTaskPolicy(Set<String> packages) { + Objects.requireNonNull(packages); + mPackages.addAll(packages); + } + + private LockTaskPolicy(Set<String> packages, int flags) { + Objects.requireNonNull(packages); + mPackages = new HashSet<>(packages); + mFlags = flags; + } + + void setPackages(@NonNull Set<String> packages) { + Objects.requireNonNull(packages); + mPackages = new HashSet<>(packages); } void setFlags(int flags) { @@ -54,6 +66,13 @@ final class LockTaskPolicy { } @Override + public LockTaskPolicy clone() { + LockTaskPolicy policy = new LockTaskPolicy(mPackages); + policy.setFlags(mFlags); + return policy; + } + + @Override public boolean equals(@Nullable Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; @@ -67,6 +86,11 @@ final class LockTaskPolicy { return Objects.hash(mPackages, mFlags); } + @Override + public String toString() { + return "mPackages= " + String.join(", ", mPackages) + "; mFlags= " + mFlags; + } + static final class LockTaskPolicySerializer extends PolicySerializer<LockTaskPolicy> { private static final String ATTR_PACKAGES = ":packages"; @@ -74,15 +98,17 @@ final class LockTaskPolicy { private static final String ATTR_FLAGS = ":flags"; @Override - void saveToXml( - TypedXmlSerializer serializer, String attributeNamePrefix, LockTaskPolicy value) - throws IOException { - if (value.mPackages != null) { - serializer.attribute( - /* namespace= */ null, - attributeNamePrefix + ATTR_PACKAGES, - String.join(ATTR_PACKAGES_SEPARATOR, value.mPackages)); + void saveToXml(TypedXmlSerializer serializer, String attributeNamePrefix, + @NonNull LockTaskPolicy value) throws IOException { + Objects.requireNonNull(value); + if (value.mPackages == null || value.mPackages.isEmpty()) { + throw new IllegalArgumentException("Error saving LockTaskPolicy to file, lock task " + + "packages must be present"); } + serializer.attribute( + /* namespace= */ null, + attributeNamePrefix + ATTR_PACKAGES, + String.join(ATTR_PACKAGES_SEPARATOR, value.mPackages)); serializer.attributeInt( /* namespace= */ null, attributeNamePrefix + ATTR_FLAGS, @@ -90,18 +116,24 @@ final class LockTaskPolicy { } @Override - LockTaskPolicy readFromXml(TypedXmlPullParser parser, String attributeNamePrefix) - throws XmlPullParserException { + LockTaskPolicy readFromXml(TypedXmlPullParser parser, String attributeNamePrefix) { String packagesStr = parser.getAttributeValue( /* namespace= */ null, attributeNamePrefix + ATTR_PACKAGES); - Set<String> packages = packagesStr == null - ? null - : Set.of(packagesStr.split(ATTR_PACKAGES_SEPARATOR)); - int flags = parser.getAttributeInt( - /* namespace= */ null, - attributeNamePrefix + ATTR_FLAGS); - return new LockTaskPolicy(packages, flags); + if (packagesStr == null) { + Log.e(DevicePolicyEngine.TAG, "Error parsing LockTask policy value."); + return null; + } + Set<String> packages = Set.of(packagesStr.split(ATTR_PACKAGES_SEPARATOR)); + try { + int flags = parser.getAttributeInt( + /* namespace= */ null, + attributeNamePrefix + ATTR_FLAGS); + return new LockTaskPolicy(packages, flags); + } catch (XmlPullParserException e) { + Log.e(DevicePolicyEngine.TAG, "Error parsing LockTask policy value", e); + return null; + } } } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java index 3a18cb9c244f..a787a0b3943b 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java @@ -25,8 +25,6 @@ import com.android.internal.util.function.QuadFunction; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; -import org.xmlpull.v1.XmlPullParserException; - import java.io.IOException; import java.util.LinkedHashMap; import java.util.List; @@ -225,8 +223,8 @@ final class PolicyDefinition<V> { mPolicySerializer.saveToXml(serializer, attributeName, value); } - V readPolicyValueFromXml(TypedXmlPullParser parser, String attributeName) - throws XmlPullParserException { + @Nullable + V readPolicyValueFromXml(TypedXmlPullParser parser, String attributeName) { return mPolicySerializer.readFromXml(parser, attributeName); } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java index b645b979ef0c..74b6f9ea114f 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java @@ -29,6 +29,7 @@ import android.provider.Settings; import com.android.server.utils.Slogf; +import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.concurrent.CountDownLatch; @@ -53,7 +54,7 @@ final class PolicyEnforcerCallbacks { static boolean setPermissionGrantState( @Nullable Integer grantState, @NonNull Context context, int userId, @NonNull String[] args) { - Binder.withCleanCallingIdentity(() -> { + return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> { if (args == null || args.length < 2) { throw new IllegalArgumentException("Package name and permission name must be " + "provided as arguments"); @@ -84,8 +85,7 @@ final class PolicyEnforcerCallbacks { // TODO: add logging return false; } - }); - return true; + })); } @NonNull @@ -106,9 +106,14 @@ final class PolicyEnforcerCallbacks { static boolean setLockTask( @Nullable LockTaskPolicy policy, @NonNull Context context, int userId) { - DevicePolicyManagerService.updateLockTaskPackagesLocked( - context, List.copyOf(policy.getPackages()), userId); - DevicePolicyManagerService.updateLockTaskFeaturesLocked(policy.getFlags(), userId); + List<String> packages = Collections.emptyList(); + int flags = LockTaskPolicy.DEFAULT_LOCK_TASK_FLAG; + if (policy != null) { + packages = List.copyOf(policy.getPackages()); + flags = policy.getFlags(); + } + DevicePolicyManagerService.updateLockTaskPackagesLocked(context, packages, userId); + DevicePolicyManagerService.updateLockTaskFeaturesLocked(flags, userId); return true; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java index b3259d34bd3f..528d3b0c8055 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java @@ -16,16 +16,15 @@ package com.android.server.devicepolicy; +import android.annotation.NonNull; + import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; -import org.xmlpull.v1.XmlPullParserException; - import java.io.IOException; abstract class PolicySerializer<V> { - abstract void saveToXml(TypedXmlSerializer serializer, String attributeName, V value) + abstract void saveToXml(TypedXmlSerializer serializer, String attributeName, @NonNull V value) throws IOException; - abstract V readFromXml(TypedXmlPullParser parser, String attributeName) - throws XmlPullParserException; + abstract V readFromXml(TypedXmlPullParser parser, String attributeName); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java index 5fc3cb00e57f..d3dee98cf7ba 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java @@ -40,7 +40,7 @@ final class PolicyState<V> { private static final String ATTR_RESOLVED_POLICY = "resolved-policy"; private final PolicyDefinition<V> mPolicyDefinition; - private final LinkedHashMap<EnforcingAdmin, V> mAdminsPolicy = new LinkedHashMap<>(); + private final LinkedHashMap<EnforcingAdmin, V> mPoliciesSetByAdmins = new LinkedHashMap<>(); private V mCurrentResolvedPolicy; PolicyState(@NonNull PolicyDefinition<V> policyDefinition) { @@ -49,13 +49,13 @@ final class PolicyState<V> { private PolicyState( @NonNull PolicyDefinition<V> policyDefinition, - @NonNull LinkedHashMap<EnforcingAdmin, V> adminsPolicy, + @NonNull LinkedHashMap<EnforcingAdmin, V> policiesSetByAdmins, V currentEnforcedPolicy) { Objects.requireNonNull(policyDefinition); - Objects.requireNonNull(adminsPolicy); + Objects.requireNonNull(policiesSetByAdmins); mPolicyDefinition = policyDefinition; - mAdminsPolicy.putAll(adminsPolicy); + mPoliciesSetByAdmins.putAll(policiesSetByAdmins); mCurrentResolvedPolicy = currentEnforcedPolicy; } @@ -63,7 +63,7 @@ final class PolicyState<V> { * Returns {@code true} if the resolved policy has changed, {@code false} otherwise. */ boolean setPolicy(@NonNull EnforcingAdmin admin, @NonNull V value) { - mAdminsPolicy.put(Objects.requireNonNull(admin), Objects.requireNonNull(value)); + mPoliciesSetByAdmins.put(Objects.requireNonNull(admin), Objects.requireNonNull(value)); return resolvePolicy(); } @@ -71,15 +71,19 @@ final class PolicyState<V> { boolean removePolicy(@NonNull EnforcingAdmin admin) { Objects.requireNonNull(admin); - if (mAdminsPolicy.remove(admin) == null) { + if (mPoliciesSetByAdmins.remove(admin) == null) { return false; } return resolvePolicy(); } + LinkedHashMap<EnforcingAdmin, V> getPoliciesSetByAdmins() { + return mPoliciesSetByAdmins; + } + private boolean resolvePolicy() { - V resolvedPolicy = mPolicyDefinition.resolvePolicy(mAdminsPolicy); + V resolvedPolicy = mPolicyDefinition.resolvePolicy(mPoliciesSetByAdmins); boolean policyChanged = !Objects.equals(resolvedPolicy, mCurrentResolvedPolicy); mCurrentResolvedPolicy = resolvedPolicy; @@ -94,14 +98,16 @@ final class PolicyState<V> { void saveToXml(TypedXmlSerializer serializer) throws IOException { mPolicyDefinition.saveToXml(serializer); - mPolicyDefinition.savePolicyValueToXml( - serializer, ATTR_RESOLVED_POLICY, mCurrentResolvedPolicy); + if (mCurrentResolvedPolicy != null) { + mPolicyDefinition.savePolicyValueToXml( + serializer, ATTR_RESOLVED_POLICY, mCurrentResolvedPolicy); + } - for (EnforcingAdmin admin : mAdminsPolicy.keySet()) { + for (EnforcingAdmin admin : mPoliciesSetByAdmins.keySet()) { serializer.startTag(/* namespace= */ null, TAG_ADMIN_POLICY_ENTRY); mPolicyDefinition.savePolicyValueToXml( - serializer, ATTR_POLICY_VALUE, mAdminsPolicy.get(admin)); + serializer, ATTR_POLICY_VALUE, mPoliciesSetByAdmins.get(admin)); serializer.startTag(/* namespace= */ null, TAG_ENFORCING_ADMIN_ENTRY); admin.saveToXml(serializer); diff --git a/services/proguard.flags b/services/proguard.flags index 27fe5056b8b4..6cdf11c3c685 100644 --- a/services/proguard.flags +++ b/services/proguard.flags @@ -88,6 +88,7 @@ -keep,allowoptimization,allowaccessmodification class com.android.server.location.gnss.GnssPowerStats { *; } -keep,allowoptimization,allowaccessmodification class com.android.server.location.gnss.hal.GnssNative { *; } -keep,allowoptimization,allowaccessmodification class com.android.server.pm.PackageManagerShellCommandDataLoader { *; } +-keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorManagerInternal$RuntimeSensorStateChangeCallback { *; } -keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorManagerInternal$ProximityActiveListener { *; } -keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorService { *; } -keep,allowoptimization,allowaccessmodification class com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareImpl$AudioSessionProvider$AudioSession { *; } diff --git a/services/tests/InputMethodSystemServerTests/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/AndroidManifest.xml index 12e7cfc28a56..212ec14b4939 100644 --- a/services/tests/InputMethodSystemServerTests/AndroidManifest.xml +++ b/services/tests/InputMethodSystemServerTests/AndroidManifest.xml @@ -18,6 +18,11 @@ package="com.android.frameworks.inputmethodtests"> <uses-sdk android:targetSdkVersion="31" /> + <queries> + <intent> + <action android:name="android.view.InputMethod" /> + </intent> + </queries> <!-- Permissions required for granting and logging --> <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/> @@ -29,9 +34,23 @@ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> + <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS"/> + <uses-permission android:name="android.permission.BIND_INPUT_METHOD" /> + <application android:testOnly="true" android:debuggable="true"> <uses-library android:name="android.test.runner" /> + <service android:name="com.android.server.inputmethod.InputMethodBindingControllerTest$EmptyInputMethodService" + android:label="Empty IME" + android:permission="android.permission.BIND_INPUT_METHOD" + android:process=":service" + android:exported="true"> + <intent-filter> + <action android:name="android.view.InputMethod"/> + </intent-filter> + <meta-data android:name="android.view.im" + android:resource="@xml/method"/> + </service> </application> <instrumentation diff --git a/services/tests/InputMethodSystemServerTests/res/xml/method.xml b/services/tests/InputMethodSystemServerTests/res/xml/method.xml new file mode 100644 index 000000000000..89b06bb6e5a1 --- /dev/null +++ b/services/tests/InputMethodSystemServerTests/res/xml/method.xml @@ -0,0 +1,18 @@ +<?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. + --> + +<input-method xmlns:android="http://schemas.android.com/apk/res/android" />
\ No newline at end of file diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java new file mode 100644 index 000000000000..42d373b9bf3e --- /dev/null +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java @@ -0,0 +1,224 @@ +/* + * 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.inputmethod; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.app.Instrumentation; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.inputmethodservice.InputMethodService; +import android.os.Process; +import android.os.RemoteException; +import android.os.UserHandle; +import android.view.inputmethod.InputMethodInfo; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.internal.inputmethod.InputBindResult; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +@RunWith(AndroidJUnit4.class) +public class InputMethodBindingControllerTest extends InputMethodManagerServiceTestBase { + + private static final String PACKAGE_NAME = "com.android.frameworks.inputmethodtests"; + private static final String TEST_SERVICE_NAME = + "com.android.server.inputmethod.InputMethodBindingControllerTest" + + "$EmptyInputMethodService"; + private static final String TEST_IME_ID = PACKAGE_NAME + "/" + TEST_SERVICE_NAME; + private static final long TIMEOUT_IN_SECONDS = 3; + + private InputMethodBindingController mBindingController; + private Instrumentation mInstrumentation; + private final int mImeConnectionBindFlags = + InputMethodBindingController.IME_CONNECTION_BIND_FLAGS + & ~Context.BIND_SCHEDULE_LIKE_TOP_APP; + private CountDownLatch mCountDownLatch; + + public static class EmptyInputMethodService extends InputMethodService {} + + @Before + public void setUp() throws RemoteException { + super.setUp(); + mInstrumentation = InstrumentationRegistry.getInstrumentation(); + mCountDownLatch = new CountDownLatch(1); + // Remove flag Context.BIND_SCHEDULE_LIKE_TOP_APP because in tests we are not calling + // from system. + mBindingController = + new InputMethodBindingController( + mInputMethodManagerService, mImeConnectionBindFlags, mCountDownLatch); + } + + @Test + public void testBindCurrentMethod_noIme() { + synchronized (ImfLock.class) { + mBindingController.setSelectedMethodId(null); + InputBindResult result = mBindingController.bindCurrentMethod(); + assertThat(result).isEqualTo(InputBindResult.NO_IME); + } + } + + @Test + public void testBindCurrentMethod_unknownId() { + synchronized (ImfLock.class) { + mBindingController.setSelectedMethodId("unknown ime id"); + } + assertThrows(IllegalArgumentException.class, () -> { + synchronized (ImfLock.class) { + mBindingController.bindCurrentMethod(); + } + }); + } + + @Test + public void testBindCurrentMethod_notConnected() { + synchronized (ImfLock.class) { + mBindingController.setSelectedMethodId(TEST_IME_ID); + doReturn(false) + .when(mContext) + .bindServiceAsUser( + any(Intent.class), + any(ServiceConnection.class), + anyInt(), + any(UserHandle.class)); + + InputBindResult result = mBindingController.bindCurrentMethod(); + assertThat(result).isEqualTo(InputBindResult.IME_NOT_CONNECTED); + } + } + + @Test + public void testBindAndUnbindMethod() throws Exception { + // Bind with main connection + testBindCurrentMethodWithMainConnection(); + + // Bind with visible connection + testBindCurrentMethodWithVisibleConnection(); + + // Unbind both main and visible connections + testUnbindCurrentMethod(); + } + + private void testBindCurrentMethodWithMainConnection() throws Exception { + synchronized (ImfLock.class) { + mBindingController.setSelectedMethodId(TEST_IME_ID); + } + InputMethodInfo info = mInputMethodManagerService.mMethodMap.get(TEST_IME_ID); + assertThat(info).isNotNull(); + assertThat(info.getId()).isEqualTo(TEST_IME_ID); + assertThat(info.getServiceName()).isEqualTo(TEST_SERVICE_NAME); + + // Bind input method with main connection. It is called on another thread because we should + // wait for onServiceConnected() to finish. + InputBindResult result = callOnMainSync(() -> { + synchronized (ImfLock.class) { + return mBindingController.bindCurrentMethod(); + } + }); + + verify(mContext, times(1)) + .bindServiceAsUser( + any(Intent.class), + any(ServiceConnection.class), + eq(mImeConnectionBindFlags), + any(UserHandle.class)); + assertThat(result.result).isEqualTo(InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING); + assertThat(result.id).isEqualTo(info.getId()); + synchronized (ImfLock.class) { + assertThat(mBindingController.hasConnection()).isTrue(); + assertThat(mBindingController.getCurId()).isEqualTo(info.getId()); + assertThat(mBindingController.getCurToken()).isNotNull(); + } + // Wait for onServiceConnected() + mCountDownLatch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS); + + // Verify onServiceConnected() is called and bound successfully. + synchronized (ImfLock.class) { + assertThat(mBindingController.getCurMethod()).isNotNull(); + assertThat(mBindingController.getCurMethodUid()).isNotEqualTo(Process.INVALID_UID); + } + } + + private void testBindCurrentMethodWithVisibleConnection() { + mInstrumentation.runOnMainSync(() -> { + synchronized (ImfLock.class) { + mBindingController.setCurrentMethodVisible(); + } + }); + // Bind input method with visible connection + verify(mContext, times(1)) + .bindServiceAsUser( + any(Intent.class), + any(ServiceConnection.class), + eq(InputMethodBindingController.IME_VISIBLE_BIND_FLAGS), + any(UserHandle.class)); + synchronized (ImfLock.class) { + assertThat(mBindingController.isVisibleBound()).isTrue(); + } + } + + private void testUnbindCurrentMethod() { + mInstrumentation.runOnMainSync(() -> { + synchronized (ImfLock.class) { + mBindingController.unbindCurrentMethod(); + } + }); + + synchronized (ImfLock.class) { + // Unbind both main connection and visible connection + assertThat(mBindingController.hasConnection()).isFalse(); + assertThat(mBindingController.isVisibleBound()).isFalse(); + verify(mContext, times(2)).unbindService(any(ServiceConnection.class)); + assertThat(mBindingController.getCurToken()).isNull(); + assertThat(mBindingController.getCurId()).isNull(); + assertThat(mBindingController.getCurMethod()).isNull(); + assertThat(mBindingController.getCurMethodUid()).isEqualTo(Process.INVALID_UID); + } + } + + private static <V> V callOnMainSync(Callable<V> callable) { + AtomicReference<V> result = new AtomicReference<>(); + InstrumentationRegistry.getInstrumentation() + .runOnMainSync( + () -> { + try { + result.set(callable.call()); + } catch (Exception e) { + throw new RuntimeException("Exception was thrown", e); + } + }); + return result.get(); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java index 66e7ec00b6d3..c87fd26fbe82 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java @@ -49,6 +49,7 @@ import android.annotation.NonNull; import android.app.Activity; import android.app.AppOpsManager; import android.app.BroadcastOptions; +import android.appwidget.AppWidgetManager; import android.content.IIntentReceiver; import android.content.Intent; import android.content.IntentFilter; @@ -538,6 +539,59 @@ public class BroadcastQueueModernImplTest { } /** + * Verify that we don't let urgent broadcasts starve delivery of non-urgent + */ + @Test + public void testUrgentStarvation() { + final BroadcastOptions optInteractive = BroadcastOptions.makeBasic(); + optInteractive.setInteractive(true); + + mConstants.MAX_CONSECUTIVE_URGENT_DISPATCHES = 2; + BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants, + PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN)); + + // mix of broadcasts, with more than 2 fg/urgent + queue.enqueueOrReplaceBroadcast( + makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0); + queue.enqueueOrReplaceBroadcast( + makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)), 0); + queue.enqueueOrReplaceBroadcast( + makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0); + queue.enqueueOrReplaceBroadcast( + makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0); + queue.enqueueOrReplaceBroadcast( + makeBroadcastRecord(new Intent(Intent.ACTION_APPLICATION_PREFERENCES), + optInteractive), 0); + queue.enqueueOrReplaceBroadcast( + makeBroadcastRecord(new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE), + optInteractive), 0); + queue.enqueueOrReplaceBroadcast( + makeBroadcastRecord(new Intent(Intent.ACTION_INPUT_METHOD_CHANGED) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0); + queue.enqueueOrReplaceBroadcast( + makeBroadcastRecord(new Intent(Intent.ACTION_NEW_OUTGOING_CALL), + optInteractive), 0); + + queue.makeActiveNextPending(); + assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction()); + queue.makeActiveNextPending(); + assertEquals(Intent.ACTION_APPLICATION_PREFERENCES, queue.getActive().intent.getAction()); + // after MAX_CONSECUTIVE_URGENT_DISPATCHES expect an ordinary one next + queue.makeActiveNextPending(); + assertEquals(Intent.ACTION_TIMEZONE_CHANGED, queue.getActive().intent.getAction()); + // and then back to prioritizing urgent ones + queue.makeActiveNextPending(); + assertEquals(AppWidgetManager.ACTION_APPWIDGET_UPDATE, + queue.getActive().intent.getAction()); + queue.makeActiveNextPending(); + assertEquals(Intent.ACTION_INPUT_METHOD_CHANGED, queue.getActive().intent.getAction()); + // verify the reset-count-then-resume worked too + queue.makeActiveNextPending(); + assertEquals(Intent.ACTION_ALARM_CHANGED, queue.getActive().intent.getAction()); + } + + /** * Verify that sending a broadcast that removes any matching pending * broadcasts is applied as expected. */ diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java index 5dc12510368c..be13bad70e16 100644 --- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java @@ -48,7 +48,7 @@ public class AppOpsLegacyRestrictionsTest { StaticMockitoSession mSession; @Mock - AppOpsService.Constants mConstants; + AppOpsServiceImpl.Constants mConstants; @Mock Context mContext; @@ -57,7 +57,7 @@ public class AppOpsLegacyRestrictionsTest { Handler mHandler; @Mock - AppOpsServiceInterface mLegacyAppOpsService; + AppOpsCheckingServiceInterface mLegacyAppOpsService; AppOpsRestrictions mAppOpsRestrictions; diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java index c0688d131610..7d4bc6f47ad4 100644 --- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java @@ -22,6 +22,8 @@ import static android.app.AppOpsManager.OP_FLAGS_ALL; import static android.app.AppOpsManager.OP_READ_SMS; import static android.app.AppOpsManager.OP_WIFI_SCAN; import static android.app.AppOpsManager.OP_WRITE_SMS; +import static android.app.AppOpsManager.resolvePackageName; +import static android.os.Process.INVALID_UID; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -39,6 +41,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; +import android.app.AppOpsManager; import android.app.AppOpsManager.OpEntry; import android.app.AppOpsManager.PackageOps; import android.content.ContentResolver; @@ -86,13 +89,13 @@ public class AppOpsServiceTest { private File mAppOpsFile; private Handler mHandler; - private AppOpsService mAppOpsService; + private AppOpsServiceImpl mAppOpsService; private int mMyUid; private long mTestStartMillis; private StaticMockitoSession mMockingSession; private void setupAppOpsService() { - mAppOpsService = new AppOpsService(mAppOpsFile, mHandler, spy(sContext)); + mAppOpsService = new AppOpsServiceImpl(mAppOpsFile, mHandler, spy(sContext)); mAppOpsService.mHistoricalRegistry.systemReady(sContext.getContentResolver()); // Always approve all permission checks @@ -161,17 +164,20 @@ public class AppOpsServiceTest { @Test public void testNoteOperationAndGetOpsForPackage() { - mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED); - mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED); + mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null); + mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED, null); // Note an op that's allowed. - mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false); + mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid, + resolvePackageName(mMyUid, sMyPackageName), null, + INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF); List<PackageOps> loggedOps = getLoggedOps(); assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED); // Note another op that's not allowed. - mAppOpsService.noteOperation(OP_WRITE_SMS, mMyUid, sMyPackageName, null, false, null, - false); + mAppOpsService.noteOperationUnchecked(OP_WRITE_SMS, mMyUid, + resolvePackageName(mMyUid, sMyPackageName), null, + INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF); loggedOps = getLoggedOps(); assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED); assertContainsOp(loggedOps, OP_WRITE_SMS, -1, mTestStartMillis, MODE_ERRORED); @@ -185,18 +191,20 @@ public class AppOpsServiceTest { @Test public void testNoteOperationAndGetOpsForPackage_controlledByDifferentOp() { // This op controls WIFI_SCAN - mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ALLOWED); + mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ALLOWED, null); - assertThat(mAppOpsService.noteOperation(OP_WIFI_SCAN, mMyUid, sMyPackageName, null, false, - null, false).getOpMode()).isEqualTo(MODE_ALLOWED); + assertThat(mAppOpsService.noteOperationUnchecked(OP_WIFI_SCAN, mMyUid, + resolvePackageName(mMyUid, sMyPackageName), null, + INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF)).isEqualTo(MODE_ALLOWED); assertContainsOp(getLoggedOps(), OP_WIFI_SCAN, mTestStartMillis, -1, MODE_ALLOWED /* default for WIFI_SCAN; this is not changed or used in this test */); // Now set COARSE_LOCATION to ERRORED -> this will make WIFI_SCAN disabled as well. - mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ERRORED); - assertThat(mAppOpsService.noteOperation(OP_WIFI_SCAN, mMyUid, sMyPackageName, null, false, - null, false).getOpMode()).isEqualTo(MODE_ERRORED); + mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ERRORED, null); + assertThat(mAppOpsService.noteOperationUnchecked(OP_WIFI_SCAN, mMyUid, + resolvePackageName(mMyUid, sMyPackageName), null, + INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF)).isEqualTo(MODE_ERRORED); assertContainsOp(getLoggedOps(), OP_WIFI_SCAN, mTestStartMillis, mTestStartMillis, MODE_ALLOWED /* default for WIFI_SCAN; this is not changed or used in this test */); @@ -205,11 +213,14 @@ public class AppOpsServiceTest { // Tests the dumping and restoring of the in-memory state to/from XML. @Test public void testStatePersistence() { - mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED); - mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED); - mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false); - mAppOpsService.noteOperation(OP_WRITE_SMS, mMyUid, sMyPackageName, null, false, null, - false); + mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null); + mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED, null); + mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid, + resolvePackageName(mMyUid, sMyPackageName), null, + INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF); + mAppOpsService.noteOperationUnchecked(OP_WRITE_SMS, mMyUid, + resolvePackageName(mMyUid, sMyPackageName), null, + INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF); mAppOpsService.writeState(); // Create a new app ops service which will initialize its state from XML. @@ -224,8 +235,10 @@ public class AppOpsServiceTest { // Tests that ops are persisted during shutdown. @Test public void testShutdown() { - mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED); - mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false); + mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null); + mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid, + resolvePackageName(mMyUid, sMyPackageName), null, + INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF); mAppOpsService.shutdown(); // Create a new app ops service which will initialize its state from XML. @@ -238,8 +251,10 @@ public class AppOpsServiceTest { @Test public void testGetOpsForPackage() { - mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED); - mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false); + mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null); + mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid, + resolvePackageName(mMyUid, sMyPackageName), null, + INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF); // Query all ops List<PackageOps> loggedOps = mAppOpsService.getOpsForPackage( @@ -267,8 +282,10 @@ public class AppOpsServiceTest { @Test public void testPackageRemoved() { - mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED); - mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false); + mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null); + mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid, + resolvePackageName(mMyUid, sMyPackageName), null, + INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF); List<PackageOps> loggedOps = getLoggedOps(); assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED); @@ -322,8 +339,10 @@ public class AppOpsServiceTest { @Test public void testUidRemoved() { - mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED); - mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false); + mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null); + mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid, + resolvePackageName(mMyUid, sMyPackageName), null, + INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF); List<PackageOps> loggedOps = getLoggedOps(); assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED); diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java index 98e895a86f9e..3efd5e701013 100644 --- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java @@ -76,7 +76,7 @@ public class AppOpsUidStateTrackerTest { ActivityManagerInternal mAmi; @Mock - AppOpsService.Constants mConstants; + AppOpsServiceImpl.Constants mConstants; AppOpsUidStateTrackerTestExecutor mExecutor = new AppOpsUidStateTrackerTestExecutor(); diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java index e08a715fbfe1..298dbf47d86d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java @@ -93,12 +93,13 @@ public class AppOpsUpgradeTest { } } - private void assertSameModes(SparseArray<AppOpsService.UidState> uidStates, int op1, int op2) { + private void assertSameModes(SparseArray<AppOpsServiceImpl.UidState> uidStates, + int op1, int op2) { int numberOfNonDefaultOps = 0; final int defaultModeOp1 = AppOpsManager.opToDefaultMode(op1); final int defaultModeOp2 = AppOpsManager.opToDefaultMode(op2); for(int i = 0; i < uidStates.size(); i++) { - final AppOpsService.UidState uidState = uidStates.valueAt(i); + final AppOpsServiceImpl.UidState uidState = uidStates.valueAt(i); SparseIntArray opModes = uidState.getNonDefaultUidModes(); if (opModes != null) { final int uidMode1 = opModes.get(op1, defaultModeOp1); @@ -112,12 +113,12 @@ public class AppOpsUpgradeTest { continue; } for (int j = 0; j < uidState.pkgOps.size(); j++) { - final AppOpsService.Ops ops = uidState.pkgOps.valueAt(j); + final AppOpsServiceImpl.Ops ops = uidState.pkgOps.valueAt(j); if (ops == null) { continue; } - final AppOpsService.Op _op1 = ops.get(op1); - final AppOpsService.Op _op2 = ops.get(op2); + final AppOpsServiceImpl.Op _op1 = ops.get(op1); + final AppOpsServiceImpl.Op _op2 = ops.get(op2); final int mode1 = (_op1 == null) ? defaultModeOp1 : _op1.getMode(); final int mode2 = (_op2 == null) ? defaultModeOp2 : _op2.getMode(); assertEquals(mode1, mode2); @@ -158,8 +159,8 @@ public class AppOpsUpgradeTest { // Stub out package calls to disable AppOpsService#updatePermissionRevokedCompat when(testPM.getPackagesForUid(anyInt())).thenReturn(null); - AppOpsService testService = spy( - new AppOpsService(mAppOpsFile, mHandler, testContext)); // trigger upgrade + AppOpsServiceImpl testService = spy( + new AppOpsServiceImpl(mAppOpsFile, mHandler, testContext)); // trigger upgrade assertSameModes(testService.mUidStates, AppOpsManager.OP_RUN_IN_BACKGROUND, AppOpsManager.OP_RUN_ANY_IN_BACKGROUND); mHandler.removeCallbacks(testService.mWriteRunner); diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java index 5c3d69547755..01674bbc6859 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java @@ -27,6 +27,7 @@ import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.timeout; @@ -107,12 +108,16 @@ public final class BackgroundDexOptServiceUnitTest { @Mock private BackgroundDexOptJobService mJobServiceForIdle; - private final JobParameters mJobParametersForPostBoot = new JobParameters(null, - BackgroundDexOptService.JOB_POST_BOOT_UPDATE, null, null, null, - 0, false, false, null, null, null); - private final JobParameters mJobParametersForIdle = new JobParameters(null, - BackgroundDexOptService.JOB_IDLE_OPTIMIZE, null, null, null, - 0, false, false, null, null, null); + private final JobParameters mJobParametersForPostBoot = + createJobParameters(BackgroundDexOptService.JOB_POST_BOOT_UPDATE); + private final JobParameters mJobParametersForIdle = + createJobParameters(BackgroundDexOptService.JOB_IDLE_OPTIMIZE); + + private static JobParameters createJobParameters(int jobId) { + JobParameters params = mock(JobParameters.class); + when(params.getJobId()).thenReturn(jobId); + return params; + } private BackgroundDexOptService mService; diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java index d477cb6f356c..799a7fe3a3db 100644 --- a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java @@ -18,6 +18,7 @@ package com.android.server.tare; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -108,7 +109,7 @@ public class AgentTrendCalculatorTest { @Before public void setUp() { final InternalResourceService irs = mock(InternalResourceService.class); - when(irs.isVip(anyInt(), anyString())).thenReturn(false); + when(irs.isVip(anyInt(), anyString(), anyLong())).thenReturn(false); mEconomicPolicy = new MockEconomicPolicy(irs); } 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 ddfa05cf5a2e..c46ebf20c3ee 100644 --- a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java @@ -402,6 +402,6 @@ public class ScribeTest { ApplicationInfo applicationInfo = new ApplicationInfo(); applicationInfo.uid = UserHandle.getUid(userId, Math.abs(pkgName.hashCode())); pkgInfo.applicationInfo = applicationInfo; - mInstalledPackages.add(userId, pkgName, new InstalledPackageInfo(pkgInfo)); + mInstalledPackages.add(userId, pkgName, new InstalledPackageInfo(getContext(), pkgInfo)); } } diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java new file mode 100644 index 000000000000..ef8a49f95a49 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java @@ -0,0 +1,140 @@ +/* + * 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.companion.virtual; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; + +import android.companion.virtual.sensor.VirtualSensorConfig; +import android.companion.virtual.sensor.VirtualSensorEvent; +import android.hardware.Sensor; +import android.os.Binder; +import android.os.IBinder; +import android.platform.test.annotations.Presubmit; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import com.android.server.LocalServices; +import com.android.server.sensors.SensorManagerInternal; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@Presubmit +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class SensorControllerTest { + + private static final int VIRTUAL_DEVICE_ID = 42; + private static final String VIRTUAL_SENSOR_NAME = "VirtualAccelerometer"; + private static final int SENSOR_HANDLE = 7; + + @Mock + private SensorManagerInternal mSensorManagerInternalMock; + private SensorController mSensorController; + private VirtualSensorEvent mSensorEvent; + private VirtualSensorConfig mVirtualSensorConfig; + private IBinder mSensorToken; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + LocalServices.removeServiceForTest(SensorManagerInternal.class); + LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock); + + mSensorController = new SensorController(new Object(), VIRTUAL_DEVICE_ID); + mSensorEvent = new VirtualSensorEvent.Builder(new float[] { 1f, 2f, 3f}).build(); + mVirtualSensorConfig = + new VirtualSensorConfig.Builder(Sensor.TYPE_ACCELEROMETER, VIRTUAL_SENSOR_NAME) + .build(); + mSensorToken = new Binder("sensorToken"); + } + + @Test + public void createSensor_invalidHandle_throwsException() { + doReturn(/* handle= */0).when(mSensorManagerInternalMock).createRuntimeSensor( + anyInt(), anyInt(), anyString(), anyString(), any()); + + Throwable thrown = assertThrows( + RuntimeException.class, + () -> mSensorController.createSensor(mSensorToken, mVirtualSensorConfig)); + + assertThat(thrown.getCause().getMessage()) + .contains("Received an invalid virtual sensor handle"); + } + + @Test + public void createSensor_success() { + doCreateSensorSuccessfully(); + + assertThat(mSensorController.getSensorDescriptors()).isNotEmpty(); + } + + @Test + public void sendSensorEvent_invalidToken_throwsException() { + doCreateSensorSuccessfully(); + + assertThrows( + IllegalArgumentException.class, + () -> mSensorController.sendSensorEvent( + new Binder("invalidSensorToken"), mSensorEvent)); + } + + @Test + public void sendSensorEvent_success() { + doCreateSensorSuccessfully(); + + mSensorController.sendSensorEvent(mSensorToken, mSensorEvent); + verify(mSensorManagerInternalMock).sendSensorEvent( + SENSOR_HANDLE, Sensor.TYPE_ACCELEROMETER, mSensorEvent.getTimestampNanos(), + mSensorEvent.getValues()); + } + + @Test + public void unregisterSensor_invalidToken_throwsException() { + doCreateSensorSuccessfully(); + + assertThrows( + IllegalArgumentException.class, + () -> mSensorController.unregisterSensor(new Binder("invalidSensorToken"))); + } + + @Test + public void unregisterSensor_success() { + doCreateSensorSuccessfully(); + + mSensorController.unregisterSensor(mSensorToken); + verify(mSensorManagerInternalMock).removeRuntimeSensor(SENSOR_HANDLE); + assertThat(mSensorController.getSensorDescriptors()).isEmpty(); + } + + private void doCreateSensorSuccessfully() { + doReturn(SENSOR_HANDLE).when(mSensorManagerInternalMock).createRuntimeSensor( + anyInt(), anyInt(), anyString(), anyString(), any()); + mSensorController.createSensor(mSensorToken, mVirtualSensorConfig); + } +} diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index 0bd6f2c2d903..afaee04d7cec 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -51,6 +51,7 @@ import android.companion.virtual.VirtualDeviceManager; import android.companion.virtual.VirtualDeviceParams; import android.companion.virtual.audio.IAudioConfigChangedCallback; import android.companion.virtual.audio.IAudioRoutingCallback; +import android.companion.virtual.sensor.VirtualSensorConfig; import android.content.ComponentName; import android.content.Context; import android.content.ContextWrapper; @@ -58,6 +59,7 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.graphics.Point; +import android.hardware.Sensor; import android.hardware.display.DisplayManagerInternal; import android.hardware.input.IInputManager; import android.hardware.input.VirtualKeyEvent; @@ -88,6 +90,7 @@ import androidx.test.InstrumentationRegistry; import com.android.internal.app.BlockedAppStreamingActivity; import com.android.server.LocalServices; import com.android.server.input.InputManagerInternal; +import com.android.server.sensors.SensorManagerInternal; import org.junit.Before; import org.junit.Test; @@ -126,16 +129,19 @@ public class VirtualDeviceManagerServiceTest { private static final int VENDOR_ID = 5; private static final String UNIQUE_ID = "uniqueid"; private static final String PHYS = "phys"; - private static final int DEVICE_ID = 42; + private static final int DEVICE_ID = 53; private static final int HEIGHT = 1800; private static final int WIDTH = 900; + private static final int SENSOR_HANDLE = 64; private static final Binder BINDER = new Binder("binder"); private static final int FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES = 0x00000; + private static final int VIRTUAL_DEVICE_ID = 42; private Context mContext; private InputManagerMockHelper mInputManagerMockHelper; private VirtualDeviceImpl mDeviceImpl; private InputController mInputController; + private SensorController mSensorController; private AssociationInfo mAssociationInfo; private VirtualDeviceManagerService mVdms; private VirtualDeviceManagerInternal mLocalService; @@ -150,6 +156,8 @@ public class VirtualDeviceManagerServiceTest { @Mock private InputManagerInternal mInputManagerInternalMock; @Mock + private SensorManagerInternal mSensorManagerInternalMock; + @Mock private IVirtualDeviceActivityListener mActivityListener; @Mock private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback; @@ -228,6 +236,9 @@ public class VirtualDeviceManagerServiceTest { LocalServices.removeServiceForTest(InputManagerInternal.class); LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock); + LocalServices.removeServiceForTest(SensorManagerInternal.class); + LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock); + final DisplayInfo displayInfo = new DisplayInfo(); displayInfo.uniqueId = UNIQUE_ID; doReturn(displayInfo).when(mDisplayManagerInternalMock).getDisplayInfo(anyInt()); @@ -252,6 +263,7 @@ public class VirtualDeviceManagerServiceTest { mInputController = new InputController(new Object(), mNativeWrapperMock, new Handler(TestableLooper.get(this).getLooper()), mContext.getSystemService(WindowManager.class), threadVerifier); + mSensorController = new SensorController(new Object(), VIRTUAL_DEVICE_ID); mAssociationInfo = new AssociationInfo(1, 0, null, MacAddress.BROADCAST_ADDRESS, "", null, null, true, false, false, 0, 0); @@ -264,9 +276,9 @@ public class VirtualDeviceManagerServiceTest { .setBlockedActivities(getBlockedActivities()) .build(); mDeviceImpl = new VirtualDeviceImpl(mContext, - mAssociationInfo, new Binder(), /* ownerUid */ 0, /* uniqueId */ 1, - mInputController, (int associationId) -> {}, mPendingTrampolineCallback, - mActivityListener, mRunningAppsChangedCallback, params); + mAssociationInfo, new Binder(), /* ownerUid */ 0, VIRTUAL_DEVICE_ID, + mInputController, mSensorController, (int associationId) -> {}, + mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params); mVdms.addVirtualDevice(mDeviceImpl); } @@ -308,9 +320,9 @@ public class VirtualDeviceManagerServiceTest { .addDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM) .build(); mDeviceImpl = new VirtualDeviceImpl(mContext, - mAssociationInfo, new Binder(), /* ownerUid */ 0, /* uniqueId */ 1, - mInputController, (int associationId) -> {}, mPendingTrampolineCallback, - mActivityListener, mRunningAppsChangedCallback, params); + mAssociationInfo, new Binder(), /* ownerUid */ 0, VIRTUAL_DEVICE_ID, + mInputController, mSensorController, (int associationId) -> {}, + mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params); mVdms.addVirtualDevice(mDeviceImpl); assertThat( @@ -576,6 +588,18 @@ public class VirtualDeviceManagerServiceTest { } @Test + public void createVirtualSensor_noPermission_failsSecurityException() { + doCallRealMethod().when(mContext).enforceCallingOrSelfPermission( + eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString()); + assertThrows( + SecurityException.class, + () -> mDeviceImpl.createVirtualSensor( + BINDER, + new VirtualSensorConfig.Builder( + Sensor.TYPE_ACCELEROMETER, DEVICE_NAME).build())); + } + + @Test public void onAudioSessionStarting_noPermission_failsSecurityException() { mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID); doCallRealMethod().when(mContext).enforceCallingOrSelfPermission( @@ -679,6 +703,17 @@ public class VirtualDeviceManagerServiceTest { } @Test + public void close_cleanSensorController() { + mSensorController.addSensorForTesting( + BINDER, SENSOR_HANDLE, Sensor.TYPE_ACCELEROMETER, DEVICE_NAME); + + mDeviceImpl.close(); + + assertThat(mSensorController.getSensorDescriptors()).isEmpty(); + verify(mSensorManagerInternalMock).removeRuntimeSensor(SENSOR_HANDLE); + } + + @Test public void sendKeyEvent_noFd() { assertThrows( IllegalArgumentException.class, diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java index 036b6df92ef9..a226ebcdc8f2 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java @@ -16,9 +16,14 @@ package com.android.server.companion.virtual; +import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM; +import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS; +import static android.hardware.Sensor.TYPE_ACCELEROMETER; + import static com.google.common.truth.Truth.assertThat; import android.companion.virtual.VirtualDeviceParams; +import android.companion.virtual.sensor.VirtualSensorConfig; import android.os.Parcel; import android.os.UserHandle; @@ -27,18 +32,25 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.List; import java.util.Set; @RunWith(AndroidJUnit4.class) public class VirtualDeviceParamsTest { + private static final String SENSOR_NAME = "VirtualSensorName"; + private static final String SENSOR_VENDOR = "VirtualSensorVendor"; + @Test public void parcelable_shouldRecreateSuccessfully() { VirtualDeviceParams originalParams = new VirtualDeviceParams.Builder() .setLockState(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED) .setUsersWithMatchingAccounts(Set.of(UserHandle.of(123), UserHandle.of(456))) - .addDevicePolicy(VirtualDeviceParams.POLICY_TYPE_SENSORS, - VirtualDeviceParams.DEVICE_POLICY_CUSTOM) + .addDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM) + .addVirtualSensorConfig( + new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME) + .setVendor(SENSOR_VENDOR) + .build()) .build(); Parcel parcel = Parcel.obtain(); originalParams.writeToParcel(parcel, 0); @@ -49,7 +61,14 @@ public class VirtualDeviceParamsTest { assertThat(params.getLockState()).isEqualTo(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED); assertThat(params.getUsersWithMatchingAccounts()) .containsExactly(UserHandle.of(123), UserHandle.of(456)); - assertThat(params.getDevicePolicy(VirtualDeviceParams.POLICY_TYPE_SENSORS)) - .isEqualTo(VirtualDeviceParams.DEVICE_POLICY_CUSTOM); + assertThat(params.getDevicePolicy(POLICY_TYPE_SENSORS)).isEqualTo(DEVICE_POLICY_CUSTOM); + + List<VirtualSensorConfig> sensorConfigs = params.getVirtualSensorConfigs(); + assertThat(sensorConfigs).hasSize(1); + VirtualSensorConfig sensorConfig = sensorConfigs.get(0); + assertThat(sensorConfig.getType()).isEqualTo(TYPE_ACCELEROMETER); + assertThat(sensorConfig.getName()).isEqualTo(SENSOR_NAME); + assertThat(sensorConfig.getVendor()).isEqualTo(SENSOR_VENDOR); + assertThat(sensorConfig.getStateChangeCallback()).isNull(); } } diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java index 062bde8f080b..ce35626a5106 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -171,22 +171,6 @@ public class DisplayManagerServiceTest { private final DisplayManagerService.Injector mBasicInjector = new BasicInjector(); - private final DisplayManagerService.Injector mAllowNonNativeRefreshRateOverrideInjector = - new BasicInjector() { - @Override - boolean getAllowNonNativeRefreshRateOverride() { - return true; - } - }; - - private final DisplayManagerService.Injector mDenyNonNativeRefreshRateOverrideInjector = - new BasicInjector() { - @Override - boolean getAllowNonNativeRefreshRateOverride() { - return false; - } - }; - @Mock InputManagerInternal mMockInputManagerInternal; @Mock VirtualDeviceManagerInternal mMockVirtualDeviceManagerInternal; @Mock IVirtualDisplayCallback.Stub mMockAppToken; @@ -408,6 +392,75 @@ public class DisplayManagerServiceTest { assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT) != 0); } + @Test + public void testCreateVirtualDisplayOwnFocus() { + DisplayManagerService displayManager = + new DisplayManagerService(mContext, mBasicInjector); + registerDefaultDisplays(displayManager); + + // This is effectively the DisplayManager service published to ServiceManager. + DisplayManagerService.BinderService bs = displayManager.new BinderService(); + + String uniqueId = "uniqueId --- Own Focus Test"; + int width = 600; + int height = 800; + int dpi = 320; + int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS + | DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED; + + when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)).thenReturn( + PackageManager.PERMISSION_GRANTED); + when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); + final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder( + VIRTUAL_DISPLAY_NAME, width, height, dpi); + builder.setFlags(flags); + builder.setUniqueId(uniqueId); + int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken, + /* projection= */ null, PACKAGE_NAME); + + displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class)); + + // flush the handler + displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0); + + DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId); + assertNotNull(ddi); + assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_OWN_FOCUS) != 0); + } + + @Test + public void testCreateVirtualDisplayOwnFocus_nonTrustedDisplay() { + DisplayManagerService displayManager = + new DisplayManagerService(mContext, mBasicInjector); + registerDefaultDisplays(displayManager); + + // This is effectively the DisplayManager service published to ServiceManager. + DisplayManagerService.BinderService bs = displayManager.new BinderService(); + + String uniqueId = "uniqueId --- Own Focus Test -- nonTrustedDisplay"; + int width = 600; + int height = 800; + int dpi = 320; + int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS; + + when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); + final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder( + VIRTUAL_DISPLAY_NAME, width, height, dpi); + builder.setFlags(flags); + builder.setUniqueId(uniqueId); + int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken, + /* projection= */ null, PACKAGE_NAME); + + displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class)); + + // flush the handler + displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0); + + DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId); + assertNotNull(ddi); + assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_OWN_FOCUS) == 0); + } + /** * Tests that the virtual display is created along-side the default display. */ @@ -1044,13 +1097,32 @@ public class DisplayManagerServiceTest { } /** - * Tests that the frame rate override is updated accordingly to the - * allowNonNativeRefreshRateOverride policy. + * Tests that the frame rate override is returning the correct value from + * DisplayInfo#getRefreshRate */ @Test public void testDisplayInfoNonNativeFrameRateOverride() throws Exception { - testDisplayInfoNonNativeFrameRateOverride(mDenyNonNativeRefreshRateOverrideInjector); - testDisplayInfoNonNativeFrameRateOverride(mAllowNonNativeRefreshRateOverrideInjector); + DisplayManagerService displayManager = + new DisplayManagerService(mContext, mBasicInjector); + DisplayManagerService.BinderService displayManagerBinderService = + displayManager.new BinderService(); + registerDefaultDisplays(displayManager); + displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY); + + FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, + new float[]{60f}); + int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService, + displayDevice); + DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId); + assertEquals(60f, displayInfo.getRefreshRate(), 0.01f); + + updateFrameRateOverride(displayManager, displayDevice, + new DisplayEventReceiver.FrameRateOverride[]{ + new DisplayEventReceiver.FrameRateOverride( + Process.myUid(), 20f) + }); + displayInfo = displayManagerBinderService.getDisplayInfo(displayId); + assertEquals(20f, displayInfo.getRefreshRate(), 0.01f); } /** @@ -1078,10 +1150,7 @@ public class DisplayManagerServiceTest { @Test @DisableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE}) public void testDisplayInfoNonNativeFrameRateOverrideModeCompat() throws Exception { - testDisplayInfoNonNativeFrameRateOverrideMode(mDenyNonNativeRefreshRateOverrideInjector, - /*compatChangeEnabled*/ false); - testDisplayInfoNonNativeFrameRateOverrideMode(mAllowNonNativeRefreshRateOverrideInjector, - /*compatChangeEnabled*/ false); + testDisplayInfoNonNativeFrameRateOverrideMode(/*compatChangeEnabled*/ false); } /** @@ -1090,10 +1159,7 @@ public class DisplayManagerServiceTest { @Test @EnableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE}) public void testDisplayInfoNonNativeFrameRateOverrideMode() throws Exception { - testDisplayInfoNonNativeFrameRateOverrideMode(mDenyNonNativeRefreshRateOverrideInjector, - /*compatChangeEnabled*/ true); - testDisplayInfoNonNativeFrameRateOverrideMode(mAllowNonNativeRefreshRateOverrideInjector, - /*compatChangeEnabled*/ true); + testDisplayInfoNonNativeFrameRateOverrideMode(/*compatChangeEnabled*/ true); } /** @@ -1316,10 +1382,9 @@ public class DisplayManagerServiceTest { assertEquals(expectedMode, displayInfo.getMode()); } - private void testDisplayInfoNonNativeFrameRateOverrideMode( - DisplayManagerService.Injector injector, boolean compatChangeEnabled) { + private void testDisplayInfoNonNativeFrameRateOverrideMode(boolean compatChangeEnabled) { DisplayManagerService displayManager = - new DisplayManagerService(mContext, injector); + new DisplayManagerService(mContext, mBasicInjector); DisplayManagerService.BinderService displayManagerBinderService = displayManager.new BinderService(); registerDefaultDisplays(displayManager); @@ -1341,40 +1406,12 @@ public class DisplayManagerServiceTest { Display.Mode expectedMode; if (compatChangeEnabled) { expectedMode = new Display.Mode(1, 100, 200, 60f); - } else if (injector.getAllowNonNativeRefreshRateOverride()) { - expectedMode = new Display.Mode(255, 100, 200, 20f); } else { - expectedMode = new Display.Mode(1, 100, 200, 60f); + expectedMode = new Display.Mode(255, 100, 200, 20f); } assertEquals(expectedMode, displayInfo.getMode()); } - private void testDisplayInfoNonNativeFrameRateOverride( - DisplayManagerService.Injector injector) { - DisplayManagerService displayManager = - new DisplayManagerService(mContext, injector); - DisplayManagerService.BinderService displayManagerBinderService = - displayManager.new BinderService(); - registerDefaultDisplays(displayManager); - displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY); - - FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, - new float[]{60f}); - int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService, - displayDevice); - DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId); - assertEquals(60f, displayInfo.getRefreshRate(), 0.01f); - - updateFrameRateOverride(displayManager, displayDevice, - new DisplayEventReceiver.FrameRateOverride[]{ - new DisplayEventReceiver.FrameRateOverride( - Process.myUid(), 20f) - }); - displayInfo = displayManagerBinderService.getDisplayInfo(displayId); - float expectedRefreshRate = injector.getAllowNonNativeRefreshRateOverride() ? 20f : 60f; - assertEquals(expectedRefreshRate, displayInfo.getRefreshRate(), 0.01f); - } - private int getDisplayIdForDisplayDevice( DisplayManagerService displayManager, DisplayManagerService.BinderService displayManagerBinderService, diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java index c81db9216c08..6258d6d29ae0 100644 --- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java @@ -2014,6 +2014,50 @@ public class PowerManagerServiceTest { } @Test + public void testMultiDisplay_defaultDozing_addNewDisplayDefaultGoesBackToDoze() { + final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1; + final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1; + final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener = + new AtomicReference<>(); + doAnswer((Answer<Void>) invocation -> { + listener.set(invocation.getArgument(0)); + return null; + }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any()); + final DisplayInfo info = new DisplayInfo(); + info.displayGroupId = nonDefaultDisplayGroupId; + when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info); + + doAnswer(inv -> { + when(mDreamManagerInternalMock.isDreaming()).thenReturn(true); + return null; + }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString()); + + createService(); + startSystem(); + + assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo( + WAKEFULNESS_AWAKE); + + forceDozing(); + advanceTime(500); + + assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo( + WAKEFULNESS_DOZING); + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING); + verify(mDreamManagerInternalMock).startDream(eq(true), anyString()); + + listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId); + advanceTime(500); + + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); + assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo( + WAKEFULNESS_AWAKE); + assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo( + WAKEFULNESS_DOZING); + verify(mDreamManagerInternalMock, times(2)).startDream(eq(true), anyString()); + } + + @Test public void testLastSleepTime_notUpdatedWhenDreaming() { createService(); startSystem(); diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java index bcdc65c19330..1e72369ac3a6 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java @@ -111,7 +111,7 @@ public class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy { } @Override - public void enableTelephonyTimeZoneFallback() { + public void enableTelephonyTimeZoneFallback(String reason) { throw new UnsupportedOperationException(); } diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java index 74efdb5d6d98..1c014d19d776 100644 --- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java @@ -29,6 +29,9 @@ import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYP import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS; import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET; import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE; +import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS; +import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_UNKNOWN; +import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_UNKNOWN; import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH; import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_LOW; @@ -65,6 +68,7 @@ import android.app.timezonedetector.TelephonyTimeZoneSuggestion; import android.app.timezonedetector.TelephonyTimeZoneSuggestion.MatchType; import android.app.timezonedetector.TelephonyTimeZoneSuggestion.Quality; import android.os.HandlerThread; +import android.service.timezone.TimeZoneProviderStatus; import com.android.server.SystemTimeZone.TimeZoneConfidence; import com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.QualifiedTelephonyTimeZoneSuggestion; @@ -1148,7 +1152,7 @@ public class TimeZoneDetectorStrategyImplTest { } @Test - public void testTelephonyFallback() { + public void testTelephonyFallback_enableTelephonyTimeZoneFallbackCalled() { ConfigurationInternal config = new ConfigurationInternal.Builder( CONFIG_AUTO_ENABLED_GEO_ENABLED) .setTelephonyFallbackSupported(true) @@ -1178,19 +1182,21 @@ public class TimeZoneDetectorStrategyImplTest { // Receiving an "uncertain" geolocation suggestion should have no effect. { + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent(); - script.simulateIncrementClock() - .simulateLocationAlgorithmEvent(locationAlgorithmEvent) + script.simulateLocationAlgorithmEvent(locationAlgorithmEvent) .verifyTimeZoneNotChanged() .verifyTelephonyFallbackIsEnabled(true); } // Receiving a "certain" geolocation suggestion should disable telephony fallback mode. { + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); LocationAlgorithmEvent locationAlgorithmEvent = createCertainLocationAlgorithmEvent("Europe/London"); - script.simulateIncrementClock() - .simulateLocationAlgorithmEvent(locationAlgorithmEvent) + script.simulateLocationAlgorithmEvent(locationAlgorithmEvent) .verifyTimeZoneChangedAndReset(locationAlgorithmEvent) .verifyTelephonyFallbackIsEnabled(false); } @@ -1214,17 +1220,19 @@ public class TimeZoneDetectorStrategyImplTest { // Geolocation suggestions should continue to be used as normal (previous telephony // suggestions are not used, even when the geolocation suggestion is uncertain). { + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); LocationAlgorithmEvent certainLocationAlgorithmEvent = createCertainLocationAlgorithmEvent("Europe/Rome"); - script.simulateIncrementClock() - .simulateLocationAlgorithmEvent(certainLocationAlgorithmEvent) + script.simulateLocationAlgorithmEvent(certainLocationAlgorithmEvent) .verifyTimeZoneChangedAndReset(certainLocationAlgorithmEvent) .verifyTelephonyFallbackIsEnabled(false); + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); LocationAlgorithmEvent uncertainLocationAlgorithmEvent = createUncertainLocationAlgorithmEvent(); - script.simulateIncrementClock() - .simulateLocationAlgorithmEvent(uncertainLocationAlgorithmEvent) + script.simulateLocationAlgorithmEvent(uncertainLocationAlgorithmEvent) .verifyTimeZoneNotChanged() .verifyTelephonyFallbackIsEnabled(false); @@ -1246,19 +1254,21 @@ public class TimeZoneDetectorStrategyImplTest { // Make the geolocation algorithm uncertain. { + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent(); - script.simulateIncrementClock() - .simulateLocationAlgorithmEvent(locationAlgorithmEvent) + script.simulateLocationAlgorithmEvent(locationAlgorithmEvent) .verifyTimeZoneChangedAndReset(lastTelephonySuggestion) .verifyTelephonyFallbackIsEnabled(true); } // Make the geolocation algorithm certain, disabling telephony fallback. { + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); LocationAlgorithmEvent locationAlgorithmEvent = createCertainLocationAlgorithmEvent("Europe/Lisbon"); - script.simulateIncrementClock() - .simulateLocationAlgorithmEvent(locationAlgorithmEvent) + script.simulateLocationAlgorithmEvent(locationAlgorithmEvent) .verifyTimeZoneChangedAndReset(locationAlgorithmEvent) .verifyTelephonyFallbackIsEnabled(false); @@ -1267,9 +1277,10 @@ public class TimeZoneDetectorStrategyImplTest { // Demonstrate what happens when geolocation is uncertain when telephony fallback is // enabled. { + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent(); - script.simulateIncrementClock() - .simulateLocationAlgorithmEvent(locationAlgorithmEvent) + script.simulateLocationAlgorithmEvent(locationAlgorithmEvent) .verifyTimeZoneNotChanged() .verifyTelephonyFallbackIsEnabled(false) .simulateEnableTelephonyFallback() @@ -1279,6 +1290,132 @@ public class TimeZoneDetectorStrategyImplTest { } @Test + public void testTelephonyFallback_locationAlgorithmEventSuggestsFallback() { + ConfigurationInternal config = new ConfigurationInternal.Builder( + CONFIG_AUTO_ENABLED_GEO_ENABLED) + .setTelephonyFallbackSupported(true) + .build(); + + Script script = new Script() + .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS) + .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW) + .simulateConfigurationInternalChange(config) + .resetConfigurationTracking(); + + // Confirm initial state is as expected. + script.verifyTelephonyFallbackIsEnabled(true) + .verifyTimeZoneNotChanged(); + + // Although geolocation detection is enabled, telephony fallback should be used initially + // and until a suitable "certain" geolocation suggestion is received. + { + TelephonyTimeZoneSuggestion telephonySuggestion = createTelephonySuggestion( + SLOT_INDEX1, MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE, + "Europe/Paris"); + script.simulateIncrementClock() + .simulateTelephonyTimeZoneSuggestion(telephonySuggestion) + .verifyTimeZoneChangedAndReset(telephonySuggestion) + .verifyTelephonyFallbackIsEnabled(true); + } + + // Receiving an "uncertain" geolocation suggestion without a status should have no effect. + { + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); + LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent(); + script.simulateLocationAlgorithmEvent(locationAlgorithmEvent) + .verifyTimeZoneNotChanged() + .verifyTelephonyFallbackIsEnabled(true); + } + + // Receiving a "certain" geolocation suggestion should disable telephony fallback mode. + { + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); + LocationAlgorithmEvent locationAlgorithmEvent = + createCertainLocationAlgorithmEvent("Europe/London"); + script.simulateLocationAlgorithmEvent(locationAlgorithmEvent) + .verifyTimeZoneChangedAndReset(locationAlgorithmEvent) + .verifyTelephonyFallbackIsEnabled(false); + } + + // Used to record the last telephony suggestion received, which will be used when fallback + // takes place. + TelephonyTimeZoneSuggestion lastTelephonySuggestion; + + // Telephony suggestions should now be ignored and geolocation detection is "in control". + { + TelephonyTimeZoneSuggestion telephonySuggestion = createTelephonySuggestion( + SLOT_INDEX1, MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE, + "Europe/Berlin"); + script.simulateIncrementClock() + .simulateTelephonyTimeZoneSuggestion(telephonySuggestion) + .verifyTimeZoneNotChanged() + .verifyTelephonyFallbackIsEnabled(false); + lastTelephonySuggestion = telephonySuggestion; + } + + // Geolocation suggestions should continue to be used as normal (previous telephony + // suggestions are not used, even when the geolocation suggestion is uncertain). + { + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); + LocationAlgorithmEvent certainLocationAlgorithmEvent = + createCertainLocationAlgorithmEvent("Europe/Rome"); + script.simulateLocationAlgorithmEvent(certainLocationAlgorithmEvent) + .verifyTimeZoneChangedAndReset(certainLocationAlgorithmEvent) + .verifyTelephonyFallbackIsEnabled(false); + + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); + LocationAlgorithmEvent uncertainLocationAlgorithmEvent = + createUncertainLocationAlgorithmEvent(); + script.simulateLocationAlgorithmEvent(uncertainLocationAlgorithmEvent) + .verifyTimeZoneNotChanged() + .verifyTelephonyFallbackIsEnabled(false); + + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); + LocationAlgorithmEvent certainLocationAlgorithmEvent2 = + createCertainLocationAlgorithmEvent("Europe/Rome"); + script.simulateLocationAlgorithmEvent(certainLocationAlgorithmEvent2) + // No change needed, device will already be set to Europe/Rome. + .verifyTimeZoneNotChanged() + .verifyTelephonyFallbackIsEnabled(false); + } + + // Enable telephony fallback via a LocationAlgorithmEvent containing an "uncertain" + // suggestion. + { + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); + TimeZoneProviderStatus primaryProviderReportedStatus = + new TimeZoneProviderStatus.Builder() + .setLocationDetectionDependencyStatus( + DEPENDENCY_STATUS_BLOCKED_BY_SETTINGS) + .setConnectivityDependencyStatus(DEPENDENCY_STATUS_UNKNOWN) + .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_UNKNOWN) + .build(); + LocationAlgorithmEvent uncertainEventBlockedBySettings = + createUncertainLocationAlgorithmEvent(primaryProviderReportedStatus); + script.simulateLocationAlgorithmEvent(uncertainEventBlockedBySettings) + .verifyTimeZoneChangedAndReset(lastTelephonySuggestion) + .verifyTelephonyFallbackIsEnabled(true); + } + + // Make the geolocation algorithm certain, disabling telephony fallback. + { + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); + LocationAlgorithmEvent locationAlgorithmEvent = + createCertainLocationAlgorithmEvent("Europe/Lisbon"); + script.simulateLocationAlgorithmEvent(locationAlgorithmEvent) + .verifyTimeZoneChangedAndReset(locationAlgorithmEvent) + .verifyTelephonyFallbackIsEnabled(false); + } + } + + @Test public void testTelephonyFallback_noTelephonySuggestionToFallBackTo() { ConfigurationInternal config = new ConfigurationInternal.Builder( CONFIG_AUTO_ENABLED_GEO_ENABLED) @@ -1297,9 +1434,10 @@ public class TimeZoneDetectorStrategyImplTest { // Receiving an "uncertain" geolocation suggestion should have no effect. { + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent(); - script.simulateIncrementClock() - .simulateLocationAlgorithmEvent(locationAlgorithmEvent) + script.simulateLocationAlgorithmEvent(locationAlgorithmEvent) .verifyTimeZoneNotChanged() .verifyTelephonyFallbackIsEnabled(true); } @@ -1307,9 +1445,10 @@ public class TimeZoneDetectorStrategyImplTest { // Make an uncertain geolocation suggestion, there is no telephony suggestion to fall back // to { + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent(); - script.simulateIncrementClock() - .simulateLocationAlgorithmEvent(locationAlgorithmEvent) + script.simulateLocationAlgorithmEvent(locationAlgorithmEvent) .verifyTimeZoneNotChanged() .verifyTelephonyFallbackIsEnabled(true); } @@ -1319,16 +1458,18 @@ public class TimeZoneDetectorStrategyImplTest { // Geolocation suggestions should continue to be used as normal (previous telephony // suggestions are not used, even when the geolocation suggestion is uncertain). { + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); LocationAlgorithmEvent certainEvent = createCertainLocationAlgorithmEvent("Europe/Rome"); - script.simulateIncrementClock() - .simulateLocationAlgorithmEvent(certainEvent) + script.simulateLocationAlgorithmEvent(certainEvent) .verifyTimeZoneChangedAndReset(certainEvent) .verifyTelephonyFallbackIsEnabled(false); + // Increment the clock before creating the event: the clock's value is used by the event + script.simulateIncrementClock(); LocationAlgorithmEvent uncertainEvent = createUncertainLocationAlgorithmEvent(); - script.simulateIncrementClock() - .simulateLocationAlgorithmEvent(uncertainEvent) + script.simulateLocationAlgorithmEvent(uncertainEvent) .verifyTimeZoneNotChanged() .verifyTelephonyFallbackIsEnabled(false); @@ -1549,9 +1690,16 @@ public class TimeZoneDetectorStrategyImplTest { } private LocationAlgorithmEvent createUncertainLocationAlgorithmEvent() { + TimeZoneProviderStatus primaryProviderReportedStatus = null; + return createUncertainLocationAlgorithmEvent(primaryProviderReportedStatus); + } + + private LocationAlgorithmEvent createUncertainLocationAlgorithmEvent( + TimeZoneProviderStatus primaryProviderReportedStatus) { GeolocationTimeZoneSuggestion suggestion = createUncertainGeolocationSuggestion(); LocationTimeZoneAlgorithmStatus algorithmStatus = new LocationTimeZoneAlgorithmStatus( - DETECTION_ALGORITHM_STATUS_RUNNING, PROVIDER_STATUS_IS_UNCERTAIN, null, + DETECTION_ALGORITHM_STATUS_RUNNING, + PROVIDER_STATUS_IS_UNCERTAIN, primaryProviderReportedStatus, PROVIDER_STATUS_NOT_PRESENT, null); LocationAlgorithmEvent event = new LocationAlgorithmEvent(algorithmStatus, suggestion); event.addDebugInfo("Test uncertain event"); @@ -1744,11 +1892,12 @@ public class TimeZoneDetectorStrategyImplTest { } /** - * Simulates the time zone detection strategty receiving a signal that allows it to do + * Simulates the time zone detection strategy receiving a signal that allows it to do * telephony fallback. */ Script simulateEnableTelephonyFallback() { - mTimeZoneDetectorStrategy.enableTelephonyTimeZoneFallback(); + mTimeZoneDetectorStrategy.enableTelephonyTimeZoneFallback( + "simulateEnableTelephonyFallback()"); return this; } diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java index 9090c5550248..aab70b5f9004 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java @@ -369,7 +369,7 @@ public class WallpaperControllerTests extends WindowTestsBase { final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); token.finishSync(t, false /* cancel */); transit.onTransactionReady(transit.getSyncId(), t); - dc.mTransitionController.finishTransition(transit); + dc.mTransitionController.finishTransition(transit.getToken()); assertFalse(wallpaperWindow.isVisible()); assertFalse(token.isVisible()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index 4429aef02f94..871030f43716 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -16,12 +16,16 @@ package com.android.server.wm; +import static android.Manifest.permission.ADD_TRUSTED_DISPLAY; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC; +import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Display.FLAG_OWN_FOCUS; import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; @@ -81,6 +85,9 @@ import android.window.ScreenCapture; import android.window.WindowContainerToken; import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.compatibility.common.util.AdoptShellPermissionsRule; import org.junit.Rule; import org.junit.Test; @@ -99,6 +106,11 @@ public class WindowManagerServiceTests extends WindowTestsBase { @Rule public ExpectedException mExpectedException = ExpectedException.none(); + @Rule + public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule( + InstrumentationRegistry.getInstrumentation().getUiAutomation(), + ADD_TRUSTED_DISPLAY); + @Test public void testAddWindowToken() { IBinder token = mock(IBinder.class); @@ -396,9 +408,15 @@ public class WindowManagerServiceTests extends WindowTestsBase { @Test public void testSetInTouchMode_multiDisplay_globalTouchModeUpdate() { // Create one extra display - final VirtualDisplay virtualDisplay = createVirtualDisplay(); + final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false); + final VirtualDisplay virtualDisplayOwnTouchMode = + createVirtualDisplay(/* ownFocus= */ true); final int numberOfDisplays = mWm.mRoot.mChildren.size(); - assertThat(numberOfDisplays).isAtLeast(2); + assertThat(numberOfDisplays).isAtLeast(3); + final int numberOfGlobalTouchModeDisplays = (int) mWm.mRoot.mChildren.stream() + .filter(d -> (d.getDisplay().getFlags() & FLAG_OWN_FOCUS) == 0) + .count(); + assertThat(numberOfGlobalTouchModeDisplays).isAtLeast(2); // Enable global touch mode (config_perDisplayFocusEnabled set to false) Resources mockResources = mock(Resources.class); @@ -417,15 +435,15 @@ public class WindowManagerServiceTests extends WindowTestsBase { mWm.setInTouchMode(!currentTouchMode, DEFAULT_DISPLAY); - verify(mWm.mInputManager, times(numberOfDisplays)).setInTouchMode( + verify(mWm.mInputManager, times(numberOfGlobalTouchModeDisplays)).setInTouchMode( eq(!currentTouchMode), eq(callingPid), eq(callingUid), /* hasPermission= */ eq(true), /* displayId= */ anyInt()); } @Test - public void testSetInTouchMode_multiDisplay_singleDisplayTouchModeUpdate() { + public void testSetInTouchMode_multiDisplay_perDisplayFocus_singleDisplayTouchModeUpdate() { // Create one extra display - final VirtualDisplay virtualDisplay = createVirtualDisplay(); + final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false); final int numberOfDisplays = mWm.mRoot.mChildren.size(); assertThat(numberOfDisplays).isAtLeast(2); @@ -452,14 +470,47 @@ public class WindowManagerServiceTests extends WindowTestsBase { virtualDisplay.getDisplay().getDisplayId()); } - private VirtualDisplay createVirtualDisplay() { + @Test + public void testSetInTouchMode_multiDisplay_ownTouchMode_singleDisplayTouchModeUpdate() { + // Create one extra display + final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ true); + final int numberOfDisplays = mWm.mRoot.mChildren.size(); + assertThat(numberOfDisplays).isAtLeast(2); + + // Enable global touch mode (config_perDisplayFocusEnabled set to false) + Resources mockResources = mock(Resources.class); + spyOn(mContext); + when(mContext.getResources()).thenReturn(mockResources); + doReturn(false).when(mockResources).getBoolean( + com.android.internal.R.bool.config_perDisplayFocusEnabled); + + // Get current touch mode state and setup WMS to run setInTouchMode + boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY); + int callingPid = Binder.getCallingPid(); + int callingUid = Binder.getCallingUid(); + doReturn(false).when(mWm).checkCallingPermission(anyString(), anyString(), anyBoolean()); + when(mWm.mAtmService.instrumentationSourceHasPermission(callingPid, + android.Manifest.permission.MODIFY_TOUCH_MODE_STATE)).thenReturn(true); + + mWm.setInTouchMode(!currentTouchMode, virtualDisplay.getDisplay().getDisplayId()); + + // Ensure that new display touch mode state has changed. + verify(mWm.mInputManager).setInTouchMode( + !currentTouchMode, callingPid, callingUid, /* hasPermission= */ true, + virtualDisplay.getDisplay().getDisplayId()); + } + + private VirtualDisplay createVirtualDisplay(boolean ownFocus) { // Create virtual display Point surfaceSize = new Point( mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(), mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height()); + int flags = VIRTUAL_DISPLAY_FLAG_PUBLIC; + if (ownFocus) { + flags |= VIRTUAL_DISPLAY_FLAG_OWN_FOCUS | VIRTUAL_DISPLAY_FLAG_TRUSTED; + } VirtualDisplay virtualDisplay = mWm.mDisplayManager.createVirtualDisplay("VirtualDisplay", - surfaceSize.x, surfaceSize.y, - DisplayMetrics.DENSITY_140, new Surface(), VIRTUAL_DISPLAY_FLAG_PUBLIC); + surfaceSize.x, surfaceSize.y, DisplayMetrics.DENSITY_140, new Surface(), flags); final int displayId = virtualDisplay.getDisplay().getDisplayId(); mWm.mRoot.onDisplayAdded(displayId); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 1348770bd807..5a261bc6526b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -1746,7 +1746,7 @@ class WindowTestsBase extends SystemServiceTestsBase { } void startTransition() { - mOrganizer.startTransition(mLastTransit, null); + mOrganizer.startTransition(mLastTransit.getToken(), null); } void onTransactionReady(SurfaceControl.Transaction t) { @@ -1759,7 +1759,7 @@ class WindowTestsBase extends SystemServiceTestsBase { } public void finish() { - mController.finishTransition(mLastTransit); + mController.finishTransition(mLastTransit.getToken()); } } } diff --git a/services/usb/Android.bp b/services/usb/Android.bp index 3b50fa43536c..52cfe25d9f26 100644 --- a/services/usb/Android.bp +++ b/services/usb/Android.bp @@ -29,7 +29,7 @@ java_library_static { "android.hardware.usb-V1.1-java", "android.hardware.usb-V1.2-java", "android.hardware.usb-V1.3-java", - "android.hardware.usb-V1-java", + "android.hardware.usb-V2-java", "android.hardware.usb.gadget-V1.0-java", "android.hardware.usb.gadget-V1.1-java", "android.hardware.usb.gadget-V1.2-java", diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java index f8df6c6ea8df..4bb9de532360 100644 --- a/services/usb/java/com/android/server/usb/UsbPortManager.java +++ b/services/usb/java/com/android/server/usb/UsbPortManager.java @@ -73,6 +73,7 @@ import android.service.ServiceProtoEnums; import android.service.usb.UsbPortInfoProto; import android.service.usb.UsbPortManagerProto; import android.util.ArrayMap; +import android.util.IntArray; import android.util.Log; import android.util.Slog; @@ -87,6 +88,7 @@ import com.android.server.usb.hal.port.RawPortInfo; import com.android.server.usb.hal.port.UsbPortHal; import com.android.server.usb.hal.port.UsbPortHalInstance; +import java.util.Arrays; import java.util.ArrayList; import java.util.NoSuchElementException; import java.util.Objects; @@ -754,6 +756,31 @@ public class UsbPortManager { } } + /** + * Sets Compliance Warnings for simulated USB port objects. + */ + public void simulateComplianceWarnings(String portId, String complianceWarningsString, + IndentingPrintWriter pw) { + synchronized (mLock) { + final RawPortInfo portInfo = mSimulatedPorts.get(portId); + if (portInfo == null) { + pw.println("Simulated port not found"); + return; + } + + IntArray complianceWarnings = new IntArray(); + for (String s : complianceWarningsString.split("[, ]")) { + if (s.length() > 0) { + complianceWarnings.add(Integer.parseInt(s)); + } + } + pw.println("Simulating Compliance Warnings: portId=" + portId + + " Warnings=" + complianceWarningsString); + portInfo.complianceWarnings = complianceWarnings.toArray(); + updatePortsLocked(pw, null); + } + } + public void disconnectSimulatedPort(String portId, IndentingPrintWriter pw) { synchronized (mLock) { final RawPortInfo portInfo = mSimulatedPorts.get(portId); @@ -842,7 +869,10 @@ public class UsbPortManager { portInfo.contaminantDetectionStatus, portInfo.usbDataStatus, portInfo.powerTransferLimited, - portInfo.powerBrickConnectionStatus, pw); + portInfo.powerBrickConnectionStatus, + portInfo.supportsComplianceWarnings, + portInfo.complianceWarnings, + pw); } } else { for (RawPortInfo currentPortInfo : newPortInfo) { @@ -857,7 +887,10 @@ public class UsbPortManager { currentPortInfo.contaminantDetectionStatus, currentPortInfo.usbDataStatus, currentPortInfo.powerTransferLimited, - currentPortInfo.powerBrickConnectionStatus, pw); + currentPortInfo.powerBrickConnectionStatus, + currentPortInfo.supportsComplianceWarnings, + currentPortInfo.complianceWarnings, + pw); } } @@ -880,6 +913,9 @@ public class UsbPortManager { handlePortRemovedLocked(portInfo, pw); break; } + if (portInfo.mComplianceWarningChange == portInfo.COMPLIANCE_WARNING_CHANGED) { + handlePortComplianceWarningLocked(portInfo, pw); + } } } @@ -896,6 +932,8 @@ public class UsbPortManager { int usbDataStatus, boolean powerTransferLimited, int powerBrickConnectionStatus, + boolean supportsComplianceWarnings, + @NonNull int[] complianceWarnings, IndentingPrintWriter pw) { // Only allow mode switch capability for dual role ports. // Validate that the current mode matches the supported modes we expect. @@ -949,13 +987,15 @@ public class UsbPortManager { portInfo = new PortInfo(mContext.getSystemService(UsbManager.class), portId, supportedModes, supportedContaminantProtectionModes, supportsEnableContaminantPresenceProtection, - supportsEnableContaminantPresenceDetection); + supportsEnableContaminantPresenceDetection, + supportsComplianceWarnings); portInfo.setStatus(currentMode, canChangeMode, currentPowerRole, canChangePowerRole, currentDataRole, canChangeDataRole, supportedRoleCombinations, contaminantProtectionStatus, contaminantDetectionStatus, usbDataStatus, - powerTransferLimited, powerBrickConnectionStatus); + powerTransferLimited, powerBrickConnectionStatus, + complianceWarnings); mPorts.put(portId, portInfo); } else { // Validate that ports aren't changing definition out from under us. @@ -987,13 +1027,13 @@ public class UsbPortManager { + ", current=" + supportsEnableContaminantPresenceDetection); } - if (portInfo.setStatus(currentMode, canChangeMode, currentPowerRole, canChangePowerRole, currentDataRole, canChangeDataRole, supportedRoleCombinations, contaminantProtectionStatus, contaminantDetectionStatus, usbDataStatus, - powerTransferLimited, powerBrickConnectionStatus)) { + powerTransferLimited, powerBrickConnectionStatus, + complianceWarnings)) { portInfo.mDisposition = PortInfo.DISPOSITION_CHANGED; } else { portInfo.mDisposition = PortInfo.DISPOSITION_READY; @@ -1019,6 +1059,11 @@ public class UsbPortManager { handlePortLocked(portInfo, pw); } + private void handlePortComplianceWarningLocked(PortInfo portInfo, IndentingPrintWriter pw) { + logAndPrint(Log.INFO, pw, "USB port compliance warning changed: " + portInfo); + sendComplianceWarningBroadcastLocked(portInfo); + } + private void handlePortRemovedLocked(PortInfo portInfo, IndentingPrintWriter pw) { logAndPrint(Log.INFO, pw, "USB port removed: " + portInfo); handlePortLocked(portInfo, pw); @@ -1056,6 +1101,23 @@ public class UsbPortManager { Manifest.permission.MANAGE_USB)); } + private void sendComplianceWarningBroadcastLocked(PortInfo portInfo) { + if (portInfo.mComplianceWarningChange == portInfo.COMPLIANCE_WARNING_UNCHANGED) { + return; + } + final Intent intent = new Intent(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED); + intent.addFlags( + Intent.FLAG_RECEIVER_FOREGROUND | + Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + intent.putExtra(UsbManager.EXTRA_PORT, ParcelableUsbPort.of(portInfo.mUsbPort)); + intent.putExtra(UsbManager.EXTRA_PORT_STATUS, portInfo.mUsbPortStatus); + + // Guard against possible reentrance by posting the broadcast from the handler + // instead of from within the critical section. + mHandler.post(() -> mContext.sendBroadcastAsUser(intent, UserHandle.ALL, + Manifest.permission.MANAGE_USB)); + } + private void enableContaminantDetectionIfNeeded(PortInfo portInfo, IndentingPrintWriter pw) { if (!mConnected.containsKey(portInfo.mUsbPort.getId())) { return; @@ -1180,6 +1242,9 @@ public class UsbPortManager { public static final int DISPOSITION_READY = 2; public static final int DISPOSITION_REMOVED = 3; + public static final int COMPLIANCE_WARNING_UNCHANGED = 0; + public static final int COMPLIANCE_WARNING_CHANGED = 1; + public final UsbPort mUsbPort; public UsbPortStatus mUsbPortStatus; public boolean mCanChangeMode; @@ -1191,15 +1256,29 @@ public class UsbPortManager { public long mConnectedAtMillis; // 0 when port is connected. Else reports the last connected duration public long mLastConnectDurationMillis; + // default initialized to 0 which means no changes reported + public int mComplianceWarningChange; PortInfo(@NonNull UsbManager usbManager, @NonNull String portId, int supportedModes, int supportedContaminantProtectionModes, boolean supportsEnableContaminantPresenceDetection, - boolean supportsEnableContaminantPresenceProtection) { + boolean supportsEnableContaminantPresenceProtection, + boolean supportsComplianceWarnings) { mUsbPort = new UsbPort(usbManager, portId, supportedModes, supportedContaminantProtectionModes, supportsEnableContaminantPresenceDetection, - supportsEnableContaminantPresenceProtection); + supportsEnableContaminantPresenceProtection, + supportsComplianceWarnings); + mComplianceWarningChange = COMPLIANCE_WARNING_UNCHANGED; + } + + public boolean complianceWarningsChanged(@NonNull int[] complianceWarnings) { + if (Arrays.equals(complianceWarnings, mUsbPortStatus.getComplianceWarnings())) { + mComplianceWarningChange = COMPLIANCE_WARNING_UNCHANGED; + return false; + } + mComplianceWarningChange = COMPLIANCE_WARNING_CHANGED; + return true; } public boolean setStatus(int currentMode, boolean canChangeMode, @@ -1221,7 +1300,8 @@ public class UsbPortManager { supportedRoleCombinations, UsbPortStatus.CONTAMINANT_PROTECTION_NONE, UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED, UsbPortStatus.DATA_STATUS_UNKNOWN, false, - UsbPortStatus.POWER_BRICK_STATUS_UNKNOWN); + UsbPortStatus.POWER_BRICK_STATUS_UNKNOWN, + new int[] {}); dispositionChanged = true; } @@ -1266,8 +1346,65 @@ public class UsbPortManager { mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole, supportedRoleCombinations, contaminantProtectionStatus, contaminantDetectionStatus, usbDataStatus, - powerTransferLimited, powerBrickConnectionStatus); + powerTransferLimited, powerBrickConnectionStatus, + new int[] {}); + dispositionChanged = true; + } + + if (mUsbPortStatus.isConnected() && mConnectedAtMillis == 0) { + mConnectedAtMillis = SystemClock.elapsedRealtime(); + mLastConnectDurationMillis = 0; + } else if (!mUsbPortStatus.isConnected() && mConnectedAtMillis != 0) { + mLastConnectDurationMillis = SystemClock.elapsedRealtime() - mConnectedAtMillis; + mConnectedAtMillis = 0; + } + + return dispositionChanged; + } + + public boolean setStatus(int currentMode, boolean canChangeMode, + int currentPowerRole, boolean canChangePowerRole, + int currentDataRole, boolean canChangeDataRole, + int supportedRoleCombinations, int contaminantProtectionStatus, + int contaminantDetectionStatus, int usbDataStatus, + boolean powerTransferLimited, int powerBrickConnectionStatus, + @NonNull int[] complianceWarnings) { + boolean dispositionChanged = false; + + mCanChangeMode = canChangeMode; + mCanChangePowerRole = canChangePowerRole; + mCanChangeDataRole = canChangeDataRole; + if (mUsbPortStatus == null + || mUsbPortStatus.getCurrentMode() != currentMode + || mUsbPortStatus.getCurrentPowerRole() != currentPowerRole + || mUsbPortStatus.getCurrentDataRole() != currentDataRole + || mUsbPortStatus.getSupportedRoleCombinations() + != supportedRoleCombinations + || mUsbPortStatus.getContaminantProtectionStatus() + != contaminantProtectionStatus + || mUsbPortStatus.getContaminantDetectionStatus() + != contaminantDetectionStatus + || mUsbPortStatus.getUsbDataStatus() + != usbDataStatus + || mUsbPortStatus.isPowerTransferLimited() + != powerTransferLimited + || mUsbPortStatus.getPowerBrickConnectionStatus() + != powerBrickConnectionStatus) { + if (mUsbPortStatus == null && complianceWarnings.length > 0) { + mComplianceWarningChange = COMPLIANCE_WARNING_CHANGED; + } + mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole, + supportedRoleCombinations, contaminantProtectionStatus, + contaminantDetectionStatus, usbDataStatus, + powerTransferLimited, powerBrickConnectionStatus, + complianceWarnings); dispositionChanged = true; + } else if (complianceWarningsChanged(complianceWarnings)) { + mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole, + supportedRoleCombinations, contaminantProtectionStatus, + contaminantDetectionStatus, usbDataStatus, + powerTransferLimited, powerBrickConnectionStatus, + complianceWarnings); } if (mUsbPortStatus.isConnected() && mConnectedAtMillis == 0) { diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java index 72f6cc3649a7..d821dee75d7d 100644 --- a/services/usb/java/com/android/server/usb/UsbService.java +++ b/services/usb/java/com/android/server/usb/UsbService.java @@ -1093,6 +1093,23 @@ public class UsbService extends IUsbManager.Stub { mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")), "", 0); } + } else if ("set-compliance-reasons".equals(args[0]) && args.length == 3) { + final String portId = args[1]; + final String complianceWarnings = args[2]; + if (mPortManager != null) { + mPortManager.simulateComplianceWarnings(portId, complianceWarnings, pw); + pw.println(); + mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")), + "", 0); + } + } else if ("clear-compliance-reasons".equals(args[0]) && args.length == 2) { + final String portId = args[1]; + if (mPortManager != null) { + mPortManager.simulateComplianceWarnings(portId, "", pw); + pw.println(); + mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")), + "", 0); + } } else if ("ports".equals(args[0]) && args.length == 1) { if (mPortManager != null) { mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")), @@ -1142,6 +1159,17 @@ public class UsbService extends IUsbManager.Stub { pw.println(" dumpsys usb set-contaminant-status \"matrix\" true"); pw.println(" dumpsys usb set-contaminant-status \"matrix\" false"); pw.println(); + pw.println("Example simulate compliance warnings:"); + pw.println(" dumpsys usb add-port \"matrix\" dual"); + pw.println(" dumpsys usb set-compliance-reasons \"matrix\" <reason-list>"); + pw.println(" dumpsys usb clear-compliance-reasons \"matrix\""); + pw.println("<reason-list> is expected to be formatted as \"1, ..., 4\""); + pw.println("with reasons that need to be simulated."); + pw.println(" 1: debug accessory"); + pw.println(" 2: bc12"); + pw.println(" 3: missing rp"); + pw.println(" 4: type c"); + pw.println(); pw.println("Example USB device descriptors:"); pw.println(" dumpsys usb dump-descriptors -dump-short"); pw.println(" dumpsys usb dump-descriptors -dump-tree"); diff --git a/services/usb/java/com/android/server/usb/hal/port/RawPortInfo.java b/services/usb/java/com/android/server/usb/hal/port/RawPortInfo.java index 128a0512e830..e6a3e5343507 100644 --- a/services/usb/java/com/android/server/usb/hal/port/RawPortInfo.java +++ b/services/usb/java/com/android/server/usb/hal/port/RawPortInfo.java @@ -40,6 +40,8 @@ public final class RawPortInfo implements Parcelable { public int usbDataStatus; public boolean powerTransferLimited; public int powerBrickConnectionStatus; + public final boolean supportsComplianceWarnings; + public int[] complianceWarnings; public RawPortInfo(String portId, int supportedModes) { this.portId = portId; @@ -50,9 +52,10 @@ public final class RawPortInfo implements Parcelable { this.supportsEnableContaminantPresenceDetection = false; this.contaminantDetectionStatus = UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED; this.usbDataStatus = UsbPortStatus.DATA_STATUS_UNKNOWN; - this.powerTransferLimited = false; this.powerBrickConnectionStatus = UsbPortStatus.POWER_BRICK_STATUS_UNKNOWN; + this.supportsComplianceWarnings = false; + this.complianceWarnings = new int[] {}; } public RawPortInfo(String portId, int supportedModes, int supportedContaminantProtectionModes, @@ -66,6 +69,29 @@ public final class RawPortInfo implements Parcelable { int usbDataStatus, boolean powerTransferLimited, int powerBrickConnectionStatus) { + this(portId, supportedModes, supportedContaminantProtectionModes, + currentMode, canChangeMode, + currentPowerRole, canChangePowerRole, + currentDataRole, canChangeDataRole, + supportsEnableContaminantPresenceProtection, contaminantProtectionStatus, + supportsEnableContaminantPresenceDetection, contaminantDetectionStatus, + usbDataStatus, powerTransferLimited, powerBrickConnectionStatus, + false, new int[] {}); + } + + public RawPortInfo(String portId, int supportedModes, int supportedContaminantProtectionModes, + int currentMode, boolean canChangeMode, + int currentPowerRole, boolean canChangePowerRole, + int currentDataRole, boolean canChangeDataRole, + boolean supportsEnableContaminantPresenceProtection, + int contaminantProtectionStatus, + boolean supportsEnableContaminantPresenceDetection, + int contaminantDetectionStatus, + int usbDataStatus, + boolean powerTransferLimited, + int powerBrickConnectionStatus, + boolean supportsComplianceWarnings, + int[] complianceWarnings) { this.portId = portId; this.supportedModes = supportedModes; this.supportedContaminantProtectionModes = supportedContaminantProtectionModes; @@ -84,6 +110,8 @@ public final class RawPortInfo implements Parcelable { this.usbDataStatus = usbDataStatus; this.powerTransferLimited = powerTransferLimited; this.powerBrickConnectionStatus = powerBrickConnectionStatus; + this.supportsComplianceWarnings = supportsComplianceWarnings; + this.complianceWarnings = complianceWarnings; } @Override @@ -109,6 +137,8 @@ public final class RawPortInfo implements Parcelable { dest.writeInt(usbDataStatus); dest.writeBoolean(powerTransferLimited); dest.writeInt(powerBrickConnectionStatus); + dest.writeBoolean(supportsComplianceWarnings); + dest.writeIntArray(complianceWarnings); } public static final Parcelable.Creator<RawPortInfo> CREATOR = @@ -131,6 +161,8 @@ public final class RawPortInfo implements Parcelable { int usbDataStatus = in.readInt(); boolean powerTransferLimited = in.readBoolean(); int powerBrickConnectionStatus = in.readInt(); + boolean supportsComplianceWarnings = in.readBoolean(); + int[] complianceWarnings = in.createIntArray(); return new RawPortInfo(id, supportedModes, supportedContaminantProtectionModes, currentMode, canChangeMode, currentPowerRole, canChangePowerRole, @@ -139,7 +171,8 @@ public final class RawPortInfo implements Parcelable { contaminantProtectionStatus, supportsEnableContaminantPresenceDetection, contaminantDetectionStatus, usbDataStatus, - powerTransferLimited, powerBrickConnectionStatus); + powerTransferLimited, powerBrickConnectionStatus, + supportsComplianceWarnings, complianceWarnings); } @Override diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java index 94273a37abcd..ca11629800a1 100644 --- a/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java +++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java @@ -34,9 +34,12 @@ import android.hardware.usb.Status; import android.hardware.usb.IUsbCallback; import android.hardware.usb.PortRole; import android.hardware.usb.PortStatus; +import android.hardware.usb.ComplianceWarning; +import android.os.Build; import android.os.ServiceManager; import android.os.IBinder; import android.os.RemoteException; +import android.util.IntArray; import android.util.Log; import android.util.LongSparseArray; import android.util.Slog; @@ -46,6 +49,7 @@ import com.android.internal.util.IndentingPrintWriter; import com.android.server.usb.UsbPortManager; import com.android.server.usb.hal.port.RawPortInfo; +import java.util.Arrays; import java.util.ArrayList; import java.util.concurrent.ThreadLocalRandom; import java.util.NoSuchElementException; @@ -551,6 +555,24 @@ public final class UsbPortAidl implements UsbPortHal { return usbDataStatus; } + private int[] formatComplianceWarnings(int[] complianceWarnings) { + Objects.requireNonNull(complianceWarnings); + IntArray newComplianceWarnings = new IntArray(); + Arrays.sort(complianceWarnings); + for (int warning : complianceWarnings) { + if (newComplianceWarnings.indexOf(warning) == -1 + && warning >= UsbPortStatus.COMPLIANCE_WARNING_OTHER) { + // ComplianceWarnings range from [1, 4] in Android U + if (warning > UsbPortStatus.COMPLIANCE_WARNING_MISSING_RP) { + newComplianceWarnings.add(UsbPortStatus.COMPLIANCE_WARNING_OTHER); + } else { + newComplianceWarnings.add(warning); + } + } + } + return newComplianceWarnings.toArray(); + } + @Override public void notifyPortStatusChange( android.hardware.usb.PortStatus[] currentPortStatus, int retval) { @@ -584,7 +606,9 @@ public final class UsbPortAidl implements UsbPortHal { current.contaminantDetectionStatus, toUsbDataStatusInt(current.usbDataStatus), current.powerTransferLimited, - current.powerBrickStatus); + current.powerBrickStatus, + current.supportsComplianceWarnings, + formatComplianceWarnings(current.complianceWarnings)); newPortInfo.add(temp); UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback AIDL V1: " + current.portName); diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java index 23d913cba733..10403c1a5f73 100644 --- a/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java +++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java @@ -421,7 +421,8 @@ public final class UsbPortHidl implements UsbPortHal { current.currentDataRole, current.canChangeDataRole, false, CONTAMINANT_PROTECTION_NONE, false, CONTAMINANT_DETECTION_NOT_SUPPORTED, sUsbDataStatus, - false, POWER_BRICK_STATUS_UNKNOWN); + false, POWER_BRICK_STATUS_UNKNOWN, + false, new int[] {}); newPortInfo.add(temp); UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback V1_0: " + current.portName); @@ -455,7 +456,8 @@ public final class UsbPortHidl implements UsbPortHal { current.status.currentDataRole, current.status.canChangeDataRole, false, CONTAMINANT_PROTECTION_NONE, false, CONTAMINANT_DETECTION_NOT_SUPPORTED, sUsbDataStatus, - false, POWER_BRICK_STATUS_UNKNOWN); + false, POWER_BRICK_STATUS_UNKNOWN, + false, new int[] {}); newPortInfo.add(temp); UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback V1_1: " + current.status.portName); @@ -493,7 +495,8 @@ public final class UsbPortHidl implements UsbPortHal { current.supportsEnableContaminantPresenceDetection, current.contaminantDetectionStatus, sUsbDataStatus, - false, POWER_BRICK_STATUS_UNKNOWN); + false, POWER_BRICK_STATUS_UNKNOWN, + false, new int[] {}); newPortInfo.add(temp); UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback V1_2: " + current.status_1_1.status.portName); diff --git a/startop/view_compiler/apk_layout_compiler.cc b/startop/view_compiler/apk_layout_compiler.cc index 5cb0c171e06f..1d3b648175cc 100644 --- a/startop/view_compiler/apk_layout_compiler.cc +++ b/startop/view_compiler/apk_layout_compiler.cc @@ -100,56 +100,60 @@ void CompileApkAssetsLayouts(const std::unique_ptr<android::ApkAssets>& assets, dex_file.MakeClass(StringPrintf("%s.CompiledView", package_name.c_str()))}; std::vector<dex::MethodBuilder> methods; - assets->GetAssetsProvider()->ForEachFile("res/", [&](const android::StringPiece& s, - android::FileType) { - if (s == "layout") { - auto path = StringPrintf("res/%s/", s.to_string().c_str()); - assets->GetAssetsProvider()->ForEachFile(path, [&](const android::StringPiece& layout_file, - android::FileType) { - auto layout_path = StringPrintf("%s%s", path.c_str(), layout_file.to_string().c_str()); - android::ApkAssetsCookie cookie = android::kInvalidCookie; - auto asset = resources.OpenNonAsset(layout_path, android::Asset::ACCESS_RANDOM, &cookie); - CHECK(asset); - CHECK(android::kInvalidCookie != cookie); - const auto dynamic_ref_table = resources.GetDynamicRefTableForCookie(cookie); - CHECK(nullptr != dynamic_ref_table); - android::ResXMLTree xml_tree{dynamic_ref_table}; - xml_tree.setTo(asset->getBuffer(/*wordAligned=*/true), - asset->getLength(), - /*copy_data=*/true); - android::ResXMLParser parser{xml_tree}; - parser.restart(); - if (CanCompileLayout(&parser)) { - parser.restart(); - const std::string layout_name = startop::util::FindLayoutNameFromFilename(layout_path); - ResXmlVisitorAdapter adapter{&parser}; - switch (target) { - case CompilationTarget::kDex: { - methods.push_back(compiled_view.CreateMethod( - layout_name, - dex::Prototype{dex::TypeDescriptor::FromClassname("android.view.View"), - dex::TypeDescriptor::FromClassname("android.content.Context"), - dex::TypeDescriptor::Int()})); - DexViewBuilder builder(&methods.back()); - builder.Start(); - LayoutCompilerVisitor visitor{&builder}; - adapter.Accept(&visitor); - builder.Finish(); - methods.back().Encode(); - break; - } - case CompilationTarget::kJavaLanguage: { - JavaLangViewBuilder builder{package_name, layout_name, target_out}; - builder.Start(); - LayoutCompilerVisitor visitor{&builder}; - adapter.Accept(&visitor); - builder.Finish(); - break; - } - } - } - }); - } + assets->GetAssetsProvider()->ForEachFile("res/", [&](android::StringPiece s, android::FileType) { + if (s == "layout") { + auto path = StringPrintf("res/%.*s/", (int)s.size(), s.data()); + assets->GetAssetsProvider() + ->ForEachFile(path, [&](android::StringPiece layout_file, android::FileType) { + auto layout_path = StringPrintf("%s%.*s", path.c_str(), + (int)layout_file.size(), layout_file.data()); + android::ApkAssetsCookie cookie = android::kInvalidCookie; + auto asset = resources.OpenNonAsset(layout_path, + android::Asset::ACCESS_RANDOM, &cookie); + CHECK(asset); + CHECK(android::kInvalidCookie != cookie); + const auto dynamic_ref_table = resources.GetDynamicRefTableForCookie(cookie); + CHECK(nullptr != dynamic_ref_table); + android::ResXMLTree xml_tree{dynamic_ref_table}; + xml_tree.setTo(asset->getBuffer(/*wordAligned=*/true), asset->getLength(), + /*copy_data=*/true); + android::ResXMLParser parser{xml_tree}; + parser.restart(); + if (CanCompileLayout(&parser)) { + parser.restart(); + const std::string layout_name = + startop::util::FindLayoutNameFromFilename(layout_path); + ResXmlVisitorAdapter adapter{&parser}; + switch (target) { + case CompilationTarget::kDex: { + methods.push_back(compiled_view.CreateMethod( + layout_name, + dex::Prototype{dex::TypeDescriptor::FromClassname( + "android.view.View"), + dex::TypeDescriptor::FromClassname( + "android.content.Context"), + dex::TypeDescriptor::Int()})); + DexViewBuilder builder(&methods.back()); + builder.Start(); + LayoutCompilerVisitor visitor{&builder}; + adapter.Accept(&visitor); + builder.Finish(); + methods.back().Encode(); + break; + } + case CompilationTarget::kJavaLanguage: { + JavaLangViewBuilder builder{package_name, layout_name, + target_out}; + builder.Start(); + LayoutCompilerVisitor visitor{&builder}; + adapter.Accept(&visitor); + builder.Finish(); + break; + } + } + } + }); + } }); if (target == CompilationTarget::kDex) { diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index ed96a9b2f996..22cd31a39f24 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1996,6 +1996,15 @@ public class CarrierConfigManager { "nr_advanced_threshold_bandwidth_khz_int"; /** + * Indicating whether to include LTE cell bandwidths when determining whether the aggregated + * cell bandwidth meets the required threshold for NR advanced. + * + * @see TelephonyDisplayInfo#OVERRIDE_NETWORK_TYPE_NR_ADVANCED + */ + public static final String KEY_INCLUDE_LTE_FOR_NR_ADVANCED_THRESHOLD_BANDWIDTH_BOOL = + "include_lte_for_nr_advanced_threshold_bandwidth_bool"; + + /** * Boolean indicating if operator name should be shown in the status bar * @hide */ @@ -9577,6 +9586,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL, true); sDefaults.putInt(KEY_LTE_PLUS_THRESHOLD_BANDWIDTH_KHZ_INT, 20000); sDefaults.putInt(KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT, 0); + sDefaults.putBoolean(KEY_INCLUDE_LTE_FOR_NR_ADVANCED_THRESHOLD_BANDWIDTH_BOOL, false); sDefaults.putIntArray(KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_NSA, CARRIER_NR_AVAILABILITY_SA}); sDefaults.putBoolean(KEY_LTE_ENABLED_BOOL, true); diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index 0c14dbaa5a3f..a1257e39f668 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -546,6 +546,8 @@ public interface RILConstants { int RIL_REQUEST_UPDATE_IMS_CALL_STATUS = 240; int RIL_REQUEST_SET_N1_MODE_ENABLED = 241; int RIL_REQUEST_IS_N1_MODE_ENABLED = 242; + int RIL_REQUEST_SET_LOCATION_PRIVACY_SETTING = 243; + int RIL_REQUEST_GET_LOCATION_PRIVACY_SETTING = 244; /* Responses begin */ int RIL_RESPONSE_ACKNOWLEDGEMENT = 800; @@ -620,4 +622,5 @@ public interface RILConstants { int RIL_UNSOL_TRIGGER_IMS_DEREGISTRATION = 1107; int RIL_UNSOL_CONNECTION_SETUP_FAILURE = 1108; int RIL_UNSOL_NOTIFY_ANBR = 1109; + int RIL_UNSOL_ON_NETWORK_INITIATED_LOCATION_RESULT = 1110; } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt index 8a1e1fabd131..3f6a75d74d36 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt @@ -172,4 +172,17 @@ constructor( open fun visibleWindowsShownMoreThanOneConsecutiveEntry() { testSpec.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry() } } + + open fun cujCompleted() { + entireScreenCovered() + navBarLayerIsVisibleAtStartAndEnd() + navBarWindowIsAlwaysVisible() + taskBarLayerIsVisibleAtStartAndEnd() + taskBarWindowIsAlwaysVisible() + statusBarLayerIsVisibleAtStartAndEnd() + statusBarLayerPositionAtStartAndEnd() + statusBarWindowIsAlwaysVisible() + visibleLayersShownMoreThanOneConsecutiveEntry() + visibleWindowsShownMoreThanOneConsecutiveEntry() + } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/TEST_MAPPING b/tests/FlickerTests/src/com/android/server/wm/flicker/TEST_MAPPING new file mode 100644 index 000000000000..945de3363669 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/TEST_MAPPING @@ -0,0 +1,15 @@ +{ + "ironwood-postsubmit": [ + { + "name": "FlickerTests", + "options": [ + { + "include-annotation": "android.platform.test.annotations.IwTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + } + ] + } + ] +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt index b9c875ab5938..ef42766bb04e 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt @@ -17,6 +17,7 @@ package com.android.server.wm.flicker.ime import android.platform.test.annotations.FlakyTest +import android.platform.test.annotations.IwTest import android.platform.test.annotations.Presubmit import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.BaseTest @@ -101,6 +102,16 @@ class CloseImeWindowToAppTest(testSpec: FlickerTestParameter) : BaseTest(testSpe testSpec.assertWm { this.isAppWindowOnTop(testApp) } } + @Test + @IwTest(focusArea = "ime") + override fun cujCompleted() { + super.cujCompleted() + navBarLayerPositionAtStartAndEnd() + imeLayerBecomesInvisible() + imeAppLayerIsAlwaysVisible() + imeAppWindowIsAlwaysVisible() + } + companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt index 1dc3ca55caba..c92fce33188e 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt @@ -16,6 +16,7 @@ package com.android.server.wm.flicker.ime +import android.platform.test.annotations.IwTest import android.platform.test.annotations.Presubmit import android.view.Surface import android.view.WindowManagerPolicyConstants @@ -100,6 +101,17 @@ class CloseImeWindowToHomeTest(testSpec: FlickerTestParameter) : BaseTest(testSp testSpec.assertLayers { this.isVisible(testApp).then().isInvisible(testApp) } } + @Test + @IwTest(focusArea = "ime") + override fun cujCompleted() { + super.cujCompleted() + navBarLayerPositionAtStartAndEnd() + imeLayerBecomesInvisible() + imeAppWindowBecomesInvisible() + imeWindowBecomesInvisible() + imeLayerBecomesInvisible() + } + companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt index a6bd791282b8..7d7953b0c4dc 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt @@ -17,6 +17,7 @@ package com.android.server.wm.flicker.ime import android.platform.test.annotations.FlakyTest +import android.platform.test.annotations.IwTest import android.platform.test.annotations.Presubmit import android.view.Surface import android.view.WindowManagerPolicyConstants @@ -79,6 +80,14 @@ class OpenImeWindowAndCloseTest(testSpec: FlickerTestParameter) : BaseTest(testS super.visibleLayersShownMoreThanOneConsecutiveEntry() } + @Test + @IwTest(focusArea = "ime") + override fun cujCompleted() { + super.cujCompleted() + imeLayerBecomesInvisible() + imeWindowBecomesInvisible() + } + companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt index b43efea4c647..9919d873525d 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt @@ -16,6 +16,7 @@ package com.android.server.wm.flicker.ime +import android.platform.test.annotations.IwTest import android.platform.test.annotations.Presubmit import android.view.Surface import android.view.WindowManagerPolicyConstants @@ -50,6 +51,15 @@ class OpenImeWindowTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) { } } + @Test + @IwTest(focusArea = "ime") + override fun cujCompleted() { + super.cujCompleted() + imeWindowBecomesVisible() + appWindowAlwaysVisibleOnTop() + layerAlwaysVisible() + } + @Presubmit @Test fun imeWindowBecomesVisible() = testSpec.imeWindowBecomesVisible() @Presubmit diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt index 1973ec0a98a8..ad14d0d2b612 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt @@ -17,6 +17,7 @@ package com.android.server.wm.flicker.rotation import android.platform.test.annotations.FlakyTest +import android.platform.test.annotations.IwTest import android.platform.test.annotations.Presubmit import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.FlickerParametersRunnerFactory @@ -125,6 +126,14 @@ class ChangeAppRotationTest(testSpec: FlickerTestParameter) : RotationTransition @Test override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd() + @Test + @IwTest(focusArea = "ime") + override fun cujCompleted() { + super.cujCompleted() + focusChanges() + rotationLayerAppearsAndVanishes() + } + companion object { /** * Creates the test configurations. diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt index 4faeb246037e..8e3fd4066000 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt @@ -73,4 +73,10 @@ abstract class RotationTransition(testSpec: FlickerTestParameter) : BaseTest(tes } } } + + override fun cujCompleted() { + super.cujCompleted() + appLayerRotates_StartingPos() + appLayerRotates_EndingPos() + } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt index a08db29fe7a4..d0d4122423fe 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt @@ -17,6 +17,7 @@ package com.android.server.wm.flicker.rotation import android.platform.test.annotations.FlakyTest +import android.platform.test.annotations.IwTest import android.platform.test.annotations.Presubmit import android.platform.test.annotations.RequiresDevice import android.view.WindowManager @@ -204,6 +205,31 @@ open class SeamlessAppRotationTest(testSpec: FlickerTestParameter) : RotationTra @Test override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd() + @Test + @IwTest(focusArea = "ime") + override fun cujCompleted() { + if (!testSpec.isTablet) { + // not yet tablet compatible + appLayerRotates() + appLayerAlwaysVisible() + } + + appWindowFullScreen() + appWindowSeamlessRotation() + focusDoesNotChange() + statusBarLayerIsAlwaysInvisible() + statusBarWindowIsAlwaysInvisible() + appLayerRotates_StartingPos() + appLayerRotates_EndingPos() + entireScreenCovered() + navBarLayerIsVisibleAtStartAndEnd() + navBarWindowIsAlwaysVisible() + taskBarLayerIsVisibleAtStartAndEnd() + taskBarWindowIsAlwaysVisible() + visibleLayersShownMoreThanOneConsecutiveEntry() + visibleWindowsShownMoreThanOneConsecutiveEntry() + } + companion object { private val FlickerTestParameter.starveUiThread get() = diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp index 9b9cde2f37da..6b1fd9ff11eb 100644 --- a/tools/aapt2/LoadedApk.cpp +++ b/tools/aapt2/LoadedApk.cpp @@ -72,7 +72,7 @@ static ApkFormat DetermineApkFormat(io::IFileCollection* apk) { } } -std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(const StringPiece& path, +std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(StringPiece path, android::IDiagnostics* diag) { android::Source source(path); std::string error; diff --git a/tools/aapt2/LoadedApk.h b/tools/aapt2/LoadedApk.h index a4aff3f8376a..4cd7eae0a5e2 100644 --- a/tools/aapt2/LoadedApk.h +++ b/tools/aapt2/LoadedApk.h @@ -45,7 +45,7 @@ class LoadedApk { virtual ~LoadedApk() = default; // Loads both binary and proto APKs from disk. - static std::unique_ptr<LoadedApk> LoadApkFromPath(const ::android::StringPiece& path, + static std::unique_ptr<LoadedApk> LoadApkFromPath(android::StringPiece path, android::IDiagnostics* diag); // Loads a proto APK from the given file collection. diff --git a/tools/aapt2/NameMangler.h b/tools/aapt2/NameMangler.h index 0b4905253d20..0b08c3276cb5 100644 --- a/tools/aapt2/NameMangler.h +++ b/tools/aapt2/NameMangler.h @@ -36,7 +36,7 @@ struct NameManglerPolicy { * We must know which references to mangle, and which to keep (android vs. * com.android.support). */ - std::set<std::string> packages_to_mangle; + std::set<std::string, std::less<>> packages_to_mangle; }; class NameMangler { @@ -54,7 +54,7 @@ class NameMangler { mangled_entry_name); } - bool ShouldMangle(const std::string& package) const { + bool ShouldMangle(std::string_view package) const { if (package.empty() || policy_.target_package_name == package) { return false; } @@ -68,8 +68,8 @@ class NameMangler { * The mangled name should contain symbols that are illegal to define in XML, * so that there will never be name mangling collisions. */ - static std::string MangleEntry(const std::string& package, const std::string& name) { - return package + "$" + name; + static std::string MangleEntry(std::string_view package, std::string_view name) { + return (std::string(package) += '$') += name; } /** diff --git a/tools/aapt2/Resource.cpp b/tools/aapt2/Resource.cpp index df8c3b9956d0..cfcb2bb4f99d 100644 --- a/tools/aapt2/Resource.cpp +++ b/tools/aapt2/Resource.cpp @@ -138,11 +138,11 @@ ResourceNamedTypeRef ResourceNamedTypeWithDefaultName(ResourceType t) { return {to_string(t), t}; } -std::optional<ResourceNamedTypeRef> ParseResourceNamedType(const android::StringPiece& s) { +std::optional<ResourceNamedTypeRef> ParseResourceNamedType(android::StringPiece s) { auto dot = std::find(s.begin(), s.end(), '.'); const ResourceType* parsedType; if (dot != s.end() && dot != std::prev(s.end())) { - parsedType = ParseResourceType(s.substr(s.begin(), dot)); + parsedType = ParseResourceType(android::StringPiece(s.begin(), dot - s.begin())); } else { parsedType = ParseResourceType(s); } @@ -152,7 +152,7 @@ std::optional<ResourceNamedTypeRef> ParseResourceNamedType(const android::String return ResourceNamedTypeRef(s, *parsedType); } -const ResourceType* ParseResourceType(const StringPiece& str) { +const ResourceType* ParseResourceType(StringPiece str) { auto iter = sResourceTypeMap.find(str); if (iter == std::end(sResourceTypeMap)) { return nullptr; diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h index 9cfaf4742ca5..7ba3277d2093 100644 --- a/tools/aapt2/Resource.h +++ b/tools/aapt2/Resource.h @@ -74,7 +74,7 @@ android::StringPiece to_string(ResourceType type); /** * Returns a pointer to a valid ResourceType, or nullptr if the string was invalid. */ -const ResourceType* ParseResourceType(const android::StringPiece& str); +const ResourceType* ParseResourceType(android::StringPiece str); /** * Pair of type name as in ResourceTable and actual resource type. @@ -87,7 +87,7 @@ struct ResourceNamedType { ResourceType type = ResourceType::kRaw; ResourceNamedType() = default; - ResourceNamedType(const android::StringPiece& n, ResourceType t); + ResourceNamedType(android::StringPiece n, ResourceType t); int compare(const ResourceNamedType& other) const; @@ -108,19 +108,19 @@ struct ResourceNamedTypeRef { ResourceNamedTypeRef(const ResourceNamedTypeRef&) = default; ResourceNamedTypeRef(ResourceNamedTypeRef&&) = default; ResourceNamedTypeRef(const ResourceNamedType& rhs); // NOLINT(google-explicit-constructor) - ResourceNamedTypeRef(const android::StringPiece& n, ResourceType t); + ResourceNamedTypeRef(android::StringPiece n, ResourceType t); ResourceNamedTypeRef& operator=(const ResourceNamedTypeRef& rhs) = default; ResourceNamedTypeRef& operator=(ResourceNamedTypeRef&& rhs) = default; ResourceNamedTypeRef& operator=(const ResourceNamedType& rhs); ResourceNamedType ToResourceNamedType() const; - std::string to_string() const; + std::string_view to_string() const; }; ResourceNamedTypeRef ResourceNamedTypeWithDefaultName(ResourceType t); -std::optional<ResourceNamedTypeRef> ParseResourceNamedType(const android::StringPiece& s); +std::optional<ResourceNamedTypeRef> ParseResourceNamedType(android::StringPiece s); /** * A resource's name. This can uniquely identify @@ -132,9 +132,8 @@ struct ResourceName { std::string entry; ResourceName() = default; - ResourceName(const android::StringPiece& p, const ResourceNamedTypeRef& t, - const android::StringPiece& e); - ResourceName(const android::StringPiece& p, ResourceType t, const android::StringPiece& e); + ResourceName(android::StringPiece p, const ResourceNamedTypeRef& t, android::StringPiece e); + ResourceName(android::StringPiece p, ResourceType t, android::StringPiece e); int compare(const ResourceName& other) const; @@ -157,9 +156,8 @@ struct ResourceNameRef { ResourceNameRef(const ResourceNameRef&) = default; ResourceNameRef(ResourceNameRef&&) = default; ResourceNameRef(const ResourceName& rhs); // NOLINT(google-explicit-constructor) - ResourceNameRef(const android::StringPiece& p, const ResourceNamedTypeRef& t, - const android::StringPiece& e); - ResourceNameRef(const android::StringPiece& p, ResourceType t, const android::StringPiece& e); + ResourceNameRef(android::StringPiece p, const ResourceNamedTypeRef& t, android::StringPiece e); + ResourceNameRef(android::StringPiece p, ResourceType t, android::StringPiece e); ResourceNameRef& operator=(const ResourceNameRef& rhs) = default; ResourceNameRef& operator=(ResourceNameRef&& rhs) = default; ResourceNameRef& operator=(const ResourceName& rhs); @@ -346,8 +344,8 @@ inline ::std::ostream& operator<<(::std::ostream& out, const ResourceType& val) // // ResourceNamedType implementation. // -inline ResourceNamedType::ResourceNamedType(const android::StringPiece& n, ResourceType t) - : name(n.to_string()), type(t) { +inline ResourceNamedType::ResourceNamedType(android::StringPiece n, ResourceType t) + : name(n), type(t) { } inline int ResourceNamedType::compare(const ResourceNamedType& other) const { @@ -380,7 +378,7 @@ inline ::std::ostream& operator<<(::std::ostream& out, const ResourceNamedType& // // ResourceNamedTypeRef implementation. // -inline ResourceNamedTypeRef::ResourceNamedTypeRef(const android::StringPiece& n, ResourceType t) +inline ResourceNamedTypeRef::ResourceNamedTypeRef(android::StringPiece n, ResourceType t) : name(n), type(t) { } @@ -398,8 +396,8 @@ inline ResourceNamedType ResourceNamedTypeRef::ToResourceNamedType() const { return ResourceNamedType(name, type); } -inline std::string ResourceNamedTypeRef::to_string() const { - return name.to_string(); +inline std::string_view ResourceNamedTypeRef::to_string() const { + return name; } inline bool operator<(const ResourceNamedTypeRef& lhs, const ResourceNamedTypeRef& rhs) { @@ -422,13 +420,12 @@ inline ::std::ostream& operator<<(::std::ostream& out, const ResourceNamedTypeRe // ResourceName implementation. // -inline ResourceName::ResourceName(const android::StringPiece& p, const ResourceNamedTypeRef& t, - const android::StringPiece& e) - : package(p.to_string()), type(t.ToResourceNamedType()), entry(e.to_string()) { +inline ResourceName::ResourceName(android::StringPiece p, const ResourceNamedTypeRef& t, + android::StringPiece e) + : package(p), type(t.ToResourceNamedType()), entry(e) { } -inline ResourceName::ResourceName(const android::StringPiece& p, ResourceType t, - const android::StringPiece& e) +inline ResourceName::ResourceName(android::StringPiece p, ResourceType t, android::StringPiece e) : ResourceName(p, ResourceNamedTypeWithDefaultName(t), e) { } @@ -471,14 +468,13 @@ inline ::std::ostream& operator<<(::std::ostream& out, const ResourceName& name) inline ResourceNameRef::ResourceNameRef(const ResourceName& rhs) : package(rhs.package), type(rhs.type), entry(rhs.entry) {} -inline ResourceNameRef::ResourceNameRef(const android::StringPiece& p, - const ResourceNamedTypeRef& t, - const android::StringPiece& e) +inline ResourceNameRef::ResourceNameRef(android::StringPiece p, const ResourceNamedTypeRef& t, + android::StringPiece e) : package(p), type(t), entry(e) { } -inline ResourceNameRef::ResourceNameRef(const android::StringPiece& p, ResourceType t, - const android::StringPiece& e) +inline ResourceNameRef::ResourceNameRef(android::StringPiece p, ResourceType t, + android::StringPiece e) : ResourceNameRef(p, ResourceNamedTypeWithDefaultName(t), e) { } diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index 19fd306d5a42..fa9a98f136cb 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -50,11 +50,11 @@ constexpr const char* kStagingPublicGroupFinalTag = "staging-public-group-final" constexpr const char* sXliffNamespaceUri = "urn:oasis:names:tc:xliff:document:1.2"; // Returns true if the element is <skip> or <eat-comment> and can be safely ignored. -static bool ShouldIgnoreElement(const StringPiece& ns, const StringPiece& name) { +static bool ShouldIgnoreElement(StringPiece ns, StringPiece name) { return ns.empty() && (name == "skip" || name == "eat-comment"); } -static uint32_t ParseFormatTypeNoEnumsOrFlags(const StringPiece& piece) { +static uint32_t ParseFormatTypeNoEnumsOrFlags(StringPiece piece) { if (piece == "reference") { return android::ResTable_map::TYPE_REFERENCE; } else if (piece == "string") { @@ -75,7 +75,7 @@ static uint32_t ParseFormatTypeNoEnumsOrFlags(const StringPiece& piece) { return 0; } -static uint32_t ParseFormatType(const StringPiece& piece) { +static uint32_t ParseFormatType(StringPiece piece) { if (piece == "enum") { return android::ResTable_map::TYPE_ENUM; } else if (piece == "flags") { @@ -84,9 +84,9 @@ static uint32_t ParseFormatType(const StringPiece& piece) { return ParseFormatTypeNoEnumsOrFlags(piece); } -static uint32_t ParseFormatAttribute(const StringPiece& str) { +static uint32_t ParseFormatAttribute(StringPiece str) { uint32_t mask = 0; - for (const StringPiece& part : util::Tokenize(str, '|')) { + for (StringPiece part : util::Tokenize(str, '|')) { StringPiece trimmed_part = util::TrimWhitespace(part); uint32_t type = ParseFormatType(trimmed_part); if (type == 0) { @@ -122,7 +122,7 @@ static bool AddResourcesToTable(ResourceTable* table, android::IDiagnostics* dia StringPiece trimmed_comment = util::TrimWhitespace(res->comment); if (trimmed_comment.size() != res->comment.size()) { // Only if there was a change do we re-assign. - res->comment = trimmed_comment.to_string(); + res->comment = std::string(trimmed_comment); } NewResourceBuilder res_builder(res->name); @@ -362,7 +362,7 @@ bool ResourceParser::FlattenXmlSubtree( // Trim leading whitespace. StringPiece trimmed = util::TrimLeadingWhitespace(first_segment->data); if (trimmed.size() != first_segment->data.size()) { - first_segment->data = trimmed.to_string(); + first_segment->data = std::string(trimmed); } } @@ -370,7 +370,7 @@ bool ResourceParser::FlattenXmlSubtree( // Trim trailing whitespace. StringPiece trimmed = util::TrimTrailingWhitespace(last_segment->data); if (trimmed.size() != last_segment->data.size()) { - last_segment->data = trimmed.to_string(); + last_segment->data = std::string(trimmed); } } } @@ -466,7 +466,7 @@ bool ResourceParser::ParseResources(xml::XmlPullParser* parser) { // Extract the product name if it exists. if (std::optional<StringPiece> maybe_product = xml::FindNonEmptyAttribute(parser, "product")) { - parsed_resource.product = maybe_product.value().to_string(); + parsed_resource.product = std::string(maybe_product.value()); } // Parse the resource regardless of product. @@ -559,7 +559,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, // Items have their type encoded in the type attribute. if (std::optional<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) { - resource_type = maybe_type.value().to_string(); + resource_type = std::string(maybe_type.value()); } else { diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) << "<item> must have a 'type' attribute"); @@ -582,7 +582,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, // Bags have their type encoded in the type attribute. if (std::optional<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) { - resource_type = maybe_type.value().to_string(); + resource_type = std::string(maybe_type.value()); } else { diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) << "<bag> must have a 'type' attribute"); @@ -603,7 +603,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, out_resource->name.type = ResourceNamedTypeWithDefaultName(ResourceType::kId).ToResourceNamedType(); - out_resource->name.entry = maybe_name.value().to_string(); + out_resource->name.entry = std::string(maybe_name.value()); // Ids either represent a unique resource id or reference another resource id auto item = ParseItem(parser, out_resource, resource_format); @@ -640,7 +640,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, out_resource->name.type = ResourceNamedTypeWithDefaultName(ResourceType::kMacro).ToResourceNamedType(); - out_resource->name.entry = maybe_name.value().to_string(); + out_resource->name.entry = std::string(maybe_name.value()); return ParseMacro(parser, out_resource); } @@ -657,7 +657,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, out_resource->name.type = ResourceNamedTypeWithDefaultName(item_iter->second.type).ToResourceNamedType(); - out_resource->name.entry = maybe_name.value().to_string(); + out_resource->name.entry = std::string(maybe_name.value()); // Only use the implied format of the type when there is no explicit format. if (resource_format == 0u) { @@ -684,7 +684,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, return false; } - out_resource->name.entry = maybe_name.value().to_string(); + out_resource->name.entry = std::string(maybe_name.value()); } // Call the associated parse method. The type will be filled in by the @@ -708,7 +708,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, } out_resource->name.type = parsed_type->ToResourceNamedType(); - out_resource->name.entry = maybe_name.value().to_string(); + out_resource->name.entry = std::string(maybe_name.value()); out_resource->value = ParseXml(parser, android::ResTable_map::TYPE_REFERENCE, kNoRawString); if (!out_resource->value) { diag_->Error(android::DiagMessage(out_resource->source) @@ -1005,7 +1005,7 @@ bool static ParseGroupImpl(xml::XmlPullParser* parser, ParsedResource* out_resou const size_t depth = parser->depth(); while (xml::XmlPullParser::NextChildNode(parser, depth)) { if (parser->event() == xml::XmlPullParser::Event::kComment) { - comment = util::TrimWhitespace(parser->comment()).to_string(); + comment = std::string(util::TrimWhitespace(parser->comment())); continue; } else if (parser->event() != xml::XmlPullParser::Event::kStartElement) { // Skip text. @@ -1045,7 +1045,7 @@ bool static ParseGroupImpl(xml::XmlPullParser* parser, ParsedResource* out_resou } ParsedResource& entry_res = out_resource->child_resources.emplace_back(ParsedResource{ - .name = ResourceName{{}, parsed_type, maybe_name.value().to_string()}, + .name = ResourceName{{}, parsed_type, std::string(maybe_name.value())}, .source = item_source, .comment = std::move(comment), }); @@ -1231,7 +1231,7 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource ParsedResource child_resource{}; child_resource.name.type = type->ToResourceNamedType(); - child_resource.name.entry = item_name.value().to_string(); + child_resource.name.entry = std::string(item_name.value()); child_resource.overlayable_item = overlayable_item; out_resource->child_resources.push_back(std::move(child_resource)); @@ -1246,7 +1246,7 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource xml::FindNonEmptyAttribute(parser, "type")) { // Parse the polices separated by vertical bar characters to allow for specifying multiple // policies. Items within the policy tag will have the specified policy. - for (const StringPiece& part : util::Tokenize(maybe_type.value(), '|')) { + for (StringPiece part : util::Tokenize(maybe_type.value(), '|')) { StringPiece trimmed_part = util::TrimWhitespace(part); const auto policy = std::find_if(kPolicyStringToFlag.begin(), kPolicyStringToFlag.end(), @@ -1377,7 +1377,7 @@ bool ResourceParser::ParseAttrImpl(xml::XmlPullParser* parser, const size_t depth = parser->depth(); while (xml::XmlPullParser::NextChildNode(parser, depth)) { if (parser->event() == xml::XmlPullParser::Event::kComment) { - comment = util::TrimWhitespace(parser->comment()).to_string(); + comment = std::string(util::TrimWhitespace(parser->comment())); continue; } else if (parser->event() != xml::XmlPullParser::Event::kStartElement) { // Skip text. @@ -1457,7 +1457,7 @@ bool ResourceParser::ParseAttrImpl(xml::XmlPullParser* parser, } std::optional<Attribute::Symbol> ResourceParser::ParseEnumOrFlagItem(xml::XmlPullParser* parser, - const StringPiece& tag) { + StringPiece tag) { const android::Source source = source_.WithLine(parser->line_number()); std::optional<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name"); @@ -1764,7 +1764,7 @@ bool ResourceParser::ParseDeclareStyleable(xml::XmlPullParser* parser, const size_t depth = parser->depth(); while (xml::XmlPullParser::NextChildNode(parser, depth)) { if (parser->event() == xml::XmlPullParser::Event::kComment) { - comment = util::TrimWhitespace(parser->comment()).to_string(); + comment = std::string(util::TrimWhitespace(parser->comment())); continue; } else if (parser->event() != xml::XmlPullParser::Event::kStartElement) { // Ignore text. diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h index 396ce9767fe9..012a056dccf3 100644 --- a/tools/aapt2/ResourceParser.h +++ b/tools/aapt2/ResourceParser.h @@ -122,7 +122,7 @@ class ResourceParser { bool ParseAttr(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseAttrImpl(xml::XmlPullParser* parser, ParsedResource* out_resource, bool weak); std::optional<Attribute::Symbol> ParseEnumOrFlagItem(xml::XmlPullParser* parser, - const android::StringPiece& tag); + android::StringPiece tag); bool ParseStyle(ResourceType type, xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseStyleItem(xml::XmlPullParser* parser, Style* style); bool ParseDeclareStyleable(xml::XmlPullParser* parser, ParsedResource* out_resource); diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index fe7eb96ffe16..b59b16574c42 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -65,11 +65,11 @@ class ResourceParserTest : public ::testing::Test { context_ = test::ContextBuilder().Build(); } - ::testing::AssertionResult TestParse(const StringPiece& str) { + ::testing::AssertionResult TestParse(StringPiece str) { return TestParse(str, ConfigDescription{}); } - ::testing::AssertionResult TestParse(const StringPiece& str, const ConfigDescription& config) { + ::testing::AssertionResult TestParse(StringPiece str, const ConfigDescription& config) { ResourceParserOptions parserOptions; ResourceParser parser(context_->GetDiagnostics(), &table_, android::Source{"test"}, config, parserOptions); diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp index cb4811445ed1..a3b0b45df5c3 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -49,21 +49,21 @@ bool less_than_type(const std::unique_ptr<ResourceTableType>& lhs, } template <typename T> -bool less_than_struct_with_name(const std::unique_ptr<T>& lhs, const StringPiece& rhs) { +bool less_than_struct_with_name(const std::unique_ptr<T>& lhs, StringPiece rhs) { return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0; } template <typename T> -bool greater_than_struct_with_name(const StringPiece& lhs, const std::unique_ptr<T>& rhs) { +bool greater_than_struct_with_name(StringPiece lhs, const std::unique_ptr<T>& rhs) { return rhs->name.compare(0, rhs->name.size(), lhs.data(), lhs.size()) > 0; } template <typename T> struct NameEqualRange { - bool operator()(const std::unique_ptr<T>& lhs, const StringPiece& rhs) const { + bool operator()(const std::unique_ptr<T>& lhs, StringPiece rhs) const { return less_than_struct_with_name<T>(lhs, rhs); } - bool operator()(const StringPiece& lhs, const std::unique_ptr<T>& rhs) const { + bool operator()(StringPiece lhs, const std::unique_ptr<T>& rhs) const { return greater_than_struct_with_name<T>(lhs, rhs); } }; @@ -78,7 +78,7 @@ bool less_than_struct_with_name_and_id(const T& lhs, } template <typename T, typename Func, typename Elements> -T* FindElementsRunAction(const android::StringPiece& name, Elements& entries, Func action) { +T* FindElementsRunAction(android::StringPiece name, Elements& entries, Func action) { const auto iter = std::lower_bound(entries.begin(), entries.end(), name, less_than_struct_with_name<T>); const bool found = iter != entries.end() && name == (*iter)->name; @@ -87,7 +87,7 @@ T* FindElementsRunAction(const android::StringPiece& name, Elements& entries, Fu struct ConfigKey { const ConfigDescription* config; - const StringPiece& product; + StringPiece product; }; template <typename T> @@ -104,12 +104,12 @@ bool lt_config_key_ref(const T& lhs, const ConfigKey& rhs) { ResourceTable::ResourceTable(ResourceTable::Validation validation) : validation_(validation) { } -ResourceTablePackage* ResourceTable::FindPackage(const android::StringPiece& name) const { +ResourceTablePackage* ResourceTable::FindPackage(android::StringPiece name) const { return FindElementsRunAction<ResourceTablePackage>( name, packages, [&](bool found, auto& iter) { return found ? iter->get() : nullptr; }); } -ResourceTablePackage* ResourceTable::FindOrCreatePackage(const android::StringPiece& name) { +ResourceTablePackage* ResourceTable::FindOrCreatePackage(android::StringPiece name) { return FindElementsRunAction<ResourceTablePackage>(name, packages, [&](bool found, auto& iter) { return found ? iter->get() : packages.emplace(iter, new ResourceTablePackage(name))->get(); }); @@ -139,18 +139,18 @@ ResourceTableType* ResourceTablePackage::FindOrCreateType(const ResourceNamedTyp }); } -ResourceEntry* ResourceTableType::CreateEntry(const android::StringPiece& name) { +ResourceEntry* ResourceTableType::CreateEntry(android::StringPiece name) { return FindElementsRunAction<ResourceEntry>(name, entries, [&](bool found, auto& iter) { return entries.emplace(iter, new ResourceEntry(name))->get(); }); } -ResourceEntry* ResourceTableType::FindEntry(const android::StringPiece& name) const { +ResourceEntry* ResourceTableType::FindEntry(android::StringPiece name) const { return FindElementsRunAction<ResourceEntry>( name, entries, [&](bool found, auto& iter) { return found ? iter->get() : nullptr; }); } -ResourceEntry* ResourceTableType::FindOrCreateEntry(const android::StringPiece& name) { +ResourceEntry* ResourceTableType::FindOrCreateEntry(android::StringPiece name) { return FindElementsRunAction<ResourceEntry>(name, entries, [&](bool found, auto& iter) { return found ? iter->get() : entries.emplace(iter, new ResourceEntry(name))->get(); }); @@ -183,7 +183,7 @@ const ResourceConfigValue* ResourceEntry::FindValue(const android::ConfigDescrip } ResourceConfigValue* ResourceEntry::FindOrCreateValue(const ConfigDescription& config, - const StringPiece& product) { + StringPiece product) { auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product}, lt_config_key_ref<std::unique_ptr<ResourceConfigValue>>); if (iter != values.end()) { diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h index f49ce8147f71..bb286a8abdaa 100644 --- a/tools/aapt2/ResourceTable.h +++ b/tools/aapt2/ResourceTable.h @@ -71,12 +71,11 @@ struct StagedId { struct Overlayable { Overlayable() = default; - Overlayable(const android::StringPiece& name, const android::StringPiece& actor) - : name(name.to_string()), actor(actor.to_string()) {} - Overlayable(const android::StringPiece& name, const android::StringPiece& actor, - const android::Source& source) - : name(name.to_string()), actor(actor.to_string()), source(source) { - } + Overlayable(android::StringPiece name, android::StringPiece actor) : name(name), actor(actor) { + } + Overlayable(android::StringPiece name, android::StringPiece actor, const android::Source& source) + : name(name), actor(actor), source(source) { + } static const char* kActorScheme; std::string name; @@ -105,8 +104,9 @@ class ResourceConfigValue { // The actual Value. std::unique_ptr<Value> value; - ResourceConfigValue(const android::ConfigDescription& config, const android::StringPiece& product) - : config(config), product(product.to_string()) {} + ResourceConfigValue(const android::ConfigDescription& config, android::StringPiece product) + : config(config), product(product) { + } private: DISALLOW_COPY_AND_ASSIGN(ResourceConfigValue); @@ -136,7 +136,8 @@ class ResourceEntry { // The resource's values for each configuration. std::vector<std::unique_ptr<ResourceConfigValue>> values; - explicit ResourceEntry(const android::StringPiece& name) : name(name.to_string()) {} + explicit ResourceEntry(android::StringPiece name) : name(name) { + } ResourceConfigValue* FindValue(const android::ConfigDescription& config, android::StringPiece product = {}); @@ -144,7 +145,7 @@ class ResourceEntry { android::StringPiece product = {}) const; ResourceConfigValue* FindOrCreateValue(const android::ConfigDescription& config, - const android::StringPiece& product); + android::StringPiece product); std::vector<ResourceConfigValue*> FindAllValues(const android::ConfigDescription& config); template <typename Func> @@ -180,9 +181,9 @@ class ResourceTableType { : named_type(type.ToResourceNamedType()) { } - ResourceEntry* CreateEntry(const android::StringPiece& name); - ResourceEntry* FindEntry(const android::StringPiece& name) const; - ResourceEntry* FindOrCreateEntry(const android::StringPiece& name); + ResourceEntry* CreateEntry(android::StringPiece name); + ResourceEntry* FindEntry(android::StringPiece name) const; + ResourceEntry* FindOrCreateEntry(android::StringPiece name); private: DISALLOW_COPY_AND_ASSIGN(ResourceTableType); @@ -194,7 +195,7 @@ class ResourceTablePackage { std::vector<std::unique_ptr<ResourceTableType>> types; - explicit ResourceTablePackage(const android::StringPiece& name) : name(name.to_string()) { + explicit ResourceTablePackage(android::StringPiece name) : name(name) { } ResourceTablePackage() = default; @@ -319,8 +320,8 @@ class ResourceTable { // Returns the package struct with the given name, or nullptr if such a package does not // exist. The empty string is a valid package and typically is used to represent the // 'current' package before it is known to the ResourceTable. - ResourceTablePackage* FindPackage(const android::StringPiece& name) const; - ResourceTablePackage* FindOrCreatePackage(const android::StringPiece& name); + ResourceTablePackage* FindPackage(android::StringPiece name) const; + ResourceTablePackage* FindOrCreatePackage(android::StringPiece name); std::unique_ptr<ResourceTable> Clone() const; diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp index 0cf84736a081..54b98d13aa0a 100644 --- a/tools/aapt2/ResourceTable_test.cpp +++ b/tools/aapt2/ResourceTable_test.cpp @@ -187,7 +187,7 @@ static StringPiece LevelToString(Visibility::Level level) { static ::testing::AssertionResult VisibilityOfResource(const ResourceTable& table, const ResourceNameRef& name, Visibility::Level level, - const StringPiece& comment) { + StringPiece comment) { std::optional<ResourceTable::SearchResult> result = table.FindResource(name); if (!result) { return ::testing::AssertionFailure() << "no resource '" << name << "' found in table"; diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp index 41c7435b534d..5a118a902963 100644 --- a/tools/aapt2/ResourceUtils.cpp +++ b/tools/aapt2/ResourceUtils.cpp @@ -109,8 +109,7 @@ std::optional<ResourceName> ToResourceName(const android::AssetManager2::Resourc return name_out; } -bool ParseResourceName(const StringPiece& str, ResourceNameRef* out_ref, - bool* out_private) { +bool ParseResourceName(StringPiece str, ResourceNameRef* out_ref, bool* out_private) { if (str.empty()) { return false; } @@ -151,8 +150,8 @@ bool ParseResourceName(const StringPiece& str, ResourceNameRef* out_ref, return true; } -bool ParseReference(const StringPiece& str, ResourceNameRef* out_ref, - bool* out_create, bool* out_private) { +bool ParseReference(StringPiece str, ResourceNameRef* out_ref, bool* out_create, + bool* out_private) { StringPiece trimmed_str(util::TrimWhitespace(str)); if (trimmed_str.empty()) { return false; @@ -198,11 +197,11 @@ bool ParseReference(const StringPiece& str, ResourceNameRef* out_ref, return false; } -bool IsReference(const StringPiece& str) { +bool IsReference(StringPiece str) { return ParseReference(str, nullptr, nullptr, nullptr); } -bool ParseAttributeReference(const StringPiece& str, ResourceNameRef* out_ref) { +bool ParseAttributeReference(StringPiece str, ResourceNameRef* out_ref) { StringPiece trimmed_str(util::TrimWhitespace(str)); if (trimmed_str.empty()) { return false; @@ -235,7 +234,7 @@ bool ParseAttributeReference(const StringPiece& str, ResourceNameRef* out_ref) { return false; } -bool IsAttributeReference(const StringPiece& str) { +bool IsAttributeReference(StringPiece str) { return ParseAttributeReference(str, nullptr); } @@ -247,7 +246,7 @@ bool IsAttributeReference(const StringPiece& str) { * <[*]package>:[style/]<entry> * [[*]package:style/]<entry> */ -std::optional<Reference> ParseStyleParentReference(const StringPiece& str, std::string* out_error) { +std::optional<Reference> ParseStyleParentReference(StringPiece str, std::string* out_error) { if (str.empty()) { return {}; } @@ -296,7 +295,7 @@ std::optional<Reference> ParseStyleParentReference(const StringPiece& str, std:: return result; } -std::optional<Reference> ParseXmlAttributeName(const StringPiece& str) { +std::optional<Reference> ParseXmlAttributeName(StringPiece str) { StringPiece trimmed_str = util::TrimWhitespace(str); const char* start = trimmed_str.data(); const char* const end = start + trimmed_str.size(); @@ -325,8 +324,7 @@ std::optional<Reference> ParseXmlAttributeName(const StringPiece& str) { return std::optional<Reference>(std::move(ref)); } -std::unique_ptr<Reference> TryParseReference(const StringPiece& str, - bool* out_create) { +std::unique_ptr<Reference> TryParseReference(StringPiece str, bool* out_create) { ResourceNameRef ref; bool private_ref = false; if (ParseReference(str, &ref, out_create, &private_ref)) { @@ -344,7 +342,7 @@ std::unique_ptr<Reference> TryParseReference(const StringPiece& str, return {}; } -std::unique_ptr<Item> TryParseNullOrEmpty(const StringPiece& str) { +std::unique_ptr<Item> TryParseNullOrEmpty(StringPiece str) { const StringPiece trimmed_str(util::TrimWhitespace(str)); if (trimmed_str == "@null") { return MakeNull(); @@ -365,8 +363,7 @@ std::unique_ptr<BinaryPrimitive> MakeEmpty() { android::Res_value::DATA_NULL_EMPTY); } -std::unique_ptr<BinaryPrimitive> TryParseEnumSymbol(const Attribute* enum_attr, - const StringPiece& str) { +std::unique_ptr<BinaryPrimitive> TryParseEnumSymbol(const Attribute* enum_attr, StringPiece str) { StringPiece trimmed_str(util::TrimWhitespace(str)); for (const Attribute::Symbol& symbol : enum_attr->symbols) { // Enum symbols are stored as @package:id/symbol resources, @@ -382,8 +379,7 @@ std::unique_ptr<BinaryPrimitive> TryParseEnumSymbol(const Attribute* enum_attr, return {}; } -std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* flag_attr, - const StringPiece& str) { +std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* flag_attr, StringPiece str) { android::Res_value flags = {}; flags.dataType = android::Res_value::TYPE_INT_HEX; flags.data = 0u; @@ -393,7 +389,7 @@ std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* flag_attr, return util::make_unique<BinaryPrimitive>(flags); } - for (const StringPiece& part : util::Tokenize(str, '|')) { + for (StringPiece part : util::Tokenize(str, '|')) { StringPiece trimmed_part = util::TrimWhitespace(part); bool flag_set = false; @@ -429,7 +425,7 @@ static uint32_t ParseHex(char c, bool* out_error) { } } -std::unique_ptr<BinaryPrimitive> TryParseColor(const StringPiece& str) { +std::unique_ptr<BinaryPrimitive> TryParseColor(StringPiece str) { StringPiece color_str(util::TrimWhitespace(str)); const char* start = color_str.data(); const size_t len = color_str.size(); @@ -484,7 +480,7 @@ std::unique_ptr<BinaryPrimitive> TryParseColor(const StringPiece& str) { : util::make_unique<BinaryPrimitive>(value); } -std::optional<bool> ParseBool(const StringPiece& str) { +std::optional<bool> ParseBool(StringPiece str) { StringPiece trimmed_str(util::TrimWhitespace(str)); if (trimmed_str == "true" || trimmed_str == "TRUE" || trimmed_str == "True") { return std::optional<bool>(true); @@ -495,7 +491,7 @@ std::optional<bool> ParseBool(const StringPiece& str) { return {}; } -std::optional<uint32_t> ParseInt(const StringPiece& str) { +std::optional<uint32_t> ParseInt(StringPiece str) { std::u16string str16 = android::util::Utf8ToUtf16(str); android::Res_value value; if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) { @@ -504,7 +500,7 @@ std::optional<uint32_t> ParseInt(const StringPiece& str) { return {}; } -std::optional<ResourceId> ParseResourceId(const StringPiece& str) { +std::optional<ResourceId> ParseResourceId(StringPiece str) { StringPiece trimmed_str(util::TrimWhitespace(str)); std::u16string str16 = android::util::Utf8ToUtf16(trimmed_str); @@ -520,7 +516,7 @@ std::optional<ResourceId> ParseResourceId(const StringPiece& str) { return {}; } -std::optional<int> ParseSdkVersion(const StringPiece& str) { +std::optional<int> ParseSdkVersion(StringPiece str) { StringPiece trimmed_str(util::TrimWhitespace(str)); std::u16string str16 = android::util::Utf8ToUtf16(trimmed_str); @@ -539,14 +535,14 @@ std::optional<int> ParseSdkVersion(const StringPiece& str) { const StringPiece::const_iterator begin = std::begin(trimmed_str); const StringPiece::const_iterator end = std::end(trimmed_str); const StringPiece::const_iterator codename_end = std::find(begin, end, '.'); - entry = GetDevelopmentSdkCodeNameVersion(trimmed_str.substr(begin, codename_end)); + entry = GetDevelopmentSdkCodeNameVersion(StringPiece(begin, codename_end - begin)); if (entry) { return entry.value(); } return {}; } -std::unique_ptr<BinaryPrimitive> TryParseBool(const StringPiece& str) { +std::unique_ptr<BinaryPrimitive> TryParseBool(StringPiece str) { if (std::optional<bool> maybe_result = ParseBool(str)) { const uint32_t data = maybe_result.value() ? 0xffffffffu : 0u; return util::make_unique<BinaryPrimitive>(android::Res_value::TYPE_INT_BOOLEAN, data); @@ -559,7 +555,7 @@ std::unique_ptr<BinaryPrimitive> MakeBool(bool val) { val ? 0xffffffffu : 0u); } -std::unique_ptr<BinaryPrimitive> TryParseInt(const StringPiece& str) { +std::unique_ptr<BinaryPrimitive> TryParseInt(StringPiece str) { std::u16string str16 = android::util::Utf8ToUtf16(util::TrimWhitespace(str)); android::Res_value value; if (!android::ResTable::stringToInt(str16.data(), str16.size(), &value)) { @@ -572,7 +568,7 @@ std::unique_ptr<BinaryPrimitive> MakeInt(uint32_t val) { return util::make_unique<BinaryPrimitive>(android::Res_value::TYPE_INT_DEC, val); } -std::unique_ptr<BinaryPrimitive> TryParseFloat(const StringPiece& str) { +std::unique_ptr<BinaryPrimitive> TryParseFloat(StringPiece str) { std::u16string str16 = android::util::Utf8ToUtf16(util::TrimWhitespace(str)); android::Res_value value; if (!android::ResTable::stringToFloat(str16.data(), str16.size(), &value)) { @@ -623,7 +619,7 @@ uint32_t AndroidTypeToAttributeTypeMask(uint16_t type) { } std::unique_ptr<Item> TryParseItemForAttribute( - const StringPiece& value, uint32_t type_mask, + StringPiece value, uint32_t type_mask, const std::function<bool(const ResourceName&)>& on_create_reference) { using android::ResTable_map; @@ -687,7 +683,7 @@ std::unique_ptr<Item> TryParseItemForAttribute( * allows. */ std::unique_ptr<Item> TryParseItemForAttribute( - const StringPiece& str, const Attribute* attr, + StringPiece str, const Attribute* attr, const std::function<bool(const ResourceName&)>& on_create_reference) { using android::ResTable_map; diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h index 22cf3459809d..f30f4acfec7a 100644 --- a/tools/aapt2/ResourceUtils.h +++ b/tools/aapt2/ResourceUtils.h @@ -38,7 +38,7 @@ namespace ResourceUtils { * `out_resource` set to the parsed resource name and `out_private` set to true * if a '*' prefix was present. */ -bool ParseResourceName(const android::StringPiece& str, ResourceNameRef* out_resource, +bool ParseResourceName(android::StringPiece str, ResourceNameRef* out_resource, bool* out_private = nullptr); /* @@ -49,27 +49,27 @@ bool ParseResourceName(const android::StringPiece& str, ResourceNameRef* out_res * If '+' was present in the reference, `out_create` is set to true. * If '*' was present in the reference, `out_private` is set to true. */ -bool ParseReference(const android::StringPiece& str, ResourceNameRef* out_reference, +bool ParseReference(android::StringPiece str, ResourceNameRef* out_reference, bool* out_create = nullptr, bool* out_private = nullptr); /* * Returns true if the string is in the form of a resource reference * (@[+][package:]type/name). */ -bool IsReference(const android::StringPiece& str); +bool IsReference(android::StringPiece str); /* * Returns true if the string was parsed as an attribute reference * (?[package:][type/]name), * with `out_reference` set to the parsed reference. */ -bool ParseAttributeReference(const android::StringPiece& str, ResourceNameRef* out_reference); +bool ParseAttributeReference(android::StringPiece str, ResourceNameRef* out_reference); /** * Returns true if the string is in the form of an attribute * reference(?[package:][type/]name). */ -bool IsAttributeReference(const android::StringPiece& str); +bool IsAttributeReference(android::StringPiece str); /** * Convert an android::ResTable::resource_name to an aapt::ResourceName struct. @@ -85,22 +85,22 @@ std::optional<ResourceName> ToResourceName(const android::AssetManager2::Resourc * Returns a boolean value if the string is equal to TRUE, true, True, FALSE, * false, or False. */ -std::optional<bool> ParseBool(const android::StringPiece& str); +std::optional<bool> ParseBool(android::StringPiece str); /** * Returns a uint32_t if the string is an integer. */ -std::optional<uint32_t> ParseInt(const android::StringPiece& str); +std::optional<uint32_t> ParseInt(android::StringPiece str); /** * Returns an ID if it the string represented a valid ID. */ -std::optional<ResourceId> ParseResourceId(const android::StringPiece& str); +std::optional<ResourceId> ParseResourceId(android::StringPiece str); /** * Parses an SDK version, which can be an integer, or a letter from A-Z. */ -std::optional<int> ParseSdkVersion(const android::StringPiece& str); +std::optional<int> ParseSdkVersion(android::StringPiece str); /* * Returns a Reference, or None Maybe instance if the string `str` was parsed as @@ -113,7 +113,7 @@ std::optional<int> ParseSdkVersion(const android::StringPiece& str); * ?[package:]style/<entry> or * <package>:[style/]<entry> */ -std::optional<Reference> ParseStyleParentReference(const android::StringPiece& str, +std::optional<Reference> ParseStyleParentReference(android::StringPiece str, std::string* out_error); /* @@ -123,7 +123,7 @@ std::optional<Reference> ParseStyleParentReference(const android::StringPiece& s * * package:entry */ -std::optional<Reference> ParseXmlAttributeName(const android::StringPiece& str); +std::optional<Reference> ParseXmlAttributeName(android::StringPiece str); /* * Returns a Reference object if the string was parsed as a resource or @@ -132,14 +132,13 @@ std::optional<Reference> ParseXmlAttributeName(const android::StringPiece& str); * if * the '+' was present in the string. */ -std::unique_ptr<Reference> TryParseReference(const android::StringPiece& str, - bool* out_create = nullptr); +std::unique_ptr<Reference> TryParseReference(android::StringPiece str, bool* out_create = nullptr); /* * Returns a BinaryPrimitve object representing @null or @empty if the string * was parsed as one. */ -std::unique_ptr<Item> TryParseNullOrEmpty(const android::StringPiece& str); +std::unique_ptr<Item> TryParseNullOrEmpty(android::StringPiece str); // Returns a Reference representing @null. // Due to runtime compatibility issues, this is encoded as a reference with ID 0. @@ -154,13 +153,13 @@ std::unique_ptr<BinaryPrimitive> MakeEmpty(); * Returns a BinaryPrimitve object representing a color if the string was parsed * as one. */ -std::unique_ptr<BinaryPrimitive> TryParseColor(const android::StringPiece& str); +std::unique_ptr<BinaryPrimitive> TryParseColor(android::StringPiece str); /* * Returns a BinaryPrimitve object representing a boolean if the string was * parsed as one. */ -std::unique_ptr<BinaryPrimitive> TryParseBool(const android::StringPiece& str); +std::unique_ptr<BinaryPrimitive> TryParseBool(android::StringPiece str); // Returns a boolean BinaryPrimitive. std::unique_ptr<BinaryPrimitive> MakeBool(bool val); @@ -169,7 +168,7 @@ std::unique_ptr<BinaryPrimitive> MakeBool(bool val); * Returns a BinaryPrimitve object representing an integer if the string was * parsed as one. */ -std::unique_ptr<BinaryPrimitive> TryParseInt(const android::StringPiece& str); +std::unique_ptr<BinaryPrimitive> TryParseInt(android::StringPiece str); // Returns an integer BinaryPrimitive. std::unique_ptr<BinaryPrimitive> MakeInt(uint32_t value); @@ -178,21 +177,21 @@ std::unique_ptr<BinaryPrimitive> MakeInt(uint32_t value); * Returns a BinaryPrimitve object representing a floating point number * (float, dimension, etc) if the string was parsed as one. */ -std::unique_ptr<BinaryPrimitive> TryParseFloat(const android::StringPiece& str); +std::unique_ptr<BinaryPrimitive> TryParseFloat(android::StringPiece str); /* * Returns a BinaryPrimitve object representing an enum symbol if the string was * parsed as one. */ std::unique_ptr<BinaryPrimitive> TryParseEnumSymbol(const Attribute* enum_attr, - const android::StringPiece& str); + android::StringPiece str); /* * Returns a BinaryPrimitve object representing a flag symbol if the string was * parsed as one. */ std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* enum_attr, - const android::StringPiece& str); + android::StringPiece str); /* * Try to convert a string to an Item for the given attribute. The attribute * will @@ -201,11 +200,11 @@ std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* enum_attr, * reference to an ID that must be created (@+id/foo). */ std::unique_ptr<Item> TryParseItemForAttribute( - const android::StringPiece& value, const Attribute* attr, + android::StringPiece value, const Attribute* attr, const std::function<bool(const ResourceName&)>& on_create_reference = {}); std::unique_ptr<Item> TryParseItemForAttribute( - const android::StringPiece& value, uint32_t type_mask, + android::StringPiece value, uint32_t type_mask, const std::function<bool(const ResourceName&)>& on_create_reference = {}); uint32_t AndroidTypeToAttributeTypeMask(uint16_t type); diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp index c4d54be01efe..a5754e0d168f 100644 --- a/tools/aapt2/ResourceValues.cpp +++ b/tools/aapt2/ResourceValues.cpp @@ -206,7 +206,7 @@ void Reference::PrettyPrint(Printer* printer) const { PrettyPrintReferenceImpl(*this, true /*print_package*/, printer); } -void Reference::PrettyPrint(const StringPiece& package, Printer* printer) const { +void Reference::PrettyPrint(StringPiece package, Printer* printer) const { const bool print_package = name ? package != name.value().package : true; PrettyPrintReferenceImpl(*this, print_package, printer); } diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h index f5167a1ac8e6..6f9dccbd3bcc 100644 --- a/tools/aapt2/ResourceValues.h +++ b/tools/aapt2/ResourceValues.h @@ -83,8 +83,8 @@ class Value { return comment_; } - void SetComment(const android::StringPiece& str) { - comment_ = str.to_string(); + void SetComment(android::StringPiece str) { + comment_.assign(str); } void SetComment(std::string&& str) { @@ -176,7 +176,7 @@ struct Reference : public TransformableItem<Reference, BaseItem<Reference>> { void PrettyPrint(text::Printer* printer) const override; // Prints the reference without a package name if the package name matches the one given. - void PrettyPrint(const android::StringPiece& package, text::Printer* printer) const; + void PrettyPrint(android::StringPiece package, text::Printer* printer) const; }; bool operator<(const Reference&, const Reference&); diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp index 34e8edb0a47f..a7c5479b56fd 100644 --- a/tools/aapt2/SdkConstants.cpp +++ b/tools/aapt2/SdkConstants.cpp @@ -77,7 +77,7 @@ ApiVersion FindAttributeSdkLevel(const ResourceId& id) { return iter->second; } -std::optional<ApiVersion> GetDevelopmentSdkCodeNameVersion(const StringPiece& code_name) { +std::optional<ApiVersion> GetDevelopmentSdkCodeNameVersion(StringPiece code_name) { return (sDevelopmentSdkCodeNames.find(code_name) == sDevelopmentSdkCodeNames.end()) ? std::optional<ApiVersion>() : sDevelopmentSdkLevel; diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h index 0bd61c04f2b2..40bcef787815 100644 --- a/tools/aapt2/SdkConstants.h +++ b/tools/aapt2/SdkConstants.h @@ -63,7 +63,7 @@ enum : ApiVersion { }; ApiVersion FindAttributeSdkLevel(const ResourceId& id); -std::optional<ApiVersion> GetDevelopmentSdkCodeNameVersion(const android::StringPiece& code_name); +std::optional<ApiVersion> GetDevelopmentSdkCodeNameVersion(android::StringPiece code_name); } // namespace aapt diff --git a/tools/aapt2/cmd/ApkInfo.cpp b/tools/aapt2/cmd/ApkInfo.cpp index 697b110443fd..3c0831c7ec0d 100644 --- a/tools/aapt2/cmd/ApkInfo.cpp +++ b/tools/aapt2/cmd/ApkInfo.cpp @@ -64,7 +64,7 @@ int ApkInfoCommand::Action(const std::vector<std::string>& args) { Usage(&std::cerr); return 1; } - const StringPiece& path = args[0]; + StringPiece path = args[0]; std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(path, diag_); if (!apk) { return 1; diff --git a/tools/aapt2/cmd/Command.cpp b/tools/aapt2/cmd/Command.cpp index b1452fad0e8f..514651e92c27 100644 --- a/tools/aapt2/cmd/Command.cpp +++ b/tools/aapt2/cmd/Command.cpp @@ -33,7 +33,7 @@ using android::StringPiece; namespace aapt { -std::string GetSafePath(const StringPiece& arg) { +std::string GetSafePath(StringPiece arg) { #ifdef _WIN32 // If the path exceeds the maximum path length for Windows, encode the path using the // extended-length prefix @@ -47,63 +47,62 @@ std::string GetSafePath(const StringPiece& arg) { return path8; #else - return arg.to_string(); + return std::string(arg); #endif } -void Command::AddRequiredFlag(const StringPiece& name, const StringPiece& description, - std::string* value, uint32_t flags) { - auto func = [value, flags](const StringPiece& arg) -> bool { - *value = (flags & Command::kPath) ? GetSafePath(arg) : arg.to_string(); +void Command::AddRequiredFlag(StringPiece name, StringPiece description, std::string* value, + uint32_t flags) { + auto func = [value, flags](StringPiece arg) -> bool { + *value = (flags & Command::kPath) ? GetSafePath(arg) : std::string(arg); return true; }; flags_.emplace_back(Flag(name, description, /* required */ true, /* num_args */ 1, func)); } -void Command::AddRequiredFlagList(const StringPiece& name, const StringPiece& description, +void Command::AddRequiredFlagList(StringPiece name, StringPiece description, std::vector<std::string>* value, uint32_t flags) { - auto func = [value, flags](const StringPiece& arg) -> bool { - value->push_back((flags & Command::kPath) ? GetSafePath(arg) : arg.to_string()); + auto func = [value, flags](StringPiece arg) -> bool { + value->push_back((flags & Command::kPath) ? GetSafePath(arg) : std::string(arg)); return true; }; flags_.emplace_back(Flag(name, description, /* required */ true, /* num_args */ 1, func)); } -void Command::AddOptionalFlag(const StringPiece& name, const StringPiece& description, +void Command::AddOptionalFlag(StringPiece name, StringPiece description, std::optional<std::string>* value, uint32_t flags) { - auto func = [value, flags](const StringPiece& arg) -> bool { - *value = (flags & Command::kPath) ? GetSafePath(arg) : arg.to_string(); + auto func = [value, flags](StringPiece arg) -> bool { + *value = (flags & Command::kPath) ? GetSafePath(arg) : std::string(arg); return true; }; flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 1, func)); } -void Command::AddOptionalFlagList(const StringPiece& name, const StringPiece& description, +void Command::AddOptionalFlagList(StringPiece name, StringPiece description, std::vector<std::string>* value, uint32_t flags) { - auto func = [value, flags](const StringPiece& arg) -> bool { - value->push_back((flags & Command::kPath) ? GetSafePath(arg) : arg.to_string()); + auto func = [value, flags](StringPiece arg) -> bool { + value->push_back((flags & Command::kPath) ? GetSafePath(arg) : std::string(arg)); return true; }; flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 1, func)); } -void Command::AddOptionalFlagList(const StringPiece& name, const StringPiece& description, +void Command::AddOptionalFlagList(StringPiece name, StringPiece description, std::unordered_set<std::string>* value) { - auto func = [value](const StringPiece& arg) -> bool { - value->insert(arg.to_string()); + auto func = [value](StringPiece arg) -> bool { + value->emplace(arg); return true; }; flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 1, func)); } -void Command::AddOptionalSwitch(const StringPiece& name, const StringPiece& description, - bool* value) { - auto func = [value](const StringPiece& arg) -> bool { +void Command::AddOptionalSwitch(StringPiece name, StringPiece description, bool* value) { + auto func = [value](StringPiece arg) -> bool { *value = true; return true; }; @@ -120,8 +119,8 @@ void Command::AddOptionalSubcommand(std::unique_ptr<Command>&& subcommand, bool } } -void Command::SetDescription(const StringPiece& description) { - description_ = description.to_string(); +void Command::SetDescription(StringPiece description) { + description_ = std::string(description); } void Command::Usage(std::ostream* out) { @@ -183,7 +182,7 @@ int Command::Execute(const std::vector<StringPiece>& args, std::ostream* out_err std::vector<std::string> file_args; for (size_t i = 0; i < args.size(); i++) { - const StringPiece& arg = args[i]; + StringPiece arg = args[i]; if (*(arg.data()) != '-') { // Continue parsing as the subcommand if the first argument matches one of the subcommands if (i == 0) { diff --git a/tools/aapt2/cmd/Command.h b/tools/aapt2/cmd/Command.h index 8678cda59856..1416e980ed19 100644 --- a/tools/aapt2/cmd/Command.h +++ b/tools/aapt2/cmd/Command.h @@ -30,13 +30,10 @@ namespace aapt { class Command { public: - explicit Command(const android::StringPiece& name) - : name_(name.to_string()), full_subcommand_name_(name.to_string()){}; + explicit Command(android::StringPiece name) : name_(name), full_subcommand_name_(name){}; - explicit Command(const android::StringPiece& name, const android::StringPiece& short_name) - : name_(name.to_string()), - short_name_(short_name.to_string()), - full_subcommand_name_(name.to_string()){}; + explicit Command(android::StringPiece name, android::StringPiece short_name) + : name_(name), short_name_(short_name), full_subcommand_name_(name){}; Command(Command&&) = default; Command& operator=(Command&&) = default; @@ -52,30 +49,26 @@ class Command { kPath = 1 << 0, }; - void AddRequiredFlag(const android::StringPiece& name, const android::StringPiece& description, + void AddRequiredFlag(android::StringPiece name, android::StringPiece description, std::string* value, uint32_t flags = 0); - void AddRequiredFlagList(const android::StringPiece& name, - const android::StringPiece& description, std::vector<std::string>* value, - uint32_t flags = 0); + void AddRequiredFlagList(android::StringPiece name, android::StringPiece description, + std::vector<std::string>* value, uint32_t flags = 0); - void AddOptionalFlag(const android::StringPiece& name, const android::StringPiece& description, + void AddOptionalFlag(android::StringPiece name, android::StringPiece description, std::optional<std::string>* value, uint32_t flags = 0); - void AddOptionalFlagList(const android::StringPiece& name, - const android::StringPiece& description, std::vector<std::string>* value, - uint32_t flags = 0); + void AddOptionalFlagList(android::StringPiece name, android::StringPiece description, + std::vector<std::string>* value, uint32_t flags = 0); - void AddOptionalFlagList(const android::StringPiece& name, - const android::StringPiece& description, + void AddOptionalFlagList(android::StringPiece name, android::StringPiece description, std::unordered_set<std::string>* value); - void AddOptionalSwitch(const android::StringPiece& name, const android::StringPiece& description, - bool* value); + void AddOptionalSwitch(android::StringPiece name, android::StringPiece description, bool* value); void AddOptionalSubcommand(std::unique_ptr<Command>&& subcommand, bool experimental = false); - void SetDescription(const android::StringPiece& name); + void SetDescription(android::StringPiece name); // Prints the help menu of the command. void Usage(std::ostream* out); @@ -90,17 +83,21 @@ class Command { private: struct Flag { - explicit Flag(const android::StringPiece& name, const android::StringPiece& description, + explicit Flag(android::StringPiece name, android::StringPiece description, const bool is_required, const size_t num_args, - std::function<bool(const android::StringPiece& value)>&& action) - : name(name.to_string()), description(description.to_string()), is_required(is_required), - num_args(num_args), action(std::move(action)) {} + std::function<bool(android::StringPiece value)>&& action) + : name(name), + description(description), + is_required(is_required), + num_args(num_args), + action(std::move(action)) { + } const std::string name; const std::string description; const bool is_required; const size_t num_args; - const std::function<bool(const android::StringPiece& value)> action; + const std::function<bool(android::StringPiece value)> action; bool found = false; }; diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp index 0409f7391f79..03f9715fb265 100644 --- a/tools/aapt2/cmd/Compile.cpp +++ b/tools/aapt2/cmd/Compile.cpp @@ -125,8 +125,12 @@ static std::optional<ResourcePathData> ExtractResourcePathData(const std::string const android::Source res_path = options.source_path ? StringPiece(options.source_path.value()) : StringPiece(path); - return ResourcePathData{res_path, dir_str.to_string(), name.to_string(), - extension.to_string(), config_str.to_string(), config}; + return ResourcePathData{res_path, + std::string(dir_str), + std::string(name), + std::string(extension), + std::string(config_str), + config}; } static std::string BuildIntermediateContainerFilename(const ResourcePathData& data) { @@ -279,7 +283,7 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, return true; } -static bool WriteHeaderAndDataToWriter(const StringPiece& output_path, const ResourceFile& file, +static bool WriteHeaderAndDataToWriter(StringPiece output_path, const ResourceFile& file, io::KnownSizeInputStream* in, IArchiveWriter* writer, android::IDiagnostics* diag) { TRACE_CALL(); @@ -311,7 +315,7 @@ static bool WriteHeaderAndDataToWriter(const StringPiece& output_path, const Res return true; } -static bool FlattenXmlToOutStream(const StringPiece& output_path, const xml::XmlResource& xmlres, +static bool FlattenXmlToOutStream(StringPiece output_path, const xml::XmlResource& xmlres, ContainerWriter* container_writer, android::IDiagnostics* diag) { pb::internal::CompiledFile pb_compiled_file; SerializeCompiledFileToPb(xmlres.file, &pb_compiled_file); @@ -538,7 +542,7 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options, if (context->IsVerbose()) { // For debugging only, use the legacy PNG cruncher and compare the resulting file sizes. // This will help catch exotic cases where the new code may generate larger PNGs. - std::stringstream legacy_stream(content.to_string()); + std::stringstream legacy_stream{std::string(content)}; android::BigBuffer legacy_buffer(4096); Png png(context->GetDiagnostics()); if (!png.process(path_data.source, &legacy_stream, &legacy_buffer, {})) { diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp index 52e113e0dbdc..612e3a630013 100644 --- a/tools/aapt2/cmd/Convert.cpp +++ b/tools/aapt2/cmd/Convert.cpp @@ -387,7 +387,7 @@ int ConvertCommand::Action(const std::vector<std::string>& args) { } Context context; - const StringPiece& path = args[0]; + StringPiece path = args[0]; unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(path, context.GetDiagnostics()); if (apk == nullptr) { context.GetDiagnostics()->Error(android::DiagMessage(path) << "failed to load APK"); diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp index 423e939398d7..5bfc73233bfe 100644 --- a/tools/aapt2/cmd/Diff.cpp +++ b/tools/aapt2/cmd/Diff.cpp @@ -78,7 +78,7 @@ class DiffContext : public IAaptContext { SymbolTable symbol_table_; }; -static void EmitDiffLine(const android::Source& source, const StringPiece& message) { +static void EmitDiffLine(const android::Source& source, StringPiece message) { std::cerr << source << ": " << message << "\n"; } diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index a8d229956b73..97404fc69af2 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -126,8 +126,8 @@ class LinkContext : public IAaptContext { return compilation_package_; } - void SetCompilationPackage(const StringPiece& package_name) { - compilation_package_ = package_name.to_string(); + void SetCompilationPackage(StringPiece package_name) { + compilation_package_ = std::string(package_name); } uint8_t GetPackageId() override { @@ -240,9 +240,9 @@ class FeatureSplitSymbolTableDelegate : public DefaultSymbolTableDelegate { IAaptContext* context_; }; -static bool FlattenXml(IAaptContext* context, const xml::XmlResource& xml_res, - const StringPiece& path, bool keep_raw_values, bool utf16, - OutputFormat format, IArchiveWriter* writer) { +static bool FlattenXml(IAaptContext* context, const xml::XmlResource& xml_res, StringPiece path, + bool keep_raw_values, bool utf16, OutputFormat format, + IArchiveWriter* writer) { TRACE_CALL(); if (context->IsVerbose()) { context->GetDiagnostics()->Note(android::DiagMessage(path) @@ -262,8 +262,8 @@ static bool FlattenXml(IAaptContext* context, const xml::XmlResource& xml_res, } io::BigBufferInputStream input_stream(&buffer); - return io::CopyInputStreamToArchive(context, &input_stream, path.to_string(), - ArchiveEntry::kCompress, writer); + return io::CopyInputStreamToArchive(context, &input_stream, path, ArchiveEntry::kCompress, + writer); } break; case OutputFormat::kProto: { @@ -272,8 +272,7 @@ static bool FlattenXml(IAaptContext* context, const xml::XmlResource& xml_res, SerializeXmlOptions options; options.remove_empty_text_nodes = (path == kAndroidManifestPath); SerializeXmlResourceToPb(xml_res, &pb_node); - return io::CopyProtoToArchive(context, &pb_node, path.to_string(), ArchiveEntry::kCompress, - writer); + return io::CopyProtoToArchive(context, &pb_node, path, ArchiveEntry::kCompress, writer); } break; } return false; @@ -329,13 +328,13 @@ struct R { }; template <typename T> -uint32_t GetCompressionFlags(const StringPiece& str, T options) { +uint32_t GetCompressionFlags(StringPiece str, T options) { if (options.do_not_compress_anything) { return 0; } - if (options.regex_to_not_compress - && std::regex_search(str.to_string(), options.regex_to_not_compress.value())) { + if (options.regex_to_not_compress && + std::regex_search(str.begin(), str.end(), options.regex_to_not_compress.value())) { return 0; } @@ -1176,7 +1175,7 @@ class Linker { return bcp47tag; } - std::unique_ptr<IArchiveWriter> MakeArchiveWriter(const StringPiece& out) { + std::unique_ptr<IArchiveWriter> MakeArchiveWriter(StringPiece out) { if (options_.output_to_directory) { return CreateDirectoryArchiveWriter(context_->GetDiagnostics(), out); } else { @@ -1212,8 +1211,8 @@ class Linker { return false; } - bool WriteJavaFile(ResourceTable* table, const StringPiece& package_name_to_generate, - const StringPiece& out_package, const JavaClassGeneratorOptions& java_options, + bool WriteJavaFile(ResourceTable* table, StringPiece package_name_to_generate, + StringPiece out_package, const JavaClassGeneratorOptions& java_options, const std::optional<std::string>& out_text_symbols_path = {}) { if (!options_.generate_java_class_path && !out_text_symbols_path) { return true; @@ -2473,14 +2472,14 @@ int LinkCommand::Action(const std::vector<std::string>& args) { for (std::string& extra_package : extra_java_packages_) { // A given package can actually be a colon separated list of packages. for (StringPiece package : util::Split(extra_package, ':')) { - options_.extra_java_packages.insert(package.to_string()); + options_.extra_java_packages.emplace(package); } } if (product_list_) { for (StringPiece product : util::Tokenize(product_list_.value(), ',')) { if (product != "" && product != "default") { - options_.products.insert(product.to_string()); + options_.products.emplace(product); } } } diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp index 042926c1943a..9c1a2f6854ff 100644 --- a/tools/aapt2/cmd/Optimize.cpp +++ b/tools/aapt2/cmd/Optimize.cpp @@ -370,8 +370,8 @@ int OptimizeCommand::Action(const std::vector<std::string>& args) { if (!kept_artifacts_.empty()) { for (const std::string& artifact_str : kept_artifacts_) { - for (const StringPiece& artifact : util::Tokenize(artifact_str, ',')) { - options_.kept_artifacts.insert(artifact.to_string()); + for (StringPiece artifact : util::Tokenize(artifact_str, ',')) { + options_.kept_artifacts.emplace(artifact); } } } @@ -403,7 +403,7 @@ int OptimizeCommand::Action(const std::vector<std::string>& args) { if (target_densities_) { // Parse the target screen densities. - for (const StringPiece& config_str : util::Tokenize(target_densities_.value(), ',')) { + for (StringPiece config_str : util::Tokenize(target_densities_.value(), ',')) { std::optional<uint16_t> target_density = ParseTargetDensityParameter(config_str, diag); if (!target_density) { return 1; diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp index 56e2f5243e96..92849cf02d48 100644 --- a/tools/aapt2/cmd/Util.cpp +++ b/tools/aapt2/cmd/Util.cpp @@ -34,8 +34,7 @@ using ::android::base::StringPrintf; namespace aapt { -std::optional<uint16_t> ParseTargetDensityParameter(const StringPiece& arg, - android::IDiagnostics* diag) { +std::optional<uint16_t> ParseTargetDensityParameter(StringPiece arg, android::IDiagnostics* diag) { ConfigDescription preferred_density_config; if (!ConfigDescription::Parse(arg, &preferred_density_config)) { diag->Error(android::DiagMessage() @@ -55,7 +54,7 @@ std::optional<uint16_t> ParseTargetDensityParameter(const StringPiece& arg, return preferred_density_config.density; } -bool ParseSplitParameter(const StringPiece& arg, android::IDiagnostics* diag, std::string* out_path, +bool ParseSplitParameter(StringPiece arg, android::IDiagnostics* diag, std::string* out_path, SplitConstraints* out_split) { CHECK(diag != nullptr); CHECK(out_path != nullptr); @@ -77,7 +76,7 @@ bool ParseSplitParameter(const StringPiece& arg, android::IDiagnostics* diag, st *out_path = parts[0]; out_split->name = parts[1]; - for (const StringPiece& config_str : util::Tokenize(parts[1], ',')) { + for (StringPiece config_str : util::Tokenize(parts[1], ',')) { ConfigDescription config; if (!ConfigDescription::Parse(config_str, &config)) { diag->Error(android::DiagMessage() @@ -93,7 +92,7 @@ std::unique_ptr<IConfigFilter> ParseConfigFilterParameters(const std::vector<std android::IDiagnostics* diag) { std::unique_ptr<AxisConfigFilter> filter = util::make_unique<AxisConfigFilter>(); for (const std::string& config_arg : args) { - for (const StringPiece& config_str : util::Tokenize(config_arg, ',')) { + for (StringPiece config_str : util::Tokenize(config_arg, ',')) { ConfigDescription config; LocaleValue lv; if (lv.InitFromFilterString(config_str)) { diff --git a/tools/aapt2/cmd/Util.h b/tools/aapt2/cmd/Util.h index 3d4ca245ee28..169d5f92f7dd 100644 --- a/tools/aapt2/cmd/Util.h +++ b/tools/aapt2/cmd/Util.h @@ -34,13 +34,13 @@ namespace aapt { // Parses a configuration density (ex. hdpi, xxhdpi, 234dpi, anydpi, etc). // Returns Nothing and logs a human friendly error message if the string was not legal. -std::optional<uint16_t> ParseTargetDensityParameter(const android::StringPiece& arg, +std::optional<uint16_t> ParseTargetDensityParameter(android::StringPiece arg, android::IDiagnostics* diag); // Parses a string of the form 'path/to/output.apk:<config>[,<config>...]' and fills in // `out_path` with the path and `out_split` with the set of ConfigDescriptions. // Returns false and logs a human friendly error message if the string was not legal. -bool ParseSplitParameter(const android::StringPiece& arg, android::IDiagnostics* diag, +bool ParseSplitParameter(android::StringPiece arg, android::IDiagnostics* diag, std::string* out_path, SplitConstraints* out_split); // Parses a set of config filter strings of the form 'en,fr-rFR' and returns an IConfigFilter. diff --git a/tools/aapt2/compile/NinePatch.cpp b/tools/aapt2/compile/NinePatch.cpp index c931da48c889..4538ecc56e4c 100644 --- a/tools/aapt2/compile/NinePatch.cpp +++ b/tools/aapt2/compile/NinePatch.cpp @@ -218,11 +218,9 @@ inline static uint32_t get_alpha(uint32_t color) { static bool PopulateBounds(const std::vector<Range>& padding, const std::vector<Range>& layout_bounds, - const std::vector<Range>& stretch_regions, - const int32_t length, int32_t* padding_start, - int32_t* padding_end, int32_t* layout_start, - int32_t* layout_end, const StringPiece& edge_name, - std::string* out_err) { + const std::vector<Range>& stretch_regions, const int32_t length, + int32_t* padding_start, int32_t* padding_end, int32_t* layout_start, + int32_t* layout_end, StringPiece edge_name, std::string* out_err) { if (padding.size() > 1) { std::stringstream err_stream; err_stream << "too many padding sections on " << edge_name << " border"; diff --git a/tools/aapt2/compile/Png.h b/tools/aapt2/compile/Png.h index 7f8d923edd03..a8b7dd18f12f 100644 --- a/tools/aapt2/compile/Png.h +++ b/tools/aapt2/compile/Png.h @@ -59,7 +59,7 @@ class Png { */ class PngChunkFilter : public io::InputStream { public: - explicit PngChunkFilter(const android::StringPiece& data); + explicit PngChunkFilter(android::StringPiece data); virtual ~PngChunkFilter() = default; bool Next(const void** buffer, size_t* len) override; diff --git a/tools/aapt2/compile/PngChunkFilter.cpp b/tools/aapt2/compile/PngChunkFilter.cpp index 4db2392b4eab..2e55d0c82b7b 100644 --- a/tools/aapt2/compile/PngChunkFilter.cpp +++ b/tools/aapt2/compile/PngChunkFilter.cpp @@ -70,7 +70,7 @@ static bool IsPngChunkAllowed(uint32_t type) { } } -PngChunkFilter::PngChunkFilter(const StringPiece& data) : data_(data) { +PngChunkFilter::PngChunkFilter(StringPiece data) : data_(data) { if (util::StartsWith(data_, kPngSignature)) { window_start_ = 0; window_end_ = kPngSignatureSize; diff --git a/tools/aapt2/compile/Pseudolocalizer.cpp b/tools/aapt2/compile/Pseudolocalizer.cpp index 3a515fad3202..463ce787dae7 100644 --- a/tools/aapt2/compile/Pseudolocalizer.cpp +++ b/tools/aapt2/compile/Pseudolocalizer.cpp @@ -20,36 +20,42 @@ using android::StringPiece; +using namespace std::literals; + namespace aapt { // String basis to generate expansion -static const std::string kExpansionString = +static constexpr auto kExpansionString = "one two three " "four five six seven eight nine ten eleven twelve thirteen " - "fourteen fiveteen sixteen seventeen nineteen twenty"; + "fourteen fiveteen sixteen seventeen nineteen twenty"sv; // Special unicode characters to override directionality of the words -static const std::string kRlm = "\u200f"; -static const std::string kRlo = "\u202e"; -static const std::string kPdf = "\u202c"; +static constexpr auto kRlm = "\u200f"sv; +static constexpr auto kRlo = "\u202e"sv; +static constexpr auto kPdf = "\u202c"sv; // Placeholder marks -static const std::string kPlaceholderOpen = "\u00bb"; -static const std::string kPlaceholderClose = "\u00ab"; +static constexpr auto kPlaceholderOpen = "\u00bb"sv; +static constexpr auto kPlaceholderClose = "\u00ab"sv; static const char kArgStart = '{'; static const char kArgEnd = '}'; class PseudoMethodNone : public PseudoMethodImpl { public: - std::string Text(const StringPiece& text) override { return text.to_string(); } - std::string Placeholder(const StringPiece& text) override { return text.to_string(); } + std::string Text(StringPiece text) override { + return std::string(text); + } + std::string Placeholder(StringPiece text) override { + return std::string(text); + } }; class PseudoMethodBidi : public PseudoMethodImpl { public: - std::string Text(const StringPiece& text) override; - std::string Placeholder(const StringPiece& text) override; + std::string Text(StringPiece text) override; + std::string Placeholder(StringPiece text) override; }; class PseudoMethodAccent : public PseudoMethodImpl { @@ -57,8 +63,8 @@ class PseudoMethodAccent : public PseudoMethodImpl { PseudoMethodAccent() : depth_(0), word_count_(0), length_(0) {} std::string Start() override; std::string End() override; - std::string Text(const StringPiece& text) override; - std::string Placeholder(const StringPiece& text) override; + std::string Text(StringPiece text) override; + std::string Placeholder(StringPiece text) override; private: size_t depth_; @@ -84,7 +90,7 @@ void Pseudolocalizer::SetMethod(Method method) { } } -std::string Pseudolocalizer::Text(const StringPiece& text) { +std::string Pseudolocalizer::Text(StringPiece text) { std::string out; size_t depth = last_depth_; size_t lastpos, pos; @@ -116,7 +122,7 @@ std::string Pseudolocalizer::Text(const StringPiece& text) { } size_t size = nextpos - lastpos; if (size) { - std::string chunk = text.substr(lastpos, size).to_string(); + std::string chunk(text.substr(lastpos, size)); if (pseudo) { chunk = impl_->Text(chunk); } else if (str[lastpos] == kArgStart && str[nextpos - 1] == kArgEnd) { @@ -301,21 +307,23 @@ static bool IsPossibleNormalPlaceholderEnd(const char c) { } static std::string PseudoGenerateExpansion(const unsigned int length) { - std::string result = kExpansionString; - const char* s = result.data(); + std::string result(kExpansionString); if (result.size() < length) { result += " "; result += PseudoGenerateExpansion(length - result.size()); } else { int ext = 0; // Should contain only whole words, so looking for a space - for (unsigned int i = length + 1; i < result.size(); ++i) { - ++ext; - if (s[i] == ' ') { - break; + { + const char* const s = result.data(); + for (unsigned int i = length + 1; i < result.size(); ++i) { + ++ext; + if (s[i] == ' ') { + break; + } } } - result = result.substr(0, length + ext); + result.resize(length + ext); } return result; } @@ -349,7 +357,7 @@ std::string PseudoMethodAccent::End() { * * Note: This leaves placeholder syntax untouched. */ -std::string PseudoMethodAccent::Text(const StringPiece& source) { +std::string PseudoMethodAccent::Text(StringPiece source) { const char* s = source.data(); std::string result; const size_t I = source.size(); @@ -435,12 +443,12 @@ std::string PseudoMethodAccent::Text(const StringPiece& source) { return result; } -std::string PseudoMethodAccent::Placeholder(const StringPiece& source) { +std::string PseudoMethodAccent::Placeholder(StringPiece source) { // Surround a placeholder with brackets - return kPlaceholderOpen + source.to_string() + kPlaceholderClose; + return (std::string(kPlaceholderOpen) += source) += kPlaceholderClose; } -std::string PseudoMethodBidi::Text(const StringPiece& source) { +std::string PseudoMethodBidi::Text(StringPiece source) { const char* s = source.data(); std::string result; bool lastspace = true; @@ -456,10 +464,10 @@ std::string PseudoMethodBidi::Text(const StringPiece& source) { space = (!escape && isspace(c)) || (escape && (c == 'n' || c == 't')); if (lastspace && !space) { // Word start - result += kRlm + kRlo; + (result += kRlm) += kRlo; } else if (!lastspace && space) { // Word end - result += kPdf + kRlm; + (result += kPdf) += kRlm; } lastspace = space; if (escape) { @@ -470,14 +478,14 @@ std::string PseudoMethodBidi::Text(const StringPiece& source) { } if (!lastspace) { // End of last word - result += kPdf + kRlm; + (result += kPdf) += kRlm; } return result; } -std::string PseudoMethodBidi::Placeholder(const StringPiece& source) { +std::string PseudoMethodBidi::Placeholder(StringPiece source) { // Surround a placeholder with directionality change sequence - return kRlm + kRlo + source.to_string() + kPdf + kRlm; + return (((std::string(kRlm) += kRlo) += source) += kPdf) += kRlm; } } // namespace aapt diff --git a/tools/aapt2/compile/Pseudolocalizer.h b/tools/aapt2/compile/Pseudolocalizer.h index 4dedc700a8e7..2b94bcc87fc9 100644 --- a/tools/aapt2/compile/Pseudolocalizer.h +++ b/tools/aapt2/compile/Pseudolocalizer.h @@ -31,8 +31,8 @@ class PseudoMethodImpl { virtual ~PseudoMethodImpl() {} virtual std::string Start() { return {}; } virtual std::string End() { return {}; } - virtual std::string Text(const android::StringPiece& text) = 0; - virtual std::string Placeholder(const android::StringPiece& text) = 0; + virtual std::string Text(android::StringPiece text) = 0; + virtual std::string Placeholder(android::StringPiece text) = 0; }; class Pseudolocalizer { @@ -47,7 +47,7 @@ class Pseudolocalizer { void SetMethod(Method method); std::string Start() { return impl_->Start(); } std::string End() { return impl_->End(); } - std::string Text(const android::StringPiece& text); + std::string Text(android::StringPiece text); private: std::unique_ptr<PseudoMethodImpl> impl_; diff --git a/tools/aapt2/configuration/ConfigurationParser.cpp b/tools/aapt2/configuration/ConfigurationParser.cpp index 6bba11e26e6a..1b0325325778 100644 --- a/tools/aapt2/configuration/ConfigurationParser.cpp +++ b/tools/aapt2/configuration/ConfigurationParser.cpp @@ -152,7 +152,7 @@ bool CopyXmlReferences(const std::optional<std::string>& name, const Group<T>& g * success, or false if the either the placeholder is not found in the name, or the value is not * present and the placeholder was. */ -bool ReplacePlaceholder(const StringPiece& placeholder, const std::optional<StringPiece>& value, +bool ReplacePlaceholder(StringPiece placeholder, const std::optional<StringPiece>& value, std::string* name, android::IDiagnostics* diag) { size_t offset = name->find(placeholder.data()); bool found = (offset != std::string::npos); @@ -338,17 +338,17 @@ std::optional<PostProcessingConfiguration> ExtractConfiguration(const std::strin return {config}; } -const StringPiece& AbiToString(Abi abi) { +StringPiece AbiToString(Abi abi) { return kAbiToStringMap.at(static_cast<size_t>(abi)); } /** * Returns the common artifact base name from a template string. */ -std::optional<std::string> ToBaseName(std::string result, const StringPiece& apk_name, +std::optional<std::string> ToBaseName(std::string result, StringPiece apk_name, android::IDiagnostics* diag) { const StringPiece ext = file::GetExtension(apk_name); - size_t end_index = apk_name.to_string().rfind(ext.to_string()); + size_t end_index = apk_name.rfind(ext); const std::string base_name = (end_index != std::string::npos) ? std::string{apk_name.begin(), end_index} : ""; @@ -371,17 +371,17 @@ std::optional<std::string> ToBaseName(std::string result, const StringPiece& apk // If no extension is specified, and the name template does not end in the current extension, // add the existing extension. if (!util::EndsWith(result, ext)) { - result.append(ext.to_string()); + result.append(ext); } } return result; } -std::optional<std::string> ConfiguredArtifact::ToArtifactName(const StringPiece& format, - const StringPiece& apk_name, +std::optional<std::string> ConfiguredArtifact::ToArtifactName(StringPiece format, + StringPiece apk_name, android::IDiagnostics* diag) const { - std::optional<std::string> base = ToBaseName(format.to_string(), apk_name, diag); + std::optional<std::string> base = ToBaseName(std::string(format), apk_name, diag); if (!base) { return {}; } @@ -414,7 +414,7 @@ std::optional<std::string> ConfiguredArtifact::ToArtifactName(const StringPiece& return result; } -std::optional<std::string> ConfiguredArtifact::Name(const StringPiece& apk_name, +std::optional<std::string> ConfiguredArtifact::Name(StringPiece apk_name, android::IDiagnostics* diag) const { if (!name) { return {}; @@ -439,7 +439,7 @@ ConfigurationParser::ConfigurationParser(std::string contents, const std::string } std::optional<std::vector<OutputArtifact>> ConfigurationParser::Parse( - const android::StringPiece& apk_path) { + android::StringPiece apk_path) { std::optional<PostProcessingConfiguration> maybe_config = ExtractConfiguration(contents_, config_path_, diag_); if (!maybe_config) { @@ -447,7 +447,7 @@ std::optional<std::vector<OutputArtifact>> ConfigurationParser::Parse( } // Convert from a parsed configuration to a list of artifacts for processing. - const std::string& apk_name = file::GetFilename(apk_path).to_string(); + const std::string apk_name(file::GetFilename(apk_path)); std::vector<OutputArtifact> output_artifacts; PostProcessingConfiguration& config = maybe_config.value(); @@ -519,7 +519,7 @@ bool ArtifactFormatTagHandler(PostProcessingConfiguration* config, Element* root for (auto& node : root_element->children) { xml::Text* t; if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { - config->artifact_format = TrimWhitespace(t->text).to_string(); + config->artifact_format.emplace(TrimWhitespace(t->text)); break; } } @@ -561,7 +561,7 @@ bool AbiGroupTagHandler(PostProcessingConfiguration* config, Element* root_eleme for (auto& node : child->children) { xml::Text* t; if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { - auto abi = kStringToAbiMap.find(TrimWhitespace(t->text).to_string()); + auto abi = kStringToAbiMap.find(TrimWhitespace(t->text)); if (abi != kStringToAbiMap.end()) { group.push_back(abi->second); } else { @@ -622,7 +622,7 @@ bool ScreenDensityGroupTagHandler(PostProcessingConfiguration* config, Element* xml::Text* t; if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { ConfigDescription config_descriptor; - const android::StringPiece& text = TrimWhitespace(t->text); + android::StringPiece text = TrimWhitespace(t->text); bool parsed = ConfigDescription::Parse(text, &config_descriptor); if (parsed && (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) == @@ -688,7 +688,7 @@ bool LocaleGroupTagHandler(PostProcessingConfiguration* config, Element* root_el xml::Text* t; if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { ConfigDescription config_descriptor; - const android::StringPiece& text = TrimWhitespace(t->text); + android::StringPiece text = TrimWhitespace(t->text); bool parsed = ConfigDescription::Parse(text, &config_descriptor); if (parsed && (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) == @@ -806,7 +806,7 @@ bool GlTextureGroupTagHandler(PostProcessingConfiguration* config, Element* root for (auto& node : element->children) { xml::Text* t; if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { - result.texture_paths.push_back(TrimWhitespace(t->text).to_string()); + result.texture_paths.emplace_back(TrimWhitespace(t->text)); } } } @@ -843,7 +843,7 @@ bool DeviceFeatureGroupTagHandler(PostProcessingConfiguration* config, Element* for (auto& node : child->children) { xml::Text* t; if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { - group.push_back(TrimWhitespace(t->text).to_string()); + group.emplace_back(TrimWhitespace(t->text)); break; } } diff --git a/tools/aapt2/configuration/ConfigurationParser.h b/tools/aapt2/configuration/ConfigurationParser.h index 2c8221d5108b..d66f4ab000a3 100644 --- a/tools/aapt2/configuration/ConfigurationParser.h +++ b/tools/aapt2/configuration/ConfigurationParser.h @@ -43,7 +43,7 @@ enum class Abi { }; /** Helper method to convert an ABI to a string representing the path within the APK. */ -const android::StringPiece& AbiToString(Abi abi); +android::StringPiece AbiToString(Abi abi); /** * Represents an individual locale. When a locale is included, it must be @@ -150,8 +150,7 @@ class ConfigurationParser { * Parses the configuration file and returns the results. If the configuration could not be parsed * the result is empty and any errors will be displayed with the provided diagnostics context. */ - std::optional<std::vector<configuration::OutputArtifact>> Parse( - const android::StringPiece& apk_path); + std::optional<std::vector<configuration::OutputArtifact>> Parse(android::StringPiece apk_path); protected: /** diff --git a/tools/aapt2/configuration/ConfigurationParser.internal.h b/tools/aapt2/configuration/ConfigurationParser.internal.h index 3028c3f58e4e..198f730f1e12 100644 --- a/tools/aapt2/configuration/ConfigurationParser.internal.h +++ b/tools/aapt2/configuration/ConfigurationParser.internal.h @@ -138,13 +138,12 @@ struct ConfiguredArtifact { std::optional<std::string> gl_texture_group; /** Convert an artifact name template into a name string based on configuration contents. */ - std::optional<std::string> ToArtifactName(const android::StringPiece& format, - const android::StringPiece& apk_name, + std::optional<std::string> ToArtifactName(android::StringPiece format, + android::StringPiece apk_name, android::IDiagnostics* diag) const; /** Convert an artifact name template into a name string based on configuration contents. */ - std::optional<std::string> Name(const android::StringPiece& apk_name, - android::IDiagnostics* diag) const; + std::optional<std::string> Name(android::StringPiece apk_name, android::IDiagnostics* diag) const; }; /** AAPT2 XML configuration file binary representation. */ diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp index c4c002d16ebb..d60869af2846 100644 --- a/tools/aapt2/dump/DumpManifest.cpp +++ b/tools/aapt2/dump/DumpManifest.cpp @@ -1076,7 +1076,7 @@ class FeatureGroup : public ManifestExtractor::Element { /** Adds a feature to the feature group. */ void AddFeature(const std::string& name, bool required = true, int32_t version = -1) { - features_.insert(std::make_pair(name, Feature{ required, version })); + features_.insert_or_assign(name, Feature{required, version}); if (required) { if (name == "android.hardware.camera.autofocus" || name == "android.hardware.camera.flash") { @@ -1348,6 +1348,11 @@ class UsesPermission : public ManifestExtractor::Element { std::string impliedReason; void Extract(xml::Element* element) override { + const auto parent_stack = extractor()->parent_stack(); + if (!extractor()->options_.only_permissions && + (parent_stack.size() != 1 || !ElementCast<Manifest>(parent_stack[0]))) { + return; + } name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), ""); std::string feature = GetAttributeStringDefault(FindAttribute(element, REQUIRED_FEATURE_ATTR), ""); @@ -1472,6 +1477,11 @@ class UsesPermissionSdk23 : public ManifestExtractor::Element { const int32_t* maxSdkVersion = nullptr; void Extract(xml::Element* element) override { + const auto parent_stack = extractor()->parent_stack(); + if (!extractor()->options_.only_permissions && + (parent_stack.size() != 1 || !ElementCast<Manifest>(parent_stack[0]))) { + return; + } name = GetAttributeString(FindAttribute(element, NAME_ATTR)); maxSdkVersion = GetAttributeInteger(FindAttribute(element, MAX_SDK_VERSION_ATTR)); diff --git a/tools/aapt2/filter/AbiFilter.cpp b/tools/aapt2/filter/AbiFilter.cpp index 9ace82ad4af7..908b1714bd14 100644 --- a/tools/aapt2/filter/AbiFilter.cpp +++ b/tools/aapt2/filter/AbiFilter.cpp @@ -23,15 +23,15 @@ namespace aapt { std::unique_ptr<AbiFilter> AbiFilter::FromAbiList(const std::vector<configuration::Abi>& abi_list) { - std::unordered_set<std::string> abi_set; + std::unordered_set<std::string_view> abi_set; for (auto& abi : abi_list) { - abi_set.insert(configuration::AbiToString(abi).to_string()); + abi_set.insert(configuration::AbiToString(abi)); } // Make unique by hand as the constructor is private. - return std::unique_ptr<AbiFilter>(new AbiFilter(abi_set)); + return std::unique_ptr<AbiFilter>(new AbiFilter(std::move(abi_set))); } -bool AbiFilter::Keep(const std::string& path) { +bool AbiFilter::Keep(std::string_view path) { // We only care about libraries. if (!util::StartsWith(path, kLibPrefix)) { return true; @@ -44,7 +44,7 @@ bool AbiFilter::Keep(const std::string& path) { } // Strip the lib/ prefix. - const std::string& path_abi = path.substr(kLibPrefixLen, abi_end - kLibPrefixLen); + const auto path_abi = path.substr(kLibPrefixLen, abi_end - kLibPrefixLen); return (abis_.find(path_abi) != abis_.end()); } diff --git a/tools/aapt2/filter/AbiFilter.h b/tools/aapt2/filter/AbiFilter.h index 2832711efb2c..7380f3f479ae 100644 --- a/tools/aapt2/filter/AbiFilter.h +++ b/tools/aapt2/filter/AbiFilter.h @@ -18,7 +18,7 @@ #define AAPT2_ABISPLITTER_H #include <memory> -#include <string> +#include <string_view> #include <unordered_set> #include <vector> @@ -39,16 +39,16 @@ class AbiFilter : public IPathFilter { static std::unique_ptr<AbiFilter> FromAbiList(const std::vector<configuration::Abi>& abi_list); /** Returns true if the path is for a native library in the list of desired ABIs. */ - bool Keep(const std::string& path) override; + bool Keep(std::string_view path) override; private: - explicit AbiFilter(std::unordered_set<std::string> abis) : abis_(std::move(abis)) { + explicit AbiFilter(std::unordered_set<std::string_view> abis) : abis_(std::move(abis)) { } /** The path prefix to where all native libs end up inside an APK file. */ static constexpr const char* kLibPrefix = "lib/"; static constexpr size_t kLibPrefixLen = 4; - const std::unordered_set<std::string> abis_; + const std::unordered_set<std::string_view> abis_; }; } // namespace aapt diff --git a/tools/aapt2/filter/Filter.h b/tools/aapt2/filter/Filter.h index f932f9ccc82e..baf4791f76c8 100644 --- a/tools/aapt2/filter/Filter.h +++ b/tools/aapt2/filter/Filter.h @@ -18,6 +18,7 @@ #define AAPT2_FILTER_H #include <string> +#include <string_view> #include <vector> #include "util/Util.h" @@ -30,7 +31,7 @@ class IPathFilter { virtual ~IPathFilter() = default; /** Returns true if the path should be kept. */ - virtual bool Keep(const std::string& path) = 0; + virtual bool Keep(std::string_view path) = 0; }; /** @@ -42,7 +43,7 @@ class PrefixFilter : public IPathFilter { } /** Returns true if the provided path matches the prefix. */ - bool Keep(const std::string& path) override { + bool Keep(std::string_view path) override { return util::StartsWith(path, prefix_); } @@ -59,7 +60,7 @@ class FilterChain : public IPathFilter { } /** Returns true if all filters keep the path. */ - bool Keep(const std::string& path) override { + bool Keep(std::string_view path) override { for (auto& filter : filters_) { if (!filter->Keep(path)) { return false; diff --git a/tools/aapt2/format/Archive.cpp b/tools/aapt2/format/Archive.cpp index 80c16188aca4..e9a93d8b12ad 100644 --- a/tools/aapt2/format/Archive.cpp +++ b/tools/aapt2/format/Archive.cpp @@ -40,8 +40,8 @@ class DirectoryWriter : public IArchiveWriter { public: DirectoryWriter() = default; - bool Open(const StringPiece& out_dir) { - dir_ = out_dir.to_string(); + bool Open(StringPiece out_dir) { + dir_ = std::string(out_dir); file::FileType type = file::GetFileType(dir_); if (type == file::FileType::kNonExistant) { error_ = "directory does not exist"; @@ -53,14 +53,14 @@ class DirectoryWriter : public IArchiveWriter { return true; } - bool StartEntry(const StringPiece& path, uint32_t flags) override { + bool StartEntry(StringPiece path, uint32_t flags) override { if (file_) { return false; } std::string full_path = dir_; file::AppendPath(&full_path, path); - file::mkdirs(file::GetStem(full_path).to_string()); + file::mkdirs(std::string(file::GetStem(full_path))); file_ = {::android::base::utf8::fopen(full_path.c_str(), "wb"), fclose}; if (!file_) { @@ -91,7 +91,7 @@ class DirectoryWriter : public IArchiveWriter { return true; } - bool WriteFile(const StringPiece& path, uint32_t flags, io::InputStream* in) override { + bool WriteFile(StringPiece path, uint32_t flags, io::InputStream* in) override { if (!StartEntry(path, flags)) { return false; } @@ -132,8 +132,8 @@ class ZipFileWriter : public IArchiveWriter { public: ZipFileWriter() = default; - bool Open(const StringPiece& path) { - file_ = {::android::base::utf8::fopen(path.to_string().c_str(), "w+b"), fclose}; + bool Open(StringPiece path) { + file_ = {::android::base::utf8::fopen(path.data(), "w+b"), fclose}; if (!file_) { error_ = SystemErrorCodeToString(errno); return false; @@ -142,7 +142,7 @@ class ZipFileWriter : public IArchiveWriter { return true; } - bool StartEntry(const StringPiece& path, uint32_t flags) override { + bool StartEntry(StringPiece path, uint32_t flags) override { if (!writer_) { return false; } @@ -182,7 +182,7 @@ class ZipFileWriter : public IArchiveWriter { return true; } - bool WriteFile(const StringPiece& path, uint32_t flags, io::InputStream* in) override { + bool WriteFile(StringPiece path, uint32_t flags, io::InputStream* in) override { while (true) { if (!StartEntry(path, flags)) { return false; @@ -257,7 +257,7 @@ class ZipFileWriter : public IArchiveWriter { } // namespace std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(android::IDiagnostics* diag, - const StringPiece& path) { + StringPiece path) { std::unique_ptr<DirectoryWriter> writer = util::make_unique<DirectoryWriter>(); if (!writer->Open(path)) { diag->Error(android::DiagMessage(path) << writer->GetError()); @@ -267,7 +267,7 @@ std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(android::IDiagnosti } std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter(android::IDiagnostics* diag, - const StringPiece& path) { + StringPiece path) { std::unique_ptr<ZipFileWriter> writer = util::make_unique<ZipFileWriter>(); if (!writer->Open(path)) { diag->Error(android::DiagMessage(path) << writer->GetError()); diff --git a/tools/aapt2/format/Archive.h b/tools/aapt2/format/Archive.h index 55b0b2f0f017..6cde753a255d 100644 --- a/tools/aapt2/format/Archive.h +++ b/tools/aapt2/format/Archive.h @@ -46,12 +46,12 @@ class IArchiveWriter : public ::google::protobuf::io::CopyingOutputStream { public: virtual ~IArchiveWriter() = default; - virtual bool WriteFile(const android::StringPiece& path, uint32_t flags, io::InputStream* in) = 0; + virtual bool WriteFile(android::StringPiece path, uint32_t flags, io::InputStream* in) = 0; // Starts a new entry and allows caller to write bytes to it sequentially. // Only use StartEntry if code you do not control needs to write to a CopyingOutputStream. // Prefer WriteFile instead of manually calling StartEntry/FinishEntry. - virtual bool StartEntry(const android::StringPiece& path, uint32_t flags) = 0; + virtual bool StartEntry(android::StringPiece path, uint32_t flags) = 0; // Called to finish writing an entry previously started by StartEntry. // Prefer WriteFile instead of manually calling StartEntry/FinishEntry. @@ -70,10 +70,10 @@ class IArchiveWriter : public ::google::protobuf::io::CopyingOutputStream { }; std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(android::IDiagnostics* diag, - const android::StringPiece& path); + android::StringPiece path); std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter(android::IDiagnostics* diag, - const android::StringPiece& path); + android::StringPiece path); } // namespace aapt diff --git a/tools/aapt2/format/Archive_test.cpp b/tools/aapt2/format/Archive_test.cpp index ceed3740f37a..3c44da710d94 100644 --- a/tools/aapt2/format/Archive_test.cpp +++ b/tools/aapt2/format/Archive_test.cpp @@ -50,7 +50,7 @@ std::unique_ptr<IArchiveWriter> MakeDirectoryWriter(const std::string& output_pa } std::unique_ptr<IArchiveWriter> MakeZipFileWriter(const std::string& output_path) { - file::mkdirs(file::GetStem(output_path).to_string()); + file::mkdirs(std::string(file::GetStem(output_path))); std::remove(output_path.c_str()); StdErrDiagnostics diag; diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp index 82918629f1f4..75dcba581c90 100644 --- a/tools/aapt2/format/binary/BinaryResourceParser.cpp +++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp @@ -373,7 +373,7 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package, std::optional<ResourceNamedTypeRef> parsed_type = ParseResourceNamedType(type_str); if (!parsed_type) { diag_->Warn(android::DiagMessage(source_) - << "invalid type name '" << type_str << "' for type with ID " << type->id); + << "invalid type name '" << type_str << "' for type with ID " << int(type->id)); return true; } diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp index d08b4a3e5deb..0f1168514c4a 100644 --- a/tools/aapt2/format/binary/TableFlattener_test.cpp +++ b/tools/aapt2/format/binary/TableFlattener_test.cpp @@ -84,7 +84,7 @@ class TableFlattenerTest : public ::testing::Test { return ::testing::AssertionSuccess(); } - ::testing::AssertionResult Exists(ResTable* table, const StringPiece& expected_name, + ::testing::AssertionResult Exists(ResTable* table, StringPiece expected_name, const ResourceId& expected_id, const ConfigDescription& expected_config, const uint8_t expected_data_type, const uint32_t expected_data, diff --git a/tools/aapt2/format/binary/XmlFlattener.cpp b/tools/aapt2/format/binary/XmlFlattener.cpp index 983e6467fab0..05f975177cd1 100644 --- a/tools/aapt2/format/binary/XmlFlattener.cpp +++ b/tools/aapt2/format/binary/XmlFlattener.cpp @@ -79,7 +79,7 @@ class XmlFlattenerVisitor : public xml::ConstVisitor { } void Visit(const xml::Text* node) override { - std::string text = util::TrimWhitespace(node->text).to_string(); + std::string text(util::TrimWhitespace(node->text)); // Skip whitespace only text nodes. if (text.empty()) { @@ -88,10 +88,10 @@ class XmlFlattenerVisitor : public xml::ConstVisitor { // Compact leading and trailing whitespace into a single space if (isspace(node->text[0])) { - text = ' ' + text; + text.insert(text.begin(), ' '); } - if (isspace(node->text[node->text.length() - 1])) { - text = text + ' '; + if (isspace(node->text.back())) { + text += ' '; } ChunkWriter writer(buffer_); @@ -165,7 +165,7 @@ class XmlFlattenerVisitor : public xml::ConstVisitor { // We are adding strings to a StringPool whose strings will be sorted and merged with other // string pools. That means we can't encode the ID of a string directly. Instead, we defer the // writing of the ID here, until after the StringPool is merged and sorted. - void AddString(const StringPiece& str, uint32_t priority, android::ResStringPool_ref* dest, + void AddString(StringPiece str, uint32_t priority, android::ResStringPool_ref* dest, bool treat_empty_string_as_null = false) { if (str.empty() && treat_empty_string_as_null) { // Some parts of the runtime treat null differently than empty string. diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp index 5adc5e639830..ecfdba83a2e8 100644 --- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp @@ -35,7 +35,7 @@ namespace aapt { class MockFileCollection : public io::IFileCollection { public: - MOCK_METHOD1(FindFile, io::IFile*(const StringPiece& path)); + MOCK_METHOD1(FindFile, io::IFile*(StringPiece path)); MOCK_METHOD0(Iterator, std::unique_ptr<io::IFileCollectionIterator>()); MOCK_METHOD0(GetDirSeparator, char()); }; @@ -491,7 +491,7 @@ TEST(ProtoSerializeTest, SerializeAndDeserializePrimitives) { EXPECT_THAT(bp->value.data, Eq(ResourceUtils::MakeEmpty()->value.data)); } -static void ExpectConfigSerializes(const StringPiece& config_str) { +static void ExpectConfigSerializes(StringPiece config_str) { const ConfigDescription expected_config = test::ParseConfigOrDie(config_str); pb::Configuration pb_config; SerializeConfig(expected_config, &pb_config); diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h index 422658a0309e..08d497def8a4 100644 --- a/tools/aapt2/io/File.h +++ b/tools/aapt2/io/File.h @@ -101,7 +101,7 @@ class IFileCollection { public: virtual ~IFileCollection() = default; - virtual IFile* FindFile(const android::StringPiece& path) = 0; + virtual IFile* FindFile(android::StringPiece path) = 0; virtual std::unique_ptr<IFileCollectionIterator> Iterator() = 0; virtual char GetDirSeparator() = 0; }; diff --git a/tools/aapt2/io/FileSystem.cpp b/tools/aapt2/io/FileSystem.cpp index 3f071af08844..a64982a7fa5c 100644 --- a/tools/aapt2/io/FileSystem.cpp +++ b/tools/aapt2/io/FileSystem.cpp @@ -67,8 +67,8 @@ IFile* FileCollectionIterator::Next() { return result; } -std::unique_ptr<FileCollection> FileCollection::Create(const android::StringPiece& root, - std::string* outError) { +std::unique_ptr<FileCollection> FileCollection::Create(android::StringPiece root, + std::string* outError) { std::unique_ptr<FileCollection> collection = std::unique_ptr<FileCollection>(new FileCollection()); @@ -80,7 +80,7 @@ std::unique_ptr<FileCollection> FileCollection::Create(const android::StringPiec std::vector<std::string> sorted_files; while (struct dirent *entry = readdir(d.get())) { - std::string prefix_path = root.to_string(); + std::string prefix_path(root); file::AppendPath(&prefix_path, entry->d_name); // The directory to iterate over looking for files @@ -117,12 +117,19 @@ std::unique_ptr<FileCollection> FileCollection::Create(const android::StringPiec return collection; } -IFile* FileCollection::InsertFile(const StringPiece& path) { - return (files_[path.to_string()] = util::make_unique<RegularFile>(android::Source(path))).get(); +IFile* FileCollection::InsertFile(StringPiece path) { + auto file = util::make_unique<RegularFile>(android::Source(path)); + auto it = files_.lower_bound(path); + if (it != files_.end() && it->first == path) { + it->second = std::move(file); + } else { + it = files_.emplace_hint(it, path, std::move(file)); + } + return it->second.get(); } -IFile* FileCollection::FindFile(const StringPiece& path) { - auto iter = files_.find(path.to_string()); +IFile* FileCollection::FindFile(StringPiece path) { + auto iter = files_.find(path); if (iter != files_.end()) { return iter->second.get(); } diff --git a/tools/aapt2/io/FileSystem.h b/tools/aapt2/io/FileSystem.h index bc03b9b4391e..0e798fc1b975 100644 --- a/tools/aapt2/io/FileSystem.h +++ b/tools/aapt2/io/FileSystem.h @@ -60,12 +60,11 @@ class FileCollection : public IFileCollection { FileCollection() = default; /** Creates a file collection containing all files contained in the specified root directory. */ - static std::unique_ptr<FileCollection> Create(const android::StringPiece& path, - std::string* outError); + static std::unique_ptr<FileCollection> Create(android::StringPiece path, std::string* outError); // Adds a file located at path. Returns the IFile representation of that file. - IFile* InsertFile(const android::StringPiece& path); - IFile* FindFile(const android::StringPiece& path) override; + IFile* InsertFile(android::StringPiece path); + IFile* FindFile(android::StringPiece path) override; std::unique_ptr<IFileCollectionIterator> Iterator() override; char GetDirSeparator() override; @@ -74,7 +73,7 @@ class FileCollection : public IFileCollection { friend class FileCollectionIterator; - std::map<std::string, std::unique_ptr<IFile>> files_; + std::map<std::string, std::unique_ptr<IFile>, std::less<>> files_; }; } // namespace io diff --git a/tools/aapt2/io/StringStream.cpp b/tools/aapt2/io/StringStream.cpp index 4ca04a8c7477..9c497882b99b 100644 --- a/tools/aapt2/io/StringStream.cpp +++ b/tools/aapt2/io/StringStream.cpp @@ -21,7 +21,7 @@ using ::android::StringPiece; namespace aapt { namespace io { -StringInputStream::StringInputStream(const StringPiece& str) : str_(str), offset_(0u) { +StringInputStream::StringInputStream(StringPiece str) : str_(str), offset_(0u) { } bool StringInputStream::Next(const void** data, size_t* size) { diff --git a/tools/aapt2/io/StringStream.h b/tools/aapt2/io/StringStream.h index f29890ab7ee5..f7bdecca0dee 100644 --- a/tools/aapt2/io/StringStream.h +++ b/tools/aapt2/io/StringStream.h @@ -29,7 +29,7 @@ namespace io { class StringInputStream : public KnownSizeInputStream { public: - explicit StringInputStream(const android::StringPiece& str); + explicit StringInputStream(android::StringPiece str); bool Next(const void** data, size_t* size) override; diff --git a/tools/aapt2/io/Util.cpp b/tools/aapt2/io/Util.cpp index afe54d408361..79d8d527fe8b 100644 --- a/tools/aapt2/io/Util.cpp +++ b/tools/aapt2/io/Util.cpp @@ -26,7 +26,7 @@ using ::google::protobuf::io::ZeroCopyOutputStream; namespace aapt { namespace io { -bool CopyInputStreamToArchive(IAaptContext* context, InputStream* in, const std::string& out_path, +bool CopyInputStreamToArchive(IAaptContext* context, InputStream* in, std::string_view out_path, uint32_t compression_flags, IArchiveWriter* writer) { TRACE_CALL(); if (context->IsVerbose()) { @@ -43,7 +43,7 @@ bool CopyInputStreamToArchive(IAaptContext* context, InputStream* in, const std: return true; } -bool CopyFileToArchive(IAaptContext* context, io::IFile* file, const std::string& out_path, +bool CopyFileToArchive(IAaptContext* context, io::IFile* file, std::string_view out_path, uint32_t compression_flags, IArchiveWriter* writer) { TRACE_CALL(); std::unique_ptr<io::IData> data = file->OpenAsData(); @@ -56,13 +56,13 @@ bool CopyFileToArchive(IAaptContext* context, io::IFile* file, const std::string } bool CopyFileToArchivePreserveCompression(IAaptContext* context, io::IFile* file, - const std::string& out_path, IArchiveWriter* writer) { + std::string_view out_path, IArchiveWriter* writer) { uint32_t compression_flags = file->WasCompressed() ? ArchiveEntry::kCompress : 0u; return CopyFileToArchive(context, file, out_path, compression_flags, writer); } bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::Message* proto_msg, - const std::string& out_path, uint32_t compression_flags, + std::string_view out_path, uint32_t compression_flags, IArchiveWriter* writer) { TRACE_CALL(); if (context->IsVerbose()) { @@ -110,7 +110,7 @@ bool Copy(OutputStream* out, InputStream* in) { return !in->HadError(); } -bool Copy(OutputStream* out, const StringPiece& in) { +bool Copy(OutputStream* out, StringPiece in) { const char* in_buffer = in.data(); size_t in_len = in.size(); while (in_len != 0) { diff --git a/tools/aapt2/io/Util.h b/tools/aapt2/io/Util.h index 1b48a288d255..685f522a2e71 100644 --- a/tools/aapt2/io/Util.h +++ b/tools/aapt2/io/Util.h @@ -17,12 +17,11 @@ #ifndef AAPT_IO_UTIL_H #define AAPT_IO_UTIL_H -#include <string> - -#include "google/protobuf/message.h" -#include "google/protobuf/io/coded_stream.h" +#include <string_view> #include "format/Archive.h" +#include "google/protobuf/io/coded_stream.h" +#include "google/protobuf/message.h" #include "io/File.h" #include "io/Io.h" #include "process/IResourceTableConsumer.h" @@ -30,23 +29,23 @@ namespace aapt { namespace io { -bool CopyInputStreamToArchive(IAaptContext* context, InputStream* in, const std::string& out_path, +bool CopyInputStreamToArchive(IAaptContext* context, InputStream* in, std::string_view out_path, uint32_t compression_flags, IArchiveWriter* writer); -bool CopyFileToArchive(IAaptContext* context, IFile* file, const std::string& out_path, +bool CopyFileToArchive(IAaptContext* context, IFile* file, std::string_view out_path, uint32_t compression_flags, IArchiveWriter* writer); bool CopyFileToArchivePreserveCompression(IAaptContext* context, IFile* file, - const std::string& out_path, IArchiveWriter* writer); + std::string_view out_path, IArchiveWriter* writer); bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::Message* proto_msg, - const std::string& out_path, uint32_t compression_flags, + std::string_view out_path, uint32_t compression_flags, IArchiveWriter* writer); // Copies the data from in to out. Returns false if there was an error. // If there was an error, check the individual streams' HadError/GetError methods. bool Copy(OutputStream* out, InputStream* in); -bool Copy(OutputStream* out, const ::android::StringPiece& in); +bool Copy(OutputStream* out, android::StringPiece in); bool Copy(::google::protobuf::io::ZeroCopyOutputStream* out, InputStream* in); class OutputStreamAdaptor : public io::OutputStream { diff --git a/tools/aapt2/io/ZipArchive.cpp b/tools/aapt2/io/ZipArchive.cpp index 400269c41230..4a5385d90d3b 100644 --- a/tools/aapt2/io/ZipArchive.cpp +++ b/tools/aapt2/io/ZipArchive.cpp @@ -91,8 +91,8 @@ IFile* ZipFileCollectionIterator::Next() { ZipFileCollection::ZipFileCollection() : handle_(nullptr) {} -std::unique_ptr<ZipFileCollection> ZipFileCollection::Create( - const StringPiece& path, std::string* out_error) { +std::unique_ptr<ZipFileCollection> ZipFileCollection::Create(StringPiece path, + std::string* out_error) { TRACE_CALL(); constexpr static const int32_t kEmptyArchive = -6; @@ -130,8 +130,8 @@ std::unique_ptr<ZipFileCollection> ZipFileCollection::Create( continue; } - std::unique_ptr<IFile> file = util::make_unique<ZipFile>( - collection->handle_, zip_data, android::Source(zip_entry_path, path.to_string())); + std::unique_ptr<IFile> file = util::make_unique<ZipFile>(collection->handle_, zip_data, + android::Source(zip_entry_path, path)); collection->files_by_name_[zip_entry_path] = file.get(); collection->files_.push_back(std::move(file)); } @@ -144,8 +144,8 @@ std::unique_ptr<ZipFileCollection> ZipFileCollection::Create( return collection; } -IFile* ZipFileCollection::FindFile(const StringPiece& path) { - auto iter = files_by_name_.find(path.to_string()); +IFile* ZipFileCollection::FindFile(StringPiece path) { + auto iter = files_by_name_.find(path); if (iter != files_by_name_.end()) { return iter->second; } diff --git a/tools/aapt2/io/ZipArchive.h b/tools/aapt2/io/ZipArchive.h index 78c9c211ab57..c263aa490d22 100644 --- a/tools/aapt2/io/ZipArchive.h +++ b/tools/aapt2/io/ZipArchive.h @@ -61,10 +61,10 @@ class ZipFileCollectionIterator : public IFileCollectionIterator { // An IFileCollection that represents a ZIP archive and the entries within it. class ZipFileCollection : public IFileCollection { public: - static std::unique_ptr<ZipFileCollection> Create(const android::StringPiece& path, + static std::unique_ptr<ZipFileCollection> Create(android::StringPiece path, std::string* outError); - io::IFile* FindFile(const android::StringPiece& path) override; + io::IFile* FindFile(android::StringPiece path) override; std::unique_ptr<IFileCollectionIterator> Iterator() override; char GetDirSeparator() override; @@ -76,7 +76,7 @@ class ZipFileCollection : public IFileCollection { ZipArchiveHandle handle_; std::vector<std::unique_ptr<IFile>> files_; - std::map<std::string, IFile*> files_by_name_; + std::map<std::string, IFile*, std::less<>> files_by_name_; }; } // namespace io diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp index 482d91aeb491..87da09a7b054 100644 --- a/tools/aapt2/java/AnnotationProcessor.cpp +++ b/tools/aapt2/java/AnnotationProcessor.cpp @@ -30,7 +30,7 @@ using ::android::StringPiece; namespace aapt { -StringPiece AnnotationProcessor::ExtractFirstSentence(const StringPiece& comment) { +StringPiece AnnotationProcessor::ExtractFirstSentence(StringPiece comment) { Utf8Iterator iter(comment); while (iter.HasNext()) { const char32_t codepoint = iter.Next(); @@ -62,7 +62,7 @@ static std::array<AnnotationRule, 2> sAnnotationRules = {{ }}; void AnnotationProcessor::AppendCommentLine(std::string comment) { - static const std::string sDeprecated = "@deprecated"; + static constexpr std::string_view sDeprecated = "@deprecated"; // Treat deprecated specially, since we don't remove it from the source comment. if (comment.find(sDeprecated) != std::string::npos) { @@ -74,7 +74,7 @@ void AnnotationProcessor::AppendCommentLine(std::string comment) { if (idx != std::string::npos) { // Captures all parameters associated with the specified annotation rule // by matching the first pair of parantheses after the rule. - std::regex re(rule.doc_str.to_string() + "\\s*\\((.+)\\)"); + std::regex re(std::string(rule.doc_str) += "\\s*\\((.+)\\)"); std::smatch match_result; const bool is_match = std::regex_search(comment, match_result, re); // We currently only capture and preserve parameters for SystemApi. @@ -97,7 +97,7 @@ void AnnotationProcessor::AppendCommentLine(std::string comment) { // If there was trimming to do, copy the string. if (trimmed.size() != comment.size()) { - comment = trimmed.to_string(); + comment = std::string(trimmed); } if (!has_comments_) { @@ -107,12 +107,12 @@ void AnnotationProcessor::AppendCommentLine(std::string comment) { comment_ << "\n * " << std::move(comment); } -void AnnotationProcessor::AppendComment(const StringPiece& comment) { +void AnnotationProcessor::AppendComment(StringPiece comment) { // We need to process line by line to clean-up whitespace and append prefixes. for (StringPiece line : util::Tokenize(comment, '\n')) { line = util::TrimWhitespace(line); if (!line.empty()) { - AppendCommentLine(line.to_string()); + AppendCommentLine(std::string(line)); } } } @@ -126,7 +126,7 @@ void AnnotationProcessor::AppendNewLine() { void AnnotationProcessor::Print(Printer* printer, bool strip_api_annotations) const { if (has_comments_) { std::string result = comment_.str(); - for (const StringPiece& line : util::Tokenize(result, '\n')) { + for (StringPiece line : util::Tokenize(result, '\n')) { printer->Println(line); } printer->Println(" */"); diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h index f217afb16f32..db3437e3b5b1 100644 --- a/tools/aapt2/java/AnnotationProcessor.h +++ b/tools/aapt2/java/AnnotationProcessor.h @@ -56,11 +56,11 @@ class AnnotationProcessor { // Extracts the first sentence of a comment. The algorithm selects the substring starting from // the beginning of the string, and ending at the first '.' character that is followed by a // whitespace character. If these requirements are not met, the whole string is returned. - static android::StringPiece ExtractFirstSentence(const android::StringPiece& comment); + static android::StringPiece ExtractFirstSentence(android::StringPiece comment); // Adds more comments. Resources can have value definitions for various configurations, and // each of the definitions may have comments that need to be processed. - void AppendComment(const android::StringPiece& comment); + void AppendComment(android::StringPiece comment); void AppendNewLine(); diff --git a/tools/aapt2/java/ClassDefinition.cpp b/tools/aapt2/java/ClassDefinition.cpp index 3163497f0da6..98f3bd2018b0 100644 --- a/tools/aapt2/java/ClassDefinition.cpp +++ b/tools/aapt2/java/ClassDefinition.cpp @@ -27,8 +27,8 @@ void ClassMember::Print(bool /*final*/, Printer* printer, bool strip_api_annotat processor_.Print(printer, strip_api_annotations); } -void MethodDefinition::AppendStatement(const StringPiece& statement) { - statements_.push_back(statement.to_string()); +void MethodDefinition::AppendStatement(StringPiece statement) { + statements_.emplace_back(statement); } void MethodDefinition::Print(bool final, Printer* printer, bool) const { @@ -110,8 +110,8 @@ constexpr static const char* sWarningHeader = " * should not be modified by hand.\n" " */\n\n"; -void ClassDefinition::WriteJavaFile(const ClassDefinition* def, const StringPiece& package, - bool final, bool strip_api_annotations, io::OutputStream* out) { +void ClassDefinition::WriteJavaFile(const ClassDefinition* def, StringPiece package, bool final, + bool strip_api_annotations, io::OutputStream* out) { Printer printer(out); printer.Print(sWarningHeader).Print("package ").Print(package).Println(";"); printer.Println(); diff --git a/tools/aapt2/java/ClassDefinition.h b/tools/aapt2/java/ClassDefinition.h index 2acdadb3c034..63c99821a836 100644 --- a/tools/aapt2/java/ClassDefinition.h +++ b/tools/aapt2/java/ClassDefinition.h @@ -59,8 +59,8 @@ class ClassMember { template <typename T> class PrimitiveMember : public ClassMember { public: - PrimitiveMember(const android::StringPiece& name, const T& val, bool staged_api = false) - : name_(name.to_string()), val_(val), staged_api_(staged_api) { + PrimitiveMember(android::StringPiece name, const T& val, bool staged_api = false) + : name_(name), val_(val), staged_api_(staged_api) { } bool empty() const override { @@ -104,8 +104,8 @@ class PrimitiveMember : public ClassMember { template <> class PrimitiveMember<std::string> : public ClassMember { public: - PrimitiveMember(const android::StringPiece& name, const std::string& val, bool staged_api = false) - : name_(name.to_string()), val_(val) { + PrimitiveMember(android::StringPiece name, const std::string& val, bool staged_api = false) + : name_(name), val_(val) { } bool empty() const override { @@ -141,7 +141,8 @@ using StringMember = PrimitiveMember<std::string>; template <typename T, typename StringConverter> class PrimitiveArrayMember : public ClassMember { public: - explicit PrimitiveArrayMember(const android::StringPiece& name) : name_(name.to_string()) {} + explicit PrimitiveArrayMember(android::StringPiece name) : name_(name) { + } void AddElement(const T& val) { elements_.emplace_back(val); @@ -209,12 +210,12 @@ using ResourceArrayMember = PrimitiveArrayMember<std::variant<ResourceId, FieldR class MethodDefinition : public ClassMember { public: // Expected method signature example: 'public static void onResourcesLoaded(int p)'. - explicit MethodDefinition(const android::StringPiece& signature) - : signature_(signature.to_string()) {} + explicit MethodDefinition(android::StringPiece signature) : signature_(signature) { + } // Appends a single statement to the method. It should include no newlines or else // formatting may be broken. - void AppendStatement(const android::StringPiece& statement); + void AppendStatement(android::StringPiece statement); // Not quite the same as a name, but good enough. const std::string& GetName() const override { @@ -239,11 +240,12 @@ enum class ClassQualifier { kNone, kStatic }; class ClassDefinition : public ClassMember { public: - static void WriteJavaFile(const ClassDefinition* def, const android::StringPiece& package, - bool final, bool strip_api_annotations, io::OutputStream* out); + static void WriteJavaFile(const ClassDefinition* def, android::StringPiece package, bool final, + bool strip_api_annotations, io::OutputStream* out); - ClassDefinition(const android::StringPiece& name, ClassQualifier qualifier, bool createIfEmpty) - : name_(name.to_string()), qualifier_(qualifier), create_if_empty_(createIfEmpty) {} + ClassDefinition(android::StringPiece name, ClassQualifier qualifier, bool createIfEmpty) + : name_(name), qualifier_(qualifier), create_if_empty_(createIfEmpty) { + } enum class Result { kAdded, diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp index a25ca22c288d..7665d0e8d9cb 100644 --- a/tools/aapt2/java/JavaClassGenerator.cpp +++ b/tools/aapt2/java/JavaClassGenerator.cpp @@ -57,14 +57,14 @@ static const std::set<StringPiece> sJavaIdentifiers = { "transient", "try", "void", "volatile", "while", "true", "false", "null"}; -static bool IsValidSymbol(const StringPiece& symbol) { +static bool IsValidSymbol(StringPiece symbol) { return sJavaIdentifiers.find(symbol) == sJavaIdentifiers.end(); } // Java symbols can not contain . or -, but those are valid in a resource name. // Replace those with '_'. -std::string JavaClassGenerator::TransformToFieldName(const StringPiece& symbol) { - std::string output = symbol.to_string(); +std::string JavaClassGenerator::TransformToFieldName(StringPiece symbol) { + std::string output(symbol); for (char& c : output) { if (c == '.' || c == '-') { c = '_'; @@ -84,7 +84,7 @@ std::string JavaClassGenerator::TransformToFieldName(const StringPiece& symbol) // Foo_bar static std::string TransformNestedAttr(const ResourceNameRef& attr_name, const std::string& styleable_class_name, - const StringPiece& package_name_to_generate) { + StringPiece package_name_to_generate) { std::string output = styleable_class_name; // We may reference IDs from other packages, so prefix the entry name with @@ -226,16 +226,15 @@ static bool operator<(const StyleableAttr& lhs, const StyleableAttr& rhs) { static FieldReference GetRFieldReference(const ResourceName& name, StringPiece fallback_package_name) { - const std::string package_name = - name.package.empty() ? fallback_package_name.to_string() : name.package; + const std::string_view package_name = name.package.empty() ? fallback_package_name : name.package; const std::string entry = JavaClassGenerator::TransformToFieldName(name.entry); - return FieldReference(StringPrintf("%s.R.%s.%s", package_name.c_str(), - name.type.to_string().data(), entry.c_str())); + return FieldReference( + StringPrintf("%s.R.%s.%s", package_name.data(), name.type.to_string().data(), entry.c_str())); } bool JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const ResourceId& id, const Styleable& styleable, - const StringPiece& package_name_to_generate, + StringPiece package_name_to_generate, ClassDefinition* out_class_def, MethodDefinition* out_rewrite_method, Printer* r_txt_printer) { @@ -314,7 +313,8 @@ bool JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res return true; } const StringPiece attr_comment_line = entry.symbol.value().attribute->GetComment(); - return attr_comment_line.contains("@removed") || attr_comment_line.contains("@hide"); + return attr_comment_line.find("@removed") != std::string::npos || + attr_comment_line.find("@hide") != std::string::npos; }); documentation_attrs.erase(documentation_remove_iter, documentation_attrs.end()); @@ -397,7 +397,7 @@ bool JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res comment = styleable_attr.symbol.value().attribute->GetComment(); } - if (comment.contains("@removed")) { + if (comment.find("@removed") != std::string::npos) { // Removed attributes are public but hidden from the documentation, so // don't emit them as part of the class documentation. continue; @@ -497,7 +497,7 @@ void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const Reso } if (out_rewrite_method != nullptr) { - const std::string type_str = name.type.to_string(); + const auto type_str = name.type.to_string(); out_rewrite_method->AppendStatement( StringPrintf("%s.%s = (%s.%s & 0x00ffffff) | packageIdBits;", type_str.data(), field_name.data(), type_str.data(), field_name.data())); @@ -505,8 +505,7 @@ void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const Reso } std::optional<std::string> JavaClassGenerator::UnmangleResource( - const StringPiece& package_name, const StringPiece& package_name_to_generate, - const ResourceEntry& entry) { + StringPiece package_name, StringPiece package_name_to_generate, const ResourceEntry& entry) { if (SkipSymbol(entry.visibility.level)) { return {}; } @@ -528,7 +527,7 @@ std::optional<std::string> JavaClassGenerator::UnmangleResource( return {std::move(unmangled_name)}; } -bool JavaClassGenerator::ProcessType(const StringPiece& package_name_to_generate, +bool JavaClassGenerator::ProcessType(StringPiece package_name_to_generate, const ResourceTablePackage& package, const ResourceTableType& type, ClassDefinition* out_type_class_def, @@ -577,7 +576,7 @@ bool JavaClassGenerator::ProcessType(const StringPiece& package_name_to_generate return true; } -bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, OutputStream* out, +bool JavaClassGenerator::Generate(StringPiece package_name_to_generate, OutputStream* out, OutputStream* out_r_txt) { return Generate(package_name_to_generate, package_name_to_generate, out, out_r_txt); } @@ -591,8 +590,8 @@ static void AppendJavaDocAnnotations(const std::vector<std::string>& annotations } } -bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, - const StringPiece& out_package_name, OutputStream* out, +bool JavaClassGenerator::Generate(StringPiece package_name_to_generate, + StringPiece out_package_name, OutputStream* out, OutputStream* out_r_txt) { ClassDefinition r_class("R", ClassQualifier::kNone, true); std::unique_ptr<MethodDefinition> rewrite_method; diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h index b45a2f12db35..234df04472ce 100644 --- a/tools/aapt2/java/JavaClassGenerator.h +++ b/tools/aapt2/java/JavaClassGenerator.h @@ -70,16 +70,16 @@ class JavaClassGenerator { // All symbols technically belong to a single package, but linked libraries will // have their names mangled, denoting that they came from a different package. // We need to generate these symbols in a separate file. Returns true on success. - bool Generate(const android::StringPiece& package_name_to_generate, io::OutputStream* out, + bool Generate(android::StringPiece package_name_to_generate, io::OutputStream* out, io::OutputStream* out_r_txt = nullptr); - bool Generate(const android::StringPiece& package_name_to_generate, - const android::StringPiece& output_package_name, io::OutputStream* out, + bool Generate(android::StringPiece package_name_to_generate, + android::StringPiece output_package_name, io::OutputStream* out, io::OutputStream* out_r_txt = nullptr); const std::string& GetError() const; - static std::string TransformToFieldName(const android::StringPiece& symbol); + static std::string TransformToFieldName(android::StringPiece symbol); private: bool SkipSymbol(Visibility::Level state); @@ -87,11 +87,11 @@ class JavaClassGenerator { // Returns the unmangled resource entry name if the unmangled package is the same as // package_name_to_generate. Returns nothing if the resource should be skipped. - std::optional<std::string> UnmangleResource(const android::StringPiece& package_name, - const android::StringPiece& package_name_to_generate, + std::optional<std::string> UnmangleResource(android::StringPiece package_name, + android::StringPiece package_name_to_generate, const ResourceEntry& entry); - bool ProcessType(const android::StringPiece& package_name_to_generate, + bool ProcessType(android::StringPiece package_name_to_generate, const ResourceTablePackage& package, const ResourceTableType& type, ClassDefinition* out_type_class_def, MethodDefinition* out_rewrite_method_def, text::Printer* r_txt_printer); @@ -106,8 +106,7 @@ class JavaClassGenerator { // its package ID if `out_rewrite_method` is not nullptr. // `package_name_to_generate` is the package bool ProcessStyleable(const ResourceNameRef& name, const ResourceId& id, - const Styleable& styleable, - const android::StringPiece& package_name_to_generate, + const Styleable& styleable, android::StringPiece package_name_to_generate, ClassDefinition* out_class_def, MethodDefinition* out_rewrite_method, text::Printer* r_txt_printer); diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp index d0850b800e4f..56d90758ee73 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -646,8 +646,8 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, android::IDiagn return true; } -static void FullyQualifyClassName(const StringPiece& package, const StringPiece& attr_ns, - const StringPiece& attr_name, xml::Element* el) { +static void FullyQualifyClassName(StringPiece package, StringPiece attr_ns, StringPiece attr_name, + xml::Element* el) { xml::Attribute* attr = el->FindAttribute(attr_ns, attr_name); if (attr != nullptr) { if (std::optional<std::string> new_value = @@ -657,7 +657,7 @@ static void FullyQualifyClassName(const StringPiece& package, const StringPiece& } } -static bool RenameManifestPackage(const StringPiece& package_override, xml::Element* manifest_el) { +static bool RenameManifestPackage(StringPiece package_override, xml::Element* manifest_el) { xml::Attribute* attr = manifest_el->FindAttribute({}, "package"); // We've already verified that the manifest element is present, with a package @@ -665,7 +665,7 @@ static bool RenameManifestPackage(const StringPiece& package_override, xml::Elem CHECK(attr != nullptr); std::string original_package = std::move(attr->value); - attr->value = package_override.to_string(); + attr->value.assign(package_override); xml::Element* application_el = manifest_el->FindChild({}, "application"); if (application_el != nullptr) { diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp index 8d1a647b494d..7180ae6b8bc7 100644 --- a/tools/aapt2/link/ManifestFixer_test.cpp +++ b/tools/aapt2/link/ManifestFixer_test.cpp @@ -61,12 +61,12 @@ struct ManifestFixerTest : public ::testing::Test { .Build(); } - std::unique_ptr<xml::XmlResource> Verify(const StringPiece& str) { + std::unique_ptr<xml::XmlResource> Verify(StringPiece str) { return VerifyWithOptions(str, {}); } - std::unique_ptr<xml::XmlResource> VerifyWithOptions( - const StringPiece& str, const ManifestFixerOptions& options) { + std::unique_ptr<xml::XmlResource> VerifyWithOptions(StringPiece str, + const ManifestFixerOptions& options) { std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(str); ManifestFixer fixer(options); if (fixer.Consume(mContext.get(), doc.get())) { diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp index f2a93a868bb7..9dadfb26a3f8 100644 --- a/tools/aapt2/link/ReferenceLinker.cpp +++ b/tools/aapt2/link/ReferenceLinker.cpp @@ -189,8 +189,7 @@ class EmptyDeclStack : public xml::IPackageDeclStack { public: EmptyDeclStack() = default; - std::optional<xml::ExtractedPackage> TransformPackageAlias( - const StringPiece& alias) const override { + std::optional<xml::ExtractedPackage> TransformPackageAlias(StringPiece alias) const override { if (alias.empty()) { return xml::ExtractedPackage{{}, true /*private*/}; } @@ -206,8 +205,7 @@ struct MacroDeclStack : public xml::IPackageDeclStack { : alias_namespaces_(std::move(namespaces)) { } - std::optional<xml::ExtractedPackage> TransformPackageAlias( - const StringPiece& alias) const override { + std::optional<xml::ExtractedPackage> TransformPackageAlias(StringPiece alias) const override { if (alias.empty()) { return xml::ExtractedPackage{{}, true /*private*/}; } diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp index c9f0964193d2..67a48283e8b6 100644 --- a/tools/aapt2/link/TableMerger.cpp +++ b/tools/aapt2/link/TableMerger.cpp @@ -66,7 +66,7 @@ bool TableMerger::MergeImpl(const android::Source& src, ResourceTable* table, bo // This will merge and mangle resources from a static library. It is assumed that all FileReferences // have correctly set their io::IFile*. -bool TableMerger::MergeAndMangle(const android::Source& src, const StringPiece& package_name, +bool TableMerger::MergeAndMangle(const android::Source& src, StringPiece package_name, ResourceTable* table) { bool error = false; for (auto& package : table->packages) { @@ -326,8 +326,8 @@ std::unique_ptr<FileReference> TableMerger::CloneAndMangleFile( const std::string& package, const FileReference& file_ref) { StringPiece prefix, entry, suffix; if (util::ExtractResFilePathParts(*file_ref.path, &prefix, &entry, &suffix)) { - std::string mangled_entry = NameMangler::MangleEntry(package, entry.to_string()); - std::string newPath = prefix.to_string() + mangled_entry + suffix.to_string(); + std::string mangled_entry = NameMangler::MangleEntry(package, entry); + std::string newPath = (std::string(prefix) += mangled_entry) += suffix; std::unique_ptr<FileReference> new_file_ref = util::make_unique<FileReference>(main_table_->string_pool.MakeRef(newPath)); new_file_ref->SetComment(file_ref.GetComment()); diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h index 2ba212372966..37daf42f51e5 100644 --- a/tools/aapt2/link/TableMerger.h +++ b/tools/aapt2/link/TableMerger.h @@ -61,7 +61,7 @@ class TableMerger { // References are made to this ResourceTable for efficiency reasons. TableMerger(IAaptContext* context, ResourceTable* out_table, const TableMergerOptions& options); - inline const std::set<std::string>& merged_packages() const { + inline const std::set<std::string, std::less<>>& merged_packages() const { return merged_packages_; } @@ -71,7 +71,7 @@ class TableMerger { // Merges resources from the given package, mangling the name. This is for static libraries. // All FileReference values must have their io::IFile set. - bool MergeAndMangle(const android::Source& src, const android::StringPiece& package, + bool MergeAndMangle(const android::Source& src, android::StringPiece package, ResourceTable* table); // Merges a compiled file that belongs to this same or empty package. @@ -84,7 +84,7 @@ class TableMerger { ResourceTable* main_table_; TableMergerOptions options_; ResourceTablePackage* main_package_; - std::set<std::string> merged_packages_; + std::set<std::string, std::less<>> merged_packages_; bool MergeImpl(const android::Source& src, ResourceTable* src_table, bool overlay, bool allow_new); diff --git a/tools/aapt2/optimize/MultiApkGenerator.cpp b/tools/aapt2/optimize/MultiApkGenerator.cpp index f994e27e4e5b..f01db3ddca2e 100644 --- a/tools/aapt2/optimize/MultiApkGenerator.cpp +++ b/tools/aapt2/optimize/MultiApkGenerator.cpp @@ -113,12 +113,12 @@ class ContextWrapper : public IAaptContext { }; class SignatureFilter : public IPathFilter { - bool Keep(const std::string& path) override { + bool Keep(std::string_view path) override { static std::regex signature_regex(R"regex(^META-INF/.*\.(RSA|DSA|EC|SF)$)regex"); - if (std::regex_search(path, signature_regex)) { + if (std::regex_search(path.begin(), path.end(), signature_regex)) { return false; } - return !(path == "META-INF/MANIFEST.MF"); + return path != "META-INF/MANIFEST.MF"; } }; diff --git a/tools/aapt2/optimize/Obfuscator.cpp b/tools/aapt2/optimize/Obfuscator.cpp index f704f26bfd29..1fdd728928df 100644 --- a/tools/aapt2/optimize/Obfuscator.cpp +++ b/tools/aapt2/optimize/Obfuscator.cpp @@ -35,7 +35,7 @@ namespace aapt { Obfuscator::Obfuscator(std::map<std::string, std::string>& path_map_out) : path_map_(path_map_out) { } -std::string ShortenFileName(const android::StringPiece& file_path, int output_length) { +std::string ShortenFileName(android::StringPiece file_path, int output_length) { std::size_t hash_num = std::hash<android::StringPiece>{}(file_path); std::string result = ""; // Convert to (modified) base64 so that it is a proper file path. @@ -58,9 +58,9 @@ int OptimalShortenedLength(int num_resources) { } } -std::string GetShortenedPath(const android::StringPiece& shortened_filename, - const android::StringPiece& extension, int collision_count) { - std::string shortened_path = "res/" + shortened_filename.to_string(); +std::string GetShortenedPath(android::StringPiece shortened_filename, + android::StringPiece extension, int collision_count) { + std::string shortened_path = std::string("res/") += shortened_filename; if (collision_count > 0) { shortened_path += std::to_string(collision_count); } diff --git a/tools/aapt2/optimize/VersionCollapser_test.cpp b/tools/aapt2/optimize/VersionCollapser_test.cpp index aa0d0c095f57..18dcd6bace77 100644 --- a/tools/aapt2/optimize/VersionCollapser_test.cpp +++ b/tools/aapt2/optimize/VersionCollapser_test.cpp @@ -23,7 +23,7 @@ using android::StringPiece; namespace aapt { static std::unique_ptr<ResourceTable> BuildTableWithConfigs( - const StringPiece& name, std::initializer_list<std::string> list) { + StringPiece name, std::initializer_list<std::string> list) { test::ResourceTableBuilder builder; for (const std::string& item : list) { builder.AddSimple(name, test::ParseConfigOrDie(item)); diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp index 92b45c397eed..bca62da447b0 100644 --- a/tools/aapt2/process/SymbolTable.cpp +++ b/tools/aapt2/process/SymbolTable.cpp @@ -218,7 +218,7 @@ std::unique_ptr<SymbolTable::Symbol> ResourceTableSymbolSource::FindByName( return symbol; } -bool AssetManagerSymbolSource::AddAssetPath(const StringPiece& path) { +bool AssetManagerSymbolSource::AddAssetPath(StringPiece path) { TRACE_CALL(); if (std::unique_ptr<const ApkAssets> apk = ApkAssets::Load(path.data())) { apk_assets_.push_back(std::move(apk)); diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h index c17837c224ab..b09ff702ca58 100644 --- a/tools/aapt2/process/SymbolTable.h +++ b/tools/aapt2/process/SymbolTable.h @@ -192,7 +192,7 @@ class AssetManagerSymbolSource : public ISymbolSource { public: AssetManagerSymbolSource() = default; - bool AddAssetPath(const android::StringPiece& path); + bool AddAssetPath(android::StringPiece path); std::map<size_t, std::string> GetAssignedPackageIds() const; bool IsPackageDynamic(uint32_t packageId, const std::string& package_name) const; diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp index 30336e27b907..65f63dc68e54 100644 --- a/tools/aapt2/test/Builders.cpp +++ b/tools/aapt2/test/Builders.cpp @@ -34,61 +34,53 @@ using ::android::StringPiece; namespace aapt { namespace test { -ResourceTableBuilder& ResourceTableBuilder::AddSimple(const StringPiece& name, - const ResourceId& id) { +ResourceTableBuilder& ResourceTableBuilder::AddSimple(StringPiece name, const ResourceId& id) { return AddValue(name, id, util::make_unique<Id>()); } -ResourceTableBuilder& ResourceTableBuilder::AddSimple(const StringPiece& name, +ResourceTableBuilder& ResourceTableBuilder::AddSimple(StringPiece name, const ConfigDescription& config, const ResourceId& id) { return AddValue(name, config, id, util::make_unique<Id>()); } -ResourceTableBuilder& ResourceTableBuilder::AddReference(const StringPiece& name, - const StringPiece& ref) { +ResourceTableBuilder& ResourceTableBuilder::AddReference(StringPiece name, StringPiece ref) { return AddReference(name, {}, ref); } -ResourceTableBuilder& ResourceTableBuilder::AddReference(const StringPiece& name, - const ResourceId& id, - const StringPiece& ref) { +ResourceTableBuilder& ResourceTableBuilder::AddReference(StringPiece name, const ResourceId& id, + StringPiece ref) { return AddValue(name, id, util::make_unique<Reference>(ParseNameOrDie(ref))); } -ResourceTableBuilder& ResourceTableBuilder::AddString(const StringPiece& name, - const StringPiece& str) { +ResourceTableBuilder& ResourceTableBuilder::AddString(StringPiece name, StringPiece str) { return AddString(name, {}, str); } -ResourceTableBuilder& ResourceTableBuilder::AddString(const StringPiece& name, const ResourceId& id, - const StringPiece& str) { +ResourceTableBuilder& ResourceTableBuilder::AddString(StringPiece name, const ResourceId& id, + StringPiece str) { return AddValue(name, id, util::make_unique<String>(table_->string_pool.MakeRef(str))); } -ResourceTableBuilder& ResourceTableBuilder::AddString(const StringPiece& name, const ResourceId& id, +ResourceTableBuilder& ResourceTableBuilder::AddString(StringPiece name, const ResourceId& id, const ConfigDescription& config, - const StringPiece& str) { + StringPiece str) { return AddValue(name, config, id, util::make_unique<String>(table_->string_pool.MakeRef(str))); } -ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name, - const StringPiece& path, +ResourceTableBuilder& ResourceTableBuilder::AddFileReference(StringPiece name, StringPiece path, io::IFile* file) { return AddFileReference(name, {}, path, file); } -ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name, - const ResourceId& id, - const StringPiece& path, - io::IFile* file) { +ResourceTableBuilder& ResourceTableBuilder::AddFileReference(StringPiece name, const ResourceId& id, + StringPiece path, io::IFile* file) { auto file_ref = util::make_unique<FileReference>(table_->string_pool.MakeRef(path)); file_ref->file = file; return AddValue(name, id, std::move(file_ref)); } -ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name, - const StringPiece& path, +ResourceTableBuilder& ResourceTableBuilder::AddFileReference(StringPiece name, StringPiece path, const ConfigDescription& config, io::IFile* file) { auto file_ref = util::make_unique<FileReference>(table_->string_pool.MakeRef(path)); @@ -96,17 +88,17 @@ ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& return AddValue(name, config, {}, std::move(file_ref)); } -ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name, +ResourceTableBuilder& ResourceTableBuilder::AddValue(StringPiece name, std::unique_ptr<Value> value) { return AddValue(name, {}, std::move(value)); } -ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name, const ResourceId& id, +ResourceTableBuilder& ResourceTableBuilder::AddValue(StringPiece name, const ResourceId& id, std::unique_ptr<Value> value) { return AddValue(name, {}, id, std::move(value)); } -ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name, +ResourceTableBuilder& ResourceTableBuilder::AddValue(StringPiece name, const ConfigDescription& config, const ResourceId& id, std::unique_ptr<Value> value) { @@ -121,8 +113,7 @@ ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name, return *this; } -ResourceTableBuilder& ResourceTableBuilder::SetSymbolState(const StringPiece& name, - const ResourceId& id, +ResourceTableBuilder& ResourceTableBuilder::SetSymbolState(StringPiece name, const ResourceId& id, Visibility::Level level, bool allow_new) { ResourceName res_name = ParseNameOrDie(name); @@ -136,9 +127,8 @@ ResourceTableBuilder& ResourceTableBuilder::SetSymbolState(const StringPiece& na return *this; } -ResourceTableBuilder& ResourceTableBuilder::SetOverlayable(const StringPiece& name, +ResourceTableBuilder& ResourceTableBuilder::SetOverlayable(StringPiece name, const OverlayableItem& overlayable) { - ResourceName res_name = ParseNameOrDie(name); CHECK(table_->AddResource( NewResourceBuilder(res_name).SetOverlayable(overlayable).SetAllowMangled(true).Build(), @@ -159,8 +149,7 @@ std::unique_ptr<ResourceTable> ResourceTableBuilder::Build() { return std::move(table_); } -std::unique_ptr<Reference> BuildReference(const StringPiece& ref, - const std::optional<ResourceId>& id) { +std::unique_ptr<Reference> BuildReference(StringPiece ref, const std::optional<ResourceId>& id) { std::unique_ptr<Reference> reference = util::make_unique<Reference>(ParseNameOrDie(ref)); reference->id = id; return reference; @@ -188,7 +177,7 @@ AttributeBuilder& AttributeBuilder::SetWeak(bool weak) { return *this; } -AttributeBuilder& AttributeBuilder::AddItem(const StringPiece& name, uint32_t value) { +AttributeBuilder& AttributeBuilder::AddItem(StringPiece name, uint32_t value) { attr_->symbols.push_back( Attribute::Symbol{Reference(ResourceName({}, ResourceType::kId, name)), value}); return *this; @@ -198,17 +187,17 @@ std::unique_ptr<Attribute> AttributeBuilder::Build() { return std::move(attr_); } -StyleBuilder& StyleBuilder::SetParent(const StringPiece& str) { +StyleBuilder& StyleBuilder::SetParent(StringPiece str) { style_->parent = Reference(ParseNameOrDie(str)); return *this; } -StyleBuilder& StyleBuilder::AddItem(const StringPiece& str, std::unique_ptr<Item> value) { +StyleBuilder& StyleBuilder::AddItem(StringPiece str, std::unique_ptr<Item> value) { style_->entries.push_back(Style::Entry{Reference(ParseNameOrDie(str)), std::move(value)}); return *this; } -StyleBuilder& StyleBuilder::AddItem(const StringPiece& str, const ResourceId& id, +StyleBuilder& StyleBuilder::AddItem(StringPiece str, const ResourceId& id, std::unique_ptr<Item> value) { AddItem(str, std::move(value)); style_->entries.back().key.id = id; @@ -219,8 +208,7 @@ std::unique_ptr<Style> StyleBuilder::Build() { return std::move(style_); } -StyleableBuilder& StyleableBuilder::AddItem(const StringPiece& str, - const std::optional<ResourceId>& id) { +StyleableBuilder& StyleableBuilder::AddItem(StringPiece str, const std::optional<ResourceId>& id) { styleable_->entries.push_back(Reference(ParseNameOrDie(str))); styleable_->entries.back().id = id; return *this; @@ -230,7 +218,7 @@ std::unique_ptr<Styleable> StyleableBuilder::Build() { return std::move(styleable_); } -std::unique_ptr<xml::XmlResource> BuildXmlDom(const StringPiece& str) { +std::unique_ptr<xml::XmlResource> BuildXmlDom(StringPiece str) { std::string input = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; input.append(str.data(), str.size()); StringInputStream in(input); @@ -241,7 +229,7 @@ std::unique_ptr<xml::XmlResource> BuildXmlDom(const StringPiece& str) { } std::unique_ptr<xml::XmlResource> BuildXmlDomForPackageName(IAaptContext* context, - const StringPiece& str) { + StringPiece str) { std::unique_ptr<xml::XmlResource> doc = BuildXmlDom(str); doc->file.name.package = context->GetCompilationPackage(); return doc; diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h index 780bd0df8f16..f03d6fcd03f1 100644 --- a/tools/aapt2/test/Builders.h +++ b/tools/aapt2/test/Builders.h @@ -38,40 +38,35 @@ class ResourceTableBuilder { public: ResourceTableBuilder() = default; - ResourceTableBuilder& AddSimple(const android::StringPiece& name, const ResourceId& id = {}); - ResourceTableBuilder& AddSimple(const android::StringPiece& name, + ResourceTableBuilder& AddSimple(android::StringPiece name, const ResourceId& id = {}); + ResourceTableBuilder& AddSimple(android::StringPiece name, const android::ConfigDescription& config, const ResourceId& id = {}); - ResourceTableBuilder& AddReference(const android::StringPiece& name, - const android::StringPiece& ref); - ResourceTableBuilder& AddReference(const android::StringPiece& name, const ResourceId& id, - const android::StringPiece& ref); - ResourceTableBuilder& AddString(const android::StringPiece& name, - const android::StringPiece& str); - ResourceTableBuilder& AddString(const android::StringPiece& name, const ResourceId& id, - const android::StringPiece& str); - ResourceTableBuilder& AddString(const android::StringPiece& name, const ResourceId& id, + ResourceTableBuilder& AddReference(android::StringPiece name, android::StringPiece ref); + ResourceTableBuilder& AddReference(android::StringPiece name, const ResourceId& id, + android::StringPiece ref); + ResourceTableBuilder& AddString(android::StringPiece name, android::StringPiece str); + ResourceTableBuilder& AddString(android::StringPiece name, const ResourceId& id, + android::StringPiece str); + ResourceTableBuilder& AddString(android::StringPiece name, const ResourceId& id, const android::ConfigDescription& config, - const android::StringPiece& str); - ResourceTableBuilder& AddFileReference(const android::StringPiece& name, - const android::StringPiece& path, + android::StringPiece str); + ResourceTableBuilder& AddFileReference(android::StringPiece name, android::StringPiece path, io::IFile* file = nullptr); - ResourceTableBuilder& AddFileReference(const android::StringPiece& name, const ResourceId& id, - const android::StringPiece& path, - io::IFile* file = nullptr); - ResourceTableBuilder& AddFileReference(const android::StringPiece& name, - const android::StringPiece& path, + ResourceTableBuilder& AddFileReference(android::StringPiece name, const ResourceId& id, + android::StringPiece path, io::IFile* file = nullptr); + ResourceTableBuilder& AddFileReference(android::StringPiece name, android::StringPiece path, const android::ConfigDescription& config, io::IFile* file = nullptr); - ResourceTableBuilder& AddValue(const android::StringPiece& name, std::unique_ptr<Value> value); - ResourceTableBuilder& AddValue(const android::StringPiece& name, const ResourceId& id, + ResourceTableBuilder& AddValue(android::StringPiece name, std::unique_ptr<Value> value); + ResourceTableBuilder& AddValue(android::StringPiece name, const ResourceId& id, + std::unique_ptr<Value> value); + ResourceTableBuilder& AddValue(android::StringPiece name, + const android::ConfigDescription& config, const ResourceId& id, std::unique_ptr<Value> value); - ResourceTableBuilder& AddValue(const android::StringPiece& name, - const android::ConfigDescription& config, - const ResourceId& id, std::unique_ptr<Value> value); - ResourceTableBuilder& SetSymbolState(const android::StringPiece& name, const ResourceId& id, + ResourceTableBuilder& SetSymbolState(android::StringPiece name, const ResourceId& id, Visibility::Level level, bool allow_new = false); - ResourceTableBuilder& SetOverlayable(const android::StringPiece& name, + ResourceTableBuilder& SetOverlayable(android::StringPiece name, const OverlayableItem& overlayable); ResourceTableBuilder& Add(NewResource&& res); @@ -84,7 +79,7 @@ class ResourceTableBuilder { std::unique_ptr<ResourceTable> table_ = util::make_unique<ResourceTable>(); }; -std::unique_ptr<Reference> BuildReference(const android::StringPiece& ref, +std::unique_ptr<Reference> BuildReference(android::StringPiece ref, const std::optional<ResourceId>& id = {}); std::unique_ptr<BinaryPrimitive> BuildPrimitive(uint8_t type, uint32_t data); @@ -101,7 +96,7 @@ class ValueBuilder { return *this; } - ValueBuilder& SetComment(const android::StringPiece& str) { + ValueBuilder& SetComment(android::StringPiece str) { value_->SetComment(str); return *this; } @@ -121,7 +116,7 @@ class AttributeBuilder { AttributeBuilder(); AttributeBuilder& SetTypeMask(uint32_t typeMask); AttributeBuilder& SetWeak(bool weak); - AttributeBuilder& AddItem(const android::StringPiece& name, uint32_t value); + AttributeBuilder& AddItem(android::StringPiece name, uint32_t value); std::unique_ptr<Attribute> Build(); private: @@ -133,9 +128,9 @@ class AttributeBuilder { class StyleBuilder { public: StyleBuilder() = default; - StyleBuilder& SetParent(const android::StringPiece& str); - StyleBuilder& AddItem(const android::StringPiece& str, std::unique_ptr<Item> value); - StyleBuilder& AddItem(const android::StringPiece& str, const ResourceId& id, + StyleBuilder& SetParent(android::StringPiece str); + StyleBuilder& AddItem(android::StringPiece str, std::unique_ptr<Item> value); + StyleBuilder& AddItem(android::StringPiece str, const ResourceId& id, std::unique_ptr<Item> value); std::unique_ptr<Style> Build(); @@ -148,8 +143,7 @@ class StyleBuilder { class StyleableBuilder { public: StyleableBuilder() = default; - StyleableBuilder& AddItem(const android::StringPiece& str, - const std::optional<ResourceId>& id = {}); + StyleableBuilder& AddItem(android::StringPiece str, const std::optional<ResourceId>& id = {}); std::unique_ptr<Styleable> Build(); private: @@ -158,9 +152,9 @@ class StyleableBuilder { std::unique_ptr<Styleable> styleable_ = util::make_unique<Styleable>(); }; -std::unique_ptr<xml::XmlResource> BuildXmlDom(const android::StringPiece& str); +std::unique_ptr<xml::XmlResource> BuildXmlDom(android::StringPiece str); std::unique_ptr<xml::XmlResource> BuildXmlDomForPackageName(IAaptContext* context, - const android::StringPiece& str); + android::StringPiece str); class ArtifactBuilder { public: diff --git a/tools/aapt2/test/Common.cpp b/tools/aapt2/test/Common.cpp index eca0c1c12bab..cdf245341844 100644 --- a/tools/aapt2/test/Common.cpp +++ b/tools/aapt2/test/Common.cpp @@ -44,10 +44,9 @@ android::IDiagnostics* GetDiagnostics() { } template <> -Value* GetValueForConfigAndProduct<Value>(ResourceTable* table, - const android::StringPiece& res_name, +Value* GetValueForConfigAndProduct<Value>(ResourceTable* table, android::StringPiece res_name, const ConfigDescription& config, - const android::StringPiece& product) { + android::StringPiece product) { std::optional<ResourceTable::SearchResult> result = table->FindResource(ParseNameOrDie(res_name)); if (result) { ResourceConfigValue* config_value = result.value().entry->FindValue(config, product); diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h index 3f28361f6c2b..83a0f3f3f652 100644 --- a/tools/aapt2/test/Common.h +++ b/tools/aapt2/test/Common.h @@ -39,22 +39,22 @@ namespace test { android::IDiagnostics* GetDiagnostics(); -inline ResourceName ParseNameOrDie(const android::StringPiece& str) { +inline ResourceName ParseNameOrDie(android::StringPiece str) { ResourceNameRef ref; CHECK(ResourceUtils::ParseResourceName(str, &ref)) << "invalid resource name: " << str; return ref.ToResourceName(); } -inline android::ConfigDescription ParseConfigOrDie(const android::StringPiece& str) { - android::ConfigDescription config; +inline android::ConfigDescription ParseConfigOrDie(android::StringPiece str) { + android::ConfigDescription config; CHECK(android::ConfigDescription::Parse(str, &config)) << "invalid configuration: " << str; return config; } template <typename T = Value> -T* GetValueForConfigAndProduct(ResourceTable* table, const android::StringPiece& res_name, +T* GetValueForConfigAndProduct(ResourceTable* table, android::StringPiece res_name, const android::ConfigDescription& config, - const android::StringPiece& product) { + android::StringPiece product) { std::optional<ResourceTable::SearchResult> result = table->FindResource(ParseNameOrDie(res_name)); if (result) { ResourceConfigValue* config_value = result.value().entry->FindValue(config, product); @@ -66,25 +66,25 @@ T* GetValueForConfigAndProduct(ResourceTable* table, const android::StringPiece& } template <> -Value* GetValueForConfigAndProduct<Value>(ResourceTable* table, - const android::StringPiece& res_name, +Value* GetValueForConfigAndProduct<Value>(ResourceTable* table, android::StringPiece res_name, const android::ConfigDescription& config, - const android::StringPiece& product); + android::StringPiece product); template <typename T = Value> -T* GetValueForConfig(ResourceTable* table, const android::StringPiece& res_name, +T* GetValueForConfig(ResourceTable* table, android::StringPiece res_name, const android::ConfigDescription& config) { return GetValueForConfigAndProduct<T>(table, res_name, config, {}); } template <typename T = Value> -T* GetValue(ResourceTable* table, const android::StringPiece& res_name) { +T* GetValue(ResourceTable* table, android::StringPiece res_name) { return GetValueForConfig<T>(table, res_name, {}); } class TestFile : public io::IFile { public: - explicit TestFile(const android::StringPiece& path) : source_(path) {} + explicit TestFile(android::StringPiece path) : source_(path) { + } std::unique_ptr<io::IData> OpenAsData() override { return {}; diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h index 4e4973ea39be..c5331fb87381 100644 --- a/tools/aapt2/test/Context.h +++ b/tools/aapt2/test/Context.h @@ -52,8 +52,8 @@ class Context : public IAaptContext { return compilation_package_.value(); } - void SetCompilationPackage(const android::StringPiece& package) { - compilation_package_ = package.to_string(); + void SetCompilationPackage(android::StringPiece package) { + compilation_package_ = std::string(package); } uint8_t GetPackageId() override { @@ -111,8 +111,8 @@ class ContextBuilder { return *this; } - ContextBuilder& SetCompilationPackage(const android::StringPiece& package) { - context_->compilation_package_ = package.to_string(); + ContextBuilder& SetCompilationPackage(android::StringPiece package) { + context_->compilation_package_ = std::string(package); return *this; } @@ -149,7 +149,7 @@ class ContextBuilder { class StaticSymbolSourceBuilder { public: - StaticSymbolSourceBuilder& AddPublicSymbol(const android::StringPiece& name, ResourceId id, + StaticSymbolSourceBuilder& AddPublicSymbol(android::StringPiece name, ResourceId id, std::unique_ptr<Attribute> attr = {}) { std::unique_ptr<SymbolTable::Symbol> symbol = util::make_unique<SymbolTable::Symbol>(id, std::move(attr), true); @@ -159,7 +159,7 @@ class StaticSymbolSourceBuilder { return *this; } - StaticSymbolSourceBuilder& AddSymbol(const android::StringPiece& name, ResourceId id, + StaticSymbolSourceBuilder& AddSymbol(android::StringPiece name, ResourceId id, std::unique_ptr<Attribute> attr = {}) { std::unique_ptr<SymbolTable::Symbol> symbol = util::make_unique<SymbolTable::Symbol>(id, std::move(attr), false); diff --git a/tools/aapt2/test/Fixture.cpp b/tools/aapt2/test/Fixture.cpp index dbc0e3643104..428372f31d0d 100644 --- a/tools/aapt2/test/Fixture.cpp +++ b/tools/aapt2/test/Fixture.cpp @@ -38,8 +38,8 @@ namespace aapt { const char* CommandTestFixture::kDefaultPackageName = "com.aapt.command.test"; -void ClearDirectory(const android::StringPiece& path) { - const std::string root_dir = path.to_string(); +void ClearDirectory(android::StringPiece path) { + const std::string root_dir(path); std::unique_ptr<DIR, decltype(closedir)*> dir(opendir(root_dir.data()), closedir); if (!dir) { StdErrDiagnostics().Error(android::DiagMessage() @@ -91,8 +91,7 @@ void TestDirectoryFixture::WriteFile(const std::string& path, const std::string& } bool CommandTestFixture::CompileFile(const std::string& path, const std::string& contents, - const android::StringPiece& out_dir, - android::IDiagnostics* diag) { + android::StringPiece out_dir, android::IDiagnostics* diag) { WriteFile(path, contents); CHECK(file::mkdirs(out_dir.data())); return CompileCommand(diag).Execute({path, "-o", out_dir, "-v"}, &std::cerr) == 0; @@ -113,8 +112,8 @@ bool CommandTestFixture::Link(const std::vector<std::string>& args, android::IDi return LinkCommand(diag).Execute(link_args, &std::cerr) == 0; } -bool CommandTestFixture::Link(const std::vector<std::string>& args, - const android::StringPiece& flat_dir, android::IDiagnostics* diag) { +bool CommandTestFixture::Link(const std::vector<std::string>& args, android::StringPiece flat_dir, + android::IDiagnostics* diag) { std::vector<android::StringPiece> link_args; for(const std::string& arg : args) { link_args.emplace_back(arg); @@ -148,7 +147,7 @@ std::string CommandTestFixture::GetDefaultManifest(const char* package_name) { } std::unique_ptr<io::IData> CommandTestFixture::OpenFileAsData(LoadedApk* apk, - const android::StringPiece& path) { + android::StringPiece path) { return apk ->GetFileCollection() ->FindFile(path) diff --git a/tools/aapt2/test/Fixture.h b/tools/aapt2/test/Fixture.h index 61403b7b0a6d..ba4a734e03bb 100644 --- a/tools/aapt2/test/Fixture.h +++ b/tools/aapt2/test/Fixture.h @@ -48,7 +48,7 @@ class TestDirectoryFixture : public ::testing::Test { // Retrieves the absolute path of the specified relative path in the test directory. Directories // should be separated using forward slashes ('/'), and these slashes will be translated to // backslashes when running Windows tests. - std::string GetTestPath(const android::StringPiece& path) { + std::string GetTestPath(android::StringPiece path) { std::string base = temp_dir_; for (android::StringPiece part : util::Split(path, '/')) { file::AppendPath(&base, part); @@ -73,22 +73,21 @@ class CommandTestFixture : public TestDirectoryFixture { // Wries the contents of the file to the specified path. The file is compiled and the flattened // file is written to the out directory. bool CompileFile(const std::string& path, const std::string& contents, - const android::StringPiece& flat_out_dir, android::IDiagnostics* diag); + android::StringPiece flat_out_dir, android::IDiagnostics* diag); // Executes the link command with the specified arguments. bool Link(const std::vector<std::string>& args, android::IDiagnostics* diag); // Executes the link command with the specified arguments. The flattened files residing in the // flat directory will be added to the link command as file arguments. - bool Link(const std::vector<std::string>& args, const android::StringPiece& flat_dir, + bool Link(const std::vector<std::string>& args, android::StringPiece flat_dir, android::IDiagnostics* diag); // Creates a minimal android manifest within the test directory and returns the file path. std::string GetDefaultManifest(const char* package_name = kDefaultPackageName); // Returns pointer to data inside APK files - std::unique_ptr<io::IData> OpenFileAsData(LoadedApk* apk, - const android::StringPiece& path); + std::unique_ptr<io::IData> OpenFileAsData(LoadedApk* apk, android::StringPiece path); // Asserts that loading the tree from the specified file in the apk succeeds. void AssertLoadXml(LoadedApk* apk, const io::IData* data, diff --git a/tools/aapt2/text/Printer.cpp b/tools/aapt2/text/Printer.cpp index 243800c9385f..8e491aca794d 100644 --- a/tools/aapt2/text/Printer.cpp +++ b/tools/aapt2/text/Printer.cpp @@ -26,7 +26,7 @@ using ::android::StringPiece; namespace aapt { namespace text { -Printer& Printer::Println(const StringPiece& str) { +Printer& Printer::Println(StringPiece str) { Print(str); return Print("\n"); } @@ -35,7 +35,7 @@ Printer& Printer::Println() { return Print("\n"); } -Printer& Printer::Print(const StringPiece& str) { +Printer& Printer::Print(StringPiece str) { if (error_) { return *this; } @@ -47,7 +47,7 @@ Printer& Printer::Print(const StringPiece& str) { const auto new_line_iter = std::find(remaining_str_begin, remaining_str_end, '\n'); // We will copy the string up until the next new-line (or end of string). - const StringPiece str_to_copy = str.substr(remaining_str_begin, new_line_iter); + const StringPiece str_to_copy(remaining_str_begin, new_line_iter - remaining_str_begin); if (!str_to_copy.empty()) { if (needs_indent_) { for (int i = 0; i < indent_level_; i++) { diff --git a/tools/aapt2/text/Printer.h b/tools/aapt2/text/Printer.h index f399f8ea5e0f..f7ad98bfd981 100644 --- a/tools/aapt2/text/Printer.h +++ b/tools/aapt2/text/Printer.h @@ -31,8 +31,8 @@ class Printer { explicit Printer(::aapt::io::OutputStream* out) : out_(out) { } - Printer& Print(const ::android::StringPiece& str); - Printer& Println(const ::android::StringPiece& str); + Printer& Print(android::StringPiece str); + Printer& Println(android::StringPiece str); Printer& Println(); void Indent(); diff --git a/tools/aapt2/text/Unicode.cpp b/tools/aapt2/text/Unicode.cpp index 3735b3e841e0..5e25be3e2812 100644 --- a/tools/aapt2/text/Unicode.cpp +++ b/tools/aapt2/text/Unicode.cpp @@ -77,7 +77,7 @@ bool IsWhitespace(char32_t codepoint) { (codepoint == 0x3000); } -bool IsJavaIdentifier(const StringPiece& str) { +bool IsJavaIdentifier(StringPiece str) { Utf8Iterator iter(str); // Check the first character. @@ -99,7 +99,7 @@ bool IsJavaIdentifier(const StringPiece& str) { return true; } -bool IsValidResourceEntryName(const StringPiece& str) { +bool IsValidResourceEntryName(StringPiece str) { Utf8Iterator iter(str); // Check the first character. diff --git a/tools/aapt2/text/Unicode.h b/tools/aapt2/text/Unicode.h index 546714e9a290..ab3e82b00f08 100644 --- a/tools/aapt2/text/Unicode.h +++ b/tools/aapt2/text/Unicode.h @@ -46,11 +46,11 @@ bool IsWhitespace(char32_t codepoint); // Returns true if the UTF8 string can be used as a Java identifier. // NOTE: This does not check against the set of reserved Java keywords. -bool IsJavaIdentifier(const android::StringPiece& str); +bool IsJavaIdentifier(android::StringPiece str); // Returns true if the UTF8 string can be used as the entry name of a resource name. // This is the `entry` part of package:type/entry. -bool IsValidResourceEntryName(const android::StringPiece& str); +bool IsValidResourceEntryName(android::StringPiece str); } // namespace text } // namespace aapt diff --git a/tools/aapt2/text/Utf8Iterator.cpp b/tools/aapt2/text/Utf8Iterator.cpp index 20b9073b9a26..0bd8a375a255 100644 --- a/tools/aapt2/text/Utf8Iterator.cpp +++ b/tools/aapt2/text/Utf8Iterator.cpp @@ -24,7 +24,7 @@ using ::android::StringPiece; namespace aapt { namespace text { -Utf8Iterator::Utf8Iterator(const StringPiece& str) +Utf8Iterator::Utf8Iterator(StringPiece str) : str_(str), current_pos_(0), next_pos_(0), current_codepoint_(0) { DoNext(); } diff --git a/tools/aapt2/text/Utf8Iterator.h b/tools/aapt2/text/Utf8Iterator.h index 9318401876d1..2bba1984a8ce 100644 --- a/tools/aapt2/text/Utf8Iterator.h +++ b/tools/aapt2/text/Utf8Iterator.h @@ -25,7 +25,7 @@ namespace text { class Utf8Iterator { public: - explicit Utf8Iterator(const android::StringPiece& str); + explicit Utf8Iterator(android::StringPiece str); bool HasNext() const; diff --git a/tools/aapt2/trace/TraceBuffer.cpp b/tools/aapt2/trace/TraceBuffer.cpp index b4b31d9daf6e..da5373936306 100644 --- a/tools/aapt2/trace/TraceBuffer.cpp +++ b/tools/aapt2/trace/TraceBuffer.cpp @@ -103,7 +103,7 @@ Trace::Trace(const std::string& tag, const std::vector<android::StringPiece>& ar s << tag; s << " "; for (auto& arg : args) { - s << arg.to_string(); + s << arg; s << " "; } tracebuffer::Add(s.str(), tracebuffer::kBegin); @@ -124,7 +124,7 @@ FlushTrace::FlushTrace(const std::string& basepath, const std::string& tag, s << tag; s << " "; for (auto& arg : args) { - s << arg.to_string(); + s << arg; s << " "; } tracebuffer::Add(s.str(), tracebuffer::kBegin); diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp index 5d5b7cd7d472..93c1b61f9a57 100644 --- a/tools/aapt2/util/Files.cpp +++ b/tools/aapt2/util/Files.cpp @@ -139,7 +139,7 @@ bool mkdirs(const std::string& path) { return ::android::base::utf8::mkdir(path.c_str(), mode) == 0 || errno == EEXIST; } -StringPiece GetStem(const StringPiece& path) { +StringPiece GetStem(StringPiece path) { const char* start = path.begin(); const char* end = path.end(); for (const char* current = end - 1; current != start - 1; --current) { @@ -150,7 +150,7 @@ StringPiece GetStem(const StringPiece& path) { return {}; } -StringPiece GetFilename(const StringPiece& path) { +StringPiece GetFilename(StringPiece path) { const char* end = path.end(); const char* last_dir_sep = path.begin(); for (const char* c = path.begin(); c != end; ++c) { @@ -161,7 +161,7 @@ StringPiece GetFilename(const StringPiece& path) { return StringPiece(last_dir_sep, end - last_dir_sep); } -StringPiece GetExtension(const StringPiece& path) { +StringPiece GetExtension(StringPiece path) { StringPiece filename = GetFilename(path); const char* const end = filename.end(); const char* c = std::find(filename.begin(), end, '.'); @@ -171,7 +171,7 @@ StringPiece GetExtension(const StringPiece& path) { return {}; } -bool IsHidden(const android::StringPiece& path) { +bool IsHidden(android::StringPiece path) { return util::StartsWith(GetFilename(path), "."); } @@ -193,16 +193,16 @@ std::string BuildPath(std::vector<const StringPiece>&& args) { if (args.empty()) { return ""; } - std::string out = args[0].to_string(); + std::string out{args[0]}; for (int i = 1; i < args.size(); i++) { file::AppendPath(&out, args[i]); } return out; } -std::string PackageToPath(const StringPiece& package) { +std::string PackageToPath(StringPiece package) { std::string out_path; - for (const StringPiece& part : util::Tokenize(package, '.')) { + for (StringPiece part : util::Tokenize(package, '.')) { AppendPath(&out_path, part); } return out_path; @@ -241,10 +241,10 @@ std::optional<FileMap> MmapPath(const std::string& path, std::string* out_error) return std::move(filemap); } -bool AppendArgsFromFile(const StringPiece& path, std::vector<std::string>* out_arglist, +bool AppendArgsFromFile(StringPiece path, std::vector<std::string>* out_arglist, std::string* out_error) { std::string contents; - if (!ReadFileToString(path.to_string(), &contents, true /*follow_symlinks*/)) { + if (!ReadFileToString(std::string(path), &contents, true /*follow_symlinks*/)) { if (out_error) { *out_error = "failed to read argument-list file"; } @@ -254,16 +254,16 @@ bool AppendArgsFromFile(const StringPiece& path, std::vector<std::string>* out_a for (StringPiece line : util::Tokenize(contents, ' ')) { line = util::TrimWhitespace(line); if (!line.empty()) { - out_arglist->push_back(line.to_string()); + out_arglist->emplace_back(line); } } return true; } -bool AppendSetArgsFromFile(const StringPiece& path, std::unordered_set<std::string>* out_argset, +bool AppendSetArgsFromFile(StringPiece path, std::unordered_set<std::string>* out_argset, std::string* out_error) { std::string contents; - if(!ReadFileToString(path.to_string(), &contents, true /*follow_symlinks*/)) { + if (!ReadFileToString(std::string(path), &contents, true /*follow_symlinks*/)) { if (out_error) { *out_error = "failed to read argument-list file"; } @@ -273,13 +273,13 @@ bool AppendSetArgsFromFile(const StringPiece& path, std::unordered_set<std::stri for (StringPiece line : util::Tokenize(contents, ' ')) { line = util::TrimWhitespace(line); if (!line.empty()) { - out_argset->insert(line.to_string()); + out_argset->emplace(line); } } return true; } -bool FileFilter::SetPattern(const StringPiece& pattern) { +bool FileFilter::SetPattern(StringPiece pattern) { pattern_tokens_ = util::SplitAndLowercase(pattern, ':'); return true; } @@ -343,10 +343,10 @@ bool FileFilter::operator()(const std::string& filename, FileType type) const { return true; } -std::optional<std::vector<std::string>> FindFiles(const android::StringPiece& path, +std::optional<std::vector<std::string>> FindFiles(android::StringPiece path, android::IDiagnostics* diag, const FileFilter* filter) { - const std::string root_dir = path.to_string(); + const auto& root_dir = path; std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()), closedir); if (!d) { diag->Error(android::DiagMessage() << SystemErrorCodeToString(errno) << ": " << root_dir); @@ -361,7 +361,7 @@ std::optional<std::vector<std::string>> FindFiles(const android::StringPiece& pa } std::string file_name = entry->d_name; - std::string full_path = root_dir; + std::string full_path{root_dir}; AppendPath(&full_path, file_name); const FileType file_type = GetFileType(full_path); @@ -380,7 +380,7 @@ std::optional<std::vector<std::string>> FindFiles(const android::StringPiece& pa // Now process subdirs. for (const std::string& subdir : subdirs) { - std::string full_subdir = root_dir; + std::string full_subdir{root_dir}; AppendPath(&full_subdir, subdir); std::optional<std::vector<std::string>> subfiles = FindFiles(full_subdir, diag, filter); if (!subfiles) { diff --git a/tools/aapt2/util/Files.h b/tools/aapt2/util/Files.h index ee95712f157d..42eeaf2d2e2a 100644 --- a/tools/aapt2/util/Files.h +++ b/tools/aapt2/util/Files.h @@ -66,31 +66,31 @@ std::string BuildPath(std::vector<const android::StringPiece>&& args); bool mkdirs(const std::string& path); // Returns all but the last part of the path. -android::StringPiece GetStem(const android::StringPiece& path); +android::StringPiece GetStem(android::StringPiece path); // Returns the last part of the path with extension. -android::StringPiece GetFilename(const android::StringPiece& path); +android::StringPiece GetFilename(android::StringPiece path); // Returns the extension of the path. This is the entire string after the first '.' of the last part // of the path. -android::StringPiece GetExtension(const android::StringPiece& path); +android::StringPiece GetExtension(android::StringPiece path); // Returns whether or not the name of the file or directory is a hidden file name -bool IsHidden(const android::StringPiece& path); +bool IsHidden(android::StringPiece path); // Converts a package name (com.android.app) to a path: com/android/app -std::string PackageToPath(const android::StringPiece& package); +std::string PackageToPath(android::StringPiece package); // Creates a FileMap for the file at path. std::optional<android::FileMap> MmapPath(const std::string& path, std::string* out_error); // Reads the file at path and appends each line to the outArgList vector. -bool AppendArgsFromFile(const android::StringPiece& path, std::vector<std::string>* out_arglist, +bool AppendArgsFromFile(android::StringPiece path, std::vector<std::string>* out_arglist, std::string* out_error); // Reads the file at path and appends each line to the outargset set. -bool AppendSetArgsFromFile(const android::StringPiece& path, - std::unordered_set<std::string>* out_argset, std::string* out_error); +bool AppendSetArgsFromFile(android::StringPiece path, std::unordered_set<std::string>* out_argset, + std::string* out_error); // Filter that determines which resource files/directories are // processed by AAPT. Takes a pattern string supplied by the user. @@ -112,7 +112,7 @@ class FileFilter { // - The special filenames "." and ".." are always ignored. // - Otherwise the full string is matched. // - match is not case-sensitive. - bool SetPattern(const android::StringPiece& pattern); + bool SetPattern(android::StringPiece pattern); // Applies the filter, returning true for pass, false for fail. bool operator()(const std::string& filename, FileType type) const; @@ -126,7 +126,7 @@ class FileFilter { // Returns a list of files relative to the directory identified by `path`. // An optional FileFilter filters out any files that don't pass. -std::optional<std::vector<std::string>> FindFiles(const android::StringPiece& path, +std::optional<std::vector<std::string>> FindFiles(android::StringPiece path, android::IDiagnostics* diag, const FileFilter* filter = nullptr); diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp index 9b7ebdd690ac..be877660ef72 100644 --- a/tools/aapt2/util/Util.cpp +++ b/tools/aapt2/util/Util.cpp @@ -43,15 +43,14 @@ namespace util { // See frameworks/base/core/java/android/content/pm/parsing/ParsingPackageUtils.java constexpr static const size_t kMaxPackageNameSize = 223; -static std::vector<std::string> SplitAndTransform( - const StringPiece& str, char sep, const std::function<char(char)>& f) { +static std::vector<std::string> SplitAndTransform(StringPiece str, char sep, char (*f)(char)) { std::vector<std::string> parts; const StringPiece::const_iterator end = std::end(str); StringPiece::const_iterator start = std::begin(str); StringPiece::const_iterator current; do { current = std::find(start, end, sep); - parts.emplace_back(str.substr(start, current).to_string()); + parts.emplace_back(start, current); if (f) { std::string& part = parts.back(); std::transform(part.begin(), part.end(), part.begin(), f); @@ -61,29 +60,29 @@ static std::vector<std::string> SplitAndTransform( return parts; } -std::vector<std::string> Split(const StringPiece& str, char sep) { +std::vector<std::string> Split(StringPiece str, char sep) { return SplitAndTransform(str, sep, nullptr); } -std::vector<std::string> SplitAndLowercase(const StringPiece& str, char sep) { - return SplitAndTransform(str, sep, ::tolower); +std::vector<std::string> SplitAndLowercase(StringPiece str, char sep) { + return SplitAndTransform(str, sep, [](char c) -> char { return ::tolower(c); }); } -bool StartsWith(const StringPiece& str, const StringPiece& prefix) { +bool StartsWith(StringPiece str, StringPiece prefix) { if (str.size() < prefix.size()) { return false; } return str.substr(0, prefix.size()) == prefix; } -bool EndsWith(const StringPiece& str, const StringPiece& suffix) { +bool EndsWith(StringPiece str, StringPiece suffix) { if (str.size() < suffix.size()) { return false; } return str.substr(str.size() - suffix.size(), suffix.size()) == suffix; } -StringPiece TrimLeadingWhitespace(const StringPiece& str) { +StringPiece TrimLeadingWhitespace(StringPiece str) { if (str.size() == 0 || str.data() == nullptr) { return str; } @@ -97,7 +96,7 @@ StringPiece TrimLeadingWhitespace(const StringPiece& str) { return StringPiece(start, end - start); } -StringPiece TrimTrailingWhitespace(const StringPiece& str) { +StringPiece TrimTrailingWhitespace(StringPiece str) { if (str.size() == 0 || str.data() == nullptr) { return str; } @@ -111,7 +110,7 @@ StringPiece TrimTrailingWhitespace(const StringPiece& str) { return StringPiece(start, end - start); } -StringPiece TrimWhitespace(const StringPiece& str) { +StringPiece TrimWhitespace(StringPiece str) { if (str.size() == 0 || str.data() == nullptr) { return str; } @@ -130,9 +129,9 @@ StringPiece TrimWhitespace(const StringPiece& str) { return StringPiece(start, end - start); } -static int IsJavaNameImpl(const StringPiece& str) { +static int IsJavaNameImpl(StringPiece str) { int pieces = 0; - for (const StringPiece& piece : Tokenize(str, '.')) { + for (StringPiece piece : Tokenize(str, '.')) { pieces++; if (!text::IsJavaIdentifier(piece)) { return -1; @@ -141,17 +140,17 @@ static int IsJavaNameImpl(const StringPiece& str) { return pieces; } -bool IsJavaClassName(const StringPiece& str) { +bool IsJavaClassName(StringPiece str) { return IsJavaNameImpl(str) >= 2; } -bool IsJavaPackageName(const StringPiece& str) { +bool IsJavaPackageName(StringPiece str) { return IsJavaNameImpl(str) >= 1; } -static int IsAndroidNameImpl(const StringPiece& str) { +static int IsAndroidNameImpl(StringPiece str) { int pieces = 0; - for (const StringPiece& piece : Tokenize(str, '.')) { + for (StringPiece piece : Tokenize(str, '.')) { if (piece.empty()) { return -1; } @@ -173,15 +172,14 @@ static int IsAndroidNameImpl(const StringPiece& str) { return pieces; } -bool IsAndroidPackageName(const StringPiece& str) { +bool IsAndroidPackageName(StringPiece str) { if (str.size() > kMaxPackageNameSize) { return false; } return IsAndroidNameImpl(str) > 1 || str == "android"; } -bool IsAndroidSharedUserId(const android::StringPiece& package_name, - const android::StringPiece& shared_user_id) { +bool IsAndroidSharedUserId(android::StringPiece package_name, android::StringPiece shared_user_id) { if (shared_user_id.size() > kMaxPackageNameSize) { return false; } @@ -189,25 +187,24 @@ bool IsAndroidSharedUserId(const android::StringPiece& package_name, package_name == "android"; } -bool IsAndroidSplitName(const StringPiece& str) { +bool IsAndroidSplitName(StringPiece str) { return IsAndroidNameImpl(str) > 0; } -std::optional<std::string> GetFullyQualifiedClassName(const StringPiece& package, - const StringPiece& classname) { +std::optional<std::string> GetFullyQualifiedClassName(StringPiece package, StringPiece classname) { if (classname.empty()) { return {}; } if (util::IsJavaClassName(classname)) { - return classname.to_string(); + return std::string(classname); } if (package.empty()) { return {}; } - std::string result = package.to_string(); + std::string result{package}; if (classname.data()[0] != '.') { result += '.'; } @@ -251,7 +248,7 @@ static size_t ConsumeDigits(const char* start, const char* end) { return static_cast<size_t>(c - start); } -bool VerifyJavaStringFormat(const StringPiece& str) { +bool VerifyJavaStringFormat(StringPiece str) { const char* c = str.begin(); const char* const end = str.end(); @@ -341,7 +338,7 @@ bool VerifyJavaStringFormat(const StringPiece& str) { return true; } -std::u16string Utf8ToUtf16(const StringPiece& utf8) { +std::u16string Utf8ToUtf16(StringPiece utf8) { ssize_t utf16_length = utf8_to_utf16_length( reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length()); if (utf16_length <= 0) { @@ -381,7 +378,7 @@ typename Tokenizer::iterator& Tokenizer::iterator::operator++() { const char* end = str_.end(); if (start == end) { end_ = true; - token_.assign(token_.end(), 0); + token_ = StringPiece(token_.end(), 0); return *this; } @@ -389,12 +386,12 @@ typename Tokenizer::iterator& Tokenizer::iterator::operator++() { const char* current = start; while (current != end) { if (*current == separator_) { - token_.assign(start, current - start); + token_ = StringPiece(start, current - start); return *this; } ++current; } - token_.assign(start, end - start); + token_ = StringPiece(start, end - start); return *this; } @@ -409,15 +406,17 @@ bool Tokenizer::iterator::operator!=(const iterator& rhs) const { return !(*this == rhs); } -Tokenizer::iterator::iterator(const StringPiece& s, char sep, const StringPiece& tok, bool end) - : str_(s), separator_(sep), token_(tok), end_(end) {} +Tokenizer::iterator::iterator(StringPiece s, char sep, StringPiece tok, bool end) + : str_(s), separator_(sep), token_(tok), end_(end) { +} -Tokenizer::Tokenizer(const StringPiece& str, char sep) +Tokenizer::Tokenizer(StringPiece str, char sep) : begin_(++iterator(str, sep, StringPiece(str.begin() - 1, 0), false)), - end_(str, sep, StringPiece(str.end(), 0), true) {} + end_(str, sep, StringPiece(str.end(), 0), true) { +} -bool ExtractResFilePathParts(const StringPiece& path, StringPiece* out_prefix, - StringPiece* out_entry, StringPiece* out_suffix) { +bool ExtractResFilePathParts(StringPiece path, StringPiece* out_prefix, StringPiece* out_entry, + StringPiece* out_suffix) { const StringPiece res_prefix("res/"); if (!StartsWith(path, res_prefix)) { return false; diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h index 8d3b41315485..40ff5b633d97 100644 --- a/tools/aapt2/util/Util.h +++ b/tools/aapt2/util/Util.h @@ -48,44 +48,44 @@ struct Range { T end; }; -std::vector<std::string> Split(const android::StringPiece& str, char sep); -std::vector<std::string> SplitAndLowercase(const android::StringPiece& str, char sep); +std::vector<std::string> Split(android::StringPiece str, char sep); +std::vector<std::string> SplitAndLowercase(android::StringPiece str, char sep); // Returns true if the string starts with prefix. -bool StartsWith(const android::StringPiece& str, const android::StringPiece& prefix); +bool StartsWith(android::StringPiece str, android::StringPiece prefix); // Returns true if the string ends with suffix. -bool EndsWith(const android::StringPiece& str, const android::StringPiece& suffix); +bool EndsWith(android::StringPiece str, android::StringPiece suffix); // Creates a new StringPiece that points to a substring of the original string without leading // whitespace. -android::StringPiece TrimLeadingWhitespace(const android::StringPiece& str); +android::StringPiece TrimLeadingWhitespace(android::StringPiece str); // Creates a new StringPiece that points to a substring of the original string without trailing // whitespace. -android::StringPiece TrimTrailingWhitespace(const android::StringPiece& str); +android::StringPiece TrimTrailingWhitespace(android::StringPiece str); // Creates a new StringPiece that points to a substring of the original string without leading or // trailing whitespace. -android::StringPiece TrimWhitespace(const android::StringPiece& str); +android::StringPiece TrimWhitespace(android::StringPiece str); // Tests that the string is a valid Java class name. -bool IsJavaClassName(const android::StringPiece& str); +bool IsJavaClassName(android::StringPiece str); // Tests that the string is a valid Java package name. -bool IsJavaPackageName(const android::StringPiece& str); +bool IsJavaPackageName(android::StringPiece str); // Tests that the string is a valid Android package name. More strict than a Java package name. // - First character of each component (separated by '.') must be an ASCII letter. // - Subsequent characters of a component can be ASCII alphanumeric or an underscore. // - Package must contain at least two components, unless it is 'android'. // - The maximum package name length is 223. -bool IsAndroidPackageName(const android::StringPiece& str); +bool IsAndroidPackageName(android::StringPiece str); // Tests that the string is a valid Android split name. // - First character of each component (separated by '.') must be an ASCII letter. // - Subsequent characters of a component can be ASCII alphanumeric or an underscore. -bool IsAndroidSplitName(const android::StringPiece& str); +bool IsAndroidSplitName(android::StringPiece str); // Tests that the string is a valid Android shared user id. // - First character of each component (separated by '.') must be an ASCII letter. @@ -93,8 +93,7 @@ bool IsAndroidSplitName(const android::StringPiece& str); // - Must contain at least two components, unless package name is 'android'. // - The maximum shared user id length is 223. // - Treat empty string as valid, it's the case of no shared user id. -bool IsAndroidSharedUserId(const android::StringPiece& package_name, - const android::StringPiece& shared_user_id); +bool IsAndroidSharedUserId(android::StringPiece package_name, android::StringPiece shared_user_id); // Converts the class name to a fully qualified class name from the given // `package`. Ex: @@ -103,8 +102,8 @@ bool IsAndroidSharedUserId(const android::StringPiece& package_name, // .asdf --> package.asdf // .a.b --> package.a.b // asdf.adsf --> asdf.adsf -std::optional<std::string> GetFullyQualifiedClassName(const android::StringPiece& package, - const android::StringPiece& class_name); +std::optional<std::string> GetFullyQualifiedClassName(android::StringPiece package, + android::StringPiece class_name); // Retrieves the formatted name of aapt2. const char* GetToolName(); @@ -152,16 +151,16 @@ template <typename Container> // explicitly specifying an index) when there are more than one argument. This is an error // because translations may rearrange the order of the arguments in the string, which will // break the string interpolation. -bool VerifyJavaStringFormat(const android::StringPiece& str); +bool VerifyJavaStringFormat(android::StringPiece str); -bool AppendStyledString(const android::StringPiece& input, bool preserve_spaces, - std::string* out_str, std::string* out_error); +bool AppendStyledString(android::StringPiece input, bool preserve_spaces, std::string* out_str, + std::string* out_error); class StringBuilder { public: StringBuilder() = default; - StringBuilder& Append(const android::StringPiece& str); + StringBuilder& Append(android::StringPiece str); const std::string& ToString() const; const std::string& Error() const; bool IsEmpty() const; @@ -229,7 +228,7 @@ class Tokenizer { private: friend class Tokenizer; - iterator(const android::StringPiece& s, char sep, const android::StringPiece& tok, bool end); + iterator(android::StringPiece s, char sep, android::StringPiece tok, bool end); android::StringPiece str_; char separator_; @@ -237,7 +236,7 @@ class Tokenizer { bool end_; }; - Tokenizer(const android::StringPiece& str, char sep); + Tokenizer(android::StringPiece str, char sep); iterator begin() const { return begin_; @@ -252,7 +251,7 @@ class Tokenizer { const iterator end_; }; -inline Tokenizer Tokenize(const android::StringPiece& str, char sep) { +inline Tokenizer Tokenize(android::StringPiece str, char sep) { return Tokenizer(str, sep); } @@ -263,7 +262,7 @@ inline Tokenizer Tokenize(const android::StringPiece& str, char sep) { // Extracts ".xml" into outSuffix. // // Returns true if successful. -bool ExtractResFilePathParts(const android::StringPiece& path, android::StringPiece* out_prefix, +bool ExtractResFilePathParts(android::StringPiece path, android::StringPiece* out_prefix, android::StringPiece* out_entry, android::StringPiece* out_suffix); } // namespace util diff --git a/tools/aapt2/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp index 4ebcb115306f..15135690d0de 100644 --- a/tools/aapt2/util/Util_test.cpp +++ b/tools/aapt2/util/Util_test.cpp @@ -84,6 +84,14 @@ TEST(UtilTest, TokenizeAtEnd) { ASSERT_THAT(*iter, Eq(StringPiece())); } +TEST(UtilTest, TokenizeNone) { + auto tokenizer = util::Tokenize(StringPiece("none"), '.'); + auto iter = tokenizer.begin(); + ASSERT_THAT(*iter, Eq("none")); + ++iter; + ASSERT_THAT(iter, Eq(tokenizer.end())); +} + TEST(UtilTest, IsJavaClassName) { EXPECT_TRUE(util::IsJavaClassName("android.test.Class")); EXPECT_TRUE(util::IsJavaClassName("android.test.Class$Inner")); diff --git a/tools/aapt2/xml/XmlActionExecutor.cpp b/tools/aapt2/xml/XmlActionExecutor.cpp index 9bdbd22b5697..3ccbaa2a4b6c 100644 --- a/tools/aapt2/xml/XmlActionExecutor.cpp +++ b/tools/aapt2/xml/XmlActionExecutor.cpp @@ -84,7 +84,7 @@ bool XmlNodeAction::Execute(XmlActionExecutorPolicy policy, std::vector<StringPi error_msg << "unexpected element "; PrintElementToDiagMessage(child_el, &error_msg); error_msg << " found in "; - for (const StringPiece& element : *bread_crumb) { + for (StringPiece element : *bread_crumb) { error_msg << "<" << element << ">"; } if (policy == XmlActionExecutorPolicy::kAllowListWarning) { diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp index f51e8a47041d..8dea8ea52f92 100644 --- a/tools/aapt2/xml/XmlDom.cpp +++ b/tools/aapt2/xml/XmlDom.cpp @@ -169,7 +169,7 @@ static void XMLCALL CharacterDataHandler(void* user_data, const char* s, int len stack->last_text_node = util::make_unique<Text>(); stack->last_text_node->line_number = XML_GetCurrentLineNumber(parser); stack->last_text_node->column_number = XML_GetCurrentColumnNumber(parser); - stack->last_text_node->text = str.to_string(); + stack->last_text_node->text.assign(str); } static void XMLCALL CommentDataHandler(void* user_data, const char* comment) { @@ -417,11 +417,11 @@ void Element::InsertChild(size_t index, std::unique_ptr<Node> child) { children.insert(children.begin() + index, std::move(child)); } -Attribute* Element::FindAttribute(const StringPiece& ns, const StringPiece& name) { +Attribute* Element::FindAttribute(StringPiece ns, StringPiece name) { return const_cast<Attribute*>(static_cast<const Element*>(this)->FindAttribute(ns, name)); } -const Attribute* Element::FindAttribute(const StringPiece& ns, const StringPiece& name) const { +const Attribute* Element::FindAttribute(StringPiece ns, StringPiece name) const { for (const auto& attr : attributes) { if (ns == attr.namespace_uri && name == attr.name) { return &attr; @@ -430,7 +430,7 @@ const Attribute* Element::FindAttribute(const StringPiece& ns, const StringPiece return nullptr; } -void Element::RemoveAttribute(const StringPiece& ns, const StringPiece& name) { +void Element::RemoveAttribute(StringPiece ns, StringPiece name) { auto new_attr_end = std::remove_if(attributes.begin(), attributes.end(), [&](const Attribute& attr) -> bool { return ns == attr.namespace_uri && name == attr.name; @@ -439,34 +439,32 @@ void Element::RemoveAttribute(const StringPiece& ns, const StringPiece& name) { attributes.erase(new_attr_end, attributes.end()); } -Attribute* Element::FindOrCreateAttribute(const StringPiece& ns, const StringPiece& name) { +Attribute* Element::FindOrCreateAttribute(StringPiece ns, StringPiece name) { Attribute* attr = FindAttribute(ns, name); if (attr == nullptr) { - attributes.push_back(Attribute{ns.to_string(), name.to_string()}); + attributes.push_back(Attribute{std::string(ns), std::string(name)}); attr = &attributes.back(); } return attr; } -Element* Element::FindChild(const StringPiece& ns, const StringPiece& name) { +Element* Element::FindChild(StringPiece ns, StringPiece name) { return FindChildWithAttribute(ns, name, {}, {}, {}); } -const Element* Element::FindChild(const StringPiece& ns, const StringPiece& name) const { +const Element* Element::FindChild(StringPiece ns, StringPiece name) const { return FindChildWithAttribute(ns, name, {}, {}, {}); } -Element* Element::FindChildWithAttribute(const StringPiece& ns, const StringPiece& name, - const StringPiece& attr_ns, const StringPiece& attr_name, - const StringPiece& attr_value) { +Element* Element::FindChildWithAttribute(StringPiece ns, StringPiece name, StringPiece attr_ns, + StringPiece attr_name, StringPiece attr_value) { return const_cast<Element*>(static_cast<const Element*>(this)->FindChildWithAttribute( ns, name, attr_ns, attr_name, attr_value)); } -const Element* Element::FindChildWithAttribute(const StringPiece& ns, const StringPiece& name, - const StringPiece& attr_ns, - const StringPiece& attr_name, - const StringPiece& attr_value) const { +const Element* Element::FindChildWithAttribute(StringPiece ns, StringPiece name, + StringPiece attr_ns, StringPiece attr_name, + StringPiece attr_value) const { for (const auto& child : children) { if (const Element* el = NodeCast<Element>(child.get())) { if (ns == el->namespace_uri && name == el->name) { @@ -559,7 +557,7 @@ void PackageAwareVisitor::AfterVisitElement(Element* el) { } std::optional<ExtractedPackage> PackageAwareVisitor::TransformPackageAlias( - const StringPiece& alias) const { + StringPiece alias) const { if (alias.empty()) { return ExtractedPackage{{}, false /*private*/}; } diff --git a/tools/aapt2/xml/XmlDom.h b/tools/aapt2/xml/XmlDom.h index 5bc55b6b68a1..c253b0a1f4a9 100644 --- a/tools/aapt2/xml/XmlDom.h +++ b/tools/aapt2/xml/XmlDom.h @@ -96,27 +96,22 @@ class Element : public Node { void AppendChild(std::unique_ptr<Node> child); void InsertChild(size_t index, std::unique_ptr<Node> child); - Attribute* FindAttribute(const android::StringPiece& ns, const android::StringPiece& name); - const Attribute* FindAttribute(const android::StringPiece& ns, - const android::StringPiece& name) const; - Attribute* FindOrCreateAttribute(const android::StringPiece& ns, - const android::StringPiece& name); - void RemoveAttribute(const android::StringPiece& ns, - const android::StringPiece& name); - - Element* FindChild(const android::StringPiece& ns, const android::StringPiece& name); - const Element* FindChild(const android::StringPiece& ns, const android::StringPiece& name) const; - - Element* FindChildWithAttribute(const android::StringPiece& ns, const android::StringPiece& name, - const android::StringPiece& attr_ns, - const android::StringPiece& attr_name, - const android::StringPiece& attr_value); - - const Element* FindChildWithAttribute(const android::StringPiece& ns, - const android::StringPiece& name, - const android::StringPiece& attr_ns, - const android::StringPiece& attr_name, - const android::StringPiece& attr_value) const; + Attribute* FindAttribute(android::StringPiece ns, android::StringPiece name); + const Attribute* FindAttribute(android::StringPiece ns, android::StringPiece name) const; + Attribute* FindOrCreateAttribute(android::StringPiece ns, android::StringPiece name); + void RemoveAttribute(android::StringPiece ns, android::StringPiece name); + + Element* FindChild(android::StringPiece ns, android::StringPiece name); + const Element* FindChild(android::StringPiece ns, android::StringPiece name) const; + + Element* FindChildWithAttribute(android::StringPiece ns, android::StringPiece name, + android::StringPiece attr_ns, android::StringPiece attr_name, + android::StringPiece attr_value); + + const Element* FindChildWithAttribute(android::StringPiece ns, android::StringPiece name, + android::StringPiece attr_ns, + android::StringPiece attr_name, + android::StringPiece attr_value) const; std::vector<Element*> GetChildElements(); @@ -235,8 +230,7 @@ class PackageAwareVisitor : public Visitor, public IPackageDeclStack { public: using Visitor::Visit; - std::optional<ExtractedPackage> TransformPackageAlias( - const android::StringPiece& alias) const override; + std::optional<ExtractedPackage> TransformPackageAlias(android::StringPiece alias) const override; protected: PackageAwareVisitor() = default; diff --git a/tools/aapt2/xml/XmlPullParser.cpp b/tools/aapt2/xml/XmlPullParser.cpp index bfa07490b9c0..d79446bfae6f 100644 --- a/tools/aapt2/xml/XmlPullParser.cpp +++ b/tools/aapt2/xml/XmlPullParser.cpp @@ -140,8 +140,7 @@ const std::string& XmlPullParser::namespace_uri() const { return event_queue_.front().data2; } -std::optional<ExtractedPackage> XmlPullParser::TransformPackageAlias( - const StringPiece& alias) const { +std::optional<ExtractedPackage> XmlPullParser::TransformPackageAlias(StringPiece alias) const { if (alias.empty()) { return ExtractedPackage{{}, false /*private*/}; } @@ -307,7 +306,7 @@ void XMLCALL XmlPullParser::EndCdataSectionHandler(void* user_data) { parser->depth_ }); } -std::optional<StringPiece> FindAttribute(const XmlPullParser* parser, const StringPiece& name) { +std::optional<StringPiece> FindAttribute(const XmlPullParser* parser, StringPiece name) { auto iter = parser->FindAttribute("", name); if (iter != parser->end_attributes()) { return StringPiece(util::TrimWhitespace(iter->value)); @@ -315,8 +314,7 @@ std::optional<StringPiece> FindAttribute(const XmlPullParser* parser, const Stri return {}; } -std::optional<StringPiece> FindNonEmptyAttribute(const XmlPullParser* parser, - const StringPiece& name) { +std::optional<StringPiece> FindNonEmptyAttribute(const XmlPullParser* parser, StringPiece name) { auto iter = parser->FindAttribute("", name); if (iter != parser->end_attributes()) { StringPiece trimmed = util::TrimWhitespace(iter->value); diff --git a/tools/aapt2/xml/XmlPullParser.h b/tools/aapt2/xml/XmlPullParser.h index ab347728ae4b..fe4cd018d808 100644 --- a/tools/aapt2/xml/XmlPullParser.h +++ b/tools/aapt2/xml/XmlPullParser.h @@ -120,8 +120,7 @@ class XmlPullParser : public IPackageDeclStack { * If xmlns:app="http://schemas.android.com/apk/res-auto", then * 'package' will be set to 'defaultPackage'. */ - std::optional<ExtractedPackage> TransformPackageAlias( - const android::StringPiece& alias) const override; + std::optional<ExtractedPackage> TransformPackageAlias(android::StringPiece alias) const override; struct PackageDecl { std::string prefix; @@ -194,7 +193,7 @@ class XmlPullParser : public IPackageDeclStack { * Finds the attribute in the current element within the global namespace. */ std::optional<android::StringPiece> FindAttribute(const XmlPullParser* parser, - const android::StringPiece& name); + android::StringPiece name); /** * Finds the attribute in the current element within the global namespace. The @@ -202,7 +201,7 @@ std::optional<android::StringPiece> FindAttribute(const XmlPullParser* parser, * must not be the empty string. */ std::optional<android::StringPiece> FindNonEmptyAttribute(const XmlPullParser* parser, - const android::StringPiece& name); + android::StringPiece name); // // Implementation diff --git a/tools/aapt2/xml/XmlUtil.cpp b/tools/aapt2/xml/XmlUtil.cpp index 114b5ba7ab1a..709755e69292 100644 --- a/tools/aapt2/xml/XmlUtil.cpp +++ b/tools/aapt2/xml/XmlUtil.cpp @@ -27,7 +27,7 @@ using ::android::StringPiece; namespace aapt { namespace xml { -std::string BuildPackageNamespace(const StringPiece& package, bool private_reference) { +std::string BuildPackageNamespace(StringPiece package, bool private_reference) { std::string result = private_reference ? kSchemaPrivatePrefix : kSchemaPublicPrefix; result.append(package.data(), package.size()); return result; @@ -41,7 +41,7 @@ std::optional<ExtractedPackage> ExtractPackageFromNamespace(const std::string& n if (package.empty()) { return {}; } - return ExtractedPackage{package.to_string(), false /* is_private */}; + return ExtractedPackage{std::string(package), false /* is_private */}; } else if (util::StartsWith(namespace_uri, kSchemaPrivatePrefix)) { StringPiece schema_prefix = kSchemaPrivatePrefix; @@ -50,7 +50,7 @@ std::optional<ExtractedPackage> ExtractPackageFromNamespace(const std::string& n if (package.empty()) { return {}; } - return ExtractedPackage{package.to_string(), true /* is_private */}; + return ExtractedPackage{std::string(package), true /* is_private */}; } else if (namespace_uri == kSchemaAuto) { return ExtractedPackage{std::string(), true /* is_private */}; diff --git a/tools/aapt2/xml/XmlUtil.h b/tools/aapt2/xml/XmlUtil.h index 1ab05a93d314..ad676ca91886 100644 --- a/tools/aapt2/xml/XmlUtil.h +++ b/tools/aapt2/xml/XmlUtil.h @@ -59,8 +59,7 @@ std::optional<ExtractedPackage> ExtractPackageFromNamespace(const std::string& n // // If privateReference == true, the package will be of the form: // http://schemas.android.com/apk/prv/res/<package> -std::string BuildPackageNamespace(const android::StringPiece& package, - bool private_reference = false); +std::string BuildPackageNamespace(android::StringPiece package, bool private_reference = false); // Interface representing a stack of XML namespace declarations. When looking up the package for a // namespace prefix, the stack is checked from top to bottom. @@ -69,7 +68,7 @@ struct IPackageDeclStack { // Returns an ExtractedPackage struct if the alias given corresponds with a package declaration. virtual std::optional<ExtractedPackage> TransformPackageAlias( - const android::StringPiece& alias) const = 0; + android::StringPiece alias) const = 0; }; // Helper function for transforming the original Reference inRef to a fully qualified reference diff --git a/tools/fonts/font-scaling-array-generator.js b/tools/fonts/font-scaling-array-generator.js new file mode 100644 index 000000000000..9754697bfb51 --- /dev/null +++ b/tools/fonts/font-scaling-array-generator.js @@ -0,0 +1,195 @@ +/* + * 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. + */ + +/** + Generates arrays for non-linear font scaling, to be pasted into + frameworks/base/core/java/android/content/res/FontScaleConverterFactory.java + + To use: + `node font-scaling-array-generator.js` + or just open a browser, open DevTools, and paste into the Console. +*/ + +/** + * Modify this to match your packages/apps/Settings/res/arrays.xml#entryvalues_font_size + * array so that all possible scales are generated. + */ +const scales = [1.15, 1.30, 1.5, 1.8, 2]; + +const commonSpSizes = [8, 10, 12, 14, 18, 20, 24, 30, 100]; + +/** + * Enum for GENERATION_STYLE which determines how to generate the arrays. + */ +const GenerationStyle = { + /** + * Interpolates between hand-tweaked curves. This is the best option and + * shouldn't require any additional tweaking. + */ + CUSTOM_TWEAKED: 'CUSTOM_TWEAKED', + + /** + * Uses a curve equation that is mostly correct, but will need manual tweaking + * at some scales. + */ + CURVE: 'CURVE', + + /** + * Uses straight linear multiplication. Good starting point for manual + * tweaking. + */ + LINEAR: 'LINEAR' +} + +/** + * Determines how arrays are generated. Must be one of the GenerationStyle + * values. + */ +const GENERATION_STYLE = GenerationStyle.CUSTOM_TWEAKED; + +// These are hand-tweaked curves from which we will derive the other +// interstitial curves using linear interpolation, in the case of using +// GenerationStyle.CUSTOM_TWEAKED. +const interpolationTargets = { + 1.0: commonSpSizes, + 1.5: [12, 15, 18, 22, 24, 26, 28, 30, 100], + 2.0: [16, 20, 24, 26, 30, 34, 36, 38, 100] +}; + +/** + * Interpolate a value with specified extrema, to a new value between new + * extrema. + * + * @param value the current value + * @param inputMin minimum the input value can reach + * @param inputMax maximum the input value can reach + * @param outputMin minimum the output value can reach + * @param outputMax maximum the output value can reach + */ +function map(value, inputMin, inputMax, outputMin, outputMax) { + return outputMin + (outputMax - outputMin) * ((value - inputMin) / (inputMax - inputMin)); +} + +/*** + * Interpolate between values a and b. + */ +function lerp(a, b, fraction) { + return (a * (1.0 - fraction)) + (b * fraction); +} + +function generateRatios(scale) { + // Find the best two arrays to interpolate between. + let startTarget, endTarget; + let startTargetScale, endTargetScale; + const targetScales = Object.keys(interpolationTargets).sort(); + for (let i = 0; i < targetScales.length - 1; i++) { + const targetScaleKey = targetScales[i]; + const targetScale = parseFloat(targetScaleKey, 10); + const startTargetScaleKey = targetScaleKey; + const endTargetScaleKey = targetScales[i + 1]; + + if (scale < parseFloat(startTargetScaleKey, 10)) { + break; + } + + startTargetScale = parseFloat(startTargetScaleKey, 10); + endTargetScale = parseFloat(endTargetScaleKey, 10); + startTarget = interpolationTargets[startTargetScaleKey]; + endTarget = interpolationTargets[endTargetScaleKey]; + } + const interpolationProgress = map(scale, startTargetScale, endTargetScale, 0, 1); + + return commonSpSizes.map((sp, i) => { + const originalSizeDp = sp; + let newSizeDp; + switch (GENERATION_STYLE) { + case GenerationStyle.CUSTOM_TWEAKED: + newSizeDp = lerp(startTarget[i], endTarget[i], interpolationProgress); + break; + case GenerationStyle.CURVE: { + let coeff1; + let coeff2; + if (scale < 1) { + // \left(1.22^{-\left(x+5\right)}+0.5\right)\cdot x + coeff1 = -5; + coeff2 = scale; + } else { + // (1.22^{-\left(x-10\right)}+1\right)\cdot x + coeff1 = map(scale, 1, 2, 2, 8); + coeff2 = 1; + } + newSizeDp = ((Math.pow(1.22, (-(originalSizeDp - coeff1))) + coeff2) * originalSizeDp); + break; + } + case GenerationStyle.LINEAR: + newSizeDp = originalSizeDp * scale; + break; + default: + throw new Error('Invalid GENERATION_STYLE'); + } + return { + fromSp: sp, + toDp: newSizeDp + } + }); +} + +const scaleArrays = + scales + .map(scale => { + const scaleString = (scale * 100).toFixed(0); + return { + scale, + name: `font_size_original_sp_to_scaled_dp_${scaleString}_percent` + } + }) + .map(scaleArray => { + const items = generateRatios(scaleArray.scale); + + return { + ...scaleArray, + items + } + }); + +function formatDigit(d) { + const twoSignificantDigits = Math.round(d * 100) / 100; + return String(twoSignificantDigits).padStart(4, ' '); +} + +console.log( + '' + + scaleArrays.reduce( + (previousScaleArray, currentScaleArray) => { + const itemsFromSp = currentScaleArray.items.map(d => d.fromSp) + .map(formatDigit) + .join('f, '); + const itemsToDp = currentScaleArray.items.map(d => d.toDp) + .map(formatDigit) + .join('f, '); + + return previousScaleArray + ` + put( + /* scaleKey= */ ${currentScaleArray.scale}f, + new FontScaleConverter( + /* fromSp= */ + new float[] {${itemsFromSp}}, + /* toDp= */ + new float[] {${itemsToDp}}) + ); + `; + }, + '')); |