diff options
580 files changed, 22917 insertions, 8851 deletions
diff --git a/Android.bp b/Android.bp index 4e7eba26c6f1..3d25bc1d17db 100644 --- a/Android.bp +++ b/Android.bp @@ -100,7 +100,7 @@ filegroup { ":android.hardware.gnss-V2-java-source", ":android.hardware.graphics.common-V3-java-source", ":android.hardware.keymaster-V4-java-source", - ":android.hardware.security.keymint-V2-java-source", + ":android.hardware.security.keymint-V3-java-source", ":android.hardware.security.secureclock-V1-java-source", ":android.hardware.tv.tuner-V1-java-source", ":android.security.apc-java-source", 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/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp index bb31c11d629c..b2300cea3a68 100644 --- a/cmds/idmap2/libidmap2/ResourceMapping.cpp +++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp @@ -89,7 +89,7 @@ Result<Unit> CheckOverlayable(const TargetResourceContainer& target, // If the overlay supplies a target overlayable name, the resource must belong to the // overlayable defined with the specified name to be overlaid. return Error(R"(<overlay> android:targetName "%s" does not match overlayable name "%s")", - overlay_info.target_name.c_str(), (*overlayable_info)->name.c_str()); + overlay_info.target_name.c_str(), (*overlayable_info)->name.data()); } // Enforce policy restrictions if the resource is declared as overlayable. diff --git a/cmds/svc/src/com/android/commands/svc/UsbCommand.java b/cmds/svc/src/com/android/commands/svc/UsbCommand.java index 7d804938dc38..26e20f601c7a 100644 --- a/cmds/svc/src/com/android/commands/svc/UsbCommand.java +++ b/cmds/svc/src/com/android/commands/svc/UsbCommand.java @@ -29,12 +29,18 @@ import android.os.ServiceManager; import java.util.function.Consumer; import java.util.concurrent.Executor; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; public class UsbCommand extends Svc.Command { public UsbCommand() { super("usb"); } + /** + * Counter for tracking UsbOperation operations. + */ + private static final AtomicInteger sUsbOperationCount = new AtomicInteger(); + @Override public String shortHelp() { return "Control Usb state"; @@ -92,8 +98,10 @@ public class UsbCommand extends Svc.Command { if ("setFunctions".equals(args[1])) { try { + int operationId = sUsbOperationCount.incrementAndGet(); + System.out.println("setCurrentFunctions opId:" + operationId); usbMgr.setCurrentFunctions(UsbManager.usbFunctionsFromString( - args.length >= 3 ? args[2] : "")); + args.length >= 3 ? args[2] : ""), operationId); } catch (RemoteException e) { System.err.println("Error communicating with UsbManager: " + e); } diff --git a/core/api/current.txt b/core/api/current.txt index dab32d25deab..787370abf2b7 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 @@ -6968,6 +6969,7 @@ package android.app { method @Deprecated public void onStart(android.content.Intent, int); method public int onStartCommand(android.content.Intent, int, int); method public void onTaskRemoved(android.content.Intent); + method public void onTimeout(int); method public void onTrimMemory(int); method public boolean onUnbind(android.content.Intent); method public final void startForeground(int, android.app.Notification); @@ -11663,6 +11665,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 +11710,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 +11767,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 +11819,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 +11852,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); @@ -12441,6 +12476,7 @@ package android.content.pm { field @Deprecated public static final int FOREGROUND_SERVICE_TYPE_NONE = 0; // 0x0 field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_PHONE_CALL}, anyOf={android.Manifest.permission.MANAGE_OWN_CALLS}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_PHONE_CALL = 4; // 0x4 field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING = 512; // 0x200 + field public static final int FOREGROUND_SERVICE_TYPE_SHORT_SERVICE = 2048; // 0x800 field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_SPECIAL_USE = 1073741824; // 0x40000000 field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED = 1024; // 0x400 field public int flags; @@ -15447,7 +15483,6 @@ package android.graphics { } public static class PathIterator.Segment { - ctor public PathIterator.Segment(@NonNull int, @NonNull float[], float); method public float getConicWeight(); method @NonNull public float[] getPoints(); method @NonNull public int getVerb(); @@ -35877,6 +35912,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 +35961,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 +42042,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"; @@ -48658,6 +48694,7 @@ package android.view { method public float getRefreshRate(); method public int getRotation(); method @Nullable public android.view.RoundedCorner getRoundedCorner(int); + method @NonNull public android.view.DisplayShape getShape(); method @Deprecated public void getSize(android.graphics.Point); method public int getState(); method public android.view.Display.Mode[] getSupportedModes(); @@ -48738,6 +48775,13 @@ package android.view { method @NonNull public android.view.DisplayCutout.Builder setWaterfallInsets(@NonNull android.graphics.Insets); } + public final class DisplayShape implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public android.graphics.Path getPath(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.view.DisplayShape> CREATOR; + } + public final class DragAndDropPermissions implements android.os.Parcelable { method public int describeContents(); method public void release(); @@ -52119,6 +52163,7 @@ package android.view { method @Deprecated @NonNull public android.view.WindowInsets consumeStableInsets(); method @Deprecated @NonNull public android.view.WindowInsets consumeSystemWindowInsets(); method @Nullable public android.view.DisplayCutout getDisplayCutout(); + method @Nullable public android.view.DisplayShape getDisplayShape(); method @NonNull public android.graphics.Insets getInsets(int); method @NonNull public android.graphics.Insets getInsetsIgnoringVisibility(int); method @Deprecated @NonNull public android.graphics.Insets getMandatorySystemGestureInsets(); @@ -52154,6 +52199,7 @@ package android.view { ctor public WindowInsets.Builder(@NonNull android.view.WindowInsets); method @NonNull public android.view.WindowInsets build(); method @NonNull public android.view.WindowInsets.Builder setDisplayCutout(@Nullable android.view.DisplayCutout); + method @NonNull public android.view.WindowInsets.Builder setDisplayShape(@NonNull android.view.DisplayShape); method @NonNull public android.view.WindowInsets.Builder setInsets(int, @NonNull android.graphics.Insets); method @NonNull public android.view.WindowInsets.Builder setInsetsIgnoringVisibility(int, @NonNull android.graphics.Insets) throws java.lang.IllegalArgumentException; method @Deprecated @NonNull public android.view.WindowInsets.Builder setMandatorySystemGestureInsets(@NonNull android.graphics.Insets); diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index b6e2d2a3e98b..3228ce6cf939 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -121,6 +121,7 @@ package android.hardware.usb { field public static final int GADGET_HAL_V1_0 = 10; // 0xa field public static final int GADGET_HAL_V1_1 = 11; // 0xb field public static final int GADGET_HAL_V1_2 = 12; // 0xc + field public static final int GADGET_HAL_V2_0 = 20; // 0x14 field public static final int USB_DATA_TRANSFER_RATE_10G = 10240; // 0x2800 field public static final int USB_DATA_TRANSFER_RATE_20G = 20480; // 0x5000 field public static final int USB_DATA_TRANSFER_RATE_40G = 40960; // 0xa000 diff --git a/core/api/system-current.txt b/core/api/system-current.txt index af188274a3eb..c3e98874b7a3 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,8 @@ 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.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public android.os.UserHandle getPreviousForegroundUser(); 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); @@ -12819,14 +12874,14 @@ package android.telephony { method @NonNull public android.telephony.BarringInfo createLocationInfoSanitizedCopy(); } - public final class CallAttributes implements android.os.Parcelable { - ctor public CallAttributes(@NonNull android.telephony.PreciseCallState, int, @NonNull android.telephony.CallQuality); - method public int describeContents(); - method @NonNull public android.telephony.CallQuality getCallQuality(); - method public int getNetworkType(); - method @NonNull public android.telephony.PreciseCallState getPreciseCallState(); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallAttributes> CREATOR; + @Deprecated public final class CallAttributes implements android.os.Parcelable { + ctor @Deprecated public CallAttributes(@NonNull android.telephony.PreciseCallState, int, @NonNull android.telephony.CallQuality); + method @Deprecated public int describeContents(); + method @Deprecated @NonNull public android.telephony.CallQuality getCallQuality(); + method @Deprecated public int getNetworkType(); + method @Deprecated @NonNull public android.telephony.PreciseCallState getPreciseCallState(); + method @Deprecated public void writeToParcel(android.os.Parcel, int); + field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallAttributes> CREATOR; } public final class CallForwardingInfo implements android.os.Parcelable { @@ -12906,6 +12961,28 @@ package android.telephony { method @NonNull public android.telephony.CallQuality.Builder setUplinkCallQualityLevel(int); } + public final class CallState implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public android.telephony.CallQuality getCallQuality(); + method public int getCallState(); + method public int getImsCallServiceType(); + method @Nullable public String getImsCallSessionId(); + method public int getImsCallType(); + method public int getNetworkType(); + method public void writeToParcel(@Nullable android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallState> CREATOR; + } + + public static final class CallState.Builder { + ctor public CallState.Builder(int); + method @NonNull public android.telephony.CallState build(); + method @NonNull public android.telephony.CallState.Builder setCallQuality(@Nullable android.telephony.CallQuality); + method @NonNull public android.telephony.CallState.Builder setImsCallServiceType(int); + method @NonNull public android.telephony.CallState.Builder setImsCallSessionId(@Nullable String); + method @NonNull public android.telephony.CallState.Builder setImsCallType(int); + method @NonNull public android.telephony.CallState.Builder setNetworkType(int); + } + public class CarrierConfigManager { method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDefaultCarrierServicePackageName(); method @NonNull public static android.os.PersistableBundle getDefaultConfig(); @@ -13656,7 +13733,8 @@ package android.telephony { } public static interface TelephonyCallback.CallAttributesListener { - method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onCallAttributesChanged(@NonNull android.telephony.CallAttributes); + method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public default void onCallAttributesChanged(@NonNull android.telephony.CallAttributes); + method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public default void onCallStatesChanged(@NonNull java.util.List<android.telephony.CallState>); } public static interface TelephonyCallback.DataEnabledListener { @@ -14757,6 +14835,7 @@ package android.telephony.ims { field public static final int CALL_RESTRICT_CAUSE_HD = 3; // 0x3 field public static final int CALL_RESTRICT_CAUSE_NONE = 0; // 0x0 field public static final int CALL_RESTRICT_CAUSE_RAT = 1; // 0x1 + field public static final int CALL_TYPE_NONE = 0; // 0x0 field public static final int CALL_TYPE_VIDEO_N_VOICE = 3; // 0x3 field public static final int CALL_TYPE_VOICE = 2; // 0x2 field public static final int CALL_TYPE_VOICE_N_VIDEO = 1; // 0x1 diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 121741e07d43..984e82215169 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); } @@ -1037,6 +1039,7 @@ package android.graphics { method @NonNull public static android.util.Pair<java.util.List<android.graphics.Typeface>,java.util.List<android.graphics.Typeface>> changeDefaultFontForTest(@NonNull java.util.List<android.graphics.Typeface>, @NonNull java.util.List<android.graphics.Typeface>); method @NonNull public static long[] deserializeFontMap(@NonNull java.nio.ByteBuffer, @NonNull java.util.Map<java.lang.String,android.graphics.Typeface>) throws java.io.IOException; method @Nullable public static android.os.SharedMemory getSystemFontMapSharedMemory(); + method public void releaseNativeObjectForTest(); method @NonNull public static android.os.SharedMemory serializeFontMap(@NonNull java.util.Map<java.lang.String,android.graphics.Typeface>) throws android.system.ErrnoException, java.io.IOException; } @@ -1249,6 +1252,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 +1909,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 +1924,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(); @@ -2882,6 +2888,10 @@ package android.view { method @NonNull public android.view.Display.Mode.Builder setResolution(int, int); } + public final class DisplayShape implements android.os.Parcelable { + method @NonNull public static android.view.DisplayShape fromSpecString(@NonNull String, float, int, int); + } + public class FocusFinder { method public static void sort(android.view.View[], int, int, android.view.ViewGroup, boolean); } 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/ActivityThread.java b/core/java/android/app/ActivityThread.java index 884870bff3af..96ced41f36ca 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -502,6 +502,7 @@ public final class ActivityThread extends ClientTransactionHandler @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) static volatile Handler sMainThreadHandler; // set once in main() + private long mStartSeq; // Only accesssed from the main thread Bundle mCoreSettings = null; @@ -6809,6 +6810,14 @@ public final class ActivityThread extends ClientTransactionHandler Application app; final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites(); final StrictMode.ThreadPolicy writesAllowedPolicy = StrictMode.getThreadPolicy(); + + final IActivityManager mgr = ActivityManager.getService(); + try { + mgr.finishAttachApplication(mStartSeq); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + try { // If the app is being launched for full backup or restore, bring it up in // a restricted environment with the base application class. @@ -7649,6 +7658,8 @@ public final class ActivityThread extends ClientTransactionHandler sCurrentActivityThread = this; mConfigurationController = new ConfigurationController(this); mSystemThread = system; + mStartSeq = startSeq; + if (!system) { android.ddm.DdmHandleAppName.setAppName("<pre-initialized>", UserHandle.myUserId()); 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/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 902f172b6ad7..3edaabde5f2a 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -147,6 +147,7 @@ interface IActivityManager { oneway void finishReceiver(in IBinder who, int resultCode, in String resultData, in Bundle map, boolean abortBroadcast, int flags); void attachApplication(in IApplicationThread app, long startSeq); + void finishAttachApplication(long startSeq); List<ActivityManager.RunningTaskInfo> getTasks(int maxNum); @UnsupportedAppUsage void moveTaskToFront(in IApplicationThread caller, in String callingPackage, int task, @@ -718,8 +719,8 @@ interface IActivityManager { /** * Control the app freezer state. Returns true in case of success, false if the operation - * didn't succeed (for example, when the app freezer isn't supported). - * Handling the freezer state via this method is reentrant, that is it can be + * didn't succeed (for example, when the app freezer isn't supported). + * Handling the freezer state via this method is reentrant, that is it can be * disabled and re-enabled multiple times in parallel. As long as there's a 1:1 disable to * enable match, the freezer is re-enabled at last enable only. * @param enable set it to true to enable the app freezer, false to disable it. diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java index 6d7a161d1687..3a7d483c69c3 100644 --- a/core/java/android/app/Service.java +++ b/core/java/android/app/Service.java @@ -1128,12 +1128,10 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac /** * Callback called on timeout for {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}. + * See {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE} for more details. * - * TODO Implement it - * TODO Javadoc - * - * @param startId - * @hide + * @param startId the startId passed to {@link #onStartCommand(Intent, int, int)} when + * the service started. */ public void onTimeout(int startId) { } 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..ed1f6a2b1457 100644 --- a/core/java/android/content/om/OverlayManager.java +++ b/core/java/android/content/om/OverlayManager.java @@ -17,6 +17,7 @@ package android.content.om; import android.annotation.NonNull; +import android.annotation.NonUiContext; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; @@ -25,12 +26,16 @@ import android.compat.Compatibility; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; import android.content.Context; +import android.content.pm.PackageManager; import android.os.Build; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; +import com.android.internal.content.om.OverlayManagerImpl; + +import java.io.IOException; import java.util.List; /** @@ -76,6 +81,7 @@ public class OverlayManager { private final IOverlayManager mService; private final Context mContext; + private final OverlayManagerImpl mOverlayManagerImpl; /** * Pre R a {@link java.lang.SecurityException} would only be thrown by setEnabled APIs (e @@ -92,6 +98,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. @@ -102,6 +123,7 @@ public class OverlayManager { public OverlayManager(Context context, IOverlayManager service) { mContext = context; mService = service; + mOverlayManagerImpl = new OverlayManagerImpl(context); } /** @hide */ @@ -286,6 +308,17 @@ public class OverlayManager { * @hide */ public void commit(@NonNull final OverlayManagerTransaction transaction) { + if (transaction.isSelfTargetingTransaction() + || mService == null + || mService.asBinder() == null) { + try { + commitSelfTarget(transaction); + } catch (PackageManager.NameNotFoundException | IOException e) { + throw new RuntimeException(e); + } + return; + } + try { mService.commit(transaction); } catch (RemoteException e) { @@ -317,4 +350,48 @@ public class OverlayManager { throw e; } } + + /** + * Get a OverlayManagerTransaction.Builder to build out a overlay manager transaction. + * + * @return a builder of the overlay manager transaction. + * @hide + */ + @NonNull + public OverlayManagerTransaction.Builder beginTransaction() { + return new OverlayManagerTransaction.Builder(this); + } + + /** + * Commit the self-targeting transaction to register or unregister overlays. + * + * <p>Applications can request OverlayManager to register overlays and unregister the registered + * overlays via {@link OverlayManagerTransaction}. + * + * @throws IOException if there is a file operation error. + * @throws PackageManager.NameNotFoundException if the package name is not found. + * @hide + */ + @NonUiContext + void commitSelfTarget(@NonNull final OverlayManagerTransaction transaction) + throws PackageManager.NameNotFoundException, IOException { + synchronized (mOverlayManagerImpl) { + mOverlayManagerImpl.commit(transaction); + } + } + + /** + * Get the related information of overlays for {@code targetPackageName}. + * + * @param targetPackageName the target package name + * @return a list of overlay information + * @hide + */ + @NonNull + @NonUiContext + public List<OverlayInfo> getOverlayInfosForTarget(@NonNull final String targetPackageName) { + synchronized (mOverlayManagerImpl) { + return mOverlayManagerImpl.getOverlayInfosForTarget(targetPackageName); + } + } } diff --git a/core/java/android/content/om/OverlayManagerTransaction.java b/core/java/android/content/om/OverlayManagerTransaction.java index 868dab298108..42b3ef3c370d 100644 --- a/core/java/android/content/om/OverlayManagerTransaction.java +++ b/core/java/android/content/om/OverlayManagerTransaction.java @@ -20,19 +20,22 @@ import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.NonUiContext; import android.annotation.Nullable; -import android.content.Context; +import android.content.pm.PackageManager; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; +import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Locale; +import java.util.Objects; /** * Container for a batch of requests to the OverlayManagerService. @@ -53,13 +56,16 @@ public class OverlayManagerTransaction // TODO: remove @hide from this class when OverlayManager is added to the // SDK, but keep OverlayManagerTransaction.Request @hidden private final List<Request> mRequests; + private final OverlayManager mOverlayManager; - OverlayManagerTransaction(@NonNull final List<Request> requests) { + OverlayManagerTransaction( + @NonNull final List<Request> requests, @Nullable OverlayManager overlayManager) { checkNotNull(requests); if (requests.contains(null)) { throw new IllegalArgumentException("null request"); } mRequests = requests; + mOverlayManager = overlayManager; } private OverlayManagerTransaction(@NonNull final Parcel source) { @@ -72,6 +78,7 @@ public class OverlayManagerTransaction final Bundle extras = source.readBundle(null); mRequests.add(new Request(request, overlay, userId, extras)); } + mOverlayManager = null; } @Override @@ -156,6 +163,20 @@ public class OverlayManagerTransaction */ public static class Builder { private final List<Request> mRequests = new ArrayList<>(); + @Nullable private final OverlayManager mOverlayManager; + + public Builder() { + mOverlayManager = null; + } + + /** + * The transaction builder for self-targeting. + * + * @param overlayManager is not null if the transaction is for self-targeting. + */ + Builder(@NonNull OverlayManager overlayManager) { + mOverlayManager = Objects.requireNonNull(overlayManager); + } /** * Request that an overlay package be enabled and change its loading @@ -205,7 +226,10 @@ public class OverlayManagerTransaction * * @hide */ + @NonNull public Builder registerFabricatedOverlay(@NonNull FabricatedOverlay overlay) { + Objects.requireNonNull(overlay); + final Bundle extras = new Bundle(); extras.putParcelable(Request.BUNDLE_FABRICATED_OVERLAY, overlay.mOverlay); mRequests.add(new Request(Request.TYPE_REGISTER_FABRICATED, overlay.getIdentifier(), @@ -220,7 +244,10 @@ public class OverlayManagerTransaction * * @hide */ + @NonNull public Builder unregisterFabricatedOverlay(@NonNull OverlayIdentifier overlay) { + Objects.requireNonNull(overlay); + mRequests.add(new Request(Request.TYPE_UNREGISTER_FABRICATED, overlay, UserHandle.USER_ALL)); return this; @@ -233,8 +260,9 @@ public class OverlayManagerTransaction * @see OverlayManager#commit * @return a new transaction */ + @NonNull public OverlayManagerTransaction build() { - return new OverlayManagerTransaction(mRequests); + return new OverlayManagerTransaction(mRequests, mOverlayManager); } } @@ -269,4 +297,23 @@ public class OverlayManagerTransaction return new OverlayManagerTransaction[size]; } }; + + /** + * Commit the overlay manager transaction to register or unregister overlays for self-targeting. + * + * <p>Applications can register overlays and unregister the registered overlays via {@link + * OverlayManagerTransaction}. + * + * @throws IOException if there is a file operation error. + * @throws PackageManager.NameNotFoundException if the package name is not found. + * @hide + */ + @NonUiContext + public void commit() throws PackageManager.NameNotFoundException, IOException { + mOverlayManager.commitSelfTarget(this); + } + + boolean isSelfTargetingTransaction() { + return mOverlayManager != null; + } } 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/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java index 9e6cf62f2397..7ea6733fa2ff 100644 --- a/core/java/android/content/pm/ServiceInfo.java +++ b/core/java/android/content/pm/ServiceInfo.java @@ -366,24 +366,48 @@ public class ServiceInfo extends ComponentInfo public static final int FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED = 1 << 10; /** - * Foreground service type corresponding to {@code shortService} in - * the {@link android.R.attr#foregroundServiceType} attribute. + * A foreground service type for "short-lived" services, which corresponds to + * {@code shortService} in the {@link android.R.attr#foregroundServiceType} attribute in the + * manifest. * - * TODO Implement it + * <p>Unlike other foreground service types, this type is not associated with a specific use + * case, and it will not require any special permissions + * (besides {@link Manifest.permission#FOREGROUND_SERVICE}). * - * TODO Expand the javadoc + * However, this type has the following restrictions. * - * This type is not associated with specific use cases unlike other types, but this has - * unique restrictions. * <ul> - * <li>Has a timeout - * <li>Cannot start other foreground services from this * <li> - * </ul> + * The type has a 1 minute timeout. + * A foreground service of this type must be stopped within the timeout by + * {@link android.app.Service#stopSelf), + * or {@link android.content.Context#stopService). + * {@link android.app.Service#stopForeground) will also work, which will demote the + * service to a "background" service, which will soon be stopped by the system. * - * @see Service#onTimeout + * <p>The system will <em>not</em> automatically stop it. * - * @hide + * <p>If the service isn't stopped within the timeout, + * {@link android.app.Service#onTimeout(int)} will be called. + * If the service is still not stopped after the callback, + * the app will be declared an ANR. + * + * <li> + * A foreground service of this type cannot be made "sticky" + * (see {@link android.app.Service#START_STICKY}). That is, if an app is killed + * due to a crash or out-of memory while it's running a short foregorund-service, + * the system will not restart the service. + * <li> + * Other foreground services cannot be started from short foreground services. + * Unlike other foreground service types, when an app is running in the background + * while only having a "short" foreground service, it's not allowed to start + * other foreground services, due to the restriction describe here: + * <a href="/guide/components/foreground-services#background-start-restrictions> + * Restrictions on background starts + * </a> + * </ul> + * + * @see android.app.Service#onTimeout(int) */ public static final int FOREGROUND_SERVICE_TYPE_SHORT_SERVICE = 1 << 11; 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/content/res/loader/ResourcesProvider.java b/core/java/android/content/res/loader/ResourcesProvider.java index 463dcaced723..a5a1fa689f9b 100644 --- a/core/java/android/content/res/loader/ResourcesProvider.java +++ b/core/java/android/content/res/loader/ResourcesProvider.java @@ -18,7 +18,10 @@ package android.content.res.loader; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.content.Context; +import android.content.om.OverlayInfo; +import android.content.om.OverlayManager; import android.content.pm.ApplicationInfo; import android.content.res.ApkAssets; import android.content.res.AssetFileDescriptor; @@ -27,11 +30,17 @@ import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.content.om.OverlayManagerImpl; import com.android.internal.util.ArrayUtils; +import com.android.internal.util.Preconditions; import java.io.Closeable; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Objects; /** * Provides methods to load resources data from APKs ({@code .apk}) and resources tables @@ -63,6 +72,48 @@ public class ResourcesProvider implements AutoCloseable, Closeable { } /** + * Creates a ResourcesProvider instance from the specified overlay information. + * + * <p>In order to enable the registered overlays, an application can create a {@link + * ResourcesProvider} instance according to the specified {@link OverlayInfo} instance and put + * them into a {@link ResourcesLoader} instance. The application calls {@link + * android.content.res.Resources#addLoaders(ResourcesLoader...)} to load the overlays. + * + * @param overlayInfo is the information about the specified overlay + * @return the resources provider instance for the {@code overlayInfo} + * @throws IOException when the files can't be loaded. + * @see OverlayManager#getOverlayInfosForTarget(String) to get the list of overlay info. + * @hide + */ + @SuppressLint("WrongConstant") // TODO(b/238713267): ApkAssets blocks PROPERTY_LOADER + @NonNull + public static ResourcesProvider loadOverlay(@NonNull OverlayInfo overlayInfo) + throws IOException { + Objects.requireNonNull(overlayInfo); + Preconditions.checkArgument(overlayInfo.isFabricated(), "Not accepted overlay"); + Preconditions.checkStringNotEmpty( + overlayInfo.getTargetOverlayableName(), "Without overlayable name"); + final String overlayName = + OverlayManagerImpl.checkOverlayNameValid(overlayInfo.getOverlayName()); + final String path = + Preconditions.checkStringNotEmpty( + overlayInfo.getBaseCodePath(), "Invalid base path"); + + final Path frroPath = Path.of(path); + if (!Files.isRegularFile(frroPath)) { + throw new FileNotFoundException("The frro file not found"); + } + final Path idmapPath = frroPath.getParent().resolve(overlayName + ".idmap"); + if (!Files.isRegularFile(idmapPath)) { + throw new FileNotFoundException("The idmap file not found"); + } + + return new ResourcesProvider( + ApkAssets.loadOverlayFromPath( + idmapPath.toString(), 0 /* flags: self targeting overlay */)); + } + + /** * Creates a ResourcesProvider from an APK ({@code .apk}) file descriptor. * * <p>The file descriptor is duplicated and the original may be closed by the application at any 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/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl index 51236fe3b2c0..248b5d0398f6 100644 --- a/core/java/android/hardware/usb/IUsbManager.aidl +++ b/core/java/android/hardware/usb/IUsbManager.aidl @@ -122,10 +122,10 @@ interface IUsbManager boolean isFunctionEnabled(String function); /* Sets the current USB function. */ - void setCurrentFunctions(long functions); + void setCurrentFunctions(long functions, int operationId); /* Compatibility version of setCurrentFunctions(long). */ - void setCurrentFunction(String function, boolean usbDataUnlocked); + void setCurrentFunction(String function, boolean usbDataUnlocked, int operationId); /* Gets the current USB functions. */ long getCurrentFunctions(); 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..7a8117c1b684 100644 --- a/core/java/android/hardware/usb/UsbManager.java +++ b/core/java/android/hardware/usb/UsbManager.java @@ -38,6 +38,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.hardware.usb.gadget.V1_0.GadgetFunction; import android.hardware.usb.gadget.V1_2.UsbSpeed; +import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.ParcelFileDescriptor; @@ -52,6 +53,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.StringJoiner; +import java.util.concurrent.atomic.AtomicInteger; /** * This class allows you to access the state of USB and communicate with USB devices. @@ -95,7 +97,7 @@ public class UsbManager { * If the sticky intent has not been found, that indicates USB is disconnected, * USB is not configued, MTP function is enabled, and all the other functions are disabled. * - * {@hide} + * @hide */ @SystemApi public static final String ACTION_USB_STATE = @@ -113,6 +115,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. * @@ -172,7 +187,7 @@ public class UsbManager { * <p>For more information about communicating with USB accessory handshake, refer to * <a href="https://source.android.com/devices/accessories/aoa">AOA</a> developer guide.</p> * - * {@hide} + * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) @SystemApi @@ -184,7 +199,7 @@ public class UsbManager { * Boolean extra indicating whether USB is connected or disconnected. * Used in extras for the {@link #ACTION_USB_STATE} broadcast. * - * {@hide} + * @hide */ @SystemApi public static final String USB_CONNECTED = "connected"; @@ -193,7 +208,7 @@ public class UsbManager { * Boolean extra indicating whether USB is connected or disconnected as host. * Used in extras for the {@link #ACTION_USB_STATE} broadcast. * - * {@hide} + * @hide */ public static final String USB_HOST_CONNECTED = "host_connected"; @@ -201,7 +216,7 @@ public class UsbManager { * Boolean extra indicating whether USB is configured. * Used in extras for the {@link #ACTION_USB_STATE} broadcast. * - * {@hide} + * @hide */ @SystemApi public static final String USB_CONFIGURED = "configured"; @@ -212,7 +227,7 @@ public class UsbManager { * has explicitly asked for this data to be unlocked. * Used in extras for the {@link #ACTION_USB_STATE} broadcast. * - * {@hide} + * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public static final String USB_DATA_UNLOCKED = "unlocked"; @@ -221,7 +236,7 @@ public class UsbManager { * A placeholder indicating that no USB function is being specified. * Used for compatibility with old init scripts to indicate no functions vs. charging function. * - * {@hide} + * @hide */ @UnsupportedAppUsage public static final String USB_FUNCTION_NONE = "none"; @@ -230,7 +245,7 @@ public class UsbManager { * Name of the adb USB function. * Used in extras for the {@link #ACTION_USB_STATE} broadcast * - * {@hide} + * @hide */ public static final String USB_FUNCTION_ADB = "adb"; @@ -238,7 +253,7 @@ public class UsbManager { * Name of the RNDIS ethernet USB function. * Used in extras for the {@link #ACTION_USB_STATE} broadcast * - * {@hide} + * @hide */ @SystemApi public static final String USB_FUNCTION_RNDIS = "rndis"; @@ -247,7 +262,7 @@ public class UsbManager { * Name of the MTP USB function. * Used in extras for the {@link #ACTION_USB_STATE} broadcast * - * {@hide} + * @hide */ public static final String USB_FUNCTION_MTP = "mtp"; @@ -255,7 +270,7 @@ public class UsbManager { * Name of the PTP USB function. * Used in extras for the {@link #ACTION_USB_STATE} broadcast * - * {@hide} + * @hide */ public static final String USB_FUNCTION_PTP = "ptp"; @@ -263,7 +278,7 @@ public class UsbManager { * Name of the audio source USB function. * Used in extras for the {@link #ACTION_USB_STATE} broadcast * - * {@hide} + * @hide */ public static final String USB_FUNCTION_AUDIO_SOURCE = "audio_source"; @@ -271,7 +286,7 @@ public class UsbManager { * Name of the MIDI USB function. * Used in extras for the {@link #ACTION_USB_STATE} broadcast * - * {@hide} + * @hide */ public static final String USB_FUNCTION_MIDI = "midi"; @@ -279,7 +294,7 @@ public class UsbManager { * Name of the Accessory USB function. * Used in extras for the {@link #ACTION_USB_STATE} broadcast * - * {@hide} + * @hide */ public static final String USB_FUNCTION_ACCESSORY = "accessory"; @@ -287,7 +302,7 @@ public class UsbManager { * Name of the NCM USB function. * Used in extras for the {@link #ACTION_USB_STATE} broadcast * - * {@hide} + * @hide */ @SystemApi public static final String USB_FUNCTION_NCM = "ncm"; @@ -295,32 +310,39 @@ public class UsbManager { /** * Name of Gadget Hal Not Present; * - * {@hide} + * @hide */ public static final String GADGET_HAL_UNKNOWN = "unknown"; /** * Name of the USB Gadget Hal Version v1.0; * - * {@hide} + * @hide */ public static final String GADGET_HAL_VERSION_1_0 = "V1_0"; /** * Name of the USB Gadget Hal Version v1.1; * - * {@hide} + * @hide */ public static final String GADGET_HAL_VERSION_1_1 = "V1_1"; /** * Name of the USB Gadget Hal Version v1.2; * - * {@hide} + * @hide */ public static final String GADGET_HAL_VERSION_1_2 = "V1_2"; /** + * Name of the USB Gadget Hal Version v2.0; + * + * @hide + */ + public static final String GADGET_HAL_VERSION_2_0 = "V2_0"; + + /** * Name of extra for {@link #ACTION_USB_PORT_CHANGED} * containing the {@link UsbPort} object for the port. * @@ -356,7 +378,7 @@ public class UsbManager { * This is obtained with SystemClock.elapsedRealtime() * Used in extras for {@link #ACTION_USB_ACCESSORY_HANDSHAKE} broadcasts. * - * {@hide} + * @hide */ @SystemApi public static final String EXTRA_ACCESSORY_UEVENT_TIME = @@ -370,7 +392,7 @@ public class UsbManager { * between communicating with USB accessory handshake, refer to * <a href="https://source.android.com/devices/accessories/aoa">AOA</a> developer guide.</p> * - * {@hide} + * @hide */ @SystemApi public static final String EXTRA_ACCESSORY_STRING_COUNT = @@ -380,7 +402,7 @@ public class UsbManager { * Boolean extra indicating whether got start accessory or not * Used in extras for {@link #ACTION_USB_ACCESSORY_HANDSHAKE} broadcasts. * - * {@hide} + * @hide */ @SystemApi public static final String EXTRA_ACCESSORY_START = @@ -392,7 +414,7 @@ public class UsbManager { * sending {@link #ACTION_USB_ACCESSORY_HANDSHAKE}. * Used in extras for {@link #ACTION_USB_ACCESSORY_HANDSHAKE} broadcasts. * - * {@hide} + * @hide */ @SystemApi public static final String EXTRA_ACCESSORY_HANDSHAKE_END = @@ -426,7 +448,7 @@ public class UsbManager { /** * The Value for USB gadget hal is not presented. * - * {@hide} + * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int GADGET_HAL_NOT_SUPPORTED = -1; @@ -434,7 +456,7 @@ public class UsbManager { /** * Value for Gadget Hal Version v1.0. * - * {@hide} + * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int GADGET_HAL_V1_0 = 10; @@ -442,7 +464,7 @@ public class UsbManager { /** * Value for Gadget Hal Version v1.1. * - * {@hide} + * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int GADGET_HAL_V1_1 = 11; @@ -450,15 +472,23 @@ public class UsbManager { /** * Value for Gadget Hal Version v1.2. * - * {@hide} + * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int GADGET_HAL_V1_2 = 12; /** + * Value for Gadget Hal Version v2.0. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int GADGET_HAL_V2_0 = 20; + + /** * Value for USB_STATE is not configured. * - * {@hide} + * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int USB_DATA_TRANSFER_RATE_UNKNOWN = -1; @@ -466,7 +496,7 @@ public class UsbManager { /** * Value for USB Transfer Rate of Low Speed in Mbps (real value is 1.5Mbps). * - * {@hide} + * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int USB_DATA_TRANSFER_RATE_LOW_SPEED = 2; @@ -474,7 +504,7 @@ public class UsbManager { /** * Value for USB Transfer Rate of Full Speed in Mbps. * - * {@hide} + * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int USB_DATA_TRANSFER_RATE_FULL_SPEED = 12; @@ -482,7 +512,7 @@ public class UsbManager { /** * Value for USB Transfer Rate of High Speed in Mbps. * - * {@hide} + * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int USB_DATA_TRANSFER_RATE_HIGH_SPEED = 480; @@ -490,7 +520,7 @@ public class UsbManager { /** * Value for USB Transfer Rate of Super Speed in Mbps. * - * {@hide} + * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int USB_DATA_TRANSFER_RATE_5G = 5 * 1024; @@ -498,7 +528,7 @@ public class UsbManager { /** * Value for USB Transfer Rate of 10G. * - * {@hide} + * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int USB_DATA_TRANSFER_RATE_10G = 10 * 1024; @@ -506,7 +536,7 @@ public class UsbManager { /** * Value for USB Transfer Rate of 20G. * - * {@hide} + * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int USB_DATA_TRANSFER_RATE_20G = 20 * 1024; @@ -514,7 +544,7 @@ public class UsbManager { /** * Value for USB Transfer Rate of 40G. * - * {@hide} + * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int USB_DATA_TRANSFER_RATE_40G = 40 * 1024; @@ -530,7 +560,7 @@ public class UsbManager { /** * The Value for USB hal is not presented. * - * {@hide} + * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int USB_HAL_NOT_SUPPORTED = -1; @@ -538,7 +568,7 @@ public class UsbManager { /** * Value for USB Hal Version v1.0. * - * {@hide} + * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int USB_HAL_V1_0 = 10; @@ -546,7 +576,7 @@ public class UsbManager { /** * Value for USB Hal Version v1.1. * - * {@hide} + * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int USB_HAL_V1_1 = 11; @@ -554,7 +584,7 @@ public class UsbManager { /** * Value for USB Hal Version v1.2. * - * {@hide} + * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int USB_HAL_V1_2 = 12; @@ -562,7 +592,7 @@ public class UsbManager { /** * Value for USB Hal Version v1.3. * - * {@hide} + * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int USB_HAL_V1_3 = 13; @@ -577,63 +607,63 @@ public class UsbManager { /** * Code for the charging usb function. Passed into {@link #setCurrentFunctions(long)} - * {@hide} + * @hide */ @SystemApi public static final long FUNCTION_NONE = 0; /** * Code for the mtp usb function. Passed as a mask into {@link #setCurrentFunctions(long)} - * {@hide} + * @hide */ @SystemApi public static final long FUNCTION_MTP = GadgetFunction.MTP; /** * Code for the ptp usb function. Passed as a mask into {@link #setCurrentFunctions(long)} - * {@hide} + * @hide */ @SystemApi public static final long FUNCTION_PTP = GadgetFunction.PTP; /** * Code for the rndis usb function. Passed as a mask into {@link #setCurrentFunctions(long)} - * {@hide} + * @hide */ @SystemApi public static final long FUNCTION_RNDIS = GadgetFunction.RNDIS; /** * Code for the midi usb function. Passed as a mask into {@link #setCurrentFunctions(long)} - * {@hide} + * @hide */ @SystemApi public static final long FUNCTION_MIDI = GadgetFunction.MIDI; /** * Code for the accessory usb function. - * {@hide} + * @hide */ @SystemApi public static final long FUNCTION_ACCESSORY = GadgetFunction.ACCESSORY; /** * Code for the audio source usb function. - * {@hide} + * @hide */ @SystemApi public static final long FUNCTION_AUDIO_SOURCE = GadgetFunction.AUDIO_SOURCE; /** * Code for the adb usb function. - * {@hide} + * @hide */ @SystemApi public static final long FUNCTION_ADB = GadgetFunction.ADB; /** * Code for the ncm source usb function. - * {@hide} + * @hide */ @SystemApi public static final long FUNCTION_NCM = 1 << 10; @@ -643,6 +673,11 @@ public class UsbManager { private static final Map<String, Long> FUNCTION_NAME_TO_CODE = new HashMap<>(); + /** + * Counter for tracking UsbOperation operations. + */ + private static final AtomicInteger sUsbOperationCount = new AtomicInteger(); + static { FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_MTP, FUNCTION_MTP); FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_PTP, FUNCTION_PTP); @@ -674,6 +709,7 @@ public class UsbManager { GADGET_HAL_V1_0, GADGET_HAL_V1_1, GADGET_HAL_V1_2, + GADGET_HAL_V2_0, }) public @interface UsbGadgetHalVersion {} @@ -692,7 +728,7 @@ public class UsbManager { private final IUsbManager mService; /** - * {@hide} + * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public UsbManager(Context context, IUsbManager service) { @@ -803,7 +839,7 @@ public class UsbManager { * {@link #FUNCTION_PTP} are supported. * @return A ParcelFileDescriptor holding the valid fd, or null if the fd was not found. * - * {@hide} + * @hide */ public ParcelFileDescriptor getControlFd(long function) { try { @@ -964,7 +1000,7 @@ public class UsbManager { * Only system components can call this function. * @param device to request permissions for * - * {@hide} + * @hide */ public void grantPermission(UsbDevice device) { grantPermission(device, Process.myUid()); @@ -976,7 +1012,7 @@ public class UsbManager { * @param device to request permissions for * @uid uid to give permission * - * {@hide} + * @hide */ public void grantPermission(UsbDevice device, int uid) { try { @@ -992,7 +1028,7 @@ public class UsbManager { * @param device to request permissions for * @param packageName of package to grant permissions * - * {@hide} + * @hide */ @SystemApi @RequiresPermission(Manifest.permission.MANAGE_USB) @@ -1017,7 +1053,7 @@ public class UsbManager { * @param function name of the USB function * @return true if the USB function is enabled * - * {@hide} + * @hide */ @Deprecated @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @@ -1049,14 +1085,17 @@ public class UsbManager { * @param functions the USB function(s) to set, as a bitwise mask. * Must satisfy {@link UsbManager#areSettableFunctions} * - * {@hide} + * @hide */ @SystemApi @RequiresPermission(Manifest.permission.MANAGE_USB) public void setCurrentFunctions(@UsbFunctionMode long functions) { + int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid(); try { - mService.setCurrentFunctions(functions); + mService.setCurrentFunctions(functions, operationId); } catch (RemoteException e) { + Log.e(TAG, "setCurrentFunctions: failed to call setCurrentFunctions. functions:" + + functions + ", opId:" + operationId, e); throw e.rethrowFromSystemServer(); } } @@ -1068,14 +1107,17 @@ public class UsbManager { * @param functions the USB function(s) to set. * @param usbDataUnlocked unused - * {@hide} + * @hide */ @Deprecated @UnsupportedAppUsage public void setCurrentFunction(String functions, boolean usbDataUnlocked) { + int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid(); try { - mService.setCurrentFunction(functions, usbDataUnlocked); + mService.setCurrentFunction(functions, usbDataUnlocked, operationId); } catch (RemoteException e) { + Log.e(TAG, "setCurrentFunction: failed to call setCurrentFunction. functions:" + + functions + ", opId:" + operationId, e); throw e.rethrowFromSystemServer(); } } @@ -1090,7 +1132,7 @@ public class UsbManager { * @return The currently enabled functions, in a bitwise mask. * A zero mask indicates that the current function is the charging function. * - * {@hide} + * @hide */ @SystemApi @RequiresPermission(Manifest.permission.MANAGE_USB) @@ -1116,7 +1158,7 @@ public class UsbManager { * @param functions functions to set, in a bitwise mask. * Must satisfy {@link UsbManager#areSettableFunctions} * - * {@hide} + * @hide */ public void setScreenUnlockedFunctions(long functions) { try { @@ -1132,7 +1174,7 @@ public class UsbManager { * @return The currently set screen enabled functions. * A zero mask indicates that the screen unlocked functions feature is not enabled. * - * {@hide} + * @hide */ public long getScreenUnlockedFunctions() { try { @@ -1154,19 +1196,17 @@ public class UsbManager { * * @return The value of currently USB Bandwidth. * - * {@hide} + * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @RequiresPermission(Manifest.permission.MANAGE_USB) public int getUsbBandwidthMbps() { int usbSpeed; - try { usbSpeed = mService.getCurrentUsbSpeed(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } - return usbSpeedToBandwidth(usbSpeed); } @@ -1178,7 +1218,7 @@ public class UsbManager { * * @return a integer {@code GADGET_HAL_*} represent hal version. * - * {@hide} + * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @RequiresPermission(Manifest.permission.MANAGE_USB) @@ -1198,7 +1238,7 @@ public class UsbManager { * * @return a integer {@code USB_HAL_*} represent hal version. * - * {@hide} + * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @RequiresPermission(Manifest.permission.MANAGE_USB) @@ -1494,7 +1534,7 @@ public class UsbManager { * @param usbDeviceConnectionHandler The component to handle usb connections, * {@code null} to unset. * - * {@hide} + * @hide */ public void setUsbDeviceConnectionHandler(@Nullable ComponentName usbDeviceConnectionHandler) { try { @@ -1513,7 +1553,7 @@ public class UsbManager { * * @return Whether the mask is settable. * - * {@hide} + * @hide */ public static boolean areSettableFunctions(long functions) { return functions == FUNCTION_NONE @@ -1527,7 +1567,7 @@ public class UsbManager { * * @return String representation of given mask * - * {@hide} + * @hide */ public static String usbFunctionsToString(long functions) { StringJoiner joiner = new StringJoiner(","); @@ -1563,7 +1603,7 @@ public class UsbManager { * * @return A mask of all valid functions in the string * - * {@hide} + * @hide */ public static long usbFunctionsFromString(String functions) { if (functions == null || functions.equals(USB_FUNCTION_NONE)) { @@ -1585,7 +1625,7 @@ public class UsbManager { * * @return a value of USB bandwidth * - * {@hide} + * @hide */ public static int usbSpeedToBandwidth(int speed) { switch (speed) { @@ -1619,12 +1659,14 @@ public class UsbManager { * * @return String representation of Usb Gadget Hal Version * - * {@hide} + * @hide */ public static @NonNull String usbGadgetHalVersionToString(int version) { String halVersion; - if (version == GADGET_HAL_V1_2) { + if (version == GADGET_HAL_V2_0) { + halVersion = GADGET_HAL_VERSION_2_0; + } else if (version == GADGET_HAL_V1_2) { halVersion = GADGET_HAL_VERSION_1_2; } else if (version == GADGET_HAL_V1_1) { halVersion = GADGET_HAL_VERSION_1_1; 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/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index fb66cb9cfcc6..872414a7c147 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -70,11 +70,14 @@ import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; +import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; import android.database.ContentObserver; import android.graphics.Rect; import android.graphics.Region; @@ -98,6 +101,7 @@ import android.text.method.MovementMethod; import android.util.Log; import android.util.PrintWriterPrinter; import android.util.Printer; +import android.util.Xml; import android.util.proto.ProtoOutputStream; import android.view.BatchedInputEventReceiver.SimpleBatchedInputEventReceiver; import android.view.Choreographer; @@ -158,6 +162,8 @@ import com.android.internal.inputmethod.InputMethodPrivilegedOperationsRegistry; import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.internal.util.RingBuffer; +import org.xmlpull.v1.XmlPullParserException; + import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -730,7 +736,6 @@ public class InputMethodService extends AbstractInputMethodService { @Override public final void initializeInternal(@NonNull IInputMethod.InitParams params) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initializeInternal"); - mConfigTracker.onInitialize(params.configChanges); mPrivOps.set(params.privilegedOperations); InputMethodPrivilegedOperationsRegistry.put(params.token, mPrivOps); mNavigationBarController.onNavButtonFlagsChanged(params.navigationBarFlags); @@ -1601,6 +1606,8 @@ public class InputMethodService extends AbstractInputMethodService { mHideNavBarForKeyboard = getApplicationContext().getResources().getBoolean( com.android.internal.R.bool.config_hideNavBarForKeyboard); + initConfigurationTracker(); + // TODO(b/111364446) Need to address context lifecycle issue if need to re-create // for update resources & configuration correctly when show soft input // in non-default display. @@ -1656,6 +1663,36 @@ public class InputMethodService extends AbstractInputMethodService { Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } + private void initConfigurationTracker() { + final int flags = PackageManager.GET_META_DATA + | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS; + final ComponentName imeComponent = new ComponentName( + getPackageName(), getClass().getName()); + final String imeId = imeComponent.flattenToShortString(); + final ServiceInfo si; + try { + si = getPackageManager().getServiceInfo(imeComponent, + PackageManager.ComponentInfoFlags.of(flags)); + } catch (PackageManager.NameNotFoundException e) { + Log.wtf(TAG, "Unable to find input method " + imeId, e); + return; + } + try (XmlResourceParser parser = si.loadXmlMetaData(getPackageManager(), + InputMethod.SERVICE_META_DATA); + TypedArray sa = getResources().obtainAttributes(Xml.asAttributeSet(parser), + com.android.internal.R.styleable.InputMethod)) { + if (parser == null) { + throw new XmlPullParserException( + "No " + InputMethod.SERVICE_META_DATA + " meta-data"); + } + final int handledConfigChanges = sa.getInt( + com.android.internal.R.styleable.InputMethod_configChanges, 0); + mConfigTracker.onInitialize(handledConfigChanges); + } catch (Exception e) { + Log.wtf(TAG, "Unable to load input method " + imeId, e); + } + } + /** * This is a hook that subclasses can use to perform initialization of * their interface. It is called for you prior to any of your UI objects diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index a887f2a6ef29..d31540a65f2f 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -58,6 +58,8 @@ interface IUserManager { void setUserIcon(int userId, in Bitmap icon); ParcelFileDescriptor getUserIcon(int userId); UserInfo getPrimaryUser(); + int getMainUserId(); + int getPreviousFullUserToEnterForeground(); 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..f76e8cdfb55b 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. @@ -4275,6 +4300,43 @@ public class UserManager { } /** + * Returns the user who was last in the foreground, not including the current user and not + * including profiles. + * + * <p>Returns {@code null} if there is no previous user, for example if there + * is only one full user (i.e. only one user which is not a profile) on the device. + * + * <p>This method may be used for example to find the user to switch back to if the + * current user is removed, or if creating a new user is aborted. + * + * <p>Note that reboots do not interrupt this calculation; the previous user need not have + * used the device since it rebooted. + * + * <p>Note also that on devices that support multiple users on multiple displays, it is possible + * that the returned user will be visible on a secondary display, as the foreground user is the + * one associated with the main display. + * + * @hide + */ + @SystemApi + @RequiresPermission(anyOf = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.CREATE_USERS, + android.Manifest.permission.QUERY_USERS + }) + public @Nullable UserHandle getPreviousForegroundUser() { + try { + final int previousUser = mService.getPreviousFullUserToEnterForeground(); + if (previousUser == UserHandle.USER_NULL) { + return null; + } + return UserHandle.of(previousUser); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Checks whether it's possible to add more users. * * @return true if more users can be added, false if limit has been reached. 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/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java index e5c9adba46a9..dded76c9a97a 100644 --- a/core/java/android/telephony/PhoneStateListener.java +++ b/core/java/android/telephony/PhoneStateListener.java @@ -26,7 +26,6 @@ import android.os.Build; import android.os.Handler; import android.os.HandlerExecutor; import android.os.Looper; -import android.telephony.Annotation.CallState; import android.telephony.Annotation.DisconnectCauses; import android.telephony.Annotation.PreciseDisconnectCauses; import android.telephony.Annotation.RadioPowerState; @@ -726,7 +725,7 @@ public class PhoneStateListener { */ @Deprecated @RequiresPermission(value = android.Manifest.permission.READ_PHONE_STATE, conditional = true) - public void onCallStateChanged(@CallState int state, String phoneNumber) { + public void onCallStateChanged(@Annotation.CallState int state, String phoneNumber) { // default implementation empty } @@ -1569,12 +1568,48 @@ public class PhoneStateListener { () -> mExecutor.execute(() -> psl.onRadioPowerStateChanged(state))); } - public void onCallAttributesChanged(CallAttributes callAttributes) { + public void onCallStatesChanged(List<CallState> callStateList) { PhoneStateListener psl = mPhoneStateListenerWeakRef.get(); if (psl == null) return; + if (callStateList == null) return; + CallAttributes ca; + if (callStateList.isEmpty()) { + ca = new CallAttributes( + new PreciseCallState(PreciseCallState.PRECISE_CALL_STATE_IDLE, + PreciseCallState.PRECISE_CALL_STATE_IDLE, + PreciseCallState.PRECISE_CALL_STATE_IDLE, + DisconnectCause.NOT_VALID, PreciseDisconnectCause.NOT_VALID), + TelephonyManager.NETWORK_TYPE_UNKNOWN, new CallQuality()); + } else { + int foregroundCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE; + int backgroundCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE; + int ringingCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE; + for (CallState cs : callStateList) { + switch (cs.getCallClassification()) { + case CallState.CALL_CLASSIFICATION_FOREGROUND: + foregroundCallState = cs.getCallState(); + break; + case CallState.CALL_CLASSIFICATION_BACKGROUND: + backgroundCallState = cs.getCallState(); + break; + case CallState.CALL_CLASSIFICATION_RINGING: + ringingCallState = cs.getCallState(); + break; + default: + break; + } + } + ca = new CallAttributes( + new PreciseCallState( + ringingCallState, foregroundCallState, backgroundCallState, + DisconnectCause.NOT_VALID, PreciseDisconnectCause.NOT_VALID), + callStateList.get(0).getNetworkType(), + callStateList.get(0).getCallQuality()); + } Binder.withCleanCallingIdentity( - () -> mExecutor.execute(() -> psl.onCallAttributesChanged(callAttributes))); + () -> mExecutor.execute( + () -> psl.onCallAttributesChanged(ca))); } public void onActiveDataSubIdChanged(int subId) { diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java index e8960b8e35cd..257f3b7dbf40 100644 --- a/core/java/android/telephony/TelephonyCallback.java +++ b/core/java/android/telephony/TelephonyCallback.java @@ -27,6 +27,7 @@ import android.os.Binder; import android.os.Build; import android.telephony.emergency.EmergencyNumber; import android.telephony.ims.ImsReasonInfo; +import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.IPhoneStateListener; @@ -62,7 +63,7 @@ import java.util.concurrent.Executor; * appropriate sub-interfaces. */ public class TelephonyCallback { - + private static final String LOG_TAG = "TelephonyCallback"; /** * Experiment flag to set the per-pid registration limit for TelephonyCallback * @@ -1332,7 +1333,9 @@ public class TelephonyCallback { @SystemApi public interface CallAttributesListener { /** - * Callback invoked when the call attributes changes on the registered subscription. + * Callback invoked when the call attributes changes on the active call on the registered + * subscription. If the user swaps between a foreground and background call the call + * attributes will be reported for the active call only. * Note, the registration subscription ID comes from {@link TelephonyManager} object * which registers TelephonyCallback by * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}. @@ -1346,9 +1349,77 @@ public class TelephonyCallback { * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}. * * @param callAttributes the call attributes + * @deprecated Use onCallStatesChanged({@link List<CallState>}) to get each of call + * state for all ongoing calls on the subscription. */ @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) - void onCallAttributesChanged(@NonNull CallAttributes callAttributes); + @Deprecated + default void onCallAttributesChanged(@NonNull CallAttributes callAttributes) { + Log.w(LOG_TAG, "onCallAttributesChanged(List<CallState>) should be " + + "overridden."); + } + + /** + * Callback invoked when the call attributes changes on the ongoing calls on the registered + * subscription. If there are 1 foreground and 1 background call, Two {@link CallState} + * will be passed. + * Note, the registration subscription ID comes from {@link TelephonyManager} object + * which registers TelephonyCallback by + * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}. + * If this TelephonyManager object was created with + * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the + * subscription ID. Otherwise, this callback applies to + * {@link SubscriptionManager#getDefaultSubscriptionId()}. + * In the event that there are no active(state is not + * {@link PreciseCallState#PRECISE_CALL_STATE_IDLE}) calls, this API will report empty list. + * + * The calling app should have carrier privileges + * (see {@link TelephonyManager#hasCarrierPrivileges}) if it does not have the + * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}. + * + * @param callStateList the list of call states for each ongoing call. If there are + * a active call and a holding call, 1 call attributes for + * {@link PreciseCallState#PRECISE_CALL_STATE_ACTIVE} and another + * for {@link PreciseCallState#PRECISE_CALL_STATE_HOLDING} + * will be in this list. + */ + // Added as default for backward compatibility + @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) + default void onCallStatesChanged(@NonNull List<CallState> callStateList) { + if (callStateList.size() > 0) { + int foregroundCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE; + int backgroundCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE; + int ringingCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE; + for (CallState cs : callStateList) { + switch (cs.getCallClassification()) { + case CallState.CALL_CLASSIFICATION_FOREGROUND: + foregroundCallState = cs.getCallState(); + break; + case CallState.CALL_CLASSIFICATION_BACKGROUND: + backgroundCallState = cs.getCallState(); + break; + case CallState.CALL_CLASSIFICATION_RINGING: + ringingCallState = cs.getCallState(); + break; + default: + break; + } + } + onCallAttributesChanged(new CallAttributes( + new PreciseCallState( + ringingCallState, foregroundCallState, backgroundCallState, + DisconnectCause.NOT_VALID, PreciseDisconnectCause.NOT_VALID), + callStateList.get(0).getNetworkType(), + callStateList.get(0).getCallQuality())); + } else { + onCallAttributesChanged(new CallAttributes( + new PreciseCallState(PreciseCallState.PRECISE_CALL_STATE_IDLE, + PreciseCallState.PRECISE_CALL_STATE_IDLE, + PreciseCallState.PRECISE_CALL_STATE_IDLE, + DisconnectCause.NOT_VALID, PreciseDisconnectCause.NOT_VALID), + TelephonyManager.NETWORK_TYPE_UNKNOWN, new CallQuality())); + } + } } /** @@ -1702,14 +1773,13 @@ public class TelephonyCallback { () -> mExecutor.execute(() -> listener.onRadioPowerStateChanged(state))); } - public void onCallAttributesChanged(CallAttributes callAttributes) { + public void onCallStatesChanged(List<CallState> callStateList) { CallAttributesListener listener = (CallAttributesListener) mTelephonyCallbackWeakRef.get(); if (listener == null) return; Binder.withCleanCallingIdentity( - () -> mExecutor.execute(() -> listener.onCallAttributesChanged( - callAttributes))); + () -> mExecutor.execute(() -> listener.onCallStatesChanged(callStateList))); } public void onActiveDataSubIdChanged(int subId) { diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java index a3696e398668..0a1538de9f5d 100644 --- a/core/java/android/telephony/TelephonyRegistryManager.java +++ b/core/java/android/telephony/TelephonyRegistryManager.java @@ -32,13 +32,13 @@ import android.telephony.Annotation.CallState; import android.telephony.Annotation.DataActivityType; import android.telephony.Annotation.DisconnectCauses; import android.telephony.Annotation.NetworkType; -import android.telephony.Annotation.PreciseCallStates; import android.telephony.Annotation.PreciseDisconnectCauses; import android.telephony.Annotation.RadioPowerState; import android.telephony.Annotation.SimActivationState; import android.telephony.Annotation.SrvccState; import android.telephony.TelephonyManager.CarrierPrivilegesCallback; import android.telephony.emergency.EmergencyNumber; +import android.telephony.ims.ImsCallSession; import android.telephony.ims.ImsReasonInfo; import android.util.ArraySet; import android.util.Log; @@ -741,17 +741,20 @@ public class TelephonyRegistryManager { * @param slotIndex for which precise call state changed. Can be derived from subId except when * subId is invalid. * @param subId for which precise call state changed. - * @param ringCallPreciseState ringCall state. - * @param foregroundCallPreciseState foreground call state. - * @param backgroundCallPreciseState background call state. + * @param callStates Array of PreciseCallState of foreground, background & ringing calls. + * @param imsCallIds Array of IMS call session ID{@link ImsCallSession#getCallId} for + * ringing, foreground & background calls. + * @param imsServiceTypes Array of IMS call service type for ringing, foreground & + * background calls. + * @param imsCallTypes Array of IMS call type for ringing, foreground & background calls. */ public void notifyPreciseCallState(int slotIndex, int subId, - @PreciseCallStates int ringCallPreciseState, - @PreciseCallStates int foregroundCallPreciseState, - @PreciseCallStates int backgroundCallPreciseState) { + @Annotation.PreciseCallStates int[] callStates, String[] imsCallIds, + @Annotation.ImsCallServiceType int[] imsServiceTypes, + @Annotation.ImsCallType int[] imsCallTypes) { try { - sRegistry.notifyPreciseCallState(slotIndex, subId, ringCallPreciseState, - foregroundCallPreciseState, backgroundCallPreciseState); + sRegistry.notifyPreciseCallState(slotIndex, subId, callStates, + imsCallIds, imsServiceTypes, imsCallTypes); } catch (RemoteException ex) { // system process is dead throw ex.rethrowFromSystemServer(); 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..4277d01c091a 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 */ @@ -133,6 +140,13 @@ public class FeatureFlagUtils { public static final String SETTINGS_ADB_METRICS_WRITER = "settings_adb_metrics_writer"; /** + * Flag to show stylus-specific preferences in Connected Devices + * @hide + */ + public static final String SETTINGS_SHOW_STYLUS_PREFERENCES = + "settings_show_stylus_preferences"; + + /** * Flag to enable/disable biometrics enrollment v2 * @hide */ @@ -171,12 +185,15 @@ 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_SHOW_STYLUS_PREFERENCES, "false"); DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false"); } private static final Set<String> PERSISTENT_FLAGS; + static { PERSISTENT_FLAGS = new HashSet<>(); PERSISTENT_FLAGS.add(SETTINGS_ALLOW_INTENT_REDIRECTION_FOR_CLONE_PROFILE); @@ -190,6 +207,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..274585892b61 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. @@ -996,6 +1009,28 @@ public final class Display { } /** + * Returns the {@link DisplayShape} which is based on display coordinates. + * + * To get the {@link DisplayShape} based on the window frame, use + * {@link WindowInsets#getDisplayShape()} instead. + * + * @see DisplayShape + */ + @SuppressLint("VisiblySynchronized") + @NonNull + public DisplayShape getShape() { + synchronized (mLock) { + updateDisplayInfoLocked(); + final DisplayShape displayShape = mDisplayInfo.displayShape; + final @Surface.Rotation int rotation = getLocalRotation(); + if (displayShape != null && rotation != mDisplayInfo.rotation) { + return displayShape.setRotation(rotation); + } + return displayShape; + } + } + + /** * Gets the pixel format of the display. * @return One of the constants defined in {@link android.graphics.PixelFormat}. * diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index 0ba3072c0813..138017c5f0ec 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -323,6 +323,9 @@ public final class DisplayInfo implements Parcelable { @Surface.Rotation public int installOrientation; + @Nullable + public DisplayShape displayShape; + public static final @android.annotation.NonNull Creator<DisplayInfo> CREATOR = new Creator<DisplayInfo>() { @Override public DisplayInfo createFromParcel(Parcel source) { @@ -395,7 +398,8 @@ public final class DisplayInfo implements Parcelable { && brightnessMaximum == other.brightnessMaximum && brightnessDefault == other.brightnessDefault && Objects.equals(roundedCorners, other.roundedCorners) - && installOrientation == other.installOrientation; + && installOrientation == other.installOrientation + && Objects.equals(displayShape, other.displayShape); } @Override @@ -448,6 +452,7 @@ public final class DisplayInfo implements Parcelable { brightnessDefault = other.brightnessDefault; roundedCorners = other.roundedCorners; installOrientation = other.installOrientation; + displayShape = other.displayShape; } public void readFromParcel(Parcel source) { @@ -506,6 +511,7 @@ public final class DisplayInfo implements Parcelable { userDisabledHdrTypes[i] = source.readInt(); } installOrientation = source.readInt(); + displayShape = source.readTypedObject(DisplayShape.CREATOR); } @Override @@ -562,6 +568,7 @@ public final class DisplayInfo implements Parcelable { dest.writeInt(userDisabledHdrTypes[i]); } dest.writeInt(installOrientation); + dest.writeTypedObject(displayShape, flags); } @Override diff --git a/core/java/android/view/DisplayShape.aidl b/core/java/android/view/DisplayShape.aidl new file mode 100644 index 000000000000..af8b4176a12d --- /dev/null +++ b/core/java/android/view/DisplayShape.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.view; + +parcelable DisplayShape; diff --git a/core/java/android/view/DisplayShape.java b/core/java/android/view/DisplayShape.java new file mode 100644 index 000000000000..43bd773159f1 --- /dev/null +++ b/core/java/android/view/DisplayShape.java @@ -0,0 +1,357 @@ +/* + * 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.view; + +import static android.view.Surface.ROTATION_0; + +import android.annotation.Nullable; +import android.annotation.TestApi; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Matrix; +import android.graphics.Path; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.DisplayUtils; +import android.util.PathParser; +import android.util.RotationUtils; + +import androidx.annotation.NonNull; + +import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Objects; + +/** + * A class representing the shape of a display. It provides a {@link Path} of the display shape of + * the display shape. + * + * {@link DisplayShape} is immutable. + */ +public final class DisplayShape implements Parcelable { + + /** @hide */ + public static final DisplayShape NONE = new DisplayShape("" /* displayShapeSpec */, + 0 /* displayWidth */, 0 /* displayHeight */, 0 /* physicalPixelDisplaySizeRatio */, + 0 /* rotation */); + + /** @hide */ + @VisibleForTesting + public final String mDisplayShapeSpec; + private final float mPhysicalPixelDisplaySizeRatio; + private final int mDisplayWidth; + private final int mDisplayHeight; + private final int mRotation; + private final int mOffsetX; + private final int mOffsetY; + private final float mScale; + + private DisplayShape(@NonNull String displayShapeSpec, int displayWidth, int displayHeight, + float physicalPixelDisplaySizeRatio, int rotation) { + this(displayShapeSpec, displayWidth, displayHeight, physicalPixelDisplaySizeRatio, + rotation, 0, 0, 1f); + } + + private DisplayShape(@NonNull String displayShapeSpec, int displayWidth, int displayHeight, + float physicalPixelDisplaySizeRatio, int rotation, int offsetX, int offsetY, + float scale) { + mDisplayShapeSpec = displayShapeSpec; + mDisplayWidth = displayWidth; + mDisplayHeight = displayHeight; + mPhysicalPixelDisplaySizeRatio = physicalPixelDisplaySizeRatio; + mRotation = rotation; + mOffsetX = offsetX; + mOffsetY = offsetY; + mScale = scale; + } + + /** + * @hide + */ + @NonNull + public static DisplayShape fromResources( + @NonNull Resources res, @NonNull String displayUniqueId, int physicalDisplayWidth, + int physicalDisplayHeight, int displayWidth, int displayHeight) { + final boolean isScreenRound = RoundedCorners.getBuiltInDisplayIsRound(res, displayUniqueId); + final String spec = getSpecString(res, displayUniqueId); + if (spec == null || spec.isEmpty()) { + return createDefaultDisplayShape(displayWidth, displayHeight, isScreenRound); + } + final float physicalPixelDisplaySizeRatio = DisplayUtils.getPhysicalPixelDisplaySizeRatio( + physicalDisplayWidth, physicalDisplayHeight, displayWidth, displayHeight); + return fromSpecString(spec, physicalPixelDisplaySizeRatio, displayWidth, displayHeight); + } + + /** + * @hide + */ + @NonNull + public static DisplayShape createDefaultDisplayShape( + int displayWidth, int displayHeight, boolean isScreenRound) { + return fromSpecString(createDefaultSpecString(displayWidth, displayHeight, isScreenRound), + 1f, displayWidth, displayHeight); + } + + /** + * @hide + */ + @TestApi + @NonNull + public static DisplayShape fromSpecString(@NonNull String spec, + float physicalPixelDisplaySizeRatio, int displayWidth, int displayHeight) { + return Cache.getDisplayShape(spec, physicalPixelDisplaySizeRatio, displayWidth, + displayHeight); + } + + private static String createDefaultSpecString(int displayWidth, int displayHeight, + boolean isCircular) { + final String spec; + if (isCircular) { + final float xRadius = displayWidth / 2f; + final float yRadius = displayHeight / 2f; + // Draw a circular display shape. + spec = "M0," + yRadius + // Draw upper half circle with arcTo command. + + " A" + xRadius + "," + yRadius + " 0 1,1 " + displayWidth + "," + yRadius + // Draw lower half circle with arcTo command. + + " A" + xRadius + "," + yRadius + " 0 1,1 0," + yRadius + " Z"; + } else { + // Draw a rectangular display shape. + spec = "M0,0" + // Draw top edge. + + " L" + displayWidth + ",0" + // Draw right edge. + + " L" + displayWidth + "," + displayHeight + // Draw bottom edge. + + " L0," + displayHeight + // Draw left edge by close command which draws a line from current position to + // the initial points (0,0). + + " Z"; + } + return spec; + } + + /** + * Gets the display shape svg spec string of a display which is determined by the given display + * unique id. + * + * Loads the default config {@link R.string#config_mainDisplayShape} if + * {@link R.array#config_displayUniqueIdArray} is not set. + * + * @hide + */ + public static String getSpecString(Resources res, String displayUniqueId) { + final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId); + final TypedArray array = res.obtainTypedArray(R.array.config_displayShapeArray); + final String spec; + if (index >= 0 && index < array.length()) { + spec = array.getString(index); + } else { + spec = res.getString(R.string.config_mainDisplayShape); + } + array.recycle(); + return spec; + } + + /** + * @hide + */ + public DisplayShape setRotation(int rotation) { + return new DisplayShape(mDisplayShapeSpec, mDisplayWidth, mDisplayHeight, + mPhysicalPixelDisplaySizeRatio, rotation, mOffsetX, mOffsetY, mScale); + } + + /** + * @hide + */ + public DisplayShape setOffset(int offsetX, int offsetY) { + return new DisplayShape(mDisplayShapeSpec, mDisplayWidth, mDisplayHeight, + mPhysicalPixelDisplaySizeRatio, mRotation, offsetX, offsetY, mScale); + } + + /** + * @hide + */ + public DisplayShape setScale(float scale) { + return new DisplayShape(mDisplayShapeSpec, mDisplayWidth, mDisplayHeight, + mPhysicalPixelDisplaySizeRatio, mRotation, mOffsetX, mOffsetY, scale); + } + + @Override + public int hashCode() { + return Objects.hash(mDisplayShapeSpec, mDisplayWidth, mDisplayHeight, + mPhysicalPixelDisplaySizeRatio, mRotation, mOffsetX, mOffsetY, mScale); + } + + @Override + public boolean equals(@Nullable Object o) { + if (o == this) { + return true; + } + if (o instanceof DisplayShape) { + DisplayShape ds = (DisplayShape) o; + return Objects.equals(mDisplayShapeSpec, ds.mDisplayShapeSpec) + && mDisplayWidth == ds.mDisplayWidth && mDisplayHeight == ds.mDisplayHeight + && mPhysicalPixelDisplaySizeRatio == ds.mPhysicalPixelDisplaySizeRatio + && mRotation == ds.mRotation && mOffsetX == ds.mOffsetX + && mOffsetY == ds.mOffsetY && mScale == ds.mScale; + } + return false; + } + + @Override + public String toString() { + return "DisplayShape{" + + " spec=" + mDisplayShapeSpec + + " displayWidth=" + mDisplayWidth + + " displayHeight=" + mDisplayHeight + + " physicalPixelDisplaySizeRatio=" + mPhysicalPixelDisplaySizeRatio + + " rotation=" + mRotation + + " offsetX=" + mOffsetX + + " offsetY=" + mOffsetY + + " scale=" + mScale + "}"; + } + + /** + * Returns a {@link Path} of the display shape. + * + * @return a {@link Path} of the display shape. + */ + @NonNull + public Path getPath() { + return Cache.getPath(this); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString8(mDisplayShapeSpec); + dest.writeInt(mDisplayWidth); + dest.writeInt(mDisplayHeight); + dest.writeFloat(mPhysicalPixelDisplaySizeRatio); + dest.writeInt(mRotation); + dest.writeInt(mOffsetX); + dest.writeInt(mOffsetY); + dest.writeFloat(mScale); + } + + public static final @NonNull Creator<DisplayShape> CREATOR = new Creator<DisplayShape>() { + @Override + public DisplayShape createFromParcel(Parcel in) { + final String spec = in.readString8(); + final int displayWidth = in.readInt(); + final int displayHeight = in.readInt(); + final float ratio = in.readFloat(); + final int rotation = in.readInt(); + final int offsetX = in.readInt(); + final int offsetY = in.readInt(); + final float scale = in.readFloat(); + return new DisplayShape(spec, displayWidth, displayHeight, ratio, rotation, offsetX, + offsetY, scale); + } + + @Override + public DisplayShape[] newArray(int size) { + return new DisplayShape[size]; + } + }; + + private static final class Cache { + private static final Object CACHE_LOCK = new Object(); + + @GuardedBy("CACHE_LOCK") + private static String sCachedSpec; + @GuardedBy("CACHE_LOCK") + private static int sCachedDisplayWidth; + @GuardedBy("CACHE_LOCK") + private static int sCachedDisplayHeight; + @GuardedBy("CACHE_LOCK") + private static float sCachedPhysicalPixelDisplaySizeRatio; + @GuardedBy("CACHE_LOCK") + private static DisplayShape sCachedDisplayShape; + + @GuardedBy("CACHE_LOCK") + private static DisplayShape sCacheForPath; + @GuardedBy("CACHE_LOCK") + private static Path sCachedPath; + + static DisplayShape getDisplayShape(String spec, float physicalPixelDisplaySizeRatio, + int displayWidth, int displayHeight) { + synchronized (CACHE_LOCK) { + if (spec.equals(sCachedSpec) + && sCachedDisplayWidth == displayWidth + && sCachedDisplayHeight == displayHeight + && sCachedPhysicalPixelDisplaySizeRatio == physicalPixelDisplaySizeRatio) { + return sCachedDisplayShape; + } + } + + final DisplayShape shape = new DisplayShape(spec, displayWidth, displayHeight, + physicalPixelDisplaySizeRatio, ROTATION_0); + + synchronized (CACHE_LOCK) { + sCachedSpec = spec; + sCachedDisplayWidth = displayWidth; + sCachedDisplayHeight = displayHeight; + sCachedPhysicalPixelDisplaySizeRatio = physicalPixelDisplaySizeRatio; + sCachedDisplayShape = shape; + } + return shape; + } + + static Path getPath(@NonNull DisplayShape shape) { + synchronized (CACHE_LOCK) { + if (shape.equals(sCacheForPath)) { + return sCachedPath; + } + } + + final Path path = PathParser.createPathFromPathData(shape.mDisplayShapeSpec); + + if (!path.isEmpty()) { + final Matrix matrix = new Matrix(); + if (shape.mRotation != ROTATION_0) { + RotationUtils.transformPhysicalToLogicalCoordinates( + shape.mRotation, shape.mDisplayWidth, shape.mDisplayHeight, matrix); + } + if (shape.mPhysicalPixelDisplaySizeRatio != 1f) { + matrix.preScale(shape.mPhysicalPixelDisplaySizeRatio, + shape.mPhysicalPixelDisplaySizeRatio); + } + if (shape.mOffsetX != 0 || shape.mOffsetY != 0) { + matrix.postTranslate(shape.mOffsetX, shape.mOffsetY); + } + if (shape.mScale != 1f) { + matrix.postScale(shape.mScale, shape.mScale); + } + path.transform(matrix); + } + + synchronized (CACHE_LOCK) { + sCacheForPath = shape; + sCachedPath = path; + } + return path; + } + } +} 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/InsetsState.java b/core/java/android/view/InsetsState.java index a8cc9b62d30a..c56d61808665 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -197,6 +197,9 @@ public class InsetsState implements Parcelable { private PrivacyIndicatorBounds mPrivacyIndicatorBounds = new PrivacyIndicatorBounds(); + /** The display shape */ + private DisplayShape mDisplayShape = DisplayShape.NONE; + public InsetsState() { } @@ -271,6 +274,7 @@ public class InsetsState implements Parcelable { alwaysConsumeSystemBars, calculateRelativeCutout(frame), calculateRelativeRoundedCorners(frame), calculateRelativePrivacyIndicatorBounds(frame), + calculateRelativeDisplayShape(frame), compatInsetsTypes, (legacySystemUiFlags & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0); } @@ -335,6 +339,16 @@ public class InsetsState implements Parcelable { return mPrivacyIndicatorBounds.inset(insetLeft, insetTop, insetRight, insetBottom); } + private DisplayShape calculateRelativeDisplayShape(Rect frame) { + if (mDisplayFrame.equals(frame)) { + return mDisplayShape; + } + if (frame == null) { + return DisplayShape.NONE; + } + return mDisplayShape.setOffset(-frame.left, -frame.top); + } + public Insets calculateInsets(Rect frame, @InsetsType int types, boolean ignoreVisibility) { Insets insets = Insets.NONE; for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) { @@ -589,6 +603,14 @@ public class InsetsState implements Parcelable { return mPrivacyIndicatorBounds; } + public void setDisplayShape(DisplayShape displayShape) { + mDisplayShape = displayShape; + } + + public DisplayShape getDisplayShape() { + return mDisplayShape; + } + /** * Modifies the state of this class to exclude a certain type to make it ready for dispatching * to the client. @@ -628,6 +650,7 @@ public class InsetsState implements Parcelable { mRoundedCorners = mRoundedCorners.scale(scale); mRoundedCornerFrame.scale(scale); mPrivacyIndicatorBounds = mPrivacyIndicatorBounds.scale(scale); + mDisplayShape = mDisplayShape.setScale(scale); for (int i = 0; i < SIZE; i++) { final InsetsSource source = mSources[i]; if (source != null) { @@ -650,6 +673,7 @@ public class InsetsState implements Parcelable { mRoundedCorners = other.getRoundedCorners(); mRoundedCornerFrame.set(other.mRoundedCornerFrame); mPrivacyIndicatorBounds = other.getPrivacyIndicatorBounds(); + mDisplayShape = other.getDisplayShape(); if (copySources) { for (int i = 0; i < SIZE; i++) { InsetsSource source = other.mSources[i]; @@ -675,6 +699,7 @@ public class InsetsState implements Parcelable { mRoundedCorners = other.getRoundedCorners(); mRoundedCornerFrame.set(other.mRoundedCornerFrame); mPrivacyIndicatorBounds = other.getPrivacyIndicatorBounds(); + mDisplayShape = other.getDisplayShape(); final ArraySet<Integer> t = toInternalType(types); for (int i = t.size() - 1; i >= 0; i--) { final int type = t.valueAt(i); @@ -807,6 +832,7 @@ public class InsetsState implements Parcelable { pw.println(newPrefix + "mRoundedCorners=" + mRoundedCorners); pw.println(newPrefix + "mRoundedCornerFrame=" + mRoundedCornerFrame); pw.println(newPrefix + "mPrivacyIndicatorBounds=" + mPrivacyIndicatorBounds); + pw.println(newPrefix + "mDisplayShape=" + mDisplayShape); for (int i = 0; i < SIZE; i++) { InsetsSource source = mSources[i]; if (source == null) continue; @@ -911,7 +937,8 @@ public class InsetsState implements Parcelable { || !mDisplayCutout.equals(state.mDisplayCutout) || !mRoundedCorners.equals(state.mRoundedCorners) || !mRoundedCornerFrame.equals(state.mRoundedCornerFrame) - || !mPrivacyIndicatorBounds.equals(state.mPrivacyIndicatorBounds)) { + || !mPrivacyIndicatorBounds.equals(state.mPrivacyIndicatorBounds) + || !mDisplayShape.equals(state.mDisplayShape)) { return false; } for (int i = 0; i < SIZE; i++) { @@ -941,7 +968,7 @@ public class InsetsState implements Parcelable { @Override public int hashCode() { return Objects.hash(mDisplayFrame, mDisplayCutout, Arrays.hashCode(mSources), - mRoundedCorners, mPrivacyIndicatorBounds, mRoundedCornerFrame); + mRoundedCorners, mPrivacyIndicatorBounds, mRoundedCornerFrame, mDisplayShape); } public InsetsState(Parcel in) { @@ -961,6 +988,7 @@ public class InsetsState implements Parcelable { dest.writeTypedObject(mRoundedCorners, flags); mRoundedCornerFrame.writeToParcel(dest, flags); dest.writeTypedObject(mPrivacyIndicatorBounds, flags); + dest.writeTypedObject(mDisplayShape, flags); } public static final @NonNull Creator<InsetsState> CREATOR = new Creator<InsetsState>() { @@ -981,6 +1009,7 @@ public class InsetsState implements Parcelable { mRoundedCorners = in.readTypedObject(RoundedCorners.CREATOR); mRoundedCornerFrame.readFromParcel(in); mPrivacyIndicatorBounds = in.readTypedObject(PrivacyIndicatorBounds.CREATOR); + mDisplayShape = in.readTypedObject(DisplayShape.CREATOR); } @Override @@ -998,6 +1027,7 @@ public class InsetsState implements Parcelable { + ", mRoundedCorners=" + mRoundedCorners + " mRoundedCornerFrame=" + mRoundedCornerFrame + ", mPrivacyIndicatorBounds=" + mPrivacyIndicatorBounds + + ", mDisplayShape=" + mDisplayShape + ", mSources= { " + joiner + " }"; } 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/WindowInsets.java b/core/java/android/view/WindowInsets.java index 03b25c25ab99..8de15c1d0ce6 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -42,7 +42,6 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.Intent; import android.graphics.Insets; import android.graphics.Rect; -import android.util.SparseArray; import android.view.View.OnApplyWindowInsetsListener; import android.view.WindowInsets.Type.InsetsType; import android.view.inputmethod.EditorInfo; @@ -83,6 +82,7 @@ public final class WindowInsets { @Nullable private final DisplayCutout mDisplayCutout; @Nullable private final RoundedCorners mRoundedCorners; @Nullable private final PrivacyIndicatorBounds mPrivacyIndicatorBounds; + @Nullable private final DisplayShape mDisplayShape; /** * In multi-window we force show the navigation bar. Because we don't want that the surface size @@ -115,24 +115,9 @@ public final class WindowInsets { public static final @NonNull WindowInsets CONSUMED; static { - CONSUMED = new WindowInsets((Rect) null, null, false, false, null); - } - - /** - * Construct a new WindowInsets from individual insets. - * - * A {@code null} inset indicates that the respective inset is consumed. - * - * @hide - * @deprecated Use {@link WindowInsets(SparseArray, SparseArray, boolean, boolean, DisplayCutout)} - */ - @Deprecated - public WindowInsets(Rect systemWindowInsetsRect, Rect stableInsetsRect, boolean isRound, - boolean alwaysConsumeSystemBars, DisplayCutout displayCutout) { - this(createCompatTypeMap(systemWindowInsetsRect), createCompatTypeMap(stableInsetsRect), - createCompatVisibilityMap(createCompatTypeMap(systemWindowInsetsRect)), - isRound, alwaysConsumeSystemBars, displayCutout, null, null, - systemBars(), false /* compatIgnoreVisibility */); + CONSUMED = new WindowInsets(createCompatTypeMap(null), createCompatTypeMap(null), + createCompatVisibilityMap(createCompatTypeMap(null)), false, false, null, null, + null, null, systemBars(), false); } /** @@ -154,6 +139,7 @@ public final class WindowInsets { boolean alwaysConsumeSystemBars, DisplayCutout displayCutout, RoundedCorners roundedCorners, PrivacyIndicatorBounds privacyIndicatorBounds, + DisplayShape displayShape, @InsetsType int compatInsetsTypes, boolean compatIgnoreVisibility) { mSystemWindowInsetsConsumed = typeInsetsMap == null; mTypeInsetsMap = mSystemWindowInsetsConsumed @@ -177,6 +163,7 @@ public final class WindowInsets { mRoundedCorners = roundedCorners; mPrivacyIndicatorBounds = privacyIndicatorBounds; + mDisplayShape = displayShape; } /** @@ -191,6 +178,7 @@ public final class WindowInsets { src.mAlwaysConsumeSystemBars, displayCutoutCopyConstructorArgument(src), src.mRoundedCorners, src.mPrivacyIndicatorBounds, + src.mDisplayShape, src.mCompatInsetsTypes, src.mCompatIgnoreVisibility); } @@ -244,15 +232,18 @@ public final class WindowInsets { @UnsupportedAppUsage public WindowInsets(Rect systemWindowInsets) { this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, false, null, - null, null, systemBars(), false /* compatIgnoreVisibility */); + null, null, null, systemBars(), false /* compatIgnoreVisibility */); } /** * Creates a indexOf(type) -> inset map for which the {@code insets} is just mapped to * {@link Type#statusBars()} and {@link Type#navigationBars()}, depending on the * location of the inset. + * + * @hide */ - private static Insets[] createCompatTypeMap(@Nullable Rect insets) { + @VisibleForTesting + public static Insets[] createCompatTypeMap(@Nullable Rect insets) { if (insets == null) { return null; } @@ -271,6 +262,10 @@ public final class WindowInsets { Insets.of(insets.left, 0, insets.right, insets.bottom); } + /** + * @hide + */ + @VisibleForTesting private static boolean[] createCompatVisibilityMap(@Nullable Insets[] typeInsetsMap) { boolean[] typeVisibilityMap = new boolean[SIZE]; if (typeInsetsMap == null) { @@ -533,6 +528,17 @@ public final class WindowInsets { } /** + * Returns the display shape in the coordinate space of the window. + * + * @return the display shape + * @see DisplayShape + */ + @Nullable + public DisplayShape getDisplayShape() { + return mDisplayShape; + } + + /** * Returns a copy of this WindowInsets with the cutout fully consumed. * * @return A modified copy of this WindowInsets @@ -547,7 +553,7 @@ public final class WindowInsets { mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap, mIsRound, mAlwaysConsumeSystemBars, - null /* displayCutout */, mRoundedCorners, mPrivacyIndicatorBounds, + null /* displayCutout */, mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape, mCompatInsetsTypes, mCompatIgnoreVisibility); } @@ -602,7 +608,7 @@ public final class WindowInsets { // it. (mCompatInsetsTypes & displayCutout()) != 0 ? null : displayCutoutCopyConstructorArgument(this), - mRoundedCorners, mPrivacyIndicatorBounds, mCompatInsetsTypes, + mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape, mCompatInsetsTypes, mCompatIgnoreVisibility); } @@ -911,6 +917,8 @@ public final class WindowInsets { result.append(mPrivacyIndicatorBounds != null ? "privacyIndicatorBounds=" + mPrivacyIndicatorBounds : ""); result.append("\n "); + result.append(mDisplayShape != null ? "displayShape=" + mDisplayShape : ""); + result.append("\n "); result.append("compatInsetsTypes=" + mCompatInsetsTypes); result.append("\n "); result.append("compatIgnoreVisibility=" + mCompatIgnoreVisibility); @@ -1018,6 +1026,7 @@ public final class WindowInsets { mPrivacyIndicatorBounds == null ? null : mPrivacyIndicatorBounds.inset(left, top, right, bottom), + mDisplayShape, mCompatInsetsTypes, mCompatIgnoreVisibility); } @@ -1037,7 +1046,8 @@ public final class WindowInsets { && Arrays.equals(mTypeVisibilityMap, that.mTypeVisibilityMap) && Objects.equals(mDisplayCutout, that.mDisplayCutout) && Objects.equals(mRoundedCorners, that.mRoundedCorners) - && Objects.equals(mPrivacyIndicatorBounds, that.mPrivacyIndicatorBounds); + && Objects.equals(mPrivacyIndicatorBounds, that.mPrivacyIndicatorBounds) + && Objects.equals(mDisplayShape, that.mDisplayShape); } @Override @@ -1045,7 +1055,7 @@ public final class WindowInsets { return Objects.hash(Arrays.hashCode(mTypeInsetsMap), Arrays.hashCode(mTypeMaxInsetsMap), Arrays.hashCode(mTypeVisibilityMap), mIsRound, mDisplayCutout, mRoundedCorners, mAlwaysConsumeSystemBars, mSystemWindowInsetsConsumed, mStableInsetsConsumed, - mDisplayCutoutConsumed, mPrivacyIndicatorBounds); + mDisplayCutoutConsumed, mPrivacyIndicatorBounds, mDisplayShape); } @@ -1106,6 +1116,7 @@ public final class WindowInsets { private DisplayCutout mDisplayCutout; private RoundedCorners mRoundedCorners = RoundedCorners.NO_ROUNDED_CORNERS; + private DisplayShape mDisplayShape = DisplayShape.NONE; private boolean mIsRound; private boolean mAlwaysConsumeSystemBars; @@ -1137,6 +1148,7 @@ public final class WindowInsets { mIsRound = insets.mIsRound; mAlwaysConsumeSystemBars = insets.mAlwaysConsumeSystemBars; mPrivacyIndicatorBounds = insets.mPrivacyIndicatorBounds; + mDisplayShape = insets.mDisplayShape; } /** @@ -1381,6 +1393,19 @@ public final class WindowInsets { return this; } + /** + * Sets the display shape. + * + * @see #getDisplayShape(). + * @param displayShape the display shape. + * @return itself. + */ + @NonNull + public Builder setDisplayShape(@NonNull DisplayShape displayShape) { + mDisplayShape = displayShape; + return this; + } + /** @hide */ @NonNull public Builder setRound(boolean round) { @@ -1405,7 +1430,8 @@ public final class WindowInsets { return new WindowInsets(mSystemInsetsConsumed ? null : mTypeInsetsMap, mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap, mIsRound, mAlwaysConsumeSystemBars, mDisplayCutout, mRoundedCorners, - mPrivacyIndicatorBounds, systemBars(), false /* compatIgnoreVisibility */); + mPrivacyIndicatorBounds, mDisplayShape, systemBars(), + false /* compatIgnoreVisibility */); } } 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/inputmethod/IInputMethod.aidl b/core/java/com/android/internal/inputmethod/IInputMethod.aidl index 1e3714eb342d..8cb568d6e0e6 100644 --- a/core/java/com/android/internal/inputmethod/IInputMethod.aidl +++ b/core/java/com/android/internal/inputmethod/IInputMethod.aidl @@ -40,7 +40,6 @@ oneway interface IInputMethod { parcelable InitParams { IBinder token; IInputMethodPrivilegedOperations privilegedOperations; - int configChanges; int navigationBarFlags; } diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl index 4b1753a82762..9cb2e68229f0 100644 --- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl +++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl @@ -17,7 +17,7 @@ package com.android.internal.telephony; import android.telephony.BarringInfo; -import android.telephony.CallAttributes; +import android.telephony.CallState; import android.telephony.CellIdentity; import android.telephony.CellInfo; import android.telephony.DataConnectionRealTimeInfo; @@ -62,7 +62,7 @@ oneway interface IPhoneStateListener { void onPhoneCapabilityChanged(in PhoneCapability capability); void onActiveDataSubIdChanged(in int subId); void onRadioPowerStateChanged(in int state); - void onCallAttributesChanged(in CallAttributes callAttributes); + void onCallStatesChanged(in List<CallState> callStateList); @SuppressWarnings(value={"untyped-collection"}) void onEmergencyNumberListChanged(in Map emergencyNumberList); void onOutgoingEmergencyCall(in EmergencyNumber placedEmergencyNumber, int subscriptionId); diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl index c7fa757ac0b7..7ba26866ee03 100644 --- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl +++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl @@ -66,8 +66,8 @@ interface ITelephonyRegistry { void notifyCellLocationForSubscriber(in int subId, in CellIdentity cellLocation); @UnsupportedAppUsage void notifyCellInfo(in List<CellInfo> cellInfo); - void notifyPreciseCallState(int phoneId, int subId, int ringingCallState, - int foregroundCallState, int backgroundCallState); + void notifyPreciseCallState(int phoneId, int subId, in int[] callStates, in String[] imsCallIds, + in int[] imsCallServiceTypes, in int[] imsCallTypes); void notifyDisconnectCause(int phoneId, int subId, int disconnectCause, int preciseDisconnectCause); void notifyCellInfoForSubscriber(in int subId, in List<CellInfo> cellInfo); 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/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 6460007b52de..eb7034437423 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -1705,8 +1705,7 @@ --> <flag name="systemExempted" value="0x400" /> <!-- "Short service" foreground service type. See - TODO: Change it to a real link - {@code android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}. + {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}. for more details. --> <flag name="shortService" value="0x800" /> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index ccce9ba6a709..2ab5b75ea25a 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> @@ -5977,4 +5985,35 @@ <string-array translatable="false" name="config_fontManagerServiceCerts"> </string-array> + <!-- A string config in svg path format for the main display shape. + (@see https://www.w3.org/TR/SVG/paths.html#PathData). + + This config must be set unless: + 1. {@link Configuration#isScreenRound} is true which means the display shape is circular + and the system will auto-generate a circular shape. + 2. The display has no rounded corner and the system will auto-generate a rectangular shape. + (@see DisplayShape#createDefaultDisplayShape) + + Note: If the display supports multiple resolutions, please define the path config based on + the highest resolution so that it can be scaled correctly in each resolution. --> + <string name="config_mainDisplayShape" translatable="false"></string> + + <!-- A string config in svg path format for the secondary display shape. + (@see https://www.w3.org/TR/SVG/paths.html#PathData). + + This config must be set unless: + 1. {@link Configuration#isScreenRound} is true which means the display shape is circular + and the system will auto-generate a circular shape. + 2. The display has no rounded corner and the system will auto-generate a rectangular shape. + (@see DisplayShape#createDefaultDisplayShape) + + Note: If the display supports multiple resolutions, please define the path config based on + the highest resolution so that it can be scaled correctly in each resolution. --> + <string name="config_secondaryDisplayShape" translatable="false"></string> + + <!-- The display shape config for each display in a multi-display device. --> + <string-array name="config_displayShapeArray" translatable="false"> + <item>@string/config_mainDisplayShape</item> + <item>@string/config_secondaryDisplayShape</item> + </string-array> </resources> 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..168806ae87d0 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" /> @@ -4913,4 +4915,8 @@ <java-symbol type="dimen" name="status_bar_height_default" /> <java-symbol type="string" name="default_card_name"/> + + <java-symbol type="string" name="config_mainDisplayShape"/> + <java-symbol type="string" name="config_secondaryDisplayShape"/> + <java-symbol type="array" name="config_displayShapeArray" /> </resources> 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/graphics/TypefaceTest.java b/core/tests/coretests/src/android/graphics/TypefaceTest.java index a528c1975177..6bf8f5678b33 100644 --- a/core/tests/coretests/src/android/graphics/TypefaceTest.java +++ b/core/tests/coretests/src/android/graphics/TypefaceTest.java @@ -203,16 +203,22 @@ public class TypefaceTest { fallbackMap); SharedMemory sharedMemory = Typeface.serializeFontMap(systemFontMap); Map<String, Typeface> copiedFontMap = new ArrayMap<>(); - Typeface.deserializeFontMap(sharedMemory.mapReadOnly().order(ByteOrder.BIG_ENDIAN), - copiedFontMap); - assertEquals(systemFontMap.size(), copiedFontMap.size()); - for (String key : systemFontMap.keySet()) { - assertTrue(copiedFontMap.containsKey(key)); - Typeface original = systemFontMap.get(key); - Typeface copied = copiedFontMap.get(key); - assertEquals(original.getStyle(), copied.getStyle()); - assertEquals(original.getWeight(), copied.getWeight()); - assertEquals(measureText(original, "hello"), measureText(copied, "hello"), 1e-6); + try { + Typeface.deserializeFontMap(sharedMemory.mapReadOnly().order(ByteOrder.BIG_ENDIAN), + copiedFontMap); + assertEquals(systemFontMap.size(), copiedFontMap.size()); + for (String key : systemFontMap.keySet()) { + assertTrue(copiedFontMap.containsKey(key)); + Typeface original = systemFontMap.get(key); + Typeface copied = copiedFontMap.get(key); + assertEquals(original.getStyle(), copied.getStyle()); + assertEquals(original.getWeight(), copied.getWeight()); + assertEquals(measureText(original, "hello"), measureText(copied, "hello"), 1e-6); + } + } finally { + for (Typeface typeface : copiedFontMap.values()) { + typeface.releaseNativeObjectForTest(); + } } } 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/coretests/src/android/view/DisplayShapeTest.java b/core/tests/coretests/src/android/view/DisplayShapeTest.java new file mode 100644 index 000000000000..77dd8bd7f976 --- /dev/null +++ b/core/tests/coretests/src/android/view/DisplayShapeTest.java @@ -0,0 +1,143 @@ +/* + * 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.view; + +import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.sameInstance; +import static org.junit.Assert.assertEquals; + +import android.graphics.Path; +import android.graphics.RectF; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for {@link DisplayShape}. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class DisplayShapeTest { + // Rectangle with w=100, height=200 + private static final String SPEC_RECTANGULAR_SHAPE = "M0,0 L100,0 L100,200 L0,200 Z"; + + @Test + public void testGetPath() { + final DisplayShape displayShape = DisplayShape.fromSpecString( + SPEC_RECTANGULAR_SHAPE, 1f, 100, 200); + final Path path = displayShape.getPath(); + final RectF actualRect = new RectF(); + path.computeBounds(actualRect, false); + + final RectF expectRect = new RectF(0f, 0f, 100f, 200f); + assertEquals(actualRect, expectRect); + } + + @Test + public void testDefaultShape_screenIsRound() { + final DisplayShape displayShape = DisplayShape.createDefaultDisplayShape(100, 100, true); + + // A circle with radius = 50. + final String expect = "M0,50.0 A50.0,50.0 0 1,1 100,50.0 A50.0,50.0 0 1,1 0,50.0 Z"; + assertEquals(displayShape.mDisplayShapeSpec, expect); + } + + @Test + public void testDefaultShape_screenIsNotRound() { + final DisplayShape displayShape = DisplayShape.createDefaultDisplayShape(100, 200, false); + + // A rectangle with width/height = 100/200. + final String expect = "M0,0 L100,0 L100,200 L0,200 Z"; + assertEquals(displayShape.mDisplayShapeSpec, expect); + } + + @Test + public void testFromSpecString_cache() { + final DisplayShape cached = DisplayShape.fromSpecString( + SPEC_RECTANGULAR_SHAPE, 1f, 100, 200); + assertThat(DisplayShape.fromSpecString(SPEC_RECTANGULAR_SHAPE, 1f, 100, 200), + sameInstance(cached)); + } + + @Test + public void testGetPath_cache() { + final Path cached = DisplayShape.fromSpecString( + SPEC_RECTANGULAR_SHAPE, 1f, 100, 200).getPath(); + assertThat(DisplayShape.fromSpecString( + SPEC_RECTANGULAR_SHAPE, 1f, 100, 200).getPath(), + sameInstance(cached)); + } + + @Test + public void testRotate_90() { + DisplayShape displayShape = DisplayShape.fromSpecString( + SPEC_RECTANGULAR_SHAPE, 1f, 100, 200); + displayShape = displayShape.setRotation(ROTATION_90); + final Path path = displayShape.getPath(); + final RectF actualRect = new RectF(); + path.computeBounds(actualRect, false); + + final RectF expectRect = new RectF(0f, 0f, 200f, 100f); + assertEquals(actualRect, expectRect); + } + + @Test + public void testRotate_270() { + DisplayShape displayShape = DisplayShape.fromSpecString( + SPEC_RECTANGULAR_SHAPE, 1f, 100, 200); + displayShape = displayShape.setRotation(ROTATION_270); + final Path path = displayShape.getPath(); + final RectF actualRect = new RectF(); + path.computeBounds(actualRect, false); + + final RectF expectRect = new RectF(0f, 0f, 200f, 100f); + assertEquals(actualRect, expectRect); + } + + @Test + public void testOffset() { + DisplayShape displayShape = DisplayShape.fromSpecString( + SPEC_RECTANGULAR_SHAPE, 1f, 100, 200); + displayShape = displayShape.setOffset(-10, -20); + final Path path = displayShape.getPath(); + final RectF actualRect = new RectF(); + path.computeBounds(actualRect, false); + + final RectF expectRect = new RectF(-10f, -20f, 90f, 180f); + assertEquals(actualRect, expectRect); + } + + @Test + public void testPhysicalPixelDisplaySizeRatio() { + final DisplayShape displayShape = DisplayShape.fromSpecString( + SPEC_RECTANGULAR_SHAPE, 0.5f, 100, 200); + final Path path = displayShape.getPath(); + final RectF actualRect = new RectF(); + path.computeBounds(actualRect, false); + + final RectF expectRect = new RectF(0f, 0f, 50f, 100f); + assertEquals(actualRect, expectRect); + } +} diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java index be9da11057a2..6a96f280d603 100644 --- a/core/tests/coretests/src/android/view/InsetsStateTest.java +++ b/core/tests/coretests/src/android/view/InsetsStateTest.java @@ -517,6 +517,19 @@ public class InsetsStateTest { windowInsets.getRoundedCorner(POSITION_BOTTOM_LEFT)); } + @Test + public void testCalculateRelativeDisplayShape() { + mState.setDisplayFrame(new Rect(0, 0, 200, 400)); + mState.setDisplayShape(DisplayShape.createDefaultDisplayShape(200, 400, false)); + WindowInsets windowInsets = mState.calculateInsets(new Rect(10, 20, 200, 400), null, false, + false, SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION, + WINDOWING_MODE_UNDEFINED, new SparseIntArray()); + + final DisplayShape expect = + DisplayShape.createDefaultDisplayShape(200, 400, false).setOffset(-10, -20); + assertEquals(expect, windowInsets.getDisplayShape()); + } + private void assertEqualsAndHashCode() { assertEquals(mState, mState2); assertEquals(mState.hashCode(), mState2.hashCode()); diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java index dd9634bd68fb..4fed396456da 100644 --- a/core/tests/coretests/src/android/view/WindowInsetsTest.java +++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java @@ -39,13 +39,16 @@ public class WindowInsetsTest { @Test public void systemWindowInsets_afterConsuming_isConsumed() { - assertTrue(new WindowInsets(new Rect(1, 2, 3, 4), null, false, false, null) + assertTrue(new WindowInsets(WindowInsets.createCompatTypeMap(new Rect(1, 2, 3, 4)), null, + null, false, false, null, null, null, null, + WindowInsets.Type.systemBars(), false) .consumeSystemWindowInsets().isConsumed()); } @Test public void multiNullConstructor_isConsumed() { - assertTrue(new WindowInsets((Rect) null, null, false, false, null).isConsumed()); + assertTrue(new WindowInsets(null, null, null, false, false, null, null, null, null, + WindowInsets.Type.systemBars(), false).isConsumed()); } @Test @@ -61,7 +64,8 @@ public class WindowInsetsTest { WindowInsets.assignCompatInsets(maxInsets, new Rect(0, 10, 0, 0)); WindowInsets.assignCompatInsets(insets, new Rect(0, 0, 0, 0)); WindowInsets windowInsets = new WindowInsets(insets, maxInsets, visible, false, false, null, - null, null, systemBars(), true /* compatIgnoreVisibility */); + null, null, DisplayShape.NONE, systemBars(), + true /* compatIgnoreVisibility */); assertEquals(Insets.of(0, 10, 0, 0), windowInsets.getSystemWindowInsets()); } } diff --git a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java index d10f173328be..4d4ec3523d39 100644 --- a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java +++ b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java @@ -168,7 +168,8 @@ public class ActionBarOverlayLayoutTest { } private WindowInsets insetsWith(Insets content, DisplayCutout cutout) { - return new WindowInsets(content.toRect(), null, false, false, cutout); + return new WindowInsets(WindowInsets.createCompatTypeMap(content.toRect()), null, null, + false, false, cutout, null, null, null, WindowInsets.Type.systemBars(), false); } private ViewGroup createViewGroupWithId(int id) { 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/data/keyboards/Generic.kl b/data/keyboards/Generic.kl index 215b60ec9d0e..a43e2251d486 100644 --- a/data/keyboards/Generic.kl +++ b/data/keyboards/Generic.kl @@ -371,6 +371,10 @@ key 405 LAST_CHANNEL # key 413 "KEY_DIGITS" # key 414 "KEY_TEEN" # key 415 "KEY_TWEN" +# key 418 "KEY_ZOOM_IN" +key 418 ZOOM_IN +# key 419 "KEY_ZOOM_OUT" +key 419 ZOOM_OUT key 528 FOCUS key 429 CONTACTS 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/graphics/PathIterator.java b/graphics/java/android/graphics/PathIterator.java index 33b9a475ad21..bfda690e0b05 100644 --- a/graphics/java/android/graphics/PathIterator.java +++ b/graphics/java/android/graphics/PathIterator.java @@ -281,7 +281,7 @@ public class PathIterator implements Iterator<PathIterator.Segment> { return mConicWeight; } - public Segment(@NonNull @Verb int verb, @NonNull float[] points, float conicWeight) { + Segment(@NonNull @Verb int verb, @NonNull float[] points, float conicWeight) { mVerb = verb; mPoints = points; mConicWeight = conicWeight; diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 3b7d0e14893c..9fb627fcc501 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -1207,6 +1207,7 @@ public class Typeface { * It is safe to call this method twice or more on the same instance. * @hide */ + @TestApi public void releaseNativeObjectForTest() { mCleaner.run(); } @@ -1294,6 +1295,13 @@ public class Typeface { /** * Deserialize the font mapping from the serialized byte buffer. * + * <p>Warning: the given {@code buffer} must outlive generated Typeface + * objects in {@code out}. In production code, this is guaranteed by + * storing the buffer in {@link #sSystemFontMapBuffer}. + * If you call this method in a test, please make sure to destroy the + * generated Typeface objects by calling + * {@link #releaseNativeObjectForTest()}. + * * @hide */ @TestApi 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..15aaae25f754 100755..100644 --- a/libs/androidfw/ApkAssets.cpp +++ b/libs/androidfw/ApkAssets.cpp @@ -18,6 +18,7 @@ #include "android-base/errors.h" #include "android-base/logging.h" +#include "android-base/utf8.h" namespace android { @@ -83,15 +84,16 @@ std::unique_ptr<ApkAssets> ApkAssets::LoadOverlay(const std::string& idmap_path, return {}; } + std::string overlay_path(loaded_idmap->OverlayApkPath()); + auto fd = unique_fd(base::utf8::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(overlay_path), flags, std::move(fd)); } if (overlay_assets == nullptr) { return {}; diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index 3fa369d7ff4a..cc7e8714c0ac 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,30 +202,39 @@ 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)); + } } void AssetManager2::DumpToLog() const { @@ -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_) { @@ -364,7 +374,7 @@ bool AssetManager2::GetOverlayablesToString(const android::StringPiece& package_ const std::string name = ToFormattedResourceString(*res_name); output.append(base::StringPrintf( "resource='%s' overlayable='%s' actor='%s' policy='0x%08x'\n", - name.c_str(), info->name.c_str(), info->actor.c_str(), info->policy_flags)); + name.c_str(), info->name.data(), info->actor.data(), info->policy_flags)); } } } @@ -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) { @@ -1346,22 +1356,22 @@ 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>(); - + for (ConfiguredPackage& package : group.packages_) { + package.filtered_configs_.forEachItem([](auto, auto& fcg) { fcg.type_entries.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); + package.loaded_package_->ForEachTypeSpec([&](const TypeSpec& type_spec, uint8_t type_id) { + 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 = &package.filtered_configs_.editItemAt(type_id - 1); + } + group->type_entries.push_back(&type_entry); } } }); + package.filtered_configs_.trimBuckets( + [](const auto& fcg) { return fcg.type_entries.empty(); }); } } } @@ -1402,30 +1412,34 @@ uint8_t AssetManager2::GetAssignedPackageId(const LoadedPackage* package) const std::unique_ptr<Theme> AssetManager2::NewTheme() { constexpr size_t kInitialReserveSize = 32; auto theme = std::unique_ptr<Theme>(new Theme(this)); + theme->keys_.reserve(kInitialReserveSize); theme->entries_.reserve(kInitialReserveSize); return theme; } +void AssetManager2::ForEachPackage(base::function_ref<bool(const std::string&, uint8_t)> func, + package_property_t excluded_property_flags) const { + for (const PackageGroup& package_group : package_groups_) { + const auto loaded_package = package_group.packages_.front().loaded_package_; + if ((loaded_package->GetPropertyFlags() & excluded_property_flags) == 0U + && !func(loaded_package->GetPackageName(), + package_group.dynamic_ref_table->mAssignedPackageId)) { + return; + } + } +} + Theme::Theme(AssetManager2* asset_manager) : asset_manager_(asset_manager) { } Theme::~Theme() = default; struct Theme::Entry { - uint32_t attr_res_id; ApkAssetsCookie cookie; uint32_t type_spec_flags; Res_value value; }; -namespace { -struct ThemeEntryKeyComparer { - bool operator() (const Theme::Entry& entry, uint32_t attr_res_id) const noexcept { - return entry.attr_res_id < attr_res_id; - } -}; -} // namespace - base::expected<std::monostate, NullOrIOError> Theme::ApplyStyle(uint32_t resid, bool force) { ATRACE_NAME("Theme::ApplyStyle"); @@ -1454,18 +1468,20 @@ base::expected<std::monostate, NullOrIOError> Theme::ApplyStyle(uint32_t resid, continue; } - auto entry_it = std::lower_bound(entries_.begin(), entries_.end(), attr_res_id, - ThemeEntryKeyComparer{}); - if (entry_it != entries_.end() && entry_it->attr_res_id == attr_res_id) { + const auto key_it = std::lower_bound(keys_.begin(), keys_.end(), attr_res_id); + const auto entry_it = entries_.begin() + (key_it - keys_.begin()); + if (key_it != keys_.end() && *key_it == attr_res_id) { if (is_undefined) { // DATA_NULL_UNDEFINED clears the value of the attribute in the theme only when `force` is - /// true. + // true. + keys_.erase(key_it); entries_.erase(entry_it); } else if (force) { - *entry_it = Entry{attr_res_id, it->cookie, (*bag)->type_spec_flags, it->value}; + *entry_it = Entry{it->cookie, (*bag)->type_spec_flags, it->value}; } } else { - entries_.insert(entry_it, Entry{attr_res_id, it->cookie, (*bag)->type_spec_flags, it->value}); + keys_.insert(key_it, attr_res_id); + entries_.insert(entry_it, Entry{it->cookie, (*bag)->type_spec_flags, it->value}); } } return {}; @@ -1476,6 +1492,7 @@ void Theme::Rebase(AssetManager2* am, const uint32_t* style_ids, const uint8_t* ATRACE_NAME("Theme::Rebase"); // Reset the entries without changing the vector capacity to prevent reallocations during // ApplyStyle. + keys_.clear(); entries_.clear(); asset_manager_ = am; for (size_t i = 0; i < style_count; i++) { @@ -1484,16 +1501,14 @@ void Theme::Rebase(AssetManager2* am, const uint32_t* style_ids, const uint8_t* } std::optional<AssetManager2::SelectedValue> Theme::GetAttribute(uint32_t resid) const { - constexpr const uint32_t kMaxIterations = 20; uint32_t type_spec_flags = 0u; for (uint32_t i = 0; i <= kMaxIterations; i++) { - auto entry_it = std::lower_bound(entries_.begin(), entries_.end(), resid, - ThemeEntryKeyComparer{}); - if (entry_it == entries_.end() || entry_it->attr_res_id != resid) { + const auto key_it = std::lower_bound(keys_.begin(), keys_.end(), resid); + if (key_it == keys_.end() || *key_it != resid) { return std::nullopt; } - + const auto entry_it = entries_.begin() + (key_it - keys_.begin()); type_spec_flags |= entry_it->type_spec_flags; if (entry_it->value.dataType == Res_value::TYPE_ATTRIBUTE) { resid = entry_it->value.data; @@ -1527,6 +1542,7 @@ base::expected<std::monostate, NullOrIOError> Theme::ResolveAttributeReference( } void Theme::Clear() { + keys_.clear(); entries_.clear(); } @@ -1538,11 +1554,12 @@ base::expected<std::monostate, IOError> Theme::SetTo(const Theme& source) { type_spec_flags_ = source.type_spec_flags_; if (asset_manager_ == source.asset_manager_) { + keys_ = source.keys_; entries_ = source.entries_; } else { - std::map<ApkAssetsCookie, ApkAssetsCookie> src_to_dest_asset_cookies; - typedef std::map<int, int> SourceToDestinationRuntimePackageMap; - std::map<ApkAssetsCookie, SourceToDestinationRuntimePackageMap> src_asset_cookie_id_map; + std::unordered_map<ApkAssetsCookie, ApkAssetsCookie> src_to_dest_asset_cookies; + using SourceToDestinationRuntimePackageMap = std::unordered_map<int, int>; + std::unordered_map<ApkAssetsCookie, SourceToDestinationRuntimePackageMap> src_asset_cookie_id_map; // Determine which ApkAssets are loaded in both theme AssetManagers. const auto src_assets = source.asset_manager_->GetApkAssets(); @@ -1570,15 +1587,17 @@ base::expected<std::monostate, IOError> Theme::SetTo(const Theme& source) { } src_to_dest_asset_cookies.insert(std::make_pair(i, j)); - src_asset_cookie_id_map.insert(std::make_pair(i, package_map)); + src_asset_cookie_id_map.insert(std::make_pair(i, std::move(package_map))); break; } } // Reset the data in the destination theme. + keys_.clear(); entries_.clear(); - for (const auto& entry : source.entries_) { + for (size_t i = 0, size = source.entries_.size(); i != size; ++i) { + const auto& entry = source.entries_[i]; bool is_reference = (entry.value.dataType == Res_value::TYPE_ATTRIBUTE || entry.value.dataType == Res_value::TYPE_REFERENCE || entry.value.dataType == Res_value::TYPE_DYNAMIC_ATTRIBUTE @@ -1618,13 +1637,15 @@ base::expected<std::monostate, IOError> Theme::SetTo(const Theme& source) { } } + const auto source_res_id = source.keys_[i]; + // The package id of the attribute needs to be rewritten to the package id of the // attribute in the destination. - int attribute_dest_package_id = get_package_id(entry.attr_res_id); + int attribute_dest_package_id = get_package_id(source_res_id); if (attribute_dest_package_id != 0x01) { // Find the cookie of the attribute resource id in the source AssetManager base::expected<FindEntryResult, NullOrIOError> attribute_entry_result = - source.asset_manager_->FindEntry(entry.attr_res_id, 0 /* density_override */ , + source.asset_manager_->FindEntry(source_res_id, 0 /* density_override */ , true /* stop_at_first_match */, true /* ignore_configuration */); if (UNLIKELY(IsIOError(attribute_entry_result))) { @@ -1648,16 +1669,15 @@ base::expected<std::monostate, IOError> Theme::SetTo(const Theme& source) { attribute_dest_package_id = attribute_dest_package->second; } - auto dest_attr_id = make_resid(attribute_dest_package_id, get_type_id(entry.attr_res_id), - get_entry_id(entry.attr_res_id)); - Theme::Entry new_entry{dest_attr_id, data_dest_cookie, entry.type_spec_flags, - Res_value{.dataType = entry.value.dataType, - .data = attribute_data}}; - + auto dest_attr_id = make_resid(attribute_dest_package_id, get_type_id(source_res_id), + get_entry_id(source_res_id)); + const auto key_it = std::lower_bound(keys_.begin(), keys_.end(), dest_attr_id); + const auto entry_it = entries_.begin() + (key_it - keys_.begin()); // Since the entries were cleared, the attribute resource id has yet been mapped to any value. - auto entry_it = std::lower_bound(entries_.begin(), entries_.end(), dest_attr_id, - ThemeEntryKeyComparer{}); - entries_.insert(entry_it, new_entry); + keys_.insert(key_it, dest_attr_id); + entries_.insert(entry_it, Entry{data_dest_cookie, entry.type_spec_flags, + Res_value{.dataType = entry.value.dataType, + .data = attribute_data}}); } } return {}; @@ -1665,9 +1685,11 @@ base::expected<std::monostate, IOError> Theme::SetTo(const Theme& source) { void Theme::Dump() const { LOG(INFO) << base::StringPrintf("Theme(this=%p, AssetManager2=%p)", this, asset_manager_); - for (auto& entry : entries_) { + for (size_t i = 0, size = keys_.size(); i != size; ++i) { + auto res_id = keys_[i]; + const auto& entry = entries_[i]; LOG(INFO) << base::StringPrintf(" entry(0x%08x)=(0x%08x) type=(0x%02x), cookie(%d)", - entry.attr_res_id, entry.value.data, entry.value.dataType, + res_id, entry.value.data, entry.value.dataType, entry.cookie); } } diff --git a/libs/androidfw/AssetsProvider.cpp b/libs/androidfw/AssetsProvider.cpp index bce34d37c90b..b9264c5d0f2d 100644 --- a/libs/androidfw/AssetsProvider.cpp +++ b/libs/androidfw/AssetsProvider.cpp @@ -92,21 +92,27 @@ ZipAssetsProvider::ZipAssetsProvider(ZipArchiveHandle handle, PathOrDebugName&& last_mod_time_(last_mod_time) {} std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path, - package_property_t flags) { + package_property_t flags, + base::unique_fd fd) { + const auto released_fd = fd.ok() ? fd.release() : -1; ZipArchiveHandle handle; - if (int32_t result = OpenArchive(path.c_str(), &handle); result != 0) { + if (int32_t result = released_fd < 0 ? OpenArchive(path.c_str(), &handle) + : OpenArchiveFd(released_fd, path.c_str(), &handle)) { LOG(ERROR) << "Failed to open APK '" << path << "': " << ::ErrorCodeString(result); CloseArchive(handle); return {}; } struct stat sb{.st_mtime = -1}; - if (stat(path.c_str(), &sb) < 0) { - // Stat requires execute permissions on all directories path to the file. If the process does - // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will - // always have to return true. - LOG(WARNING) << "Failed to stat file '" << path << "': " - << base::SystemErrorCodeToString(errno); + // Skip all up-to-date checks if the file won't ever change. + if (!isReadonlyFilesystem(path.c_str())) { + if ((released_fd < 0 ? stat(path.c_str(), &sb) : fstat(released_fd, &sb)) < 0) { + // Stat requires execute permissions on all directories path to the file. If the process does + // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will + // always have to return true. + LOG(WARNING) << "Failed to stat file '" << path << "': " + << base::SystemErrorCodeToString(errno); + } } return std::unique_ptr<ZipAssetsProvider>( @@ -133,12 +139,15 @@ std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd, } struct stat sb{.st_mtime = -1}; - if (fstat(released_fd, &sb) < 0) { - // Stat requires execute permissions on all directories path to the file. If the process does - // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will - // always have to return true. - LOG(WARNING) << "Failed to fstat file '" << friendly_name << "': " - << base::SystemErrorCodeToString(errno); + // Skip all up-to-date checks if the file won't ever change. + if (!isReadonlyFilesystem(released_fd)) { + if (fstat(released_fd, &sb) < 0) { + // Stat requires execute permissions on all directories path to the file. If the process does + // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will + // always have to return true. + LOG(WARNING) << "Failed to fstat file '" << friendly_name + << "': " << base::SystemErrorCodeToString(errno); + } } return std::unique_ptr<ZipAssetsProvider>( @@ -211,8 +220,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 +246,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); @@ -277,6 +284,9 @@ const std::string& ZipAssetsProvider::GetDebugName() const { } bool ZipAssetsProvider::IsUpToDate() const { + if (last_mod_time_ == -1) { + return true; + } struct stat sb{}; if (fstat(GetFileDescriptor(zip_handle_.get()), &sb) < 0) { // If fstat fails on the zip archive, return true so the zip archive the resource system does @@ -290,7 +300,7 @@ DirectoryAssetsProvider::DirectoryAssetsProvider(std::string&& path, time_t last : dir_(std::forward<std::string>(path)), last_mod_time_(last_mod_time) {} std::unique_ptr<DirectoryAssetsProvider> DirectoryAssetsProvider::Create(std::string path) { - struct stat sb{}; + struct stat sb; const int result = stat(path.c_str(), &sb); if (result == -1) { LOG(ERROR) << "Failed to find directory '" << path << "'."; @@ -306,8 +316,9 @@ std::unique_ptr<DirectoryAssetsProvider> DirectoryAssetsProvider::Create(std::st path += OS_PATH_SEPARATOR; } - return std::unique_ptr<DirectoryAssetsProvider>(new DirectoryAssetsProvider(std::move(path), - sb.st_mtime)); + const bool isReadonly = isReadonlyFilesystem(path.c_str()); + return std::unique_ptr<DirectoryAssetsProvider>( + new DirectoryAssetsProvider(std::move(path), isReadonly ? -1 : sb.st_mtime)); } std::unique_ptr<Asset> DirectoryAssetsProvider::OpenInternal(const std::string& path, @@ -324,8 +335,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; } @@ -338,7 +348,10 @@ const std::string& DirectoryAssetsProvider::GetDebugName() const { } bool DirectoryAssetsProvider::IsUpToDate() const { - struct stat sb{}; + if (last_mod_time_ == -1) { + return true; + } + struct stat sb; if (stat(dir_.c_str(), &sb) < 0) { // If stat fails on the zip archive, return true so the zip archive the resource system does // attempt to refresh the ApkAsset. @@ -373,8 +386,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 +409,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 +424,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; } @@ -435,4 +447,4 @@ bool EmptyAssetsProvider::IsUpToDate() const { return true; } -} // namespace android
\ No newline at end of file +} // namespace android 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/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp index 386f718208b3..c0fdfe25da21 100644 --- a/libs/androidfw/LoadedArsc.cpp +++ b/libs/androidfw/LoadedArsc.cpp @@ -645,16 +645,16 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, } std::string name; - util::ReadUtf16StringFromDevice(overlayable->name, arraysize(overlayable->name), &name); + util::ReadUtf16StringFromDevice(overlayable->name, std::size(overlayable->name), &name); std::string actor; - util::ReadUtf16StringFromDevice(overlayable->actor, arraysize(overlayable->actor), &actor); - - if (loaded_package->overlayable_map_.find(name) != - loaded_package->overlayable_map_.end()) { - LOG(ERROR) << "Multiple <overlayable> blocks with the same name '" << name << "'."; + util::ReadUtf16StringFromDevice(overlayable->actor, std::size(overlayable->actor), &actor); + auto [name_to_actor_it, inserted] = + loaded_package->overlayable_map_.emplace(std::move(name), std::move(actor)); + if (!inserted) { + LOG(ERROR) << "Multiple <overlayable> blocks with the same name '" + << name_to_actor_it->first << "'."; return {}; } - loaded_package->overlayable_map_.emplace(name, actor); // Iterate over the overlayable policy chunks contained within the overlayable chunk data ChunkIterator overlayable_iter(child_chunk.data_ptr(), child_chunk.data_size()); @@ -669,7 +669,6 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, LOG(ERROR) << "RES_TABLE_OVERLAYABLE_POLICY_TYPE too small."; return {}; } - if ((overlayable_child_chunk.data_size() / sizeof(ResTable_ref)) < dtohl(policy_header->entry_count)) { LOG(ERROR) << "RES_TABLE_OVERLAYABLE_POLICY_TYPE too small to hold entries."; @@ -691,8 +690,8 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, // Add the pairing of overlayable properties and resource ids to the package OverlayableInfo overlayable_info { - .name = name, - .actor = actor, + .name = name_to_actor_it->first, + .actor = name_to_actor_it->second, .policy_flags = policy_header->policy_flags }; loaded_package->overlayable_infos_.emplace_back(std::move(overlayable_info), std::move(ids)); @@ -736,6 +735,7 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, const auto entry_end = entry_begin + dtohl(lib_alias->count); std::unordered_set<uint32_t> finalized_ids; finalized_ids.reserve(entry_end - entry_begin); + loaded_package->alias_id_map_.reserve(entry_end - entry_begin); for (auto entry_iter = entry_begin; entry_iter != entry_end; ++entry_iter) { if (!entry_iter) { LOG(ERROR) << "NULL ResTable_staged_alias_entry record??"; @@ -749,13 +749,20 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, } auto staged_id = dtohl(entry_iter->stagedResId); - auto [_, success] = loaded_package->alias_id_map_.emplace(staged_id, finalized_id); - if (!success) { + loaded_package->alias_id_map_.emplace_back(staged_id, finalized_id); + } + + std::sort(loaded_package->alias_id_map_.begin(), loaded_package->alias_id_map_.end(), + [](auto&& l, auto&& r) { return l.first < r.first; }); + const auto duplicate_it = + std::adjacent_find(loaded_package->alias_id_map_.begin(), + loaded_package->alias_id_map_.end(), + [](auto&& l, auto&& r) { return l.first == r.first; }); + if (duplicate_it != loaded_package->alias_id_map_.end()) { LOG(ERROR) << StringPrintf("Repeated staged resource id '%08x' in staged aliases.", - staged_id); + duplicate_it->first); return {}; } - } } break; default: 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..e4d1218debe6 100644 --- a/libs/androidfw/include/androidfw/AssetManager2.h +++ b/libs/androidfw/include/androidfw/AssetManager2.h @@ -17,6 +17,7 @@ #ifndef ANDROIDFW_ASSETMANAGER2_H_ #define ANDROIDFW_ASSETMANAGER2_H_ +#include "android-base/function_ref.h" #include "android-base/macros.h" #include <array> @@ -124,8 +125,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; @@ -321,17 +321,8 @@ class AssetManager2 { // Creates a new Theme from this AssetManager. std::unique_ptr<Theme> NewTheme(); - void ForEachPackage(const std::function<bool(const std::string&, uint8_t)> func, - package_property_t excluded_property_flags = 0U) const { - for (const PackageGroup& package_group : package_groups_) { - const auto loaded_package = package_group.packages_.front().loaded_package_; - if ((loaded_package->GetPropertyFlags() & excluded_property_flags) == 0U - && !func(loaded_package->GetPackageName(), - package_group.dynamic_ref_table->mAssignedPackageId)) { - return; - } - } - } + void ForEachPackage(base::function_ref<bool(const std::string&, uint8_t)> func, + package_property_t excluded_property_flags = 0U) const; void DumpToLog() const; @@ -572,6 +563,7 @@ class Theme { AssetManager2* asset_manager_ = nullptr; uint32_t type_spec_flags_ = 0u; + std::vector<uint32_t> keys_; std::vector<Entry> entries_; }; diff --git a/libs/androidfw/include/androidfw/AssetsProvider.h b/libs/androidfw/include/androidfw/AssetsProvider.h index 966ec74c1786..7891194a65bd 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. @@ -80,8 +80,8 @@ struct AssetsProvider { // Supplies assets from a zip archive. struct ZipAssetsProvider : public AssetsProvider { - static std::unique_ptr<ZipAssetsProvider> Create(std::string path, - package_property_t flags); + static std::unique_ptr<ZipAssetsProvider> Create(std::string path, package_property_t flags, + base::unique_fd fd = {}); static std::unique_ptr<ZipAssetsProvider> Create(base::unique_fd fd, std::string friendly_name, @@ -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..ca0a9eda9caa 100644 --- a/libs/androidfw/include/androidfw/ByteBucketArray.h +++ b/libs/androidfw/include/androidfw/ByteBucketArray.h @@ -17,6 +17,7 @@ #ifndef __BYTE_BUCKET_ARRAY_H #define __BYTE_BUCKET_ARRAY_H +#include <algorithm> #include <cstdint> #include <cstring> @@ -31,14 +32,16 @@ namespace android { template <typename T> class ByteBucketArray { public: - ByteBucketArray() : default_() { memset(buckets_, 0, sizeof(buckets_)); } + ByteBucketArray() { + memset(buckets_, 0, sizeof(buckets_)); + } ~ByteBucketArray() { - for (size_t i = 0; i < kNumBuckets; i++) { - if (buckets_[i] != NULL) { - delete[] buckets_[i]; - } - } + deleteBuckets(); + } + + void clear() { + deleteBuckets(); memset(buckets_, 0, sizeof(buckets_)); } @@ -53,7 +56,7 @@ class ByteBucketArray { uint8_t bucket_index = static_cast<uint8_t>(index) >> 4; T* bucket = buckets_[bucket_index]; - if (bucket == NULL) { + if (bucket == nullptr) { return default_; } return bucket[0x0f & static_cast<uint8_t>(index)]; @@ -64,9 +67,9 @@ class ByteBucketArray { << ") with size=" << size(); uint8_t bucket_index = static_cast<uint8_t>(index) >> 4; - T* bucket = buckets_[bucket_index]; - if (bucket == NULL) { - bucket = buckets_[bucket_index] = new T[kBucketSize](); + T*& bucket = buckets_[bucket_index]; + if (bucket == nullptr) { + bucket = new T[kBucketSize](); } return bucket[0x0f & static_cast<uint8_t>(index)]; } @@ -80,11 +83,44 @@ class ByteBucketArray { return true; } + template <class Func> + void forEachItem(Func f) { + for (size_t i = 0; i < kNumBuckets; i++) { + const auto bucket = buckets_[i]; + if (bucket != nullptr) { + for (size_t j = 0; j < kBucketSize; j++) { + f((i << 4) | j, bucket[j]); + } + } + } + } + + template <class Func> + void trimBuckets(Func isEmptyFunc) { + for (size_t i = 0; i < kNumBuckets; i++) { + const auto bucket = buckets_[i]; + if (bucket != nullptr) { + if (std::all_of(bucket, bucket + kBucketSize, isEmptyFunc)) { + delete[] bucket; + buckets_[i] = nullptr; + } + } + } + } + private: enum { kNumBuckets = 16, kBucketSize = 16 }; + void deleteBuckets() { + for (size_t i = 0; i < kNumBuckets; i++) { + if (buckets_[i] != nullptr) { + delete[] buckets_[i]; + } + } + } + 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/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h index 79d962829046..4d12885ad291 100644 --- a/libs/androidfw/include/androidfw/LoadedArsc.h +++ b/libs/androidfw/include/androidfw/LoadedArsc.h @@ -99,8 +99,8 @@ enum : package_property_t { }; struct OverlayableInfo { - std::string name; - std::string actor; + std::string_view name; + std::string_view actor; uint32_t policy_flags; }; @@ -275,7 +275,7 @@ class LoadedPackage { return overlayable_map_; } - const std::map<uint32_t, uint32_t>& GetAliasResourceIdMap() const { + const std::vector<std::pair<uint32_t, uint32_t>>& GetAliasResourceIdMap() const { return alias_id_map_; } @@ -295,8 +295,8 @@ class LoadedPackage { std::unordered_map<uint8_t, TypeSpec> type_specs_; ByteBucketArray<uint32_t> resource_ids_; std::vector<DynamicPackageEntry> dynamic_package_map_; - std::vector<const std::pair<OverlayableInfo, std::unordered_set<uint32_t>>> overlayable_infos_; - std::map<uint32_t, uint32_t> alias_id_map_; + std::vector<std::pair<OverlayableInfo, std::unordered_set<uint32_t>>> overlayable_infos_; + std::vector<std::pair<uint32_t, uint32_t>> alias_id_map_; // A map of overlayable name to actor std::unordered_map<std::string, std::string> overlayable_map_; 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/include/androidfw/misc.h b/libs/androidfw/include/androidfw/misc.h index 5a5a0e29125d..d40d24ede769 100644 --- a/libs/androidfw/include/androidfw/misc.h +++ b/libs/androidfw/include/androidfw/misc.h @@ -44,6 +44,10 @@ FileType getFileType(const char* fileName); /* get the file's modification date; returns -1 w/errno set on failure */ time_t getFileModDate(const char* fileName); +// Check if |path| or |fd| resides on a readonly filesystem. +bool isReadonlyFilesystem(const char* path); +bool isReadonlyFilesystem(int fd); + }; // namespace android #endif // _LIBS_ANDROID_FW_MISC_H diff --git a/libs/androidfw/misc.cpp b/libs/androidfw/misc.cpp index 52854205207c..7af506638ebc 100644 --- a/libs/androidfw/misc.cpp +++ b/libs/androidfw/misc.cpp @@ -21,12 +21,17 @@ // #include <androidfw/misc.h> -#include <sys/stat.h> +#include "android-base/logging.h" + +#ifndef _WIN32 +#include <sys/statvfs.h> +#include <sys/vfs.h> +#endif // _WIN32 + #include <cstring> -#include <errno.h> #include <cstdio> - -using namespace android; +#include <errno.h> +#include <sys/stat.h> namespace android { @@ -41,8 +46,7 @@ FileType getFileType(const char* fileName) if (errno == ENOENT || errno == ENOTDIR) return kFileTypeNonexistent; else { - fprintf(stderr, "getFileType got errno=%d on '%s'\n", - errno, fileName); + PLOG(ERROR) << "getFileType(): stat(" << fileName << ") failed"; return kFileTypeUnknown; } } else { @@ -82,4 +86,32 @@ time_t getFileModDate(const char* fileName) return sb.st_mtime; } +#ifdef _WIN32 +// No need to implement these for Windows, the functions only matter on a device. +bool isReadonlyFilesystem(const char*) { + return false; +} +bool isReadonlyFilesystem(int) { + return false; +} +#else // _WIN32 +bool isReadonlyFilesystem(const char* path) { + struct statfs sfs; + if (::statfs(path, &sfs)) { + PLOG(ERROR) << "isReadonlyFilesystem(): statfs(" << path << ") failed"; + return false; + } + return (sfs.f_flags & ST_RDONLY) != 0; +} + +bool isReadonlyFilesystem(int fd) { + struct statfs sfs; + if (::fstatfs(fd, &sfs)) { + PLOG(ERROR) << "isReadonlyFilesystem(): fstatfs(" << fd << ") failed"; + return false; + } + return (sfs.f_flags & ST_RDONLY) != 0; +} +#endif // _WIN32 + }; // namespace android 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/ByteBucketArray_test.cpp b/libs/androidfw/tests/ByteBucketArray_test.cpp index 5d464c7dc0f7..9c36cfb212c5 100644 --- a/libs/androidfw/tests/ByteBucketArray_test.cpp +++ b/libs/androidfw/tests/ByteBucketArray_test.cpp @@ -52,4 +52,57 @@ TEST(ByteBucketArrayTest, TestSparseInsertion) { } } +TEST(ByteBucketArrayTest, TestForEach) { + ByteBucketArray<int> bba; + ASSERT_TRUE(bba.set(0, 1)); + ASSERT_TRUE(bba.set(10, 2)); + ASSERT_TRUE(bba.set(26, 3)); + ASSERT_TRUE(bba.set(129, 4)); + ASSERT_TRUE(bba.set(234, 5)); + + int count = 0; + bba.forEachItem([&count](auto i, auto val) { + ++count; + switch (i) { + case 0: + EXPECT_EQ(1, val); + break; + case 10: + EXPECT_EQ(2, val); + break; + case 26: + EXPECT_EQ(3, val); + break; + case 129: + EXPECT_EQ(4, val); + break; + case 234: + EXPECT_EQ(5, val); + break; + default: + EXPECT_EQ(0, val); + break; + } + }); + ASSERT_EQ(4 * 16, count); +} + +TEST(ByteBucketArrayTest, TestTrimBuckets) { + ByteBucketArray<int> bba; + ASSERT_TRUE(bba.set(0, 1)); + ASSERT_TRUE(bba.set(255, 2)); + { + bba.trimBuckets([](auto val) { return val < 2; }); + int count = 0; + bba.forEachItem([&count](auto, auto) { ++count; }); + ASSERT_EQ(1 * 16, count); + } + { + bba.trimBuckets([](auto val) { return val < 3; }); + int count = 0; + bba.forEachItem([&count](auto, auto) { ++count; }); + ASSERT_EQ(0, count); + } +} + } // namespace android 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/values/strings.xml b/packages/CredentialManager/res/values/strings.xml index 0882af72bb27..8c9023c55eff 100644 --- a/packages/CredentialManager/res/values/strings.xml +++ b/packages/CredentialManager/res/values/strings.xml @@ -1,44 +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 244a11fddede..0cc11946ca85 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt @@ -44,6 +44,8 @@ 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.CreatePasswordRequest.Companion.toBundle @@ -142,25 +144,22 @@ class CredentialManagerRepo( 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} } + 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), ) } @@ -425,7 +424,40 @@ class CredentialManagerRepo( } private fun testCreatePasskeyRequestInfo(): RequestInfo { - val request = CreatePublicKeyCredentialRequest("json") + 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(), @@ -476,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 c33af8fe9e35..b96f686c02fb 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -39,6 +39,7 @@ import com.android.credentialmanager.jetpack.developer.CreatePublicKeyCredential 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 { @@ -235,33 +236,42 @@ class CreateFlowUtils { it ) } - // TODO: covert from real requestInfo when (createCredentialRequestJetpack) { - is CreatePasswordRequest -> { - return RequestDisplayInfo( - createCredentialRequestJetpack.id, - createCredentialRequestJetpack.password, - createCredentialRequestJetpack.type, - "tribank", - context.getDrawable(R.drawable.ic_password)!! - ) - } + 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( - "beckett-bakert@gmail.com", - "Elisa Beckett", + name, + displayName, createCredentialRequestJetpack.type, - "tribank", + 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", - "tribank", + requestInfo.appPackageName, context.getDrawable(R.drawable.ic_other_sign_in)!!) - } + } } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt index c9cb3ce49db5..5552d0509355 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,9 +108,16 @@ 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, + scrimColor = MaterialTheme.colorScheme.scrim.copy(alpha = 0.8f), sheetShape = EntryShape.TopRoundedCorner, ) {} LaunchedEffect(state.currentValue) { @@ -202,7 +209,7 @@ fun ProviderSelectionCard( 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) }, @@ -273,6 +280,10 @@ fun ProviderSelectionCard( } } } + Divider( + thickness = 24.dp, + color = Color.Transparent + ) Row( horizontalArrangement = Arrangement.Start, modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp) @@ -305,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 ) @@ -398,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) ) @@ -485,7 +496,7 @@ fun CreationSelectionCard( ) { PrimaryCreateOptionRow( requestDisplayInfo = requestDisplayInfo, - createOptionInfo = createOptionInfo, + entryInfo = createOptionInfo, onOptionSelected = onOptionSelected ) } @@ -558,16 +569,85 @@ 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 = { Icon( - bitmap = createOptionInfo.profileIcon.toBitmap().asImageBitmap(), + 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).size(32.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 31d0365a821f..9ac524a4e6d9 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt @@ -95,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..720f231609c3 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt @@ -61,6 +61,7 @@ import com.android.credentialmanager.common.ui.CancelButton import com.android.credentialmanager.common.ui.Entry import com.android.credentialmanager.common.ui.TransparentBackgroundEntry import com.android.credentialmanager.jetpack.developer.PublicKeyCredential +import com.android.credentialmanager.ui.theme.EntryShape @Composable fun GetCredentialScreen( @@ -94,8 +95,8 @@ fun GetCredentialScreen( ) } }, - scrimColor = Color.Transparent, - sheetShape = MaterialTheme.shapes.medium, + scrimColor = MaterialTheme.colorScheme.scrim.copy(alpha = 0.8f), + sheetShape = EntryShape.TopRoundedCorner, ) {} LaunchedEffect(state.currentValue) { if (state.currentValue == ModalBottomSheetValue.Hidden) { @@ -167,7 +168,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/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/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt index 0c9a0430bc1e..238204a66bb4 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt @@ -36,6 +36,7 @@ import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory import com.android.settingslib.spa.framework.common.createSettingsPage import com.android.settingslib.spa.framework.compose.toState import com.android.settingslib.spa.framework.theme.SettingsTheme +import com.android.settingslib.spa.framework.util.createIntent import com.android.settingslib.spa.gallery.R import com.android.settingslib.spa.gallery.SettingsPageProviderEnum import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_SUMMARY @@ -91,6 +92,7 @@ object PreferencePageProvider : SettingsPageProvider { spaLogger.message(TAG, "create macro for ${EntryEnum.SIMPLE_PREFERENCE}") SimplePreferenceMacro(title = SIMPLE_PREFERENCE_TITLE) } + .setStatusDataFn { EntryStatusData(isDisabled = false) } .build() ) entryList.add( @@ -103,6 +105,7 @@ object PreferencePageProvider : SettingsPageProvider { searchKeywords = SIMPLE_PREFERENCE_KEYWORDS, ) } + .setStatusDataFn { EntryStatusData(isDisabled = true) } .build() ) entryList.add(singleLineSummaryEntry()) @@ -269,7 +272,7 @@ object PreferencePageProvider : SettingsPageProvider { ) } .setSliceDataFn { sliceUri, _ -> - val intent = owner.createBrowseIntent()?.createBrowsePendingIntent() + val intent = owner.createIntent()?.createBrowsePendingIntent() ?: return@setSliceDataFn null return@setSliceDataFn object : EntrySliceData() { init { diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt index 238268a9ee08..f7cbdae6909f 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt @@ -39,7 +39,10 @@ import com.android.settingslib.spa.framework.compose.localNavController import com.android.settingslib.spa.framework.compose.navigator import com.android.settingslib.spa.framework.compose.toState import com.android.settingslib.spa.framework.theme.SettingsTheme -import com.android.settingslib.spa.slice.appendSliceParams +import com.android.settingslib.spa.framework.util.SESSION_BROWSE +import com.android.settingslib.spa.framework.util.SESSION_SEARCH +import com.android.settingslib.spa.framework.util.createIntent +import com.android.settingslib.spa.slice.fromEntry import com.android.settingslib.spa.slice.presenter.SliceDemo import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel @@ -158,14 +161,13 @@ class DebugActivity : ComponentActivity() { remember { entryRepository.getAllEntries().filter { it.hasSliceSupport } } RegularScaffold(title = "All Slices (${allSliceEntry.size})") { for (entry in allSliceEntry) { - SliceDemo(sliceUri = entry.createSliceUri(authority)) + SliceDemo(sliceUri = Uri.Builder().fromEntry(entry, authority).build()) } } } @Composable fun OnePage(arguments: Bundle?) { - val context = LocalContext.current val entryRepository by spaEnvironment.entryRepository val id = arguments!!.getString(PARAM_NAME_PAGE_ID, "") val pageWithEntry = entryRepository.getPageWithEntry(id)!! @@ -176,8 +178,8 @@ class DebugActivity : ComponentActivity() { Text(text = "Entry size: ${pageWithEntry.entries.size}") Preference(model = object : PreferenceModel { override val title = "open page" - override val enabled = - page.isBrowsable(context, spaEnvironment.browseActivityClass).toState() + override val enabled = (spaEnvironment.browseActivityClass != null && + page.isBrowsable()).toState() override val onClick = openPage(page) }) EntryList(pageWithEntry.entries) @@ -186,7 +188,6 @@ class DebugActivity : ComponentActivity() { @Composable fun OneEntry(arguments: Bundle?) { - val context = LocalContext.current val entryRepository by spaEnvironment.entryRepository val id = arguments!!.getString(PARAM_NAME_ENTRY_ID, "") val entry = entryRepository.getEntry(id)!! @@ -194,9 +195,9 @@ class DebugActivity : ComponentActivity() { RegularScaffold(title = "Entry - ${entry.debugBrief()}") { Preference(model = object : PreferenceModel { override val title = "open entry" - override val enabled = - entry.containerPage().isBrowsable(context, spaEnvironment.browseActivityClass) - .toState() + override val enabled = (spaEnvironment.browseActivityClass != null && + entry.containerPage().isBrowsable()) + .toState() override val onClick = openEntry(entry) }) Text(text = entryContent) @@ -219,7 +220,7 @@ class DebugActivity : ComponentActivity() { private fun openPage(page: SettingsPage): (() -> Unit)? { val context = LocalContext.current val intent = - page.createBrowseIntent(context, spaEnvironment.browseActivityClass) ?: return null + page.createIntent(SESSION_BROWSE) ?: return null val route = page.buildRoute() return { spaEnvironment.logger.message( @@ -232,8 +233,7 @@ class DebugActivity : ComponentActivity() { @Composable private fun openEntry(entry: SettingsEntry): (() -> Unit)? { val context = LocalContext.current - val intent = entry.containerPage() - .createBrowseIntent(context, spaEnvironment.browseActivityClass, entry.id) + val intent = entry.createIntent(SESSION_SEARCH) ?: return null val route = entry.containerPage().buildRoute() return { @@ -245,18 +245,6 @@ class DebugActivity : ComponentActivity() { } } -private fun SettingsEntry.createSliceUri( - authority: String?, - runtimeArguments: Bundle? = null -): Uri { - if (authority == null) return Uri.EMPTY - return Uri.Builder().scheme("content").authority(authority).appendSliceParams( - route = this.containerPage().buildRoute(), - entryId = this.id, - runtimeArguments = runtimeArguments, - ).build() -} - /** * A blank activity without any page. */ diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt index 3df77277c205..59ec985ba253 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt @@ -32,6 +32,12 @@ import com.android.settingslib.spa.framework.common.QueryEnum import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory import com.android.settingslib.spa.framework.common.addUri import com.android.settingslib.spa.framework.common.getColumns +import com.android.settingslib.spa.framework.util.KEY_DESTINATION +import com.android.settingslib.spa.framework.util.KEY_HIGHLIGHT_ENTRY +import com.android.settingslib.spa.framework.util.KEY_SESSION_SOURCE_NAME +import com.android.settingslib.spa.framework.util.SESSION_BROWSE +import com.android.settingslib.spa.framework.util.SESSION_SEARCH +import com.android.settingslib.spa.framework.util.createIntent private const val TAG = "DebugProvider" @@ -116,9 +122,11 @@ class DebugProvider : ContentProvider() { val entryRepository by spaEnvironment.entryRepository val cursor = MatrixCursor(QueryEnum.PAGE_DEBUG_QUERY.getColumns()) for (pageWithEntry in entryRepository.getAllPageWithEntry()) { - val command = pageWithEntry.page.createBrowseAdbCommand( - context, - spaEnvironment.browseActivityClass + val page = pageWithEntry.page + if (!page.isBrowsable()) continue + val command = createBrowseAdbCommand( + destination = page.buildRoute(), + sessionName = SESSION_BROWSE ) if (command != null) { cursor.newRow().add(ColumnEnum.PAGE_START_ADB.id, command) @@ -131,8 +139,13 @@ class DebugProvider : ContentProvider() { val entryRepository by spaEnvironment.entryRepository val cursor = MatrixCursor(QueryEnum.ENTRY_DEBUG_QUERY.getColumns()) for (entry in entryRepository.getAllEntries()) { - val command = entry.containerPage() - .createBrowseAdbCommand(context, spaEnvironment.browseActivityClass, entry.id) + val page = entry.containerPage() + if (!page.isBrowsable()) continue + val command = createBrowseAdbCommand( + destination = page.buildRoute(), + entryId = entry.id, + sessionName = SESSION_SEARCH + ) if (command != null) { cursor.newRow().add(ColumnEnum.ENTRY_START_ADB.id, command) } @@ -145,8 +158,7 @@ class DebugProvider : ContentProvider() { val cursor = MatrixCursor(QueryEnum.PAGE_INFO_QUERY.getColumns()) for (pageWithEntry in entryRepository.getAllPageWithEntry()) { val page = pageWithEntry.page - val intent = - page.createBrowseIntent(context, spaEnvironment.browseActivityClass) ?: Intent() + val intent = page.createIntent(SESSION_BROWSE) ?: Intent() cursor.newRow() .add(ColumnEnum.PAGE_ID.id, page.id) .add(ColumnEnum.PAGE_NAME.id, page.displayName) @@ -162,17 +174,36 @@ class DebugProvider : ContentProvider() { val entryRepository by spaEnvironment.entryRepository val cursor = MatrixCursor(QueryEnum.ENTRY_INFO_QUERY.getColumns()) for (entry in entryRepository.getAllEntries()) { - val intent = entry.containerPage() - .createBrowseIntent(context, spaEnvironment.browseActivityClass, entry.id) - ?: Intent() + val intent = entry.createIntent(SESSION_SEARCH) ?: Intent() cursor.newRow() .add(ColumnEnum.ENTRY_ID.id, entry.id) .add(ColumnEnum.ENTRY_NAME.id, entry.displayName) .add(ColumnEnum.ENTRY_ROUTE.id, entry.containerPage().buildRoute()) .add(ColumnEnum.ENTRY_INTENT_URI.id, intent.toUri(URI_INTENT_SCHEME)) - .add(ColumnEnum.ENTRY_HIERARCHY_PATH.id, - entryRepository.getEntryPathWithDisplayName(entry.id)) + .add( + ColumnEnum.ENTRY_HIERARCHY_PATH.id, + entryRepository.getEntryPathWithDisplayName(entry.id) + ) } return cursor } } + +private fun createBrowseAdbCommand( + destination: String? = null, + entryId: String? = null, + sessionName: String? = null, +): String? { + val context = SpaEnvironmentFactory.instance.appContext + val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null + val packageName = context.packageName + val activityName = browseActivityClass.name.replace(packageName, "") + val destinationParam = + if (destination != null) " -e $KEY_DESTINATION $destination" else "" + val highlightParam = + if (entryId != null) " -e $KEY_HIGHLIGHT_ENTRY $entryId" else "" + val sessionParam = + if (sessionName != null) " -e $KEY_SESSION_SOURCE_NAME $sessionName" else "" + return "adb shell am start -n $packageName/$activityName" + + "$destinationParam$highlightParam$sessionParam" +} 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..aa10cc82a14e 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,16 @@ 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.getDestination +import com.android.settingslib.spa.framework.util.getEntryId +import com.android.settingslib.spa.framework.util.getSessionName import com.android.settingslib.spa.framework.util.navRoute private const val TAG = "BrowseActivity" @@ -77,57 +77,20 @@ 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) } } } - - companion object { - const val KEY_DESTINATION = "spaActivityDestination" - const val KEY_HIGHLIGHT_ENTRY = "highlightEntry" - } } @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 +107,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 +116,22 @@ 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?.getDestination() ?: defaultDestination + if (initialDestination.isEmpty()) return + val initialEntryId = initialIntent?.getEntryId() + val sessionSourceName = initialIntent?.getSessionName() + 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..7a39b730342c 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 @@ -16,13 +16,8 @@ package com.android.settingslib.spa.framework.common -import android.app.Activity -import android.content.ComponentName -import android.content.Context -import android.content.Intent import android.os.Bundle import androidx.navigation.NamedNavArgument -import com.android.settingslib.spa.framework.BrowseActivity import com.android.settingslib.spa.framework.util.isRuntimeParam import com.android.settingslib.spa.framework.util.navLink import com.android.settingslib.spa.framework.util.normalize @@ -95,63 +90,8 @@ 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 - return createBrowseIntent(context, browseActivityClass, entryId) - } - - fun createBrowseIntent( - context: Context?, - browseActivityClass: Class<out Activity>?, - entryId: String? = null - ): Intent? { - if (!isBrowsable(context, browseActivityClass)) return null - return Intent().setComponent(ComponentName(context!!, browseActivityClass!!)) - .apply { - putExtra(BrowseActivity.KEY_DESTINATION, buildRoute()) - if (entryId != null) { - putExtra(BrowseActivity.KEY_HIGHLIGHT_ENTRY, entryId) - } - } - } - - fun createBrowseAdbCommand( - context: Context?, - browseActivityClass: Class<out Activity>?, - entryId: String? = null - ): String? { - if (!isBrowsable(context, browseActivityClass)) return null - val packageName = context!!.packageName - val activityName = browseActivityClass!!.name.replace(packageName, "") - val destinationParam = " -e ${BrowseActivity.KEY_DESTINATION} ${buildRoute()}" - val highlightParam = - if (entryId != null) " -e ${BrowseActivity.KEY_HIGHLIGHT_ENTRY} $entryId" else "" - return "adb shell am start -n $packageName/$activityName$destinationParam$highlightParam" - } - - fun isBrowsable(context: Context?, browseActivityClass: Class<out Activity>?): Boolean { - return context != null && - browseActivityClass != null && - !isCreateBy(NULL_PAGE_NAME) && + fun isBrowsable(): Boolean { + return !isCreateBy(NULL_PAGE_NAME) && !hasRuntimeParam() } } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt index 00a0362abd91..6ecb7fa94c3c 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt @@ -16,6 +16,7 @@ package com.android.settingslib.spa.framework.common +import android.os.Bundle import android.util.Log // Defines the category of the log, for quick filter @@ -38,10 +39,13 @@ enum class LogEvent { // Entry related events. ENTRY_CLICK, - ENTRY_SWITCH_ON, - ENTRY_SWITCH_OFF, + ENTRY_SWITCH, } +internal const val LOG_DATA_DISPLAY_NAME = "name" +internal const val LOG_DATA_SESSION_NAME = "session" +internal const val LOG_DATA_SWITCH_STATUS = "switch" + /** * The interface of logger in Spa */ @@ -54,7 +58,7 @@ interface SpaLogger { id: String, event: LogEvent, category: LogCategory = LogCategory.DEFAULT, - details: String? = null + extraData: Bundle = Bundle.EMPTY ) { } } @@ -64,8 +68,8 @@ class LocalLogger : SpaLogger { Log.d("SpaMsg-$category", "[$tag] $msg") } - override fun event(id: String, event: LogEvent, category: LogCategory, details: String?) { - val extraMsg = if (details == null) "" else " ($details)" - Log.d("SpaEvent-$category", "[$id] $event$extraMsg") + override fun event(id: String, event: LogEvent, category: LogCategory, extraData: Bundle) { + val extraMsg = extraData.toString().removeRange(0, 6) + Log.d("SpaEvent-$category", "[$id] $event $extraMsg") } }
\ No newline at end of file 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..1c881878f751 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 @@ -16,17 +16,22 @@ package com.android.settingslib.spa.framework.util +import android.os.Bundle import androidx.compose.runtime.Composable +import androidx.core.os.bundleOf +import com.android.settingslib.spa.framework.common.LOG_DATA_SWITCH_STATUS import com.android.settingslib.spa.framework.common.LocalEntryDataProvider import com.android.settingslib.spa.framework.common.LogCategory import com.android.settingslib.spa.framework.common.LogEvent import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory @Composable -fun logEntryEvent(): (event: LogEvent) -> Unit { - val entryId = LocalEntryDataProvider.current.entryId ?: return {} - return { - SpaEnvironmentFactory.instance.logger.event(entryId, it, category = LogCategory.VIEW) +fun logEntryEvent(): (event: LogEvent, extraData: Bundle) -> Unit { + val entryId = LocalEntryDataProvider.current.entryId ?: return { _, _ -> } + return { event, extraData -> + SpaEnvironmentFactory.instance.logger.event( + entryId, event, category = LogCategory.VIEW, extraData = extraData + ) } } @@ -35,7 +40,7 @@ fun wrapOnClickWithLog(onClick: (() -> Unit)?): (() -> Unit)? { if (onClick == null) return null val logEvent = logEntryEvent() return { - logEvent(LogEvent.ENTRY_CLICK) + logEvent(LogEvent.ENTRY_CLICK, Bundle.EMPTY) onClick() } } @@ -45,8 +50,7 @@ fun wrapOnSwitchWithLog(onSwitch: ((checked: Boolean) -> Unit)?): ((checked: Boo if (onSwitch == null) return null val logEvent = logEntryEvent() return { - val event = if (it) LogEvent.ENTRY_SWITCH_ON else LogEvent.ENTRY_SWITCH_OFF - logEvent(event) + logEvent(LogEvent.ENTRY_SWITCH, bundleOf(LOG_DATA_SWITCH_STATUS to it)) onSwitch(it) } } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt index d801840565ed..97e3ac2147ca 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt @@ -27,6 +27,13 @@ import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map /** + * Returns a [Flow] whose values are a list which containing the results of applying the given + * [transform] function to each element in the original flow's list. + */ +inline fun <T, R> Flow<List<T>>.mapItem(crossinline transform: (T) -> R): Flow<List<R>> = + map { list -> list.map(transform) } + +/** * Returns a [Flow] whose values are a list which containing the results of asynchronously applying * the given [transform] function to each element in the original flow's list. */ 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..a88125472b52 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt @@ -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.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.core.os.bundleOf +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import com.android.settingslib.spa.framework.common.LOG_DATA_DISPLAY_NAME +import com.android.settingslib.spa.framework.common.LOG_DATA_SESSION_NAME +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 logPageEvent: (event: LogEvent) -> Unit = { + SpaEnvironmentFactory.instance.logger.event( + id = page.id, + event = it, + category = LogCategory.FRAMEWORK, + extraData = bundleOf( + LOG_DATA_DISPLAY_NAME to page.displayName, + LOG_DATA_SESSION_NAME to navController.sessionSourceName, + ) + ) + } + if (event == Lifecycle.Event.ON_START) { + logPageEvent(LogEvent.PAGE_ENTER) + } else if (event == Lifecycle.Event.ON_STOP) { + logPageEvent(LogEvent.PAGE_LEAVE) + } + } + + // 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/util/SpaIntent.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt new file mode 100644 index 000000000000..2c3c2e003832 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt @@ -0,0 +1,85 @@ +/* + * 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.content.ComponentName +import android.content.Intent +import com.android.settingslib.spa.framework.common.SettingsEntry +import com.android.settingslib.spa.framework.common.SettingsPage +import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory + +const val SESSION_BROWSE = "browse" +const val SESSION_SEARCH = "search" +const val SESSION_SLICE = "slice" + +const val KEY_DESTINATION = "spaActivityDestination" +const val KEY_HIGHLIGHT_ENTRY = "highlightEntry" +const val KEY_SESSION_SOURCE_NAME = "sessionSource" + +val SPA_INTENT_RESERVED_KEYS = listOf( + KEY_DESTINATION, + KEY_HIGHLIGHT_ENTRY, + KEY_SESSION_SOURCE_NAME +) + +private fun createBaseIntent(): Intent? { + val context = SpaEnvironmentFactory.instance.appContext + val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null + return Intent().setComponent(ComponentName(context, browseActivityClass)) +} + +fun SettingsPage.createIntent(sessionName: String? = null): Intent? { + if (!isBrowsable()) return null + return createBaseIntent()?.appendSpaParams( + destination = buildRoute(), + sessionName = sessionName + ) +} + +fun SettingsEntry.createIntent(sessionName: String? = null): Intent? { + val sp = containerPage() + if (!sp.isBrowsable()) return null + return createBaseIntent()?.appendSpaParams( + destination = sp.buildRoute(), + entryId = id, + sessionName = sessionName + ) +} + +fun Intent.appendSpaParams( + destination: String? = null, + entryId: String? = null, + sessionName: String? = null +): Intent { + return apply { + if (destination != null) putExtra(KEY_DESTINATION, destination) + if (entryId != null) putExtra(KEY_HIGHLIGHT_ENTRY, entryId) + if (sessionName != null) putExtra(KEY_SESSION_SOURCE_NAME, sessionName) + } +} + +fun Intent.getDestination(): String? { + return getStringExtra(KEY_DESTINATION) +} + +fun Intent.getEntryId(): String? { + return getStringExtra(KEY_HIGHLIGHT_ENTRY) +} + +fun Intent.getSessionName(): String? { + return getStringExtra(KEY_SESSION_SOURCE_NAME) +} 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..02aed1ca3399 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,12 +26,15 @@ 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 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory import com.android.settingslib.spa.framework.common.addUri import com.android.settingslib.spa.framework.common.getColumns +import com.android.settingslib.spa.framework.util.SESSION_SEARCH +import com.android.settingslib.spa.framework.util.createIntent private const val TAG = "SpaSearchProvider" @@ -115,7 +118,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 +129,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 +140,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 +151,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()) { @@ -157,20 +164,19 @@ class SpaSearchProvider : ContentProvider() { private fun fetchSearchData(entry: SettingsEntry, cursor: MatrixCursor) { val entryRepository by spaEnvironment.entryRepository - val browseActivityClass = spaEnvironment.browseActivityClass // Fetch search data. We can add runtime arguments later if necessary val searchData = entry.getSearchData() ?: return - val intent = entry.containerPage() - .createBrowseIntent(context, browseActivityClass, entry.id) - ?: Intent() + val intent = entry.createIntent(SESSION_SEARCH) ?: Intent() cursor.newRow() .add(ColumnEnum.ENTRY_ID.id, entry.id) .add(ColumnEnum.ENTRY_INTENT_URI.id, intent.toUri(Intent.URI_INTENT_SCHEME)) .add(ColumnEnum.SEARCH_TITLE.id, searchData.title) .add(ColumnEnum.SEARCH_KEYWORD.id, searchData.keyword) - .add(ColumnEnum.SEARCH_PATH.id, - entryRepository.getEntryPathWithTitle(entry.id, searchData.title)) + .add( + ColumnEnum.SEARCH_PATH.id, + entryRepository.getEntryPathWithTitle(entry.id, searchData.title) + ) } private fun fetchStatusData(entry: SettingsEntry, cursor: MatrixCursor) { diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt index 14855a8aed59..7a4750dfb134 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt @@ -20,6 +20,7 @@ import android.net.Uri import android.util.Log import com.android.settingslib.spa.framework.common.EntrySliceData import com.android.settingslib.spa.framework.common.SettingsEntryRepository +import com.android.settingslib.spa.framework.util.getEntryId private const val TAG = "SliceDataRepository" diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt index ff143ed864c8..f3628903dc6d 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt @@ -24,9 +24,15 @@ import android.content.Context import android.content.Intent import android.net.Uri import android.os.Bundle -import com.android.settingslib.spa.framework.BrowseActivity.Companion.KEY_DESTINATION -import com.android.settingslib.spa.framework.BrowseActivity.Companion.KEY_HIGHLIGHT_ENTRY +import com.android.settingslib.spa.framework.common.SettingsEntry import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory +import com.android.settingslib.spa.framework.util.KEY_DESTINATION +import com.android.settingslib.spa.framework.util.KEY_HIGHLIGHT_ENTRY +import com.android.settingslib.spa.framework.util.SESSION_SLICE +import com.android.settingslib.spa.framework.util.SPA_INTENT_RESERVED_KEYS +import com.android.settingslib.spa.framework.util.appendSpaParams +import com.android.settingslib.spa.framework.util.getDestination +import com.android.settingslib.spa.framework.util.getEntryId // Defines SliceUri, which contains special query parameters: // -- KEY_DESTINATION: The route that this slice is navigated to. @@ -35,11 +41,6 @@ import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory // Use {entryId, runtimeParams} as the unique Id of this Slice. typealias SliceUri = Uri -val RESERVED_KEYS = listOf( - KEY_DESTINATION, - KEY_HIGHLIGHT_ENTRY -) - fun SliceUri.getEntryId(): String? { return getQueryParameter(KEY_HIGHLIGHT_ENTRY) } @@ -51,7 +52,7 @@ fun SliceUri.getDestination(): String? { fun SliceUri.getRuntimeArguments(): Bundle { val params = Bundle() for (queryName in queryParameterNames) { - if (RESERVED_KEYS.contains(queryName)) continue + if (SPA_INTENT_RESERVED_KEYS.contains(queryName)) continue params.putString(queryName, getQueryParameter(queryName)) } return params @@ -63,12 +64,12 @@ fun SliceUri.getSliceId(): String? { return "${entryId}_$params" } -fun Uri.Builder.appendSliceParams( - route: String? = null, +fun Uri.Builder.appendSpaParams( + destination: String? = null, entryId: String? = null, runtimeArguments: Bundle? = null ): Uri.Builder { - if (route != null) appendQueryParameter(KEY_DESTINATION, route) + if (destination != null) appendQueryParameter(KEY_DESTINATION, destination) if (entryId != null) appendQueryParameter(KEY_HIGHLIGHT_ENTRY, entryId) if (runtimeArguments != null) { for (key in runtimeArguments.keySet()) { @@ -78,6 +79,20 @@ fun Uri.Builder.appendSliceParams( return this } +fun Uri.Builder.fromEntry( + entry: SettingsEntry, + authority: String?, + runtimeArguments: Bundle? = null +): Uri.Builder { + if (authority == null) return this + val sp = entry.containerPage() + return scheme("content").authority(authority).appendSpaParams( + destination = sp.buildRoute(), + entryId = entry.id, + runtimeArguments = runtimeArguments + ) +} + fun SliceUri.createBroadcastPendingIntent(): PendingIntent? { val context = SpaEnvironmentFactory.instance.appContext val sliceBroadcastClass = @@ -97,8 +112,8 @@ fun SliceUri.createBrowsePendingIntent(): PendingIntent? { fun Intent.createBrowsePendingIntent(): PendingIntent? { val context = SpaEnvironmentFactory.instance.appContext val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null - val destination = getStringExtra(KEY_DESTINATION) ?: return null - val entryId = getStringExtra(KEY_HIGHLIGHT_ENTRY) + val destination = getDestination() ?: return null + val entryId = getEntryId() return createBrowsePendingIntent(context, browseActivityClass, destination, entryId) } @@ -109,15 +124,12 @@ private fun createBrowsePendingIntent( entryId: String? ): PendingIntent { val intent = Intent().setComponent(ComponentName(context, browseActivityClass)) + .appendSpaParams(destination, entryId, SESSION_SLICE) .apply { // Set both extra and data (which is a Uri) in Slice Intent: // 1) extra is used in SPA navigation framework // 2) data is used in Slice framework - putExtra(KEY_DESTINATION, destination) - if (entryId != null) { - putExtra(KEY_HIGHLIGHT_ENTRY, entryId) - } - data = Uri.Builder().appendSliceParams(destination, entryId).build() + data = Uri.Builder().appendSpaParams(destination, entryId).build() flags = Intent.FLAG_ACTIVITY_NEW_TASK } @@ -130,7 +142,7 @@ private fun createBroadcastPendingIntent( entryId: String ): PendingIntent { val intent = Intent().setComponent(ComponentName(context, sliceBroadcastClass)) - .apply { data = Uri.Builder().appendSliceParams(entryId = entryId).build() } + .apply { data = Uri.Builder().appendSpaParams(entryId = entryId).build() } return PendingIntent.getBroadcast( context, 0 /* requestCode */, intent, PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE 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..c0b7464e69e6 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 @@ -23,6 +23,8 @@ import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest import com.android.settingslib.spa.tests.testutils.SppHome import com.android.settingslib.spa.tests.testutils.SppLayer1 import com.android.settingslib.spa.tests.testutils.SppLayer2 +import com.android.settingslib.spa.tests.testutils.getUniqueEntryId +import com.android.settingslib.spa.tests.testutils.getUniquePageId import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @@ -30,7 +32,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..f98963c869de 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 @@ -20,6 +20,8 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.test.junit4.createComposeRule import androidx.core.os.bundleOf import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spa.tests.testutils.getUniqueEntryId +import com.android.settingslib.spa.tests.testutils.getUniquePageId import com.google.common.truth.Truth.assertThat import org.junit.Rule import org.junit.Test @@ -64,6 +66,7 @@ class SettingsEntryTest { assertThat(entry.isAllowSearch).isFalse() assertThat(entry.isSearchDataDynamic).isFalse() assertThat(entry.hasMutableStatus).isFalse() + assertThat(entry.hasSliceSupport).isFalse() } @Test @@ -121,12 +124,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 +138,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 +158,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..1f5de2d97245 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 @@ -22,9 +22,8 @@ import androidx.navigation.NavType import androidx.navigation.navArgument 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.android.settingslib.spa.tests.testutils.getUniquePageId 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() { @@ -45,9 +43,7 @@ class SettingsPageTest { assertThat(page.isCreateBy("NULL")).isTrue() assertThat(page.isCreateBy("Spp")).isFalse() assertThat(page.hasRuntimeParam()).isFalse() - assertThat(page.isBrowsable(context, BlankActivity::class.java)).isFalse() - assertThat(page.createBrowseIntent(context, BlankActivity::class.java)).isNull() - assertThat(page.createBrowseAdbCommand(context, BlankActivity::class.java)).isNull() + assertThat(page.isBrowsable()).isFalse() } @Test @@ -60,11 +56,7 @@ class SettingsPageTest { assertThat(page.isCreateBy("NULL")).isFalse() assertThat(page.isCreateBy("mySpp")).isTrue() assertThat(page.hasRuntimeParam()).isFalse() - assertThat(page.isBrowsable(context, BlankActivity::class.java)).isTrue() - assertThat(page.createBrowseIntent(context, BlankActivity::class.java)).isNotNull() - assertThat(page.createBrowseAdbCommand(context, BlankActivity::class.java)).contains( - "-e spaActivityDestination mySpp" - ) + assertThat(page.isBrowsable()).isTrue() } @Test @@ -74,20 +66,20 @@ 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") assertThat(page.isCreateBy("SppWithParam")).isTrue() assertThat(page.hasRuntimeParam()).isFalse() - assertThat(page.isBrowsable(context, BlankActivity::class.java)).isTrue() - assertThat(page.createBrowseIntent(context, BlankActivity::class.java)).isNotNull() - assertThat(page.createBrowseAdbCommand(context, BlankActivity::class.java)).contains( - "-e spaActivityDestination SppWithParam/myStr/10" - ) + assertThat(page.isBrowsable()).isTrue() } @Test @@ -98,33 +90,20 @@ 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") assertThat(page.isCreateBy("SppWithRtParam")).isTrue() assertThat(page.hasRuntimeParam()).isTrue() - assertThat(page.isBrowsable(context, BlankActivity::class.java)).isFalse() - 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) + assertThat(page.isBrowsable()).isFalse() } } diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SpaIntentTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SpaIntentTest.kt new file mode 100644 index 000000000000..18547286eee0 --- /dev/null +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SpaIntentTest.kt @@ -0,0 +1,62 @@ +/* + * 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.content.Context +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +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.tests.testutils.SpaEnvironmentForTest +import com.google.common.truth.Truth +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class SpaIntentTest { + private val context: Context = ApplicationProvider.getApplicationContext() + private val spaEnvironment = SpaEnvironmentForTest(context) + + @Before + fun setEnvironment() { + SpaEnvironmentFactory.reset(spaEnvironment) + } + + @Test + fun testCreateIntent() { + val nullPage = SettingsPage.createNull() + Truth.assertThat(nullPage.createIntent()).isNull() + Truth.assertThat(SettingsEntryBuilder.createInject(nullPage).build().createIntent()) + .isNull() + + val page = spaEnvironment.createPage("SppHome") + val pageIntent = page.createIntent() + Truth.assertThat(pageIntent).isNotNull() + Truth.assertThat(pageIntent!!.getDestination()).isEqualTo(page.buildRoute()) + Truth.assertThat(pageIntent.getEntryId()).isNull() + Truth.assertThat(pageIntent.getSessionName()).isNull() + + val entry = SettingsEntryBuilder.createInject(page).build() + val entryIntent = entry.createIntent(SESSION_SEARCH) + Truth.assertThat(entryIntent).isNotNull() + Truth.assertThat(entryIntent!!.getDestination()).isEqualTo(page.buildRoute()) + Truth.assertThat(entryIntent.getEntryId()).isEqualTo(entry.id) + Truth.assertThat(entryIntent.getSessionName()).isEqualTo(SESSION_SEARCH) + } +}
\ No newline at end of file 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..90e25f9a3b70 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 @@ -23,10 +23,11 @@ import androidx.slice.Slice import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 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.android.settingslib.spa.tests.testutils.getUniqueEntryId import com.google.common.truth.Truth.assertThat import org.junit.Rule import org.junit.Test @@ -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 @@ -48,7 +50,7 @@ class SettingsSliceDataRepositoryTest { // Slice supported val page = SppLayer2.createSettingsPage() val entryId = getUniqueEntryId("Layer2Entry1", page) - val sliceUri = Uri.Builder().appendSliceParams(page.buildRoute(), entryId).build() + val sliceUri = Uri.Builder().appendSpaParams(page.buildRoute(), entryId).build() assertThat(sliceUri.getDestination()).isEqualTo("SppLayer2") assertThat(sliceUri.getSliceId()).isEqualTo("${entryId}_Bundle[{}]") val sliceData = sliceDataRepository.getOrBuildSliceData(sliceUri) @@ -57,7 +59,7 @@ class SettingsSliceDataRepositoryTest { // Slice unsupported val entryId2 = getUniqueEntryId("Layer2Entry2", page) - val sliceUri2 = Uri.Builder().appendSliceParams(page.buildRoute(), entryId2).build() + val sliceUri2 = Uri.Builder().appendSpaParams(page.buildRoute(), entryId2).build() assertThat(sliceUri2.getDestination()).isEqualTo("SppLayer2") assertThat(sliceUri2.getSliceId()).isEqualTo("${entryId2}_Bundle[{}]") assertThat(sliceDataRepository.getOrBuildSliceData(sliceUri2)).isNull() @@ -67,7 +69,7 @@ class SettingsSliceDataRepositoryTest { fun getActiveSliceDataTest() { val page = SppLayer2.createSettingsPage() val entryId = getUniqueEntryId("Layer2Entry1", page) - val sliceUri = Uri.Builder().appendSliceParams(page.buildRoute(), entryId).build() + val sliceUri = Uri.Builder().appendSpaParams(page.buildRoute(), entryId).build() // build slice data first val sliceData = sliceDataRepository.getOrBuildSliceData(sliceUri) diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt index 16a87f603447..d1c4e5110f60 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt @@ -43,14 +43,14 @@ class SliceUtilTest { // valid slice uri val dest = "myRoute" val entryId = "myEntry" - val sliceUriWithoutParams = Uri.Builder().appendSliceParams(dest, entryId).build() + val sliceUriWithoutParams = Uri.Builder().appendSpaParams(dest, entryId).build() assertThat(sliceUriWithoutParams.getEntryId()).isEqualTo(entryId) assertThat(sliceUriWithoutParams.getDestination()).isEqualTo(dest) assertThat(sliceUriWithoutParams.getRuntimeArguments().size()).isEqualTo(0) assertThat(sliceUriWithoutParams.getSliceId()).isEqualTo("${entryId}_Bundle[{}]") val sliceUriWithParams = - Uri.Builder().appendSliceParams(dest, entryId, bundleOf("p1" to "v1")).build() + Uri.Builder().appendSpaParams(dest, entryId, bundleOf("p1" to "v1")).build() assertThat(sliceUriWithParams.getEntryId()).isEqualTo(entryId) assertThat(sliceUriWithParams.getDestination()).isEqualTo(dest) assertThat(sliceUriWithParams.getRuntimeArguments().size()).isEqualTo(1) @@ -67,7 +67,7 @@ class SliceUtilTest { // Valid Slice Uri val dest = "myRoute" val entryId = "myEntry" - val sliceUriWithoutParams = Uri.Builder().appendSliceParams(dest, entryId).build() + val sliceUriWithoutParams = Uri.Builder().appendSpaParams(dest, entryId).build() val pendingIntent = sliceUriWithoutParams.createBroadcastPendingIntent() assertThat(pendingIntent).isNotNull() assertThat(pendingIntent!!.isBroadcast).isTrue() @@ -87,7 +87,7 @@ class SliceUtilTest { // Valid Slice Uri val dest = "myRoute" val entryId = "myEntry" - val sliceUri = Uri.Builder().appendSliceParams(dest, entryId).build() + val sliceUri = Uri.Builder().appendSpaParams(dest, entryId).build() val pendingIntent = sliceUri.createBrowsePendingIntent() assertThat(pendingIntent).isNotNull() assertThat(pendingIntent!!.isActivity).isTrue() 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..f38bd088060a 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 @@ -49,7 +51,7 @@ class SpaLoggerForTest : SpaLogger { messageCount[key] = (messageCount[key] ?: 0) + 1 } - override fun event(id: String, event: LogEvent, category: LogCategory, details: String?) { + override fun event(id: String, event: LogEvent, category: LogCategory, extraData: Bundle) { val key = EventCountKey(id, event, category) eventCount[key] = (eventCount[key] ?: 0) + 1 } @@ -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/Spa/tests/src/com/android/settingslib/spa/framework/common/UniqueIdHelper.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt index 93f9afe16fb6..7e51fea69041 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/UniqueIdHelper.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt @@ -14,10 +14,12 @@ * limitations under the License. */ -package com.android.settingslib.spa.framework.common +package com.android.settingslib.spa.tests.testutils import android.os.Bundle import androidx.navigation.NamedNavArgument +import com.android.settingslib.spa.framework.common.SettingsPage +import com.android.settingslib.spa.framework.common.toHashId import com.android.settingslib.spa.framework.util.normalize fun getUniquePageId( diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt index a7122d0eb03a..69999089b280 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt @@ -33,7 +33,8 @@ interface AppListModel<T : AppRecord> { * * @return the [AppRecord] list which will be displayed. */ - fun filter(userIdFlow: Flow<Int>, option: Int, recordListFlow: Flow<List<T>>): Flow<List<T>> + fun filter(userIdFlow: Flow<Int>, option: Int, recordListFlow: Flow<List<T>>): Flow<List<T>> = + recordListFlow /** * This function is called when the App List's loading is finished and displayed to the user. @@ -67,5 +68,5 @@ interface AppListModel<T : AppRecord> { * @return null if no summary should be displayed. */ @Composable - fun getSummary(option: Int, record: T): State<String>? + fun getSummary(option: Int, record: T): State<String>? = null } 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..b9c875ba803a 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 @@ -21,7 +21,7 @@ import android.content.pm.ApplicationInfo import androidx.compose.runtime.Composable import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settingslib.spa.framework.compose.stateOf -import com.android.settingslib.spa.framework.util.asyncMapItem +import com.android.settingslib.spa.framework.util.mapItem import com.android.settingslib.spa.testutils.waitUntil import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -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 @@ -117,16 +116,7 @@ private class TestAppListModel : AppListModel<TestAppRecord> { var onFirstLoadedCalled = false 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 + appListFlow.mapItem(::TestAppRecord) override suspend fun onFirstLoaded(recordList: List<TestAppRecord>) { onFirstLoadedCalled = true 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..ada4016bea13 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt @@ -0,0 +1,41 @@ +/* + * 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 com.android.settingslib.spa.framework.util.mapItem +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.mapItem(::TestAppRecord) + + 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/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt new file mode 100644 index 000000000000..f490c5459d46 --- /dev/null +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt @@ -0,0 +1,190 @@ +/* + * 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.shared.quickaffordance.data.content + +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine + +class FakeKeyguardQuickAffordanceProviderClient( + slots: List<KeyguardQuickAffordanceProviderClient.Slot> = + listOf( + KeyguardQuickAffordanceProviderClient.Slot( + id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, + capacity = 1, + ), + KeyguardQuickAffordanceProviderClient.Slot( + id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, + capacity = 1, + ), + ), + affordances: List<KeyguardQuickAffordanceProviderClient.Affordance> = + listOf( + KeyguardQuickAffordanceProviderClient.Affordance( + id = AFFORDANCE_1, + name = AFFORDANCE_1, + iconResourceId = 0, + ), + KeyguardQuickAffordanceProviderClient.Affordance( + id = AFFORDANCE_2, + name = AFFORDANCE_2, + iconResourceId = 0, + ), + KeyguardQuickAffordanceProviderClient.Affordance( + id = AFFORDANCE_3, + name = AFFORDANCE_3, + iconResourceId = 0, + ), + ), + flags: List<KeyguardQuickAffordanceProviderClient.Flag> = + listOf( + KeyguardQuickAffordanceProviderClient.Flag( + name = KeyguardQuickAffordanceProviderContract.FlagsTable.FLAG_NAME_FEATURE_ENABLED, + value = true, + ) + ), +) : KeyguardQuickAffordanceProviderClient { + + private val slots = MutableStateFlow(slots) + private val affordances = MutableStateFlow(affordances) + private val flags = MutableStateFlow(flags) + + private val selections = MutableStateFlow<Map<String, List<String>>>(emptyMap()) + + override suspend fun insertSelection(slotId: String, affordanceId: String) { + val slotCapacity = + querySlots().find { it.id == slotId }?.capacity + ?: error("Slot with ID \"$slotId\" not found!") + val affordances = selections.value.getOrDefault(slotId, mutableListOf()).toMutableList() + while (affordances.size + 1 > slotCapacity) { + affordances.removeAt(0) + } + affordances.remove(affordanceId) + affordances.add(affordanceId) + selections.value = selections.value.toMutableMap().apply { this[slotId] = affordances } + } + + override suspend fun querySlots(): List<KeyguardQuickAffordanceProviderClient.Slot> { + return slots.value + } + + override suspend fun queryFlags(): List<KeyguardQuickAffordanceProviderClient.Flag> { + return flags.value + } + + override fun observeSlots(): Flow<List<KeyguardQuickAffordanceProviderClient.Slot>> { + return slots.asStateFlow() + } + + override fun observeFlags(): Flow<List<KeyguardQuickAffordanceProviderClient.Flag>> { + return flags.asStateFlow() + } + + override suspend fun queryAffordances(): + List<KeyguardQuickAffordanceProviderClient.Affordance> { + return affordances.value + } + + override fun observeAffordances(): + Flow<List<KeyguardQuickAffordanceProviderClient.Affordance>> { + return affordances.asStateFlow() + } + + override suspend fun querySelections(): List<KeyguardQuickAffordanceProviderClient.Selection> { + return toSelectionList(selections.value, affordances.value) + } + + override fun observeSelections(): Flow<List<KeyguardQuickAffordanceProviderClient.Selection>> { + return combine(selections, affordances) { selections, affordances -> + toSelectionList(selections, affordances) + } + } + + override suspend fun deleteSelection(slotId: String, affordanceId: String) { + val affordances = selections.value.getOrDefault(slotId, mutableListOf()).toMutableList() + affordances.remove(affordanceId) + + selections.value = selections.value.toMutableMap().apply { this[slotId] = affordances } + } + + override suspend fun deleteAllSelections(slotId: String) { + selections.value = selections.value.toMutableMap().apply { this[slotId] = emptyList() } + } + + override suspend fun getAffordanceIcon(iconResourceId: Int, tintColor: Int): Drawable { + return BitmapDrawable() + } + + fun setFlag( + name: String, + value: Boolean, + ) { + flags.value = + flags.value.toMutableList().apply { + removeIf { it.name == name } + add(KeyguardQuickAffordanceProviderClient.Flag(name = name, value = value)) + } + } + + fun setSlotCapacity(slotId: String, capacity: Int) { + slots.value = + slots.value.toMutableList().apply { + val index = indexOfFirst { it.id == slotId } + check(index != -1) { "Slot with ID \"$slotId\" doesn't exist!" } + set( + index, + KeyguardQuickAffordanceProviderClient.Slot(id = slotId, capacity = capacity) + ) + } + } + + fun addAffordance(affordance: KeyguardQuickAffordanceProviderClient.Affordance): Int { + affordances.value = affordances.value + listOf(affordance) + return affordances.value.size - 1 + } + + private fun toSelectionList( + selections: Map<String, List<String>>, + affordances: List<KeyguardQuickAffordanceProviderClient.Affordance>, + ): List<KeyguardQuickAffordanceProviderClient.Selection> { + return selections + .map { (slotId, affordanceIds) -> + affordanceIds.map { affordanceId -> + val affordanceName = + affordances.find { it.id == affordanceId }?.name + ?: error("No affordance with ID of \"$affordanceId\"!") + KeyguardQuickAffordanceProviderClient.Selection( + slotId = slotId, + affordanceId = affordanceId, + affordanceName = affordanceName, + ) + } + } + .flatten() + } + + companion object { + const val AFFORDANCE_1 = "affordance_1" + const val AFFORDANCE_2 = "affordance_2" + const val AFFORDANCE_3 = "affordance_3" + } +} diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderClient.kt new file mode 100644 index 000000000000..3213b2e97ac9 --- /dev/null +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderClient.kt @@ -0,0 +1,479 @@ +/* + * 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.shared.quickaffordance.data.content + +import android.annotation.SuppressLint +import android.content.ContentValues +import android.content.Context +import android.database.ContentObserver +import android.graphics.Color +import android.graphics.drawable.Drawable +import android.net.Uri +import androidx.annotation.DrawableRes +import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.withContext + +/** Client for using a content provider implementing the [Contract]. */ +interface KeyguardQuickAffordanceProviderClient { + + /** + * Selects an affordance with the given ID for a slot on the lock screen with the given ID. + * + * Note that the maximum number of selected affordances on this slot is automatically enforced. + * Selecting a slot that is already full (e.g. already has a number of selected affordances at + * its maximum capacity) will automatically remove the oldest selected affordance before adding + * the one passed in this call. Additionally, selecting an affordance that's already one of the + * selected affordances on the slot will move the selected affordance to the newest location in + * the slot. + */ + suspend fun insertSelection( + slotId: String, + affordanceId: String, + ) + + /** Returns all available slots supported by the device. */ + suspend fun querySlots(): List<Slot> + + /** Returns the list of flags. */ + suspend fun queryFlags(): List<Flag> + + /** + * Returns [Flow] for observing the collection of slots. + * + * @see [querySlots] + */ + fun observeSlots(): Flow<List<Slot>> + + /** + * Returns [Flow] for observing the collection of flags. + * + * @see [queryFlags] + */ + fun observeFlags(): Flow<List<Flag>> + + /** + * Returns all available affordances supported by the device, regardless of current slot + * placement. + */ + suspend fun queryAffordances(): List<Affordance> + + /** + * Returns [Flow] for observing the collection of affordances. + * + * @see [queryAffordances] + */ + fun observeAffordances(): Flow<List<Affordance>> + + /** Returns the current slot-affordance selections. */ + suspend fun querySelections(): List<Selection> + + /** + * Returns [Flow] for observing the collection of selections. + * + * @see [querySelections] + */ + fun observeSelections(): Flow<List<Selection>> + + /** Unselects an affordance with the given ID from the slot with the given ID. */ + suspend fun deleteSelection( + slotId: String, + affordanceId: String, + ) + + /** Unselects all affordances from the slot with the given ID. */ + suspend fun deleteAllSelections( + slotId: String, + ) + + /** Returns a [Drawable] with the given ID, loaded from the system UI package. */ + suspend fun getAffordanceIcon( + @DrawableRes iconResourceId: Int, + tintColor: Int = Color.WHITE, + ): Drawable + + /** Models a slot. A position that quick affordances can be positioned in. */ + data class Slot( + /** Unique ID of the slot. */ + val id: String, + /** + * The maximum number of quick affordances that are allowed to be positioned in this slot. + */ + val capacity: Int, + ) + + /** + * Models a quick affordance. An action that can be selected by the user to appear in one or + * more slots on the lock screen. + */ + data class Affordance( + /** Unique ID of the quick affordance. */ + val id: String, + /** User-facing label for this affordance. */ + val name: String, + /** + * Resource ID for the user-facing icon for this affordance. This resource is hosted by the + * System UI process so it must be used with + * `PackageManager.getResourcesForApplication(String)`. + */ + val iconResourceId: Int, + /** + * Whether the affordance is enabled. Disabled affordances should be shown on the picker but + * should be rendered as "disabled". When tapped, the enablement properties should be used + * to populate UI that would explain to the user what to do in order to re-enable this + * affordance. + */ + val isEnabled: Boolean = true, + /** + * If the affordance is disabled, this is a set of instruction messages to be shown to the + * user when the disabled affordance is selected. The instructions should help the user + * figure out what to do in order to re-neable this affordance. + */ + val enablementInstructions: List<String>? = null, + /** + * If the affordance is disabled, this is a label for a button shown together with the set + * of instruction messages when the disabled affordance is selected. The button should help + * send the user to a flow that would help them achieve the instructions and re-enable this + * affordance. + * + * If `null`, the button should not be shown. + */ + val enablementActionText: String? = null, + /** + * If the affordance is disabled, this is a "component name" of the format + * `packageName/action` to be used as an `Intent` for `startActivity` when the action button + * (shown together with the set of instruction messages when the disabled affordance is + * selected) is clicked by the user. The button should help send the user to a flow that + * would help them achieve the instructions and re-enable this affordance. + * + * If `null`, the button should not be shown. + */ + val enablementActionComponentName: String? = null, + ) + + /** Models a selection of a quick affordance on a slot. */ + data class Selection( + /** The unique ID of the slot. */ + val slotId: String, + /** The unique ID of the quick affordance. */ + val affordanceId: String, + /** The user-visible label for the quick affordance. */ + val affordanceName: String, + ) + + /** Models a System UI flag. */ + data class Flag( + /** The name of the flag. */ + val name: String, + /** The value of the flag. */ + val value: Boolean, + ) +} + +class KeyguardQuickAffordanceProviderClientImpl( + private val context: Context, + private val backgroundDispatcher: CoroutineDispatcher, +) : KeyguardQuickAffordanceProviderClient { + + override suspend fun insertSelection( + slotId: String, + affordanceId: String, + ) { + withContext(backgroundDispatcher) { + context.contentResolver.insert( + Contract.SelectionTable.URI, + ContentValues().apply { + put(Contract.SelectionTable.Columns.SLOT_ID, slotId) + put(Contract.SelectionTable.Columns.AFFORDANCE_ID, affordanceId) + } + ) + } + } + + override suspend fun querySlots(): List<KeyguardQuickAffordanceProviderClient.Slot> { + return withContext(backgroundDispatcher) { + context.contentResolver + .query( + Contract.SlotTable.URI, + null, + null, + null, + null, + ) + ?.use { cursor -> + buildList { + val idColumnIndex = cursor.getColumnIndex(Contract.SlotTable.Columns.ID) + val capacityColumnIndex = + cursor.getColumnIndex(Contract.SlotTable.Columns.CAPACITY) + if (idColumnIndex == -1 || capacityColumnIndex == -1) { + return@buildList + } + + while (cursor.moveToNext()) { + add( + KeyguardQuickAffordanceProviderClient.Slot( + id = cursor.getString(idColumnIndex), + capacity = cursor.getInt(capacityColumnIndex), + ) + ) + } + } + } + } + ?: emptyList() + } + + override suspend fun queryFlags(): List<KeyguardQuickAffordanceProviderClient.Flag> { + return withContext(backgroundDispatcher) { + context.contentResolver + .query( + Contract.FlagsTable.URI, + null, + null, + null, + null, + ) + ?.use { cursor -> + buildList { + val nameColumnIndex = + cursor.getColumnIndex(Contract.FlagsTable.Columns.NAME) + val valueColumnIndex = + cursor.getColumnIndex(Contract.FlagsTable.Columns.VALUE) + if (nameColumnIndex == -1 || valueColumnIndex == -1) { + return@buildList + } + + while (cursor.moveToNext()) { + add( + KeyguardQuickAffordanceProviderClient.Flag( + name = cursor.getString(nameColumnIndex), + value = cursor.getInt(valueColumnIndex) == 1, + ) + ) + } + } + } + } + ?: emptyList() + } + + override fun observeSlots(): Flow<List<KeyguardQuickAffordanceProviderClient.Slot>> { + return observeUri(Contract.SlotTable.URI).map { querySlots() } + } + + override fun observeFlags(): Flow<List<KeyguardQuickAffordanceProviderClient.Flag>> { + return observeUri(Contract.FlagsTable.URI).map { queryFlags() } + } + + override suspend fun queryAffordances(): + List<KeyguardQuickAffordanceProviderClient.Affordance> { + return withContext(backgroundDispatcher) { + context.contentResolver + .query( + Contract.AffordanceTable.URI, + null, + null, + null, + null, + ) + ?.use { cursor -> + buildList { + val idColumnIndex = + cursor.getColumnIndex(Contract.AffordanceTable.Columns.ID) + val nameColumnIndex = + cursor.getColumnIndex(Contract.AffordanceTable.Columns.NAME) + val iconColumnIndex = + cursor.getColumnIndex(Contract.AffordanceTable.Columns.ICON) + val isEnabledColumnIndex = + cursor.getColumnIndex(Contract.AffordanceTable.Columns.IS_ENABLED) + val enablementInstructionsColumnIndex = + cursor.getColumnIndex( + Contract.AffordanceTable.Columns.ENABLEMENT_INSTRUCTIONS + ) + val enablementActionTextColumnIndex = + cursor.getColumnIndex( + Contract.AffordanceTable.Columns.ENABLEMENT_ACTION_TEXT + ) + val enablementComponentNameColumnIndex = + cursor.getColumnIndex( + Contract.AffordanceTable.Columns.ENABLEMENT_COMPONENT_NAME + ) + if ( + idColumnIndex == -1 || + nameColumnIndex == -1 || + iconColumnIndex == -1 || + isEnabledColumnIndex == -1 || + enablementInstructionsColumnIndex == -1 || + enablementActionTextColumnIndex == -1 || + enablementComponentNameColumnIndex == -1 + ) { + return@buildList + } + + while (cursor.moveToNext()) { + add( + KeyguardQuickAffordanceProviderClient.Affordance( + id = cursor.getString(idColumnIndex), + name = cursor.getString(nameColumnIndex), + iconResourceId = cursor.getInt(iconColumnIndex), + isEnabled = cursor.getInt(isEnabledColumnIndex) == 1, + enablementInstructions = + cursor + .getString(enablementInstructionsColumnIndex) + ?.split( + Contract.AffordanceTable + .ENABLEMENT_INSTRUCTIONS_DELIMITER + ), + enablementActionText = + cursor.getString(enablementActionTextColumnIndex), + enablementActionComponentName = + cursor.getString(enablementComponentNameColumnIndex), + ) + ) + } + } + } + } + ?: emptyList() + } + + override fun observeAffordances(): + Flow<List<KeyguardQuickAffordanceProviderClient.Affordance>> { + return observeUri(Contract.AffordanceTable.URI).map { queryAffordances() } + } + + override suspend fun querySelections(): List<KeyguardQuickAffordanceProviderClient.Selection> { + return withContext(backgroundDispatcher) { + context.contentResolver + .query( + Contract.SelectionTable.URI, + null, + null, + null, + null, + ) + ?.use { cursor -> + buildList { + val slotIdColumnIndex = + cursor.getColumnIndex(Contract.SelectionTable.Columns.SLOT_ID) + val affordanceIdColumnIndex = + cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_ID) + val affordanceNameColumnIndex = + cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_NAME) + if ( + slotIdColumnIndex == -1 || + affordanceIdColumnIndex == -1 || + affordanceNameColumnIndex == -1 + ) { + return@buildList + } + + while (cursor.moveToNext()) { + add( + KeyguardQuickAffordanceProviderClient.Selection( + slotId = cursor.getString(slotIdColumnIndex), + affordanceId = cursor.getString(affordanceIdColumnIndex), + affordanceName = cursor.getString(affordanceNameColumnIndex), + ) + ) + } + } + } + } + ?: emptyList() + } + + override fun observeSelections(): Flow<List<KeyguardQuickAffordanceProviderClient.Selection>> { + return observeUri(Contract.SelectionTable.URI).map { querySelections() } + } + + override suspend fun deleteSelection( + slotId: String, + affordanceId: String, + ) { + withContext(backgroundDispatcher) { + context.contentResolver.delete( + Contract.SelectionTable.URI, + "${Contract.SelectionTable.Columns.SLOT_ID} = ? AND" + + " ${Contract.SelectionTable.Columns.AFFORDANCE_ID} = ?", + arrayOf( + slotId, + affordanceId, + ), + ) + } + } + + override suspend fun deleteAllSelections( + slotId: String, + ) { + withContext(backgroundDispatcher) { + context.contentResolver.delete( + Contract.SelectionTable.URI, + Contract.SelectionTable.Columns.SLOT_ID, + arrayOf( + slotId, + ), + ) + } + } + + @SuppressLint("UseCompatLoadingForDrawables") + override suspend fun getAffordanceIcon( + @DrawableRes iconResourceId: Int, + tintColor: Int, + ): Drawable { + return withContext(backgroundDispatcher) { + context.packageManager + .getResourcesForApplication(SYSTEM_UI_PACKAGE_NAME) + .getDrawable(iconResourceId, context.theme) + .apply { setTint(tintColor) } + } + } + + private fun observeUri( + uri: Uri, + ): Flow<Unit> { + return callbackFlow { + val observer = + object : ContentObserver(null) { + override fun onChange(selfChange: Boolean) { + trySend(Unit) + } + } + + context.contentResolver.registerContentObserver( + uri, + /* notifyForDescendants= */ true, + observer, + ) + + awaitClose { context.contentResolver.unregisterContentObserver(observer) } + } + .onStart { emit(Unit) } + } + + companion object { + private const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui" + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt index 98d8d3eb9a4a..17be74b08690 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt @@ -15,7 +15,7 @@ * */ -package com.android.systemui.shared.keyguard.data.content +package com.android.systemui.shared.quickaffordance.data.content import android.content.ContentResolver import android.net.Uri diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/shared/model/KeyguardQuickAffordanceSlots.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordanceSlots.kt index 2dc7a280e423..2dc7a280e423 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/shared/model/KeyguardQuickAffordanceSlots.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordanceSlots.kt 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/backup/BackupHelper.kt b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt index 5616a00592f2..621b99d6804a 100644 --- a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt @@ -29,13 +29,15 @@ import android.os.UserHandle import android.util.Log import com.android.systemui.controls.controller.AuxiliaryPersistenceWrapper import com.android.systemui.controls.controller.ControlsFavoritePersistenceWrapper +import com.android.systemui.keyguard.domain.backup.KeyguardQuickAffordanceBackupHelper import com.android.systemui.people.widget.PeopleBackupHelper /** * Helper for backing up elements in SystemUI * - * This helper is invoked by BackupManager whenever a backup or restore is required in SystemUI. - * The helper can be used to back up any element that is stored in [Context.getFilesDir]. + * This helper is invoked by BackupManager whenever a backup or restore is required in SystemUI. The + * helper can be used to back up any element that is stored in [Context.getFilesDir] or + * [Context.getSharedPreferences]. * * After restoring is done, a [ACTION_RESTORE_FINISHED] intent will be send to SystemUI user 0, * indicating that restoring is finished for a given user. @@ -47,9 +49,11 @@ open class BackupHelper : BackupAgentHelper() { internal const val CONTROLS = ControlsFavoritePersistenceWrapper.FILE_NAME private const val NO_OVERWRITE_FILES_BACKUP_KEY = "systemui.files_no_overwrite" private const val PEOPLE_TILES_BACKUP_KEY = "systemui.people.shared_preferences" + private const val KEYGUARD_QUICK_AFFORDANCES_BACKUP_KEY = + "systemui.keyguard.quickaffordance.shared_preferences" val controlsDataLock = Any() const val ACTION_RESTORE_FINISHED = "com.android.systemui.backup.RESTORE_FINISHED" - private const val PERMISSION_SELF = "com.android.systemui.permission.SELF" + const val PERMISSION_SELF = "com.android.systemui.permission.SELF" } override fun onCreate(userHandle: UserHandle, operationType: Int) { @@ -67,17 +71,27 @@ open class BackupHelper : BackupAgentHelper() { } val keys = PeopleBackupHelper.getFilesToBackup() - addHelper(PEOPLE_TILES_BACKUP_KEY, PeopleBackupHelper( - this, userHandle, keys.toTypedArray())) + addHelper( + PEOPLE_TILES_BACKUP_KEY, + PeopleBackupHelper(this, userHandle, keys.toTypedArray()) + ) + addHelper( + KEYGUARD_QUICK_AFFORDANCES_BACKUP_KEY, + KeyguardQuickAffordanceBackupHelper( + context = this, + userId = userHandle.identifier, + ), + ) } override fun onRestoreFinished() { super.onRestoreFinished() - val intent = Intent(ACTION_RESTORE_FINISHED).apply { - `package` = packageName - putExtra(Intent.EXTRA_USER_ID, userId) - flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY - } + val intent = + Intent(ACTION_RESTORE_FINISHED).apply { + `package` = packageName + putExtra(Intent.EXTRA_USER_ID, userId) + flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY + } sendBroadcastAsUser(intent, UserHandle.SYSTEM, PERMISSION_SELF) } @@ -90,7 +104,9 @@ open class BackupHelper : BackupAgentHelper() { * @property lock a lock to hold while backing up and restoring the files. * @property context the context of the [BackupAgent] * @property fileNamesAndPostProcess a map from the filenames to back up and the post processing + * ``` * actions to take + * ``` */ private class NoOverwriteFileBackupHelper( val lock: Any, @@ -115,23 +131,23 @@ open class BackupHelper : BackupAgentHelper() { data: BackupDataOutput?, newState: ParcelFileDescriptor? ) { - synchronized(lock) { - super.performBackup(oldState, data, newState) - } + synchronized(lock) { super.performBackup(oldState, data, newState) } } } } + private fun getPPControlsFile(context: Context): () -> Unit { return { val filesDir = context.filesDir val file = Environment.buildPath(filesDir, BackupHelper.CONTROLS) if (file.exists()) { - val dest = Environment.buildPath(filesDir, - AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME) + val dest = + Environment.buildPath(filesDir, AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME) file.copyTo(dest) val jobScheduler = context.getSystemService(JobScheduler::class.java) jobScheduler?.schedule( - AuxiliaryPersistenceWrapper.DeletionJobService.getJobForContext(context)) + AuxiliaryPersistenceWrapper.DeletionJobService.getJobForContext(context) + ) } } -}
\ No newline at end of file +} 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/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt index 537cbc5a267d..a0a892de0085 100644 --- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt +++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt @@ -64,8 +64,9 @@ private const val TAG = "BroadcastDispatcher" * from SystemUI. That way the number of calls to [BroadcastReceiver.onReceive] can be reduced for * a given broadcast. * - * Use only for IntentFilters with actions and optionally categories. It does not support, - * permissions, schemes, data types, data authorities or priority different than 0. + * Use only for IntentFilters with actions and optionally categories. It does not support schemes, + * data types, data authorities or priority different than 0. + * * Cannot be used for getting sticky broadcasts (either as return of registering or as re-delivery). * Broadcast handling may be asynchronous *without* calling goAsync(), as it's running within sysui * and doesn't need to worry about being killed. diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index aa6c619d9969..2d558ad49e2d 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -346,6 +346,12 @@ object Flags { // TODO(b/256873975): Tracking Bug @JvmField @Keep val WM_BUBBLE_BAR = unreleasedFlag(1111, "wm_bubble_bar") + // TODO(b/260271148): Tracking bug + @Keep + @JvmField + val WM_DESKTOP_WINDOWING_2 = + sysPropBooleanFlag(1112, "persist.wm.debug.desktop_mode_2", default = false) + // 1200 - predictive back @Keep @JvmField diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt index 29febb6dd0d9..4ae37c51f278 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt @@ -29,7 +29,7 @@ import android.util.Log import com.android.systemui.SystemUIAppComponentFactoryBase import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCallback import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor -import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract +import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract import javax.inject.Inject import kotlinx.coroutines.runBlocking 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/data/quickaffordance/CameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt index 3c09aab60443..dbc376e62950 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt @@ -26,14 +26,17 @@ import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import dagger.Lazy +import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf -import javax.inject.Inject @SysUISingleton -class CameraQuickAffordanceConfig @Inject constructor( - @Application private val context: Context, - private val cameraGestureHelper: CameraGestureHelper, +class CameraQuickAffordanceConfig +@Inject +constructor( + @Application private val context: Context, + private val cameraGestureHelper: Lazy<CameraGestureHelper>, ) : KeyguardQuickAffordanceConfig { override val key: String @@ -46,17 +49,23 @@ class CameraQuickAffordanceConfig @Inject constructor( get() = com.android.internal.R.drawable.perm_group_camera override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> - get() = flowOf( - KeyguardQuickAffordanceConfig.LockScreenState.Visible( - icon = Icon.Resource( + get() = + flowOf( + KeyguardQuickAffordanceConfig.LockScreenState.Visible( + icon = + Icon.Resource( com.android.internal.R.drawable.perm_group_camera, ContentDescription.Resource(R.string.accessibility_camera_button) - ) + ) + ) ) - ) - override fun onTriggered(expandable: Expandable?): KeyguardQuickAffordanceConfig.OnTriggeredResult { - cameraGestureHelper.launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE) + override fun onTriggered( + expandable: Expandable? + ): KeyguardQuickAffordanceConfig.OnTriggeredResult { + cameraGestureHelper + .get() + .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE) return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt index 4477310dca41..98b1a731b82c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt @@ -21,7 +21,7 @@ import android.content.Intent import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Icon import com.android.systemui.keyguard.shared.quickaffordance.ActivationState -import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract +import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract import kotlinx.coroutines.flow.Flow /** Defines interface that can act as data source for a single quick affordance model. */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt index b29cf45cc709..4f37e5f389ee 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt @@ -18,9 +18,11 @@ package com.android.systemui.keyguard.data.quickaffordance import android.content.Context +import android.content.IntentFilter import android.content.SharedPreferences -import androidx.annotation.VisibleForTesting import com.android.systemui.R +import com.android.systemui.backup.BackupHelper +import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton @@ -28,14 +30,18 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.onStart /** * Manages and provides access to the current "selections" of keyguard quick affordances, answering * the question "which affordances should the keyguard show?". */ +@OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class KeyguardQuickAffordanceSelectionManager @Inject @@ -43,15 +49,10 @@ constructor( @Application context: Context, private val userFileManager: UserFileManager, private val userTracker: UserTracker, + broadcastDispatcher: BroadcastDispatcher, ) { - private val sharedPrefs: SharedPreferences - get() = - userFileManager.getSharedPreferences( - FILE_NAME, - Context.MODE_PRIVATE, - userTracker.userId, - ) + private var sharedPrefs: SharedPreferences = instantiateSharedPrefs() private val userId: Flow<Int> = conflatedCallbackFlow { val callback = @@ -78,21 +79,54 @@ constructor( } } + /** + * Emits an event each time a Backup & Restore restoration job is completed. Does not emit an + * initial value. + */ + private val backupRestorationEvents: Flow<Unit> = + broadcastDispatcher.broadcastFlow( + filter = IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED), + flags = Context.RECEIVER_NOT_EXPORTED, + permission = BackupHelper.PERMISSION_SELF, + ) + /** IDs of affordances to show, indexed by slot ID, and sorted in descending priority order. */ val selections: Flow<Map<String, List<String>>> = - userId.flatMapLatest { - conflatedCallbackFlow { - val listener = - SharedPreferences.OnSharedPreferenceChangeListener { _, _ -> - trySend(getSelections()) - } + combine( + userId, + backupRestorationEvents.onStart { + // We emit an initial event to make sure that the combine emits at least once, + // even + // if we never get a Backup & Restore restoration event (which is the most + // common + // case anyway as restoration really only happens on initial device setup). + emit(Unit) + } + ) { _, _ -> + } + .flatMapLatest { + conflatedCallbackFlow { + // We want to instantiate a new SharedPreferences instance each time either the + // user + // ID changes or we have a backup & restore restoration event. The reason is + // that + // our sharedPrefs instance needs to be replaced with a new one as it depends on + // the + // user ID and when the B&R job completes, the backing file is replaced but the + // existing instance still has a stale in-memory cache. + sharedPrefs = instantiateSharedPrefs() - sharedPrefs.registerOnSharedPreferenceChangeListener(listener) - send(getSelections()) + val listener = + SharedPreferences.OnSharedPreferenceChangeListener { _, _ -> + trySend(getSelections()) + } - awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) } + sharedPrefs.registerOnSharedPreferenceChangeListener(listener) + send(getSelections()) + + awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) } + } } - } /** * Returns a snapshot of the IDs of affordances to show, indexed by slot ID, and sorted in @@ -144,9 +178,17 @@ constructor( sharedPrefs.edit().putString(key, value).apply() } + private fun instantiateSharedPrefs(): SharedPreferences { + return userFileManager.getSharedPreferences( + FILE_NAME, + Context.MODE_PRIVATE, + userTracker.userId, + ) + } + companion object { private const val TAG = "KeyguardQuickAffordanceSelectionManager" - @VisibleForTesting const val FILE_NAME = "quick_affordance_selections" + const val FILE_NAME = "quick_affordance_selections" private const val KEY_PREFIX_SLOT = "slot_" private const val SLOT_AFFORDANCES_DELIMITER = ":" private const val AFFORDANCE_DELIMITER = "," diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt new file mode 100644 index 000000000000..0e865cee0b76 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt @@ -0,0 +1,44 @@ +/* + * 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.keyguard.domain.backup + +import android.app.backup.SharedPreferencesBackupHelper +import android.content.Context +import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager +import com.android.systemui.settings.UserFileManagerImpl + +/** Handles backup & restore for keyguard quick affordances. */ +class KeyguardQuickAffordanceBackupHelper( + context: Context, + userId: Int, +) : + SharedPreferencesBackupHelper( + context, + if (UserFileManagerImpl.isPrimaryUser(userId)) { + KeyguardQuickAffordanceSelectionManager.FILE_NAME + } else { + UserFileManagerImpl.secondaryUserFile( + context = context, + fileName = KeyguardQuickAffordanceSelectionManager.FILE_NAME, + directoryName = UserFileManagerImpl.SHARED_PREFS, + userId = userId, + ) + .also { UserFileManagerImpl.ensureParentDirExists(it) } + .toString() + } + ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt index 2d94d760cb54..ee7154ff7219 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt @@ -34,8 +34,8 @@ import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentati import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition import com.android.systemui.plugins.ActivityStarter import com.android.systemui.settings.UserTracker -import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots +import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract import com.android.systemui.statusbar.policy.KeyguardStateController import dagger.Lazy import javax.inject.Inject 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/settings/UserFileManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt index d450afa59c7d..bfba6dfddfac 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt @@ -35,12 +35,14 @@ import java.io.File import javax.inject.Inject /** - * Implementation for retrieving file paths for file storage of system and secondary users. - * Files lie in {File Directory}/UserFileManager/{User Id} for secondary user. - * For system user, we use the conventional {File Directory} + * Implementation for retrieving file paths for file storage of system and secondary users. Files + * lie in {File Directory}/UserFileManager/{User Id} for secondary user. For system user, we use the + * conventional {File Directory} */ @SysUISingleton -class UserFileManagerImpl @Inject constructor( +class UserFileManagerImpl +@Inject +constructor( // Context of system process and system user. private val context: Context, val userManager: UserManager, @@ -49,80 +51,114 @@ class UserFileManagerImpl @Inject constructor( ) : UserFileManager, CoreStartable { companion object { private const val FILES = "files" - @VisibleForTesting internal const val SHARED_PREFS = "shared_prefs" + const val SHARED_PREFS = "shared_prefs" @VisibleForTesting internal const val ID = "UserFileManager" - } - private val broadcastReceiver = object : BroadcastReceiver() { + /** Returns `true` if the given user ID is that for the primary/system user. */ + fun isPrimaryUser(userId: Int): Boolean { + return UserHandle(userId).isSystem + } + /** - * Listen to Intent.ACTION_USER_REMOVED to clear user data. + * Returns a [File] pointing to the correct path for a secondary user ID. + * + * Note that there is no check for the type of user. This should only be called for + * secondary users, never for the system user. For that, make sure to call [isPrimaryUser]. + * + * Note also that there is no guarantee that the parent directory structure for the file + * exists on disk. For that, call [ensureParentDirExists]. + * + * @param context The context + * @param fileName The name of the file + * @param directoryName The name of the directory that would contain the file + * @param userId The ID of the user to build a file path for */ - override fun onReceive(context: Context, intent: Intent) { - if (intent.action == Intent.ACTION_USER_REMOVED) { - clearDeletedUserData() + fun secondaryUserFile( + context: Context, + fileName: String, + directoryName: String, + userId: Int, + ): File { + return Environment.buildPath( + context.filesDir, + ID, + userId.toString(), + directoryName, + fileName, + ) + } + + /** + * Checks to see if parent dir of the file exists. If it does not, we create the parent dirs + * recursively. + */ + fun ensureParentDirExists(file: File) { + val parent = file.parentFile + if (!parent.exists()) { + if (!parent.mkdirs()) { + Log.e(ID, "Could not create parent directory for file: ${file.absolutePath}") + } } } } - /** - * Poll for user-specific directories to delete upon start up. - */ + private val broadcastReceiver = + object : BroadcastReceiver() { + /** Listen to Intent.ACTION_USER_REMOVED to clear user data. */ + override fun onReceive(context: Context, intent: Intent) { + if (intent.action == Intent.ACTION_USER_REMOVED) { + clearDeletedUserData() + } + } + } + + /** Poll for user-specific directories to delete upon start up. */ override fun start() { clearDeletedUserData() - val filter = IntentFilter().apply { - addAction(Intent.ACTION_USER_REMOVED) - } + val filter = IntentFilter().apply { addAction(Intent.ACTION_USER_REMOVED) } broadcastDispatcher.registerReceiver(broadcastReceiver, filter, backgroundExecutor) } - /** - * Return the file based on current user. - */ + /** Return the file based on current user. */ override fun getFile(fileName: String, userId: Int): File { - return if (UserHandle(userId).isSystem) { - Environment.buildPath( - context.filesDir, - fileName - ) + return if (isPrimaryUser(userId)) { + Environment.buildPath(context.filesDir, fileName) } else { - val secondaryFile = Environment.buildPath( - context.filesDir, - ID, - userId.toString(), - FILES, - fileName - ) + val secondaryFile = + secondaryUserFile( + context = context, + userId = userId, + directoryName = FILES, + fileName = fileName, + ) ensureParentDirExists(secondaryFile) secondaryFile } } - /** - * Get shared preferences from user. - */ + /** Get shared preferences from user. */ override fun getSharedPreferences( fileName: String, @Context.PreferencesMode mode: Int, userId: Int ): SharedPreferences { - if (UserHandle(userId).isSystem) { + if (isPrimaryUser(userId)) { return context.getSharedPreferences(fileName, mode) } - val secondaryUserDir = Environment.buildPath( - context.filesDir, - ID, - userId.toString(), - SHARED_PREFS, - fileName - ) + + val secondaryUserDir = + secondaryUserFile( + context = context, + fileName = fileName, + directoryName = SHARED_PREFS, + userId = userId, + ) ensureParentDirExists(secondaryUserDir) return context.getSharedPreferences(secondaryUserDir, mode) } - /** - * Remove dirs for deleted users. - */ + /** Remove dirs for deleted users. */ @VisibleForTesting internal fun clearDeletedUserData() { backgroundExecutor.execute { @@ -133,10 +169,11 @@ class UserFileManagerImpl @Inject constructor( dirsToDelete.forEach { dir -> try { - val dirToDelete = Environment.buildPath( - file, - dir, - ) + val dirToDelete = + Environment.buildPath( + file, + dir, + ) dirToDelete.deleteRecursively() } catch (e: Exception) { Log.e(ID, "Deletion failed.", e) @@ -144,18 +181,4 @@ class UserFileManagerImpl @Inject constructor( } } } - - /** - * Checks to see if parent dir of the file exists. If it does not, we create the parent dirs - * recursively. - */ - @VisibleForTesting - internal fun ensureParentDirExists(file: File) { - val parent = file.parentFile - if (!parent.exists()) { - if (!parent.mkdirs()) { - Log.e(ID, "Could not create parent directory for file: ${file.absolutePath}") - } - } - } } 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/keyguard/KeyguardQuickAffordanceProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt index cedde58746d2..32c5b3f99d41 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt @@ -36,8 +36,8 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceIn import com.android.systemui.plugins.ActivityStarter import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker -import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots +import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.mockito.mock @@ -89,6 +89,7 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, + broadcastDispatcher = fakeBroadcastDispatcher, ) val quickAffordanceRepository = KeyguardQuickAffordanceRepository( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt index 623becf166d3..7205f3068abb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt @@ -37,25 +37,29 @@ class CameraQuickAffordanceConfigTest : SysuiTestCase() { @Mock private lateinit var cameraGestureHelper: CameraGestureHelper @Mock private lateinit var context: Context + private lateinit var underTest: CameraQuickAffordanceConfig @Before fun setUp() { MockitoAnnotations.initMocks(this) - underTest = CameraQuickAffordanceConfig( + + underTest = + CameraQuickAffordanceConfig( context, - cameraGestureHelper, - ) + ) { + cameraGestureHelper + } } @Test fun `affordance triggered -- camera launch called`() { - //when + // When val result = underTest.onTriggered(null) - //then + // Then verify(cameraGestureHelper) - .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE) + .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE) assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt index 8ef921eaa50a..552b8cb96525 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt @@ -89,6 +89,7 @@ class KeyguardQuickAffordanceLegacySettingSyncerTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = FakeUserTracker(), + broadcastDispatcher = fakeBroadcastDispatcher, ) settings = FakeSettings() settings.putInt(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 0) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt index d8ee9f113d33..6a2376b5bc4e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.data.quickaffordance +import android.content.Intent import android.content.SharedPreferences import android.content.pm.UserInfo import androidx.test.filters.SmallTest @@ -27,10 +28,15 @@ import com.android.systemui.settings.UserFileManager import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.toList import kotlinx.coroutines.launch import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain +import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -38,8 +44,12 @@ import org.junit.runners.JUnit4 import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyString import org.mockito.Mock +import org.mockito.Mockito.atLeastOnce +import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(JUnit4::class) class KeyguardQuickAffordanceSelectionManagerTest : SysuiTestCase() { @@ -60,15 +70,23 @@ class KeyguardQuickAffordanceSelectionManagerTest : SysuiTestCase() { sharedPrefs.getOrPut(userId) { FakeSharedPreferences() } } userTracker = FakeUserTracker() + val dispatcher = UnconfinedTestDispatcher() + Dispatchers.setMain(dispatcher) underTest = KeyguardQuickAffordanceSelectionManager( context = context, userFileManager = userFileManager, userTracker = userTracker, + broadcastDispatcher = fakeBroadcastDispatcher, ) } + @After + fun tearDown() { + Dispatchers.resetMain() + } + @Test fun setSelections() = runTest { overrideResource(R.array.config_keyguardQuickAffordanceDefaults, arrayOf<String>()) @@ -318,6 +336,22 @@ class KeyguardQuickAffordanceSelectionManagerTest : SysuiTestCase() { job.cancel() } + @Test + fun `responds to backup and restore by reloading the selections from disk`() = runTest { + overrideResource(R.array.config_keyguardQuickAffordanceDefaults, arrayOf<String>()) + val affordanceIdsBySlotId = mutableListOf<Map<String, List<String>>>() + val job = + launch(UnconfinedTestDispatcher()) { + underTest.selections.toList(affordanceIdsBySlotId) + } + clearInvocations(userFileManager) + + fakeBroadcastDispatcher.registeredReceivers.firstOrNull()?.onReceive(context, Intent()) + + verify(userFileManager, atLeastOnce()).getSharedPreferences(anyString(), anyInt(), anyInt()) + job.cancel() + } + private fun assertSelections( observed: Map<String, List<String>>?, expected: Map<String, List<String>>, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt index 5c75417c3473..652fae968744 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt @@ -76,6 +76,7 @@ class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = FakeUserTracker(), + broadcastDispatcher = fakeBroadcastDispatcher, ) underTest = diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt index c2650ec455d8..ba7c40b6b381 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt @@ -252,6 +252,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, + broadcastDispatcher = fakeBroadcastDispatcher, ) val quickAffordanceRepository = KeyguardQuickAffordanceRepository( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt index b79030602368..8d0c4ef4b3da 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt @@ -113,6 +113,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, + broadcastDispatcher = fakeBroadcastDispatcher, ) val quickAffordanceRepository = KeyguardQuickAffordanceRepository( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt index 8b166bd89426..32849cdce02e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt @@ -136,6 +136,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, + broadcastDispatcher = fakeBroadcastDispatcher, ) val quickAffordanceRepository = KeyguardQuickAffordanceRepository( diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt index 6d9b01e28aa4..020a86611552 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt @@ -50,24 +50,20 @@ class UserFileManagerImplTest : SysuiTestCase() { lateinit var userFileManager: UserFileManagerImpl lateinit var backgroundExecutor: FakeExecutor - @Mock - lateinit var userManager: UserManager - @Mock - lateinit var broadcastDispatcher: BroadcastDispatcher + @Mock lateinit var userManager: UserManager + @Mock lateinit var broadcastDispatcher: BroadcastDispatcher @Before fun setUp() { MockitoAnnotations.initMocks(this) backgroundExecutor = FakeExecutor(FakeSystemClock()) - userFileManager = UserFileManagerImpl(context, userManager, - broadcastDispatcher, backgroundExecutor) + userFileManager = + UserFileManagerImpl(context, userManager, broadcastDispatcher, backgroundExecutor) } @After fun end() { - val dir = Environment.buildPath( - context.filesDir, - UserFileManagerImpl.ID) + val dir = Environment.buildPath(context.filesDir, UserFileManagerImpl.ID) dir.deleteRecursively() } @@ -82,13 +78,14 @@ class UserFileManagerImplTest : SysuiTestCase() { @Test fun testGetSharedPreferences() { val secondarySharedPref = userFileManager.getSharedPreferences(TEST_FILE_NAME, 0, 11) - val secondaryUserDir = Environment.buildPath( - context.filesDir, - UserFileManagerImpl.ID, - "11", - UserFileManagerImpl.SHARED_PREFS, - TEST_FILE_NAME - ) + val secondaryUserDir = + Environment.buildPath( + context.filesDir, + UserFileManagerImpl.ID, + "11", + UserFileManagerImpl.SHARED_PREFS, + TEST_FILE_NAME + ) assertThat(secondarySharedPref).isNotNull() assertThat(secondaryUserDir.exists()) @@ -101,32 +98,35 @@ class UserFileManagerImplTest : SysuiTestCase() { val userFileManager = spy(userFileManager) userFileManager.start() verify(userFileManager).clearDeletedUserData() - verify(broadcastDispatcher).registerReceiver(any(BroadcastReceiver::class.java), - any(IntentFilter::class.java), - any(Executor::class.java), isNull(), eq(Context.RECEIVER_EXPORTED), isNull()) + verify(broadcastDispatcher) + .registerReceiver( + any(BroadcastReceiver::class.java), + any(IntentFilter::class.java), + any(Executor::class.java), + isNull(), + eq(Context.RECEIVER_EXPORTED), + isNull() + ) } @Test fun testClearDeletedUserData() { - val dir = Environment.buildPath( - context.filesDir, - UserFileManagerImpl.ID, - "11", - "files" - ) + val dir = Environment.buildPath(context.filesDir, UserFileManagerImpl.ID, "11", "files") dir.mkdirs() - val file = Environment.buildPath( - context.filesDir, - UserFileManagerImpl.ID, - "11", - "files", - TEST_FILE_NAME - ) - val secondaryUserDir = Environment.buildPath( - context.filesDir, - UserFileManagerImpl.ID, - "11", - ) + val file = + Environment.buildPath( + context.filesDir, + UserFileManagerImpl.ID, + "11", + "files", + TEST_FILE_NAME + ) + val secondaryUserDir = + Environment.buildPath( + context.filesDir, + UserFileManagerImpl.ID, + "11", + ) file.createNewFile() assertThat(secondaryUserDir.exists()).isTrue() assertThat(file.exists()).isTrue() @@ -139,15 +139,16 @@ class UserFileManagerImplTest : SysuiTestCase() { @Test fun testEnsureParentDirExists() { - val file = Environment.buildPath( - context.filesDir, - UserFileManagerImpl.ID, - "11", - "files", - TEST_FILE_NAME - ) + val file = + Environment.buildPath( + context.filesDir, + UserFileManagerImpl.ID, + "11", + "files", + TEST_FILE_NAME + ) assertThat(file.parentFile.exists()).isFalse() - userFileManager.ensureParentDirExists(file) + UserFileManagerImpl.ensureParentDirExists(file) assertThat(file.parentFile.exists()).isTrue() } } 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/api/current.txt b/services/api/current.txt index 42ae10ef8d5e..834ed2f67865 100644 --- a/services/api/current.txt +++ b/services/api/current.txt @@ -76,6 +76,7 @@ package com.android.server.pm.pkg { method @Nullable public String getSdkLibraryName(); method @NonNull public java.util.List<com.android.server.pm.pkg.AndroidPackageSplit> getSplits(); method @Nullable public String getStaticSharedLibraryName(); + method @NonNull public java.util.UUID getStorageUuid(); method public int getTargetSdkVersion(); method public boolean isDebuggable(); method public boolean isIsolatedSplitLoading(); @@ -99,6 +100,7 @@ package com.android.server.pm.pkg { method public int getAppId(); method @NonNull public String getPackageName(); method @Nullable public String getPrimaryCpuAbi(); + method @Nullable public String getSeInfo(); method @Nullable public String getSecondaryCpuAbi(); method @NonNull public com.android.server.pm.pkg.PackageUserState getStateForUser(@NonNull android.os.UserHandle); method @NonNull public java.util.List<com.android.server.pm.pkg.SharedLibrary> getUsesLibraries(); 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..59024e797d7a 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 @@ -485,24 +489,10 @@ public class BinaryTransparencyService extends SystemService { Integer algorithmId = entry.getKey(); byte[] contentDigest = entry.getValue(); - // TODO(b/259348134): consider refactoring the following to a helper method - switch (algorithmId) { - case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256: - pw.print("CHUNKED_SHA256:"); - break; - case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512: - pw.print("CHUNKED_SHA512:"); - break; - case ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256: - pw.print("VERITY_CHUNKED_SHA256:"); - break; - case ApkSigningBlockUtils.CONTENT_DIGEST_SHA256: - pw.print("SHA256:"); - break; - default: - pw.print("UNKNOWN_ALGO_ID(" + algorithmId + "):"); - } + pw.print(translateContentDigestAlgorithmIdToString(algorithmId)); + pw.print(":"); pw.print(HexEncoding.encodeToString(contentDigest, false)); + pw.print("\n"); } } @@ -529,31 +519,13 @@ public class BinaryTransparencyService extends SystemService { pw.println("ERROR: Failed to compute package content digest for " + origPackageFilepath); } else { - // TODO(b/259348134): consider refactoring this to a helper method for (Map.Entry<Integer, byte[]> entry : contentDigests.entrySet()) { Integer algorithmId = entry.getKey(); byte[] contentDigest = entry.getValue(); - pw.print("|--> Pre-installed package content digest algorithm: "); - switch (algorithmId) { - case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256: - pw.print("CHUNKED_SHA256"); - break; - case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512: - pw.print("CHUNKED_SHA512"); - break; - case ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256: - pw.print("VERITY_CHUNKED_SHA256"); - break; - case ApkSigningBlockUtils.CONTENT_DIGEST_SHA256: - pw.print("SHA256"); - break; - default: - pw.print("UNKNOWN"); - } - pw.print("\n"); - pw.print("|--> Pre-installed package content digest: "); - pw.print(HexEncoding.encodeToString(contentDigest, false)); - pw.print("\n"); + pw.println("|--> Pre-installed package content digest: " + + HexEncoding.encodeToString(contentDigest, false)); + pw.println("|--> Pre-installed package content digest algorithm: " + + translateContentDigestAlgorithmIdToString(algorithmId)); } } } @@ -735,7 +707,6 @@ public class BinaryTransparencyService extends SystemService { pw.print(packageName + "," + packageInfo.getLongVersionCode() + ","); printPackageMeasurements(packageInfo, pw); - pw.print("\n"); if (verbose) { ModuleInfo moduleInfo; @@ -798,7 +769,6 @@ public class BinaryTransparencyService extends SystemService { pw.print(packageInfo.packageName + ","); pw.print(packageInfo.getLongVersionCode() + ","); printPackageMeasurements(packageInfo, pw); - pw.print("\n"); if (verbose) { printModuleDetails(module, pw); @@ -854,7 +824,6 @@ public class BinaryTransparencyService extends SystemService { pw.print(packageInfo.packageName + ","); pw.print(packageInfo.getLongVersionCode() + ","); printPackageMeasurements(packageInfo, pw); - pw.print("\n"); if (verbose) { printAppDetails(packageInfo, printLibraries, pw); @@ -1075,6 +1044,21 @@ public class BinaryTransparencyService extends SystemService { FrameworkStatsLog.write(FrameworkStatsLog.VBMETA_DIGEST_REPORTED, mVbmetaDigest); } + private String translateContentDigestAlgorithmIdToString(int algorithmId) { + switch (algorithmId) { + case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256: + return "CHUNKED_SHA256"; + case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512: + return "CHUNKED_SHA512"; + case ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256: + return "VERITY_CHUNKED_SHA256"; + case ApkSigningBlockUtils.CONTENT_DIGEST_SHA256: + return "SHA256"; + default: + return "UNKNOWN_ALGO_ID(" + algorithmId + ")"; + } + } + @NonNull private List<PackageInfo> getCurrentInstalledApexs() { List<PackageInfo> results = new ArrayList<>(); @@ -1110,18 +1094,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..3487613d313c 100644 --- a/services/core/java/com/android/server/DockObserver.java +++ b/services/core/java/com/android/server/DockObserver.java @@ -19,6 +19,7 @@ package com.android.server; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.database.ContentObserver; import android.media.AudioManager; import android.media.Ringtone; import android.media.RingtoneManager; @@ -73,6 +74,7 @@ final class DockObserver extends SystemService { private final boolean mAllowTheaterModeWakeFromDock; private final List<ExtconStateConfig> mExtconStateConfigs; + private DeviceProvisionedObserver mDeviceProvisionedObserver; static final class ExtconStateProvider { private final Map<String, String> mState; @@ -110,7 +112,7 @@ final class DockObserver extends SystemService { Slog.w(TAG, "No state file found at: " + stateFilePath); return new ExtconStateProvider(new HashMap<>()); } catch (Exception e) { - Slog.e(TAG, "" , e); + Slog.e(TAG, "", e); return new ExtconStateProvider(new HashMap<>()); } } @@ -136,7 +138,7 @@ final class DockObserver extends SystemService { private static List<ExtconStateConfig> loadExtconStateConfigs(Context context) { String[] rows = context.getResources().getStringArray( - com.android.internal.R.array.config_dockExtconStateMapping); + com.android.internal.R.array.config_dockExtconStateMapping); try { ArrayList<ExtconStateConfig> configs = new ArrayList<>(); for (String row : rows) { @@ -167,6 +169,7 @@ final class DockObserver extends SystemService { com.android.internal.R.bool.config_allowTheaterModeWakeFromDock); mKeepDreamingWhenUndocking = context.getResources().getBoolean( com.android.internal.R.bool.config_keepDreamingWhenUndocking); + mDeviceProvisionedObserver = new DeviceProvisionedObserver(mHandler); mExtconStateConfigs = loadExtconStateConfigs(context); @@ -199,15 +202,19 @@ final class DockObserver extends SystemService { if (phase == PHASE_ACTIVITY_MANAGER_READY) { synchronized (mLock) { mSystemReady = true; - - // don't bother broadcasting undocked here - if (mReportedDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) { - updateLocked(); - } + mDeviceProvisionedObserver.onSystemReady(); + updateIfDockedLocked(); } } } + private void updateIfDockedLocked() { + // don't bother broadcasting undocked here + if (mReportedDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) { + updateLocked(); + } + } + private void setActualDockStateLocked(int newState) { mActualDockState = newState; if (!mUpdatesStopped) { @@ -252,8 +259,7 @@ final class DockObserver extends SystemService { // Skip the dock intent if not yet provisioned. final ContentResolver cr = getContext().getContentResolver(); - if (Settings.Global.getInt(cr, - Settings.Global.DEVICE_PROVISIONED, 0) == 0) { + if (!mDeviceProvisionedObserver.isDeviceProvisioned()) { Slog.i(TAG, "Device not provisioned, skipping dock broadcast"); return; } @@ -302,6 +308,7 @@ final class DockObserver extends SystemService { getContext(), soundUri); if (sfx != null) { sfx.setStreamType(AudioManager.STREAM_SYSTEM); + sfx.preferBuiltinDevice(true); sfx.play(); } } @@ -418,4 +425,48 @@ final class DockObserver extends SystemService { } } } + + private final class DeviceProvisionedObserver extends ContentObserver { + private boolean mRegistered; + + public DeviceProvisionedObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + synchronized (mLock) { + updateRegistration(); + if (isDeviceProvisioned()) { + // Send the dock broadcast if device is docked after provisioning. + updateIfDockedLocked(); + } + } + } + + void onSystemReady() { + updateRegistration(); + } + + private void updateRegistration() { + boolean register = !isDeviceProvisioned(); + if (register == mRegistered) { + return; + } + final ContentResolver resolver = getContext().getContentResolver(); + if (register) { + resolver.registerContentObserver( + Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), + false, this); + } else { + resolver.unregisterContentObserver(this); + } + mRegistered = register; + } + + boolean isDeviceProvisioned() { + return Settings.Global.getInt(getContext().getContentResolver(), + Settings.Global.DEVICE_PROVISIONED, 0) != 0; + } + } } diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java index b56d1fca7f6a..b4ab254b06e7 100644 --- a/services/core/java/com/android/server/RescueParty.java +++ b/services/core/java/com/android/server/RescueParty.java @@ -447,6 +447,13 @@ public class RescueParty { thread.start(); break; case LEVEL_FACTORY_RESET: + // Before the completion of Reboot, if any crash happens then PackageWatchdog + // escalates to next level i.e. factory reset, as they happen in separate threads. + // Adding a check to prevent factory reset to execute before above reboot completes. + // Note: this reboot property is not persistent resets after reboot is completed. + if (isRebootPropertySet()) { + break; + } SystemProperties.set(PROP_ATTEMPTING_FACTORY_RESET, "true"); runnable = new Runnable() { @Override diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index ca86021cd629..bd90d85e3fd0 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -52,8 +52,8 @@ import android.telephony.Annotation; import android.telephony.Annotation.RadioPowerState; import android.telephony.Annotation.SrvccState; import android.telephony.BarringInfo; -import android.telephony.CallAttributes; import android.telephony.CallQuality; +import android.telephony.CallState; import android.telephony.CellIdentity; import android.telephony.CellInfo; import android.telephony.CellSignalStrength; @@ -82,6 +82,7 @@ import android.telephony.TelephonyDisplayInfo; import android.telephony.TelephonyManager; import android.telephony.data.ApnSetting; import android.telephony.emergency.EmergencyNumber; +import android.telephony.ims.ImsCallSession; import android.telephony.ims.ImsReasonInfo; import android.text.TextUtils; import android.util.ArrayMap; @@ -349,9 +350,9 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { private CallQuality[] mCallQuality; - private CallAttributes[] mCallAttributes; + private ArrayList<List<CallState>> mCallStateLists; - // network type of the call associated with the mCallAttributes and mCallQuality + // network type of the call associated with the mCallStateLists and mCallQuality private int[] mCallNetworkType; private int[] mSrvccState; @@ -687,7 +688,6 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mCallPreciseDisconnectCause = copyOf(mCallPreciseDisconnectCause, mNumPhones); mCallQuality = copyOf(mCallQuality, mNumPhones); mCallNetworkType = copyOf(mCallNetworkType, mNumPhones); - mCallAttributes = copyOf(mCallAttributes, mNumPhones); mOutgoingCallEmergencyNumber = copyOf(mOutgoingCallEmergencyNumber, mNumPhones); mOutgoingSmsEmergencyNumber = copyOf(mOutgoingSmsEmergencyNumber, mNumPhones); mTelephonyDisplayInfos = copyOf(mTelephonyDisplayInfos, mNumPhones); @@ -707,6 +707,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { cutListToSize(mLinkCapacityEstimateLists, mNumPhones); cutListToSize(mCarrierPrivilegeStates, mNumPhones); cutListToSize(mCarrierServiceStates, mNumPhones); + cutListToSize(mCallStateLists, mNumPhones); return; } @@ -730,8 +731,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mCallDisconnectCause[i] = DisconnectCause.NOT_VALID; mCallPreciseDisconnectCause[i] = PreciseDisconnectCause.NOT_VALID; mCallQuality[i] = createCallQuality(); - mCallAttributes[i] = new CallAttributes(createPreciseCallState(), - TelephonyManager.NETWORK_TYPE_UNKNOWN, createCallQuality()); + mCallStateLists.add(i, new ArrayList<>()); mCallNetworkType[i] = TelephonyManager.NETWORK_TYPE_UNKNOWN; mPreciseCallState[i] = createPreciseCallState(); mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE; @@ -799,7 +799,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mCallPreciseDisconnectCause = new int[numPhones]; mCallQuality = new CallQuality[numPhones]; mCallNetworkType = new int[numPhones]; - mCallAttributes = new CallAttributes[numPhones]; + mCallStateLists = new ArrayList<>(); mPreciseDataConnectionStates = new ArrayList<>(); mCellInfo = new ArrayList<>(numPhones); mImsReasonInfo = new ArrayList<>(); @@ -837,8 +837,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mCallDisconnectCause[i] = DisconnectCause.NOT_VALID; mCallPreciseDisconnectCause[i] = PreciseDisconnectCause.NOT_VALID; mCallQuality[i] = createCallQuality(); - mCallAttributes[i] = new CallAttributes(createPreciseCallState(), - TelephonyManager.NETWORK_TYPE_UNKNOWN, createCallQuality()); + mCallStateLists.add(i, new ArrayList<>()); mCallNetworkType[i] = TelephonyManager.NETWORK_TYPE_UNKNOWN; mPreciseCallState[i] = createPreciseCallState(); mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE; @@ -1336,7 +1335,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } if (events.contains(TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED)) { try { - r.callback.onCallAttributesChanged(mCallAttributes[r.phoneId]); + r.callback.onCallStatesChanged(mCallStateLists.get(r.phoneId)); } catch (RemoteException ex) { remove(r.binder); } @@ -2171,11 +2170,30 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } - public void notifyPreciseCallState(int phoneId, int subId, int ringingCallState, - int foregroundCallState, int backgroundCallState) { + /** + * Send a notification to registrants that the precise call state has changed. + * + * @param phoneId the phoneId carrying the data connection + * @param subId the subscriptionId for the data connection + * @param callStates Array of PreciseCallState of foreground, background & ringing calls. + * @param imsCallIds Array of IMS call session ID{@link ImsCallSession#getCallId()} for + * ringing, foreground & background calls. + * @param imsServiceTypes Array of IMS call service type for ringing, foreground & + * background calls. + * @param imsCallTypes Array of IMS call type for ringing, foreground & background calls. + */ + public void notifyPreciseCallState(int phoneId, int subId, + @Annotation.PreciseCallStates int[] callStates, String[] imsCallIds, + @Annotation.ImsCallServiceType int[] imsServiceTypes, + @Annotation.ImsCallType int[] imsCallTypes) { if (!checkNotifyPermission("notifyPreciseCallState()")) { return; } + + int ringingCallState = callStates[CallState.CALL_CLASSIFICATION_RINGING]; + int foregroundCallState = callStates[CallState.CALL_CLASSIFICATION_FOREGROUND]; + int backgroundCallState = callStates[CallState.CALL_CLASSIFICATION_BACKGROUND]; + synchronized (mRecords) { if (validatePhoneId(phoneId)) { mRingingCallState[phoneId] = ringingCallState; @@ -2186,11 +2204,11 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { backgroundCallState, DisconnectCause.NOT_VALID, PreciseDisconnectCause.NOT_VALID); - boolean notifyCallAttributes = true; + boolean notifyCallState = true; if (mCallQuality == null) { log("notifyPreciseCallState: mCallQuality is null, " + "skipping call attributes"); - notifyCallAttributes = false; + notifyCallState = false; } else { // If the precise call state is no longer active, reset the call network type // and call quality. @@ -2199,8 +2217,65 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mCallNetworkType[phoneId] = TelephonyManager.NETWORK_TYPE_UNKNOWN; mCallQuality[phoneId] = createCallQuality(); } - mCallAttributes[phoneId] = new CallAttributes(mPreciseCallState[phoneId], - mCallNetworkType[phoneId], mCallQuality[phoneId]); + mCallStateLists.get(phoneId).clear(); + if (foregroundCallState != PreciseCallState.PRECISE_CALL_STATE_NOT_VALID + && foregroundCallState != PreciseCallState.PRECISE_CALL_STATE_IDLE) { + CallQuality callQuality = mCallQuality[phoneId]; + CallState.Builder builder = new CallState.Builder( + callStates[CallState.CALL_CLASSIFICATION_FOREGROUND]) + .setNetworkType(mCallNetworkType[phoneId]) + .setCallQuality(callQuality) + .setCallClassification( + CallState.CALL_CLASSIFICATION_FOREGROUND); + if (imsCallIds != null && imsServiceTypes != null && imsCallTypes != null) { + builder = builder + .setImsCallSessionId(imsCallIds[ + CallState.CALL_CLASSIFICATION_FOREGROUND]) + .setImsCallServiceType(imsServiceTypes[ + CallState.CALL_CLASSIFICATION_FOREGROUND]) + .setImsCallType(imsCallTypes[ + CallState.CALL_CLASSIFICATION_FOREGROUND]); + } + mCallStateLists.get(phoneId).add(builder.build()); + } + if (backgroundCallState != PreciseCallState.PRECISE_CALL_STATE_NOT_VALID + && backgroundCallState != PreciseCallState.PRECISE_CALL_STATE_IDLE) { + CallState.Builder builder = new CallState.Builder( + callStates[CallState.CALL_CLASSIFICATION_BACKGROUND]) + .setNetworkType(mCallNetworkType[phoneId]) + .setCallQuality(createCallQuality()) + .setCallClassification( + CallState.CALL_CLASSIFICATION_BACKGROUND); + if (imsCallIds != null && imsServiceTypes != null && imsCallTypes != null) { + builder = builder + .setImsCallSessionId(imsCallIds[ + CallState.CALL_CLASSIFICATION_BACKGROUND]) + .setImsCallServiceType(imsServiceTypes[ + CallState.CALL_CLASSIFICATION_BACKGROUND]) + .setImsCallType(imsCallTypes[ + CallState.CALL_CLASSIFICATION_BACKGROUND]); + } + mCallStateLists.get(phoneId).add(builder.build()); + } + if (ringingCallState != PreciseCallState.PRECISE_CALL_STATE_NOT_VALID + && ringingCallState != PreciseCallState.PRECISE_CALL_STATE_IDLE) { + CallState.Builder builder = new CallState.Builder( + callStates[CallState.CALL_CLASSIFICATION_RINGING]) + .setNetworkType(mCallNetworkType[phoneId]) + .setCallQuality(createCallQuality()) + .setCallClassification( + CallState.CALL_CLASSIFICATION_RINGING); + if (imsCallIds != null && imsServiceTypes != null && imsCallTypes != null) { + builder = builder + .setImsCallSessionId(imsCallIds[ + CallState.CALL_CLASSIFICATION_RINGING]) + .setImsCallServiceType(imsServiceTypes[ + CallState.CALL_CLASSIFICATION_RINGING]) + .setImsCallType(imsCallTypes[ + CallState.CALL_CLASSIFICATION_RINGING]); + } + mCallStateLists.get(phoneId).add(builder.build()); + } } for (Record r : mRecords) { @@ -2213,11 +2288,11 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mRemoveList.add(r.binder); } } - if (notifyCallAttributes && r.matchTelephonyCallbackEvent( + if (notifyCallState && r.matchTelephonyCallbackEvent( TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED) && idMatch(r, subId, phoneId)) { try { - r.callback.onCallAttributesChanged(mCallAttributes[phoneId]); + r.callback.onCallStatesChanged(mCallStateLists.get(phoneId)); } catch (RemoteException ex) { mRemoveList.add(r.binder); } @@ -2515,15 +2590,29 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { // merge CallQuality with PreciseCallState and network type mCallQuality[phoneId] = callQuality; mCallNetworkType[phoneId] = callNetworkType; - mCallAttributes[phoneId] = new CallAttributes(mPreciseCallState[phoneId], - callNetworkType, callQuality); + if (mCallStateLists.get(phoneId).size() > 0 + && mCallStateLists.get(phoneId).get(0).getCallState() + == PreciseCallState.PRECISE_CALL_STATE_ACTIVE) { + CallState prev = mCallStateLists.get(phoneId).remove(0); + mCallStateLists.get(phoneId).add( + 0, new CallState.Builder(prev.getCallState()) + .setNetworkType(callNetworkType) + .setCallQuality(callQuality) + .setCallClassification(prev.getCallClassification()) + .setImsCallSessionId(prev.getImsCallSessionId()) + .setImsCallServiceType(prev.getImsCallServiceType()) + .setImsCallType(prev.getImsCallType()).build()); + } else { + log("There is no active call to report CallQaulity"); + return; + } for (Record r : mRecords) { if (r.matchTelephonyCallbackEvent( TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED) && idMatch(r, subId, phoneId)) { try { - r.callback.onCallAttributesChanged(mCallAttributes[phoneId]); + r.callback.onCallStatesChanged(mCallStateLists.get(phoneId)); } catch (RemoteException ex) { mRemoveList.add(r.binder); } @@ -2991,7 +3080,6 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { pw.println("mSrvccState=" + mSrvccState[i]); pw.println("mCallPreciseDisconnectCause=" + mCallPreciseDisconnectCause[i]); pw.println("mCallQuality=" + mCallQuality[i]); - pw.println("mCallAttributes=" + mCallAttributes[i]); pw.println("mCallNetworkType=" + mCallNetworkType[i]); pw.println("mPreciseDataConnectionStates=" + mPreciseDataConnectionStates.get(i)); pw.println("mOutgoingCallEmergencyNumber=" + mOutgoingCallEmergencyNumber[i]); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 35b46c1104f7..50be45804e4f 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -87,6 +87,7 @@ import static android.os.Process.getTotalMemory; import static android.os.Process.isSdkSandboxUid; import static android.os.Process.isThreadInProcess; import static android.os.Process.killProcess; +import static android.os.Process.killProcessGroup; import static android.os.Process.killProcessQuiet; import static android.os.Process.myPid; import static android.os.Process.myUid; @@ -952,13 +953,6 @@ public class ActivityManagerService extends IActivityManager.Stub } return false; } - - boolean doRemoveIfNoThreadInternal(int pid, ProcessRecord app) { - if (app == null || app.getThread() != null) { - return false; - } - return doRemoveInternal(pid, app); - } } private final PendingStartActivityUids mPendingStartActivityUids; @@ -990,7 +984,7 @@ public class ActivityManagerService extends IActivityManager.Stub * method. */ @GuardedBy("this") - void removePidLocked(int pid, ProcessRecord app) { + boolean removePidLocked(int pid, ProcessRecord app) { final boolean removed; synchronized (mPidsSelfLocked) { removed = mPidsSelfLocked.doRemoveInternal(pid, app); @@ -1001,26 +995,6 @@ public class ActivityManagerService extends IActivityManager.Stub } mAtmInternal.onProcessUnMapped(pid); } - } - - /** - * Removes the process record from the map if it doesn't have a thread. - * <p>NOTE: Callers should avoid acquiring the mPidsSelfLocked lock before calling this - * method. - */ - @GuardedBy("this") - private boolean removePidIfNoThreadLocked(ProcessRecord app) { - final boolean removed; - final int pid = app.getPid(); - synchronized (mPidsSelfLocked) { - removed = mPidsSelfLocked.doRemoveIfNoThreadInternal(pid, app); - } - if (removed) { - synchronized (sActiveProcessInfoSelfLocked) { - sActiveProcessInfoSelfLocked.remove(pid); - } - mAtmInternal.onProcessUnMapped(pid); - } return removed; } @@ -2364,7 +2338,7 @@ public class ActivityManagerService extends IActivityManager.Stub mAppErrors = null; mPackageWatchdog = null; mAppOpsService = mInjector.getAppOpsService(null /* file */, null /* handler */); - mBatteryStatsService = null; + mBatteryStatsService = mInjector.getBatteryStatsService(); mHandler = new MainHandler(handlerThread.getLooper()); mHandlerThread = handlerThread; mConstants = new ActivityManagerConstants(mContext, this, mHandler); @@ -2379,7 +2353,7 @@ public class ActivityManagerService extends IActivityManager.Stub mIntentFirewall = null; mProcessStats = new ProcessStatsService(this, mContext.getCacheDir()); mCpHelper = new ContentProviderHelper(this, false); - mServices = null; + mServices = mInjector.getActiveServices(this); mSystemThread = null; mUiHandler = injector.getUiHandler(null /* service */); mUidObserverController = new UidObserverController(mUiHandler); @@ -4771,7 +4745,7 @@ public class ActivityManagerService extends IActivityManager.Stub @GuardedBy("this") void handleProcessStartOrKillTimeoutLocked(ProcessRecord app, boolean isKillTimeout) { final int pid = app.getPid(); - boolean gone = isKillTimeout || removePidIfNoThreadLocked(app); + boolean gone = isKillTimeout || removePidLocked(pid, app); if (gone) { if (isKillTimeout) { @@ -4852,7 +4826,7 @@ public class ActivityManagerService extends IActivityManager.Stub } @GuardedBy("this") - private boolean attachApplicationLocked(@NonNull IApplicationThread thread, + private void attachApplicationLocked(@NonNull IApplicationThread thread, int pid, int callingUid, long startSeq) { // Find the application record that is being attached... either via @@ -4917,7 +4891,7 @@ public class ActivityManagerService extends IActivityManager.Stub // Ignore exceptions. } } - return false; + return; } // If this application record is still attached to a previous @@ -4942,7 +4916,7 @@ public class ActivityManagerService extends IActivityManager.Stub mProcessList.startProcessLocked(app, new HostingRecord(HostingRecord.HOSTING_TYPE_LINK_FAIL, processName), ZYGOTE_POLICY_FLAG_EMPTY); - return false; + return; } EventLogTags.writeAmProcBound(app.userId, pid, app.processName); @@ -4965,8 +4939,6 @@ public class ActivityManagerService extends IActivityManager.Stub app.setUnlocked(StorageManager.isUserKeyUnlocked(app.userId)); } - mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app); - boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info); List<ProviderInfo> providers = normalMode ? mCpHelper.generateApplicationProvidersLocked(app) @@ -5132,7 +5104,7 @@ public class ActivityManagerService extends IActivityManager.Stub app.killLocked("error during bind", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE, true); handleAppDiedLocked(app, pid, false, true, false /* fromBinderDied */); - return false; + return; } // Remove this record from the list of starting applications. @@ -5140,103 +5112,155 @@ public class ActivityManagerService extends IActivityManager.Stub if (DEBUG_PROCESSES && mProcessesOnHold.contains(app)) Slog.v(TAG_PROCESSES, "Attach application locked removing on hold: " + app); mProcessesOnHold.remove(app); + } - boolean badApp = false; - boolean didSomething = false; + @Override + public final void attachApplication(IApplicationThread thread, long startSeq) { + if (thread == null) { + throw new SecurityException("Invalid application interface"); + } + synchronized (this) { + int callingPid = Binder.getCallingPid(); + final int callingUid = Binder.getCallingUid(); + final long origId = Binder.clearCallingIdentity(); + attachApplicationLocked(thread, callingPid, callingUid, startSeq); + Binder.restoreCallingIdentity(origId); + } + } - // See if the top visible activity is waiting to run in this process... - if (normalMode) { - try { - didSomething = mAtmInternal.attachApplication(app.getWindowProcessController()); - } catch (Exception e) { - Slog.wtf(TAG, "Exception thrown launching activities in " + app, e); - badApp = true; - } + private void finishAttachApplicationInner(long startSeq, int uid, int pid) { + final long startTime = SystemClock.uptimeMillis(); + // Find the application record that is being attached... either via + // the pid if we are running in multiple processes, or just pull the + // next app record if we are emulating process with anonymous threads. + final ProcessRecord app; + synchronized (mPidsSelfLocked) { + app = mPidsSelfLocked.get(pid); } - // Find any services that should be running in this process... - if (!badApp) { - try { - didSomething |= mServices.attachApplicationLocked(app, processName); - checkTime(startTime, "attachApplicationLocked: after mServices.attachApplicationLocked"); - } catch (Exception e) { - Slog.wtf(TAG, "Exception thrown starting services in " + app, e); - badApp = true; - } + if (app != null && app.getStartUid() == uid && app.getStartSeq() == startSeq) { + mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app); + } else { + Slog.wtf(TAG, "Mismatched or missing ProcessRecord: " + app + ". Pid: " + pid + + ". Uid: " + uid); + killProcess(pid); + killProcessGroup(uid, pid); + mProcessList.noteAppKill(pid, uid, + ApplicationExitInfo.REASON_INITIALIZATION_FAILURE, + ApplicationExitInfo.SUBREASON_UNKNOWN, + "wrong startSeq"); + app.killLocked("unexpected process record", + ApplicationExitInfo.REASON_OTHER, true); + return; } - // Check if a next-broadcast receiver is in this process... - if (!badApp) { - try { - for (BroadcastQueue queue : mBroadcastQueues) { - didSomething |= queue.onApplicationAttachedLocked(app); + synchronized (this) { + final boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info); + final String processName = app.processName; + boolean badApp = false; + boolean didSomething = false; + + // See if the top visible activity is waiting to run in this process... + if (normalMode) { + try { + didSomething = mAtmInternal.attachApplication(app.getWindowProcessController()); + } catch (Exception e) { + Slog.wtf(TAG, "Exception thrown launching activities in " + app, e); + badApp = true; } - checkTime(startTime, "attachApplicationLocked: after dispatching broadcasts"); - } catch (Exception e) { - // If the app died trying to launch the receiver we declare it 'bad' - Slog.wtf(TAG, "Exception thrown dispatching broadcasts in " + app, e); - badApp = true; } - } - // Check whether the next backup agent is in this process... - if (!badApp && backupTarget != null && backupTarget.app == app) { - if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, - "New app is backup target, launching agent for " + app); - notifyPackageUse(backupTarget.appInfo.packageName, - PackageManager.NOTIFY_PACKAGE_USE_BACKUP); - try { - thread.scheduleCreateBackupAgent(backupTarget.appInfo, - backupTarget.backupMode, backupTarget.userId, - backupTarget.backupDestination); - } catch (Exception e) { - Slog.wtf(TAG, "Exception thrown creating backup agent in " + app, e); - badApp = true; + // Find any services that should be running in this process... + if (!badApp) { + try { + didSomething |= mServices.attachApplicationLocked(app, processName); + checkTime(startTime, "finishAttachApplicationInner: " + + "after mServices.attachApplicationLocked"); + } catch (Exception e) { + Slog.wtf(TAG, "Exception thrown starting services in " + app, e); + badApp = true; + } } - } - if (badApp) { - app.killLocked("error during init", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE, - true); - handleAppDiedLocked(app, pid, false, true, false /* fromBinderDied */); - return false; - } + // Check if a next-broadcast receiver is in this process... + if (!badApp) { + try { + for (BroadcastQueue queue : mBroadcastQueues) { + didSomething |= queue.onApplicationAttachedLocked(app); + } + checkTime(startTime, "finishAttachApplicationInner: " + + "after dispatching broadcasts"); + } catch (Exception e) { + // If the app died trying to launch the receiver we declare it 'bad' + Slog.wtf(TAG, "Exception thrown dispatching broadcasts in " + app, e); + badApp = true; + } + } - if (!didSomething) { - updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN); - checkTime(startTime, "attachApplicationLocked: after updateOomAdjLocked"); - } + // Check whether the next backup agent is in this process... + final BackupRecord backupTarget = mBackupTargets.get(app.userId); + if (!badApp && backupTarget != null && backupTarget.app == app) { + if (DEBUG_BACKUP) { + Slog.v(TAG_BACKUP, + "New app is backup target, launching agent for " + app); + } + notifyPackageUse(backupTarget.appInfo.packageName, + PackageManager.NOTIFY_PACKAGE_USE_BACKUP); + try { + app.getThread().scheduleCreateBackupAgent(backupTarget.appInfo, + backupTarget.backupMode, backupTarget.userId, + backupTarget.backupDestination); + } catch (Exception e) { + Slog.wtf(TAG, "Exception thrown creating backup agent in " + app, e); + badApp = true; + } + } - final HostingRecord hostingRecord = app.getHostingRecord(); - String shortAction = getShortAction(hostingRecord.getAction()); - FrameworkStatsLog.write( - FrameworkStatsLog.PROCESS_START_TIME, - app.info.uid, - pid, - app.info.packageName, - FrameworkStatsLog.PROCESS_START_TIME__TYPE__COLD, - app.getStartElapsedTime(), - (int) (bindApplicationTimeMillis - app.getStartUptime()), - (int) (SystemClock.uptimeMillis() - app.getStartUptime()), - hostingRecord.getType(), - hostingRecord.getName(), - shortAction, - HostingRecord.getHostingTypeIdStatsd(hostingRecord.getType()), - HostingRecord.getTriggerTypeForStatsd(hostingRecord.getTriggerType())); - return true; + if (badApp) { + app.killLocked("error during init", + ApplicationExitInfo.REASON_INITIALIZATION_FAILURE, true); + handleAppDiedLocked(app, pid, false, true, false /* fromBinderDied */); + return; + } + + if (!didSomething) { + updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN); + checkTime(startTime, "finishAttachApplicationInner: after updateOomAdjLocked"); + } + + final HostingRecord hostingRecord = app.getHostingRecord(); + final String shortAction = getShortAction(hostingRecord.getAction()); + FrameworkStatsLog.write( + FrameworkStatsLog.PROCESS_START_TIME, + app.info.uid, + pid, + app.info.packageName, + FrameworkStatsLog.PROCESS_START_TIME__TYPE__COLD, + app.getStartElapsedTime(), + (int) (app.getBindApplicationTime() - app.getStartUptime()), + (int) (SystemClock.uptimeMillis() - app.getStartUptime()), + hostingRecord.getType(), + hostingRecord.getName(), + shortAction, + HostingRecord.getHostingTypeIdStatsd(hostingRecord.getType()), + HostingRecord.getTriggerTypeForStatsd(hostingRecord.getTriggerType())); + } } @Override - public final void attachApplication(IApplicationThread thread, long startSeq) { - if (thread == null) { - throw new SecurityException("Invalid application interface"); + public final void finishAttachApplication(long startSeq) { + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + + if (pid == MY_PID && uid == SYSTEM_UID) { + return; } - synchronized (this) { - int callingPid = Binder.getCallingPid(); - final int callingUid = Binder.getCallingUid(); - final long origId = Binder.clearCallingIdentity(); - attachApplicationLocked(thread, callingPid, callingUid, startSeq); + + final long origId = Binder.clearCallingIdentity(); + try { + finishAttachApplicationInner(startSeq, uid, pid); + } finally { Binder.restoreCallingIdentity(origId); } } @@ -18805,6 +18829,21 @@ public class ActivityManagerService extends IActivityManager.Stub return new ProcessList(); } + /** + * Returns the {@link BatteryStatsService} instance + */ + public BatteryStatsService getBatteryStatsService() { + return new BatteryStatsService(mContext, SystemServiceManager.ensureSystemDir(), + BackgroundThread.get().getHandler()); + } + + /** + * Returns the {@link ActiveServices} instance + */ + public ActiveServices getActiveServices(ActivityManagerService service) { + return new ActiveServices(service); + } + private boolean ensureHasNetworkManagementInternal() { if (mNmi == null) { mNmi = LocalServices.getService(NetworkManagementInternal.class); 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/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index ecea96e927e3..937bbc9cced6 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -2508,7 +2508,7 @@ public final class ProcessList { } @GuardedBy("mService") - private String isProcStartValidLocked(ProcessRecord app, long expectedStartSeq) { + String isProcStartValidLocked(ProcessRecord app, long expectedStartSeq) { StringBuilder sb = null; if (app.isKilledByAm()) { if (sb == null) sb = new StringBuilder(); diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 0a8c6400a6fd..4706c26889de 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -200,6 +200,11 @@ class ProcessRecord implements WindowProcessListener { private volatile long mStartElapsedTime; /** + * When the process was sent the bindApplication request + */ + private volatile long mBindApplicationTime; + + /** * This will be same as {@link #uid} usually except for some apps used during factory testing. */ private volatile int mStartUid; @@ -739,6 +744,10 @@ class ProcessRecord implements WindowProcessListener { return mStartElapsedTime; } + long getBindApplicationTime() { + return mBindApplicationTime; + } + int getStartUid() { return mStartUid; } diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index b92c1635d7c6..a954164fb79f 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -338,7 +338,18 @@ public final class GameManagerService extends IGameManagerService.Stub { + " and userId " + userId); break; } + if (mHandler.hasMessages(CANCEL_GAME_LOADING_MODE)) { + mHandler.removeMessages(CANCEL_GAME_LOADING_MODE); + } mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, isLoading); + if (isLoading) { + int loadingBoostDuration = getLoadingBoostDuration(packageName, userId); + loadingBoostDuration = loadingBoostDuration > 0 ? loadingBoostDuration + : LOADING_BOOST_MAX_DURATION; + mHandler.sendMessageDelayed( + mHandler.obtainMessage(CANCEL_GAME_LOADING_MODE), + loadingBoostDuration); + } } break; } 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..881199964d45 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java +++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java @@ -23,6 +23,7 @@ import android.view.Display; import android.view.DisplayAddress; import android.view.DisplayCutout; import android.view.DisplayEventReceiver; +import android.view.DisplayShape; import android.view.RoundedCorners; import android.view.Surface; @@ -158,6 +159,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; @@ -303,6 +317,11 @@ final class DisplayDeviceInfo { public RoundedCorners roundedCorners; /** + * The {@link RoundedCorners} if present or {@code null} otherwise. + */ + public DisplayShape displayShape; + + /** * The touch attachment, per {@link DisplayViewport#touch}. */ public int touch; @@ -438,7 +457,8 @@ final class DisplayDeviceInfo { || !BrightnessSynchronizer.floatEquals(brightnessDefault, other.brightnessDefault) || !Objects.equals(roundedCorners, other.roundedCorners) - || installOrientation != other.installOrientation) { + || installOrientation != other.installOrientation + || !Objects.equals(displayShape, other.displayShape)) { diff |= DIFF_OTHER; } return diff; @@ -484,6 +504,7 @@ final class DisplayDeviceInfo { brightnessDefault = other.brightnessDefault; roundedCorners = other.roundedCorners; installOrientation = other.installOrientation; + displayShape = other.displayShape; } // For debugging purposes @@ -533,6 +554,9 @@ final class DisplayDeviceInfo { } sb.append(flagsToString(flags)); sb.append(", installOrientation ").append(installOrientation); + if (displayShape != null) { + sb.append(", displayShape ").append(displayShape); + } sb.append("}"); return sb.toString(); } @@ -584,9 +608,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/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 5a714f59485c..4bf1e98f99a5 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -38,6 +38,7 @@ import android.view.Display; import android.view.DisplayAddress; import android.view.DisplayCutout; import android.view.DisplayEventReceiver; +import android.view.DisplayShape; import android.view.RoundedCorners; import android.view.SurfaceControl; @@ -686,6 +687,9 @@ final class LocalDisplayAdapter extends DisplayAdapter { res, mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height); mInfo.installOrientation = mStaticDisplayInfo.installOrientation; + mInfo.displayShape = DisplayShape.fromResources( + res, mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height); + if (mStaticDisplayInfo.isInternal) { mInfo.type = Display.TYPE_INTERNAL; mInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL; diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index dedc56ac9b41..28bdce3d8b34 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -233,6 +233,7 @@ final class LogicalDisplay { info.displayCutout = mOverrideDisplayInfo.displayCutout; info.logicalDensityDpi = mOverrideDisplayInfo.logicalDensityDpi; info.roundedCorners = mOverrideDisplayInfo.roundedCorners; + info.displayShape = mOverrideDisplayInfo.displayShape; } mInfo.set(info); } @@ -384,6 +385,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; @@ -434,6 +438,7 @@ final class LogicalDisplay { mBaseDisplayInfo.brightnessDefault = deviceInfo.brightnessDefault; mBaseDisplayInfo.roundedCorners = deviceInfo.roundedCorners; mBaseDisplayInfo.installOrientation = deviceInfo.installOrientation; + mBaseDisplayInfo.displayShape = deviceInfo.displayShape; mPrimaryDisplayDeviceInfo = deviceInfo; mInfo.set(null); } diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java index b0de844389b6..0e11b53e0257 100644 --- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java +++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java @@ -29,6 +29,7 @@ import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.Slog; import android.view.Display; +import android.view.DisplayShape; import android.view.Gravity; import android.view.Surface; import android.view.SurfaceControl; @@ -361,6 +362,8 @@ final class OverlayDisplayAdapter extends DisplayAdapter { mInfo.state = mState; // The display is trusted since it is created by system. mInfo.flags |= FLAG_TRUSTED; + mInfo.displayShape = + DisplayShape.createDefaultDisplayShape(mInfo.width, mInfo.height, false); } return mInfo; } diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index 20b82c3e3c97..d0e518b876dd 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; @@ -51,6 +52,7 @@ import android.os.SystemProperties; import android.util.ArrayMap; import android.util.Slog; import android.view.Display; +import android.view.DisplayShape; import android.view.Surface; import android.view.SurfaceControl; @@ -495,13 +497,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) ? @@ -511,6 +525,9 @@ public class VirtualDisplayAdapter extends DisplayAdapter { mInfo.ownerUid = mOwnerUid; mInfo.ownerPackageName = mOwnerPackageName; + + mInfo.displayShape = + DisplayShape.createDefaultDisplayShape(mInfo.width, mInfo.height, false); } return mInfo; } diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java index 146b003650ac..c759d98561f9 100644 --- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java +++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java @@ -34,6 +34,7 @@ import android.os.UserHandle; import android.util.Slog; import android.view.Display; import android.view.DisplayAddress; +import android.view.DisplayShape; import android.view.Surface; import android.view.SurfaceControl; @@ -655,6 +656,8 @@ final class WifiDisplayAdapter extends DisplayAdapter { mInfo.setAssumedDensityForExternalDisplay(mWidth, mHeight); // The display is trusted since it is created by system. mInfo.flags |= DisplayDeviceInfo.FLAG_TRUSTED; + mInfo.displayShape = + DisplayShape.createDefaultDisplayShape(mInfo.width, mInfo.height, false); } return mInfo; } diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java index 015e5768d505..c53f1a52306d 100644 --- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java +++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java @@ -110,11 +110,10 @@ final class IInputMethodInvoker { @AnyThread void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privilegedOperations, - int configChanges, @InputMethodNavButtonFlags int navigationBarFlags) { + @InputMethodNavButtonFlags int navigationBarFlags) { final IInputMethod.InitParams params = new IInputMethod.InitParams(); params.token = token; params.privilegedOperations = privilegedOperations; - params.configChanges = configChanges; params.navigationBarFlags = navigationBarFlags; try { mTarget.initializeInternal(params); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java index 6dbb362db030..079234c2f95c 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(); } } @@ -279,7 +296,7 @@ final class InputMethodBindingController { if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken); final InputMethodInfo info = mMethodMap.get(mSelectedMethodId); mSupportsStylusHw = info.supportsStylusHandwriting(); - mService.initializeImeLocked(mCurMethod, mCurToken, info.getConfigChanges()); + mService.initializeImeLocked(mCurMethod, mCurToken); mService.scheduleNotifyImeUidToAudioService(mCurMethodUid); mService.reRequestCurrentClientSessionLocked(); mService.performOnCreateInlineSuggestionsRequestLocked(); @@ -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/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 8b083bd72722..080d5829f9d9 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -2692,14 +2692,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @GuardedBy("ImfLock.class") - void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token, - @android.content.pm.ActivityInfo.Config int configChanges) { + void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token) { if (DEBUG) { Slog.v(TAG, "Sending attach of token: " + token + " for display: " + mCurTokenDisplayId); } inputMethod.initializeInternal(token, new InputMethodPrivilegedOperationsImpl(this, token), - configChanges, getInputMethodNavButtonFlagsLocked()); + getInputMethodNavButtonFlagsLocked()); } @AnyThread 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/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java index f3cfa95ff86d..b8bdabe365f8 100644 --- a/services/core/java/com/android/server/pm/AppDataHelper.java +++ b/services/core/java/com/android/server/pm/AppDataHelper.java @@ -213,7 +213,7 @@ public class AppDataHelper { final int appId = UserHandle.getAppId(pkg.getUid()); - String pkgSeInfo = AndroidPackageUtils.getSeInfo(pkg, ps); + String pkgSeInfo = ps.getSeInfo(); Preconditions.checkNotNull(pkgSeInfo); 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 b02d1a8c0353..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; @@ -763,7 +765,7 @@ final class InstallPackageHelper { || (installFlags & PackageManager.INSTALL_REQUEST_DOWNGRADE) != 0); if (ps != null && doSnapshotOrRestore) { - final String seInfo = AndroidPackageUtils.getSeInfo(request.getPkg(), ps); + final String seInfo = ps.getSeInfo(); final RollbackManagerInternal rollbackManager = mInjector.getLocalService(RollbackManagerInternal.class); rollbackManager.snapshotAndRestoreUserData(packageName, @@ -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/MovePackageHelper.java b/services/core/java/com/android/server/pm/MovePackageHelper.java index b27373e1d9f1..b66c6ac472b4 100644 --- a/services/core/java/com/android/server/pm/MovePackageHelper.java +++ b/services/core/java/com/android/server/pm/MovePackageHelper.java @@ -129,7 +129,7 @@ public final class MovePackageHelper { final InstallSource installSource = packageState.getInstallSource(); final String packageAbiOverride = packageState.getCpuAbiOverride(); final int appId = UserHandle.getAppId(pkg.getUid()); - final String seinfo = AndroidPackageUtils.getSeInfo(pkg, packageState); + final String seinfo = packageState.getSeInfo(); final String label = String.valueOf(pm.getApplicationLabel( AndroidPackageUtils.generateAppInfoWithoutState(pkg))); final int targetSdkVersion = pkg.getTargetSdkVersion(); diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index 226a27eccc03..49f3a3c02879 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -493,7 +493,7 @@ public class PackageDexOptimizer { // TODO: Consider adding 2 different APIs for primary and secondary dexopt. // installd only uses downgrade flag for secondary dex files and ignores it for // primary dex files. - String seInfo = AndroidPackageUtils.getSeInfo(pkg, pkgSetting); + String seInfo = pkgSetting.getSeInfo(); boolean completed = getInstallerLI().dexopt(path, uid, pkg.getPackageName(), isa, dexoptNeeded, oatDir, dexoptFlags, compilerFilter, pkg.getVolumeUuid(), classLoaderContext, seInfo, /* downgrade= */ false , 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/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 8f8cc8a43017..9e1bffb7abd5 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1560,7 +1560,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService AndroidPackage pkg = packageState.getPkg(); SharedUserApi sharedUser = snapshot.getSharedUser( packageState.getSharedUserAppId()); - String oldSeInfo = AndroidPackageUtils.getSeInfo(pkg, packageState); + String oldSeInfo = packageState.getSeInfo(); if (pkg == null) { Slog.e(TAG, "Failed to find package " + packageName); 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/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 6d90593c2f48..3ec6e7dcdef6 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -1358,6 +1358,17 @@ public class PackageSetting extends SettingBase implements PackageStateInternal } @Nullable + @Override + public String getSeInfo() { + String overrideSeInfo = getTransientState().getOverrideSeInfo(); + if (!TextUtils.isEmpty(overrideSeInfo)) { + return overrideSeInfo; + } + + return getTransientState().getSeInfo(); + } + + @Nullable public String getPrimaryCpuAbiLegacy() { return mPrimaryCpuAbi; } @@ -1518,10 +1529,10 @@ public class PackageSetting extends SettingBase implements PackageStateInternal } @DataClass.Generated( - time = 1662666062860L, + time = 1665779003744L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java", - inputSignatures = "private int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate boolean installPermissionsFixed\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate boolean updateAvailable\nprivate boolean forceQueryableOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackageName(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isAnyInstalled(int[])\n int[] queryInstalledUsers(int[],boolean)\n long getCeDataInode(int)\n void setCeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\n com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getUsesLibraries()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)") + inputSignatures = "private int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate boolean installPermissionsFixed\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate boolean updateAvailable\nprivate boolean forceQueryableOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackageName(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isAnyInstalled(int[])\n int[] queryInstalledUsers(int[],boolean)\n long getCeDataInode(int)\n void setCeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\n com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getUsesLibraries()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java index a905df98e577..6572d7b27e2b 100644 --- a/services/core/java/com/android/server/pm/ScanPackageUtils.java +++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java @@ -265,8 +265,8 @@ final class ScanPackageUtils { pkgSetting.getPkgState().setUpdatedSystemApp(true); } - parsedPackage.setSeInfo(SELinuxMMAC.getSeInfo(parsedPackage, sharedUserSetting, - injector.getCompatibility())); + pkgSetting.getTransientState().setSeInfo(SELinuxMMAC.getSeInfo(parsedPackage, + sharedUserSetting, injector.getCompatibility())); if (parsedPackage.isSystem()) { configurePackageComponents(parsedPackage); diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index a40d40467735..4aba01637abf 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -102,7 +102,6 @@ import com.android.server.LocalServices; import com.android.server.backup.PreferredActivityBackupHelper; import com.android.server.pm.Installer.InstallerException; import com.android.server.pm.parsing.PackageInfoUtils; -import com.android.server.pm.parsing.pkg.AndroidPackageUtils; import com.android.server.pm.permission.LegacyPermissionDataProvider; import com.android.server.pm.permission.LegacyPermissionSettings; import com.android.server.pm.permission.LegacyPermissionState; @@ -2900,7 +2899,7 @@ public final class Settings implements Watchable, Snappable { sb.append(isDebug ? " 1 " : " 0 "); sb.append(dataPath); sb.append(" "); - sb.append(AndroidPackageUtils.getSeInfo(pkg.getPkg(), pkg)); + sb.append(pkg.getSeInfo()); sb.append(" "); final int gidsSize = gids.size(); if (gids != null && gids.size() > 0) { @@ -4359,7 +4358,7 @@ public final class Settings implements Watchable, Snappable { // (CE storage is not ready yet; the CE data directories will be created later, // when the user is "unlocked".) Accumulate all required args, and call the // installer after the mPackages lock has been released. - final String seInfo = AndroidPackageUtils.getSeInfo(ps.getPkg(), ps); + final String seInfo = ps.getSeInfo(); final boolean usesSdk = !ps.getPkg().getUsesSdkLibraries().isEmpty(); final CreateAppDataArgs args = Installer.buildCreateAppDataArgs( ps.getVolumeUuid(), ps.getPackageName(), userHandle, diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 1da442b3dc20..74594cce0041 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -53,7 +53,6 @@ import com.android.internal.util.Preconditions; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.SystemServiceManager; -import com.android.server.pm.parsing.pkg.AndroidPackageUtils; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.pm.pkg.PackageStateUtils; @@ -347,7 +346,7 @@ public class StagingManager { // an update, and hence need to restore data for all installed users. final int[] installedUsers = PackageStateUtils.queryInstalledUsers(ps, allUsers, true); - final String seInfo = AndroidPackageUtils.getSeInfo(pkg, ps); + final String seInfo = ps.getSeInfo(); rm.snapshotAndRestoreUserData(packageName, UserHandle.toUserHandles(installedUsers), appId, ceDataInode, seInfo, 0 /*token*/); } 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..494f46ca826f 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -191,6 +191,7 @@ public class UserManagerService extends IUserManager.Stub { private static final String ATTR_CREATION_TIME = "created"; private static final String ATTR_LAST_LOGGED_IN_TIME = "lastLoggedIn"; private static final String ATTR_LAST_LOGGED_IN_FINGERPRINT = "lastLoggedInFingerprint"; + private static final String ATTR_LAST_ENTERED_FOREGROUND_TIME = "lastEnteredForeground"; private static final String ATTR_SERIAL_NO = "serialNumber"; private static final String ATTR_NEXT_SERIAL_NO = "nextSerialNumber"; private static final String ATTR_PARTIAL = "partial"; @@ -341,6 +342,9 @@ public class UserManagerService extends IUserManager.Stub { /** Elapsed realtime since boot when the user was unlocked. */ long unlockRealtime; + /** Wall clock time in millis when the user last entered the foreground. */ + long mLastEnteredForegroundTimeMillis; + private long mLastRequestQuietModeEnabledMillis; /** @@ -680,6 +684,10 @@ public class UserManagerService extends IUserManager.Stub { final UserData user = mUms.getUserDataLU(targetUser.getUserIdentifier()); if (user != null) { user.startRealtime = SystemClock.elapsedRealtime(); + if (targetUser.getUserIdentifier() == UserHandle.USER_SYSTEM + && targetUser.isFull()) { + mUms.setLastEnteredForegroundTimeToNow(user); + } } } } @@ -695,6 +703,16 @@ public class UserManagerService extends IUserManager.Stub { } @Override + public void onUserSwitching(@NonNull TargetUser from, @NonNull TargetUser to) { + synchronized (mUms.mUsersLock) { + final UserData user = mUms.getUserDataLU(to.getUserIdentifier()); + if (user != null) { + mUms.setLastEnteredForegroundTimeToNow(user); + } + } + } + + @Override public void onUserStopping(@NonNull TargetUser targetUser) { synchronized (mUms.mUsersLock) { final UserData user = mUms.getUserDataLU(targetUser.getUserIdentifier()); @@ -901,6 +919,49 @@ 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; + } + + @Override + public int getPreviousFullUserToEnterForeground() { + checkQueryOrCreateUsersPermission("get previous user"); + int previousUser = UserHandle.USER_NULL; + long latestEnteredTime = 0; + final int currentUser = getCurrentUserId(); + synchronized (mUsersLock) { + final int userSize = mUsers.size(); + for (int i = 0; i < userSize; i++) { + final UserData userData = mUsers.valueAt(i); + final int userId = userData.info.id; + if (userId != currentUser && userData.info.isFull() && !userData.info.partial + && !mRemovingUserIds.get(userId)) { + final long userEnteredTime = userData.mLastEnteredForegroundTimeMillis; + if (userEnteredTime > latestEnteredTime) { + latestEnteredTime = userEnteredTime; + previousUser = userId; + } + } + } + } + return previousUser; + } + public @NonNull List<UserInfo> getUsers(boolean excludeDying) { return getUsers(/*excludePartial= */ true, excludeDying, /* excludePreCreated= */ true); @@ -3339,13 +3400,13 @@ public class UserManagerService extends IUserManager.Stub { Slogf.wtf(LOG_TAG, "emulateSystemUserModeIfNeeded(): no system user data"); return; } + final int oldMainUserId = getMainUserIdUnchecked(); final int oldFlags = systemUserData.info.flags; final int newFlags; final String newUserType; - // TODO(b/256624031): Also handle FLAG_MAIN if (newHeadlessSystemUserMode) { newUserType = UserManager.USER_TYPE_SYSTEM_HEADLESS; - newFlags = oldFlags & ~UserInfo.FLAG_FULL; + newFlags = oldFlags & ~UserInfo.FLAG_FULL & ~UserInfo.FLAG_MAIN; } else { newUserType = UserManager.USER_TYPE_FULL_SYSTEM; newFlags = oldFlags | UserInfo.FLAG_FULL; @@ -3360,9 +3421,38 @@ public class UserManagerService extends IUserManager.Stub { + "%s, flags changed from %s to %s", systemUserData.info.userType, newUserType, UserInfo.flagsToString(oldFlags), UserInfo.flagsToString(newFlags)); + systemUserData.info.userType = newUserType; systemUserData.info.flags = newFlags; writeUserLP(systemUserData); + + // Switch the MainUser to a reasonable choice if needed. + // (But if there was no MainUser, we deliberately continue to have no MainUser.) + final UserData oldMain = getUserDataNoChecks(oldMainUserId); + if (newHeadlessSystemUserMode) { + if (oldMain != null && (oldMain.info.flags & UserInfo.FLAG_SYSTEM) != 0) { + // System was MainUser. So we need a new choice for Main. Pick the oldest. + // If no oldest, don't set any. Let the BootUserInitializer do that later. + final UserInfo newMainUser = getEarliestCreatedFullUser(); + if (newMainUser != null) { + Slogf.i(LOG_TAG, "Designating user " + newMainUser.id + " to be Main"); + newMainUser.flags |= UserInfo.FLAG_MAIN; + writeUserLP(getUserDataNoChecks(newMainUser.id)); + } + } + } else { + // TODO(b/256624031): For now, we demand the Main user (if there is one) is + // always the system in non-HSUM. In the future, when we relax this, change how + // we handle MAIN. + if (oldMain != null && (oldMain.info.flags & UserInfo.FLAG_SYSTEM) == 0) { + // Someone else was the MainUser; transfer it to System. + Slogf.i(LOG_TAG, "Transferring Main to user 0 from " + oldMain.info.id); + oldMain.info.flags &= ~UserInfo.FLAG_MAIN; + systemUserData.info.flags |= UserInfo.FLAG_MAIN; + writeUserLP(oldMain); + writeUserLP(systemUserData); + } + } } } @@ -3639,8 +3729,10 @@ public class UserManagerService extends IUserManager.Stub { // Add FLAG_MAIN if (isHeadlessSystemUserMode()) { final UserInfo earliestCreatedUser = getEarliestCreatedFullUser(); - earliestCreatedUser.flags |= UserInfo.FLAG_MAIN; - userIdsToWrite.add(earliestCreatedUser.id); + if (earliestCreatedUser != null) { + earliestCreatedUser.flags |= UserInfo.FLAG_MAIN; + userIdsToWrite.add(earliestCreatedUser.id); + } } else { synchronized (mUsersLock) { final UserData userData = mUsers.get(UserHandle.USER_SYSTEM); @@ -3780,13 +3872,14 @@ public class UserManagerService extends IUserManager.Stub { userInfo.profileBadge = getFreeProfileBadgeLU(userInfo.profileGroupId, userInfo.userType); } - private UserInfo getEarliestCreatedFullUser() { + /** Returns the oldest Full Admin user, or null is if there none. */ + private @Nullable UserInfo getEarliestCreatedFullUser() { final List<UserInfo> users = getUsersInternal(true, true, true); - UserInfo earliestUser = users.get(0); - long earliestCreationTime = earliestUser.creationTime; + UserInfo earliestUser = null; + 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; @@ -3922,6 +4015,8 @@ public class UserManagerService extends IUserManager.Stub { serializer.attribute(null, ATTR_LAST_LOGGED_IN_FINGERPRINT, userInfo.lastLoggedInFingerprint); } + serializer.attributeLong( + null, ATTR_LAST_ENTERED_FOREGROUND_TIME, userData.mLastEnteredForegroundTimeMillis); if (userInfo.iconPath != null) { serializer.attribute(null, ATTR_ICON_PATH, userInfo.iconPath); } @@ -4095,6 +4190,7 @@ public class UserManagerService extends IUserManager.Stub { long lastLoggedInTime = 0L; long lastRequestQuietModeEnabledTimestamp = 0L; String lastLoggedInFingerprint = null; + long lastEnteredForegroundTime = 0L; int profileGroupId = UserInfo.NO_PROFILE_GROUP_ID; int profileBadge = 0; int restrictedProfileParentId = UserInfo.NO_PROFILE_GROUP_ID; @@ -4140,6 +4236,8 @@ public class UserManagerService extends IUserManager.Stub { lastLoggedInTime = parser.getAttributeLong(null, ATTR_LAST_LOGGED_IN_TIME, 0); lastLoggedInFingerprint = parser.getAttributeValue(null, ATTR_LAST_LOGGED_IN_FINGERPRINT); + lastEnteredForegroundTime = + parser.getAttributeLong(null, ATTR_LAST_ENTERED_FOREGROUND_TIME, 0L); profileGroupId = parser.getAttributeInt(null, ATTR_PROFILE_GROUP_ID, UserInfo.NO_PROFILE_GROUP_ID); profileBadge = parser.getAttributeInt(null, ATTR_PROFILE_BADGE, 0); @@ -4234,6 +4332,7 @@ public class UserManagerService extends IUserManager.Stub { userData.seedAccountOptions = seedAccountOptions; userData.userProperties = userProperties; userData.setLastRequestQuietModeEnabledMillis(lastRequestQuietModeEnabledTimestamp); + userData.mLastEnteredForegroundTimeMillis = lastEnteredForegroundTime; if (ignorePrepareStorageErrors) { userData.setIgnorePrepareStorageErrors(); } @@ -6153,6 +6252,11 @@ public class UserManagerService extends IUserManager.Stub { || someUserHasSeedAccountNoChecks(accountName, accountType)); } + private void setLastEnteredForegroundTimeToNow(@NonNull UserData userData) { + userData.mLastEnteredForegroundTimeMillis = System.currentTimeMillis(); + scheduleWriteUser(userData); + } + @Override public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, @@ -6377,6 +6481,9 @@ public class UserManagerService extends IUserManager.Stub { pw.print(" Unlock time: "); dumpTimeAgo(pw, tempStringBuilder, nowRealtime, userData.unlockRealtime); + pw.print(" Last entered foreground: "); + dumpTimeAgo(pw, tempStringBuilder, now, userData.mLastEnteredForegroundTimeMillis); + pw.print(" Has profile owner: "); pw.println(mIsUserManaged.get(userId)); pw.println(" Restrictions:"); @@ -6898,6 +7005,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/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java index a7d4ceadc37a..558202b99de2 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java +++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java @@ -452,7 +452,7 @@ public class PackageInfoUtils { info.category = pkgSetting.getCategoryOverride(); } - info.seInfo = AndroidPackageUtils.getSeInfo(pkg, pkgSetting); + info.seInfo = pkgSetting.getSeInfo(); info.primaryCpuAbi = pkgSetting.getPrimaryCpuAbi(); info.secondaryCpuAbi = pkgSetting.getSecondaryCpuAbi(); diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageHidden.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageHidden.java index 944e4ad6eb69..876bf17c9634 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageHidden.java +++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageHidden.java @@ -40,13 +40,6 @@ interface AndroidPackageHidden { String getPrimaryCpuAbi(); /** - * @see ApplicationInfo#seInfo - * TODO: This field is deriveable and might not have to be cached here. - */ - @Nullable - String getSeInfo(); - - /** * @see ApplicationInfo#secondaryCpuAbi */ @Nullable diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java index 5b0cc51c8531..c76b12983656 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java +++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java @@ -27,7 +27,6 @@ import android.content.pm.dex.DexMetadataHelper; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.os.incremental.IncrementalManager; -import android.text.TextUtils; import com.android.internal.content.NativeLibraryHelper; import com.android.internal.util.ArrayUtils; @@ -288,16 +287,6 @@ public class AndroidPackageUtils { return ((AndroidPackageHidden) pkg).getSecondaryCpuAbi(); } - public static String getSeInfo(AndroidPackage pkg, @Nullable PackageStateInternal pkgSetting) { - if (pkgSetting != null) { - String overrideSeInfo = pkgSetting.getTransientState().getOverrideSeInfo(); - if (!TextUtils.isEmpty(overrideSeInfo)) { - return overrideSeInfo; - } - } - return ((AndroidPackageHidden) pkg).getSeInfo(); - } - @Deprecated @NonNull public static ApplicationInfo generateAppInfoWithoutState(AndroidPackage pkg) { diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java index a43b9796dc76..ba36ab7a1f02 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java +++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java @@ -462,10 +462,6 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, @DataClass.ParcelWith(ForInternedString.class) protected String secondaryNativeLibraryDir; - @Nullable - @DataClass.ParcelWith(ForInternedString.class) - protected String seInfo; - /** * This is an appId, the uid if the userId is == USER_SYSTEM */ @@ -1339,6 +1335,11 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, } @Override + public UUID getStorageUuid() { + return mStorageUuid; + } + + @Override public int getTargetSandboxVersion() { return targetSandboxVersion; } @@ -2905,12 +2906,6 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, } @Override - public PackageImpl setSeInfo(@Nullable String seInfo) { - this.seInfo = TextUtils.safeIntern(seInfo); - return this; - } - - @Override public PackageImpl setSplitCodePaths(@Nullable String[] splitCodePaths) { this.splitCodePaths = splitCodePaths; if (splitCodePaths != null) { @@ -2993,7 +2988,6 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, appInfo.primaryCpuAbi = primaryCpuAbi; appInfo.secondaryCpuAbi = secondaryCpuAbi; appInfo.secondaryNativeLibraryDir = secondaryNativeLibraryDir; - appInfo.seInfo = seInfo; appInfo.seInfoUser = SELinuxUtil.COMPLETE_STR; appInfo.uid = uid; return appInfo; @@ -3147,7 +3141,6 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, sForInternedString.parcel(this.primaryCpuAbi, dest, flags); sForInternedString.parcel(this.secondaryCpuAbi, dest, flags); dest.writeString(this.secondaryNativeLibraryDir); - dest.writeString(this.seInfo); dest.writeInt(this.uid); dest.writeLong(this.mBooleans); dest.writeLong(this.mBooleans2); @@ -3307,7 +3300,6 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, this.primaryCpuAbi = sForInternedString.unparcel(in); this.secondaryCpuAbi = sForInternedString.unparcel(in); this.secondaryNativeLibraryDir = in.readString(); - this.seInfo = in.readString(); this.uid = in.readInt(); this.mBooleans = in.readLong(); this.mBooleans2 = in.readLong(); @@ -3377,12 +3369,6 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, return secondaryNativeLibraryDir; } - @Nullable - @Override - public String getSeInfo() { - return seInfo; - } - @Override public boolean isCoreApp() { return getBoolean(Booleans.CORE_APP); diff --git a/services/core/java/com/android/server/pm/parsing/pkg/ParsedPackage.java b/services/core/java/com/android/server/pm/parsing/pkg/ParsedPackage.java index d3063419bca0..aeaff6dd1458 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/ParsedPackage.java +++ b/services/core/java/com/android/server/pm/parsing/pkg/ParsedPackage.java @@ -103,8 +103,6 @@ public interface ParsedPackage extends AndroidPackage { ParsedPackage setRestrictUpdateHash(byte[] restrictUpdateHash); - ParsedPackage setSeInfo(String seInfo); - ParsedPackage setSecondaryNativeLibraryDir(String secondaryNativeLibraryDir); /** diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java index e3dad452c9f4..84907a57c03d 100644 --- a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java +++ b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java @@ -34,6 +34,7 @@ import android.content.pm.ProviderInfo; import android.content.pm.ServiceInfo; import android.content.pm.SigningDetails; import android.os.Bundle; +import android.os.storage.StorageManager; import android.processor.immutability.Immutable; import android.util.ArraySet; import android.util.Pair; @@ -58,6 +59,7 @@ import java.security.PublicKey; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.UUID; /** * The representation of an application on disk, as parsed from its split APKs' manifests. @@ -111,6 +113,13 @@ public interface AndroidPackage { String getStaticSharedLibraryName(); /** + * @return The {@link UUID} for use with {@link StorageManager} APIs identifying where this + * package was installed. + */ + @NonNull + UUID getStorageUuid(); + + /** * @see ApplicationInfo#targetSdkVersion * @see R.styleable#AndroidManifestUsesSdk_targetSdkVersion */ diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java index 3c79cdfa5c0e..e8d0640a675c 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageState.java +++ b/services/core/java/com/android/server/pm/pkg/PackageState.java @@ -131,6 +131,14 @@ public interface PackageState { String getSecondaryCpuAbi(); /** + * @see ApplicationInfo#seInfo + * @return The SE info for this package, which may be overridden by a system configured value, + * or null if the package isn't available. + */ + @Nullable + String getSeInfo(); + + /** * @see AndroidPackage#isPrivileged() */ boolean isPrivileged(); diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java index c6ce40e39604..e552a34be70d 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java +++ b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java @@ -129,6 +129,8 @@ public class PackageStateImpl implements PackageState { private final String mPrimaryCpuAbi; @Nullable private final String mSecondaryCpuAbi; + @Nullable + private final String mSeInfo; private final boolean mHasSharedUser; private final int mSharedUserAppId; @NonNull @@ -175,6 +177,7 @@ public class PackageStateImpl implements PackageState { mPath = pkgState.getPath(); mPrimaryCpuAbi = pkgState.getPrimaryCpuAbi(); mSecondaryCpuAbi = pkgState.getSecondaryCpuAbi(); + mSeInfo = pkgState.getSeInfo(); mHasSharedUser = pkgState.hasSharedUser(); mSharedUserAppId = pkgState.getSharedUserAppId(); mUsesSdkLibraries = pkgState.getUsesSdkLibraries(); @@ -542,7 +545,7 @@ public class PackageStateImpl implements PackageState { } @DataClass.Generated( - time = 1661977809886L, + time = 1665778832625L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java", inputSignatures = "private int mBooleans\nprivate final long mCeDataInode\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate final int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final long mFirstInstallTime\npublic static com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final int HIDDEN\nprivate static final int INSTALLED\nprivate static final int INSTANT_APP\nprivate static final int NOT_LAUNCHED\nprivate static final int STOPPED\nprivate static final int SUSPENDED\nprivate static final int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)") @@ -641,6 +644,11 @@ public class PackageStateImpl implements PackageState { } @DataClass.Generated.Member + public @Nullable String getSeInfo() { + return mSeInfo; + } + + @DataClass.Generated.Member public boolean isHasSharedUser() { return mHasSharedUser; } @@ -697,10 +705,10 @@ public class PackageStateImpl implements PackageState { } @DataClass.Generated( - time = 1661977809932L, + time = 1665778832668L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java", - inputSignatures = "private int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackage mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final int mAppId\nprivate final int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final long mLastModifiedTime\nprivate final long mLastUpdateTime\nprivate final long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final boolean mHasSharedUser\nprivate final int mSharedUserAppId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesSdkLibraries\nprivate final @android.annotation.NonNull long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibrary> mUsesLibraries\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserState> mUserStates\npublic static com.android.server.pm.pkg.PackageState copy(com.android.server.pm.pkg.PackageStateInternal)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @java.lang.Override boolean isVendor()\npublic @java.lang.Override long getVersionCode()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override int getSharedUserAppId()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final int SYSTEM\nprivate static final int EXTERNAL_STORAGE\nprivate static final int PRIVILEGED\nprivate static final int OEM\nprivate static final int VENDOR\nprivate static final int PRODUCT\nprivate static final int SYSTEM_EXT\nprivate static final int REQUIRED_FOR_SYSTEM_USER\nprivate static final int ODM\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nprivate static final int HIDDEN_UNTIL_INSTALLED\nprivate static final int INSTALL_PERMISSIONS_FIXED\nprivate static final int UPDATE_AVAILABLE\nprivate static final int UPDATED_SYSTEM_APP\nprivate static final int APK_IN_UPDATED_APEX\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)") + inputSignatures = "private int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackage mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final int mAppId\nprivate final int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final long mLastModifiedTime\nprivate final long mLastUpdateTime\nprivate final long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSeInfo\nprivate final boolean mHasSharedUser\nprivate final int mSharedUserAppId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesSdkLibraries\nprivate final @android.annotation.NonNull long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibrary> mUsesLibraries\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserState> mUserStates\npublic static com.android.server.pm.pkg.PackageState copy(com.android.server.pm.pkg.PackageStateInternal)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @java.lang.Override boolean isVendor()\npublic @java.lang.Override long getVersionCode()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override int getSharedUserAppId()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final int SYSTEM\nprivate static final int EXTERNAL_STORAGE\nprivate static final int PRIVILEGED\nprivate static final int OEM\nprivate static final int VENDOR\nprivate static final int PRODUCT\nprivate static final int SYSTEM_EXT\nprivate static final int REQUIRED_FOR_SYSTEM_USER\nprivate static final int ODM\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nprivate static final int HIDDEN_UNTIL_INSTALLED\nprivate static final int INSTALL_PERMISSIONS_FIXED\nprivate static final int UPDATE_AVAILABLE\nprivate static final int UPDATED_SYSTEM_APP\nprivate static final int APK_IN_UPDATED_APEX\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java b/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java index b22c0386c0c1..57fbfe91193b 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java +++ b/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java @@ -22,6 +22,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.PackageManager; import android.content.pm.SharedLibraryInfo; +import android.text.TextUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.DataClass; @@ -62,6 +63,9 @@ public class PackageStateUnserialized { @Nullable private String overrideSeInfo; + @NonNull + private String seInfo; + // TODO: Remove in favor of finer grained change notification @NonNull private final PackageSetting mPackageSetting; @@ -138,6 +142,7 @@ public class PackageStateUnserialized { this.apkInUpdatedApex = other.apkInUpdatedApex; this.lastPackageUsageTimeInMills = other.lastPackageUsageTimeInMills; this.overrideSeInfo = other.overrideSeInfo; + this.seInfo = other.seInfo; mPackageSetting.onChanged(); } @@ -206,6 +211,13 @@ public class PackageStateUnserialized { return this; } + @NonNull + public PackageStateUnserialized setSeInfo(@NonNull String value) { + seInfo = TextUtils.safeIntern(value); + mPackageSetting.onChanged(); + return this; + } + // Code below generated by codegen v1.0.23. @@ -271,15 +283,20 @@ public class PackageStateUnserialized { } @DataClass.Generated.Member + public @NonNull String getSeInfo() { + return seInfo; + } + + @DataClass.Generated.Member public @NonNull PackageSetting getPackageSetting() { return mPackageSetting; } @DataClass.Generated( - time = 1661373697219L, + time = 1666291743725L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java", - inputSignatures = "private boolean hiddenUntilInstalled\nprivate @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibraryWrapper> usesLibraryInfos\nprivate @android.annotation.NonNull java.util.List<java.lang.String> usesLibraryFiles\nprivate boolean updatedSystemApp\nprivate boolean apkInApex\nprivate boolean apkInUpdatedApex\nprivate volatile @android.annotation.NonNull long[] lastPackageUsageTimeInMills\nprivate @android.annotation.Nullable java.lang.String overrideSeInfo\nprivate final @android.annotation.NonNull com.android.server.pm.PackageSetting mPackageSetting\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized addUsesLibraryInfo(com.android.server.pm.pkg.SharedLibraryWrapper)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized addUsesLibraryFile(java.lang.String)\nprivate long[] lazyInitLastPackageUsageTimeInMills()\npublic com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(int,long)\npublic long getLatestPackageUseTimeInMills()\npublic long getLatestForegroundPackageUseTimeInMills()\npublic void updateFrom(com.android.server.pm.pkg.PackageStateUnserialized)\npublic @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> getNonNativeUsesLibraryInfos()\npublic com.android.server.pm.pkg.PackageStateUnserialized setHiddenUntilInstalled(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryInfos(java.util.List<android.content.pm.SharedLibraryInfo>)\npublic com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryFiles(java.util.List<java.lang.String>)\npublic com.android.server.pm.pkg.PackageStateUnserialized setUpdatedSystemApp(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setApkInApex(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setApkInUpdatedApex(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(long)\npublic com.android.server.pm.pkg.PackageStateUnserialized setOverrideSeInfo(java.lang.String)\nclass PackageStateUnserialized extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genConstructor=false, genBuilder=false)") + inputSignatures = "private boolean hiddenUntilInstalled\nprivate @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibraryWrapper> usesLibraryInfos\nprivate @android.annotation.NonNull java.util.List<java.lang.String> usesLibraryFiles\nprivate boolean updatedSystemApp\nprivate boolean apkInApex\nprivate boolean apkInUpdatedApex\nprivate volatile @android.annotation.NonNull long[] lastPackageUsageTimeInMills\nprivate @android.annotation.Nullable java.lang.String overrideSeInfo\nprivate @android.annotation.NonNull java.lang.String seInfo\nprivate final @android.annotation.NonNull com.android.server.pm.PackageSetting mPackageSetting\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized addUsesLibraryInfo(com.android.server.pm.pkg.SharedLibraryWrapper)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized addUsesLibraryFile(java.lang.String)\nprivate long[] lazyInitLastPackageUsageTimeInMills()\npublic com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(int,long)\npublic long getLatestPackageUseTimeInMills()\npublic long getLatestForegroundPackageUseTimeInMills()\npublic void updateFrom(com.android.server.pm.pkg.PackageStateUnserialized)\npublic @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> getNonNativeUsesLibraryInfos()\npublic com.android.server.pm.pkg.PackageStateUnserialized setHiddenUntilInstalled(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryInfos(java.util.List<android.content.pm.SharedLibraryInfo>)\npublic com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryFiles(java.util.List<java.lang.String>)\npublic com.android.server.pm.pkg.PackageStateUnserialized setUpdatedSystemApp(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setApkInApex(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setApkInUpdatedApex(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(long)\npublic com.android.server.pm.pkg.PackageStateUnserialized setOverrideSeInfo(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized setSeInfo(java.lang.String)\nclass PackageStateUnserialized extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genConstructor=false, genBuilder=false)") @Deprecated private void __metadata() {} 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/utils/Slogf.java b/services/core/java/com/android/server/utils/Slogf.java index e88ac63615ca..6efbd89daf4c 100644 --- a/services/core/java/com/android/server/utils/Slogf.java +++ b/services/core/java/com/android/server/utils/Slogf.java @@ -162,7 +162,7 @@ public final class Slogf { } /** - * Logs a {@link Log.VEBOSE} message with an exception + * Logs a {@link Log.VEBOSE} message with a throwable * * <p><strong>Note: </strong>the message will only be formatted if {@link Log#VERBOSE} logging * is enabled for the given {@code tag}, but the compiler will still create an intermediate @@ -170,10 +170,10 @@ public final class Slogf { * you're calling this method in a critical path, make sure to explicitly do the check before * calling it. */ - public static void v(String tag, Exception exception, String format, @Nullable Object... args) { + public static void v(String tag, Throwable throwable, String format, @Nullable Object... args) { if (!isLoggable(tag, Log.VERBOSE)) return; - v(tag, getMessage(format, args), exception); + v(tag, getMessage(format, args), throwable); } /** @@ -192,7 +192,7 @@ public final class Slogf { } /** - * Logs a {@link Log.DEBUG} message with an exception + * Logs a {@link Log.DEBUG} message with a throwable * * <p><strong>Note: </strong>the message will only be formatted if {@link Log#DEBUG} logging * is enabled for the given {@code tag}, but the compiler will still create an intermediate @@ -200,10 +200,10 @@ public final class Slogf { * you're calling this method in a critical path, make sure to explicitly do the check before * calling it. */ - public static void d(String tag, Exception exception, String format, @Nullable Object... args) { + public static void d(String tag, Throwable throwable, String format, @Nullable Object... args) { if (!isLoggable(tag, Log.DEBUG)) return; - d(tag, getMessage(format, args), exception); + d(tag, getMessage(format, args), throwable); } /** @@ -222,7 +222,7 @@ public final class Slogf { } /** - * Logs a {@link Log.INFO} message with an exception + * Logs a {@link Log.INFO} message with a throwable * * <p><strong>Note: </strong>the message will only be formatted if {@link Log#INFO} logging * is enabled for the given {@code tag}, but the compiler will still create an intermediate @@ -230,10 +230,10 @@ public final class Slogf { * you're calling this method in a critical path, make sure to explicitly do the check before * calling it. */ - public static void i(String tag, Exception exception, String format, @Nullable Object... args) { + public static void i(String tag, Throwable throwable, String format, @Nullable Object... args) { if (!isLoggable(tag, Log.INFO)) return; - i(tag, getMessage(format, args), exception); + i(tag, getMessage(format, args), throwable); } /** @@ -252,7 +252,7 @@ public final class Slogf { } /** - * Logs a {@link Log.WARN} message with an exception + * Logs a {@link Log.WARN} message with a throwable * * <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is * enabled for the given {@code tag}, but the compiler will still create an intermediate array @@ -260,10 +260,10 @@ public final class Slogf { * calling this method in a critical path, make sure to explicitly do the check before calling * it. */ - public static void w(String tag, Exception exception, String format, @Nullable Object... args) { + public static void w(String tag, Throwable throwable, String format, @Nullable Object... args) { if (!isLoggable(tag, Log.WARN)) return; - w(tag, getMessage(format, args), exception); + w(tag, getMessage(format, args), throwable); } /** @@ -282,7 +282,7 @@ public final class Slogf { } /** - * Logs a {@link Log.ERROR} message with an exception + * Logs a {@link Log.ERROR} message with a throwable * * <p><strong>Note: </strong>the message will only be formatted if {@link Log#ERROR} logging is * enabled for the given {@code tag}, but the compiler will still create an intermediate array @@ -290,10 +290,10 @@ public final class Slogf { * calling this method in a critical path, make sure to explicitly do the check before calling * it. */ - public static void e(String tag, Exception exception, String format, @Nullable Object... args) { + public static void e(String tag, Throwable throwable, String format, @Nullable Object... args) { if (!isLoggable(tag, Log.ERROR)) return; - e(tag, getMessage(format, args), exception); + e(tag, getMessage(format, args), throwable); } /** @@ -304,11 +304,11 @@ public final class Slogf { } /** - * Logs a {@code wtf} message with an exception. + * Logs a {@code wtf} message with a throwable. */ - public static void wtf(String tag, Exception exception, String format, + public static void wtf(String tag, Throwable throwable, String format, @Nullable Object... args) { - wtf(tag, getMessage(format, args), exception); + wtf(tag, getMessage(format, args), throwable); } private static String getMessage(String format, @Nullable Object... args) { 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/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 530fa4d6d497..12424c0c6a6f 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -3317,9 +3317,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mAtmService.mUgmInternal.grantUriPermissionUncheckedFromIntent(resultGrants, resultTo.getUriPermissionsLocked()); } - if (mForceSendResultForMediaProjection) { - resultTo.sendResult(this.getUid(), resultWho, requestCode, resultCode, - resultData, resultGrants, true /* forceSendForMediaProjection */); + if (mForceSendResultForMediaProjection || resultTo.isState(RESUMED)) { + // Sending the result to the resultTo activity asynchronously to prevent the + // resultTo activity getting results before this Activity paused. + final ActivityRecord resultToActivity = resultTo; + mAtmService.mH.post(() -> { + synchronized (mAtmService.mGlobalLock) { + resultToActivity.sendResult(this.getUid(), resultWho, requestCode, + resultCode, resultData, resultGrants, + mForceSendResultForMediaProjection); + } + }); } else { resultTo.addResultLocked(this, resultWho, requestCode, resultCode, resultData); } @@ -4630,7 +4638,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A false /* forceSendForMediaProjection */); } - private void sendResult(int callingUid, String resultWho, int requestCode, int resultCode, + void sendResult(int callingUid, String resultWho, int requestCode, int resultCode, Intent data, NeededUriGrants dataGrants, boolean forceSendForMediaProjection) { if (callingUid > 0) { mAtmService.mUgmInternal.grantUriPermissionUncheckedFromIntent(dataGrants, diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 5938e7f095ea..0b16a4d47a7d 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -78,7 +78,6 @@ import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT; import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED; import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION; import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK; -import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT; import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST; import static com.android.server.wm.WindowContainer.POSITION_TOP; @@ -2777,11 +2776,6 @@ class ActivityStarter { errMsg = "The app:" + mCallingUid + "is not trusted to " + mStartActivity; break; } - case EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT: { - errMsg = "Cannot embed activity across TaskFragments for result, resultTo: " - + mStartActivity.resultTo; - break; - } default: errMsg = "Unhandled embed result:" + result; } 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..d1122e14c57b 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -207,6 +207,7 @@ import android.view.ContentRecordingSession; import android.view.Display; import android.view.DisplayCutout; import android.view.DisplayInfo; +import android.view.DisplayShape; import android.view.Gravity; import android.view.IDisplayWindowInsetsController; import android.view.ISystemGestureExclusionListener; @@ -391,6 +392,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mPrivacyIndicatorBoundsCache = new RotationCache<>(this::calculatePrivacyIndicatorBoundsForRotationUncached); + DisplayShape mInitialDisplayShape; + private final RotationCache<DisplayShape, DisplayShape> mDisplayShapeCache = + new RotationCache<>(this::calculateDisplayShapeForRotationUncached); + /** * Overridden display size. Initialized with {@link #mInitialDisplayWidth} * and {@link #mInitialDisplayHeight}, but can be set via shell command "adb shell wm size". @@ -1095,7 +1100,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mDisplayFrames = new DisplayFrames(mInsetsStateController.getRawInsetsState(), mDisplayInfo, calculateDisplayCutoutForRotation(mDisplayInfo.rotation), calculateRoundedCornersForRotation(mDisplayInfo.rotation), - calculatePrivacyIndicatorBoundsForRotation(mDisplayInfo.rotation)); + calculatePrivacyIndicatorBoundsForRotation(mDisplayInfo.rotation), + calculateDisplayShapeForRotation(mDisplayInfo.rotation)); initializeDisplayBaseInfo(); mHoldScreenWakeLock = mWmService.mPowerManager.newWakeLock( @@ -1969,8 +1975,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation); final PrivacyIndicatorBounds indicatorBounds = calculatePrivacyIndicatorBoundsForRotation(rotation); + final DisplayShape displayShape = calculateDisplayShapeForRotation(rotation); final DisplayFrames displayFrames = new DisplayFrames(new InsetsState(), info, - cutout, roundedCorners, indicatorBounds); + cutout, roundedCorners, indicatorBounds, displayShape); token.applyFixedRotationTransform(info, displayFrames, mTmpConfiguration); } @@ -2178,6 +2185,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // Update application display metrics. final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(rotation); final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation); + final DisplayShape displayShape = calculateDisplayShapeForRotation(rotation); final Rect appFrame = mDisplayPolicy.getDecorInsetsInfo(rotation, dw, dh).mNonDecorFrame; mDisplayInfo.rotation = rotation; @@ -2194,6 +2202,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } mDisplayInfo.displayCutout = displayCutout.isEmpty() ? null : displayCutout; mDisplayInfo.roundedCorners = roundedCorners; + mDisplayInfo.displayShape = displayShape; mDisplayInfo.getAppMetrics(mDisplayMetrics); if (mDisplayScalingDisabled) { mDisplayInfo.flags |= Display.FLAG_SCALING_DISABLED; @@ -2280,6 +2289,23 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return bounds.rotate(rotation); } + DisplayShape calculateDisplayShapeForRotation(int rotation) { + return mDisplayShapeCache.getOrCompute(mInitialDisplayShape, rotation); + } + + private DisplayShape calculateDisplayShapeForRotationUncached( + DisplayShape displayShape, int rotation) { + if (displayShape == null) { + return DisplayShape.NONE; + } + + if (rotation == ROTATION_0) { + return displayShape; + } + + return displayShape.setRotation(rotation); + } + /** * Compute display info and configuration according to the given rotation without changing * current display. @@ -2781,7 +2807,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return displayFrames.update(rotation, w, h, calculateDisplayCutoutForRotation(rotation), calculateRoundedCornersForRotation(rotation), - calculatePrivacyIndicatorBoundsForRotation(rotation)); + calculatePrivacyIndicatorBoundsForRotation(rotation), + calculateDisplayShapeForRotation(rotation)); } @Override @@ -2821,6 +2848,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mInitialRoundedCorners = mDisplayInfo.roundedCorners; mCurrentPrivacyIndicatorBounds = new PrivacyIndicatorBounds(new Rect[4], mDisplayInfo.rotation); + mInitialDisplayShape = mDisplayInfo.displayShape; final Display.Mode maxDisplayMode = DisplayUtils.getMaximumResolutionDisplayMode(mDisplayInfo.supportedModes); mPhysicalDisplaySize = new Point( @@ -2848,6 +2876,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp ? DisplayCutout.NO_CUTOUT : mDisplayInfo.displayCutout; final String newUniqueId = mDisplayInfo.uniqueId; final RoundedCorners newRoundedCorners = mDisplayInfo.roundedCorners; + final DisplayShape newDisplayShape = mDisplayInfo.displayShape; final boolean displayMetricsChanged = mInitialDisplayWidth != newWidth || mInitialDisplayHeight != newHeight @@ -2855,7 +2884,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp || mInitialPhysicalXDpi != newXDpi || mInitialPhysicalYDpi != newYDpi || !Objects.equals(mInitialDisplayCutout, newCutout) - || !Objects.equals(mInitialRoundedCorners, newRoundedCorners); + || !Objects.equals(mInitialRoundedCorners, newRoundedCorners) + || !Objects.equals(mInitialDisplayShape, newDisplayShape); final boolean physicalDisplayChanged = !newUniqueId.equals(mCurrentUniqueDisplayId); if (displayMetricsChanged || physicalDisplayChanged) { @@ -2893,6 +2923,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mInitialPhysicalYDpi = newYDpi; mInitialDisplayCutout = newCutout; mInitialRoundedCorners = newRoundedCorners; + mInitialDisplayShape = newDisplayShape; mCurrentUniqueDisplayId = newUniqueId; reconfigureDisplayLocked(); @@ -3380,7 +3411,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 +3714,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 +6346,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/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java index 33641f72b2ff..e984456161e4 100644 --- a/services/core/java/com/android/server/wm/DisplayFrames.java +++ b/services/core/java/com/android/server/wm/DisplayFrames.java @@ -26,6 +26,7 @@ import android.graphics.Rect; import android.util.proto.ProtoOutputStream; import android.view.DisplayCutout; import android.view.DisplayInfo; +import android.view.DisplayShape; import android.view.InsetsState; import android.view.PrivacyIndicatorBounds; import android.view.RoundedCorners; @@ -56,10 +57,11 @@ public class DisplayFrames { public int mRotation; public DisplayFrames(InsetsState insetsState, DisplayInfo info, DisplayCutout cutout, - RoundedCorners roundedCorners, PrivacyIndicatorBounds indicatorBounds) { + RoundedCorners roundedCorners, PrivacyIndicatorBounds indicatorBounds, + DisplayShape displayShape) { mInsetsState = insetsState; update(info.rotation, info.logicalWidth, info.logicalHeight, cutout, roundedCorners, - indicatorBounds); + indicatorBounds, displayShape); } DisplayFrames() { @@ -73,7 +75,8 @@ public class DisplayFrames { */ public boolean update(int rotation, int w, int h, @NonNull DisplayCutout displayCutout, @NonNull RoundedCorners roundedCorners, - @NonNull PrivacyIndicatorBounds indicatorBounds) { + @NonNull PrivacyIndicatorBounds indicatorBounds, + @NonNull DisplayShape displayShape) { final InsetsState state = mInsetsState; final Rect safe = mDisplayCutoutSafe; if (mRotation == rotation && mWidth == w && mHeight == h @@ -91,6 +94,7 @@ public class DisplayFrames { state.setDisplayCutout(displayCutout); state.setRoundedCorners(roundedCorners); state.setPrivacyIndicatorBounds(indicatorBounds); + state.setDisplayShape(displayShape); state.getDisplayCutoutSafe(safe); if (safe.left > unrestricted.left) { state.getSource(ITYPE_LEFT_DISPLAY_CUTOUT).setFrame( 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/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 91cb037a386e..d3a3cf5c6468 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -172,13 +172,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { * indicate that an Activity can't be embedded because the Activity is started on a new task. */ static final int EMBEDDING_DISALLOWED_NEW_TASK = 3; - /** - * An embedding check result of - * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}: - * indicate that an Activity can't be embedded because the Activity is started on a new - * TaskFragment, e.g. start an Activity on a new TaskFragment for result. - */ - static final int EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT = 4; /** * Embedding check results of {@link #isAllowedToEmbedActivity(ActivityRecord)} or @@ -189,7 +182,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { EMBEDDING_DISALLOWED_UNTRUSTED_HOST, EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION, EMBEDDING_DISALLOWED_NEW_TASK, - EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT, }) @interface EmbeddingCheckResult {} @@ -616,14 +608,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { return EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION; } - // Cannot embed activity across TaskFragments for activity result. - // If the activity that started for result is finishing, it's likely that this start mode - // is used to place an activity in the same task. Since the finishing activity won't be - // able to get the results, so it's OK to embed in a different TaskFragment. - if (a.resultTo != null && !a.resultTo.finishing && a.resultTo.getTaskFragment() != this) { - return EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT; - } - return EMBEDDING_ALLOWED; } 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/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt index 1f66a1180aeb..7bf9a9ef121f 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt @@ -17,7 +17,12 @@ package com.android.server.pm.test.parsing.parcelling import android.content.Intent -import android.content.pm.* +import android.content.pm.ApplicationInfo +import android.content.pm.ConfigurationInfo +import android.content.pm.FeatureGroupInfo +import android.content.pm.FeatureInfo +import android.content.pm.PackageManager +import android.content.pm.SigningDetails import android.net.Uri import android.os.Bundle import android.os.Parcelable @@ -27,16 +32,32 @@ import android.util.SparseIntArray import com.android.internal.R import com.android.server.pm.parsing.pkg.PackageImpl import com.android.server.pm.pkg.AndroidPackage -import com.android.server.pm.pkg.component.* +import com.android.server.pm.pkg.component.ParsedActivityImpl +import com.android.server.pm.pkg.component.ParsedApexSystemServiceImpl +import com.android.server.pm.pkg.component.ParsedAttributionImpl +import com.android.server.pm.pkg.component.ParsedComponentImpl +import com.android.server.pm.pkg.component.ParsedInstrumentationImpl +import com.android.server.pm.pkg.component.ParsedIntentInfoImpl +import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl +import com.android.server.pm.pkg.component.ParsedPermissionImpl +import com.android.server.pm.pkg.component.ParsedProcessImpl +import com.android.server.pm.pkg.component.ParsedProviderImpl +import com.android.server.pm.pkg.component.ParsedServiceImpl +import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl import com.android.server.testutils.mockThrowOnUnmocked import com.android.server.testutils.whenever import java.security.KeyPairGenerator import java.security.PublicKey +import java.util.UUID import kotlin.contracts.ExperimentalContracts @ExperimentalContracts class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, PackageImpl::class) { + companion object { + private val TEST_UUID = UUID.fromString("57554103-df3e-4475-ae7a-8feba49353ac") + } + override val defaultImpl = PackageImpl.forTesting("com.example.test") override val creator = PackageImpl.CREATOR @@ -72,8 +93,6 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag "getLongVersionCode", // Tested through constructor "getManifestPackageName", - // Utility methods - "getStorageUuid", // Removal not tested, irrelevant for parcelling concerns "removeUsesOptionalLibrary", "clearAdoptPermissions", @@ -85,6 +104,7 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag // Tested manually "getMimeGroups", "getRequestedPermissions", + "getStorageUuid", // Tested through asSplit "asSplit", "getSplits", @@ -155,7 +175,6 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag AndroidPackage::getResizeableActivity, AndroidPackage::getRestrictedAccountType, AndroidPackage::getRoundIconRes, - PackageImpl::getSeInfo, PackageImpl::getSecondaryCpuAbi, AndroidPackage::getSecondaryNativeLibraryDir, AndroidPackage::getSharedUserId, @@ -241,7 +260,7 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag ) override fun extraParams() = listOf( - getter(AndroidPackage::getVolumeUuid, "57554103-df3e-4475-ae7a-8feba49353ac"), + getter(AndroidPackage::getVolumeUuid, TEST_UUID.toString()), getter(AndroidPackage::isProfileable, true), getter(PackageImpl::getVersionCode, 3), getter(PackageImpl::getVersionCodeMajor, 9), @@ -602,6 +621,8 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag expect.that(after.usesStaticLibrariesCertDigests!!.size).isEqualTo(1) expect.that(after.usesStaticLibrariesCertDigests!![0]).asList() .containsExactly("testCertDigest2") + + expect.that(after.storageUuid).isEqualTo(TEST_UUID) } private fun testKey() = KeyPairGenerator.getInstance("RSA") diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java index 51fa8517e45f..e7c384b48152 100644 --- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java @@ -252,6 +252,7 @@ public class RescuePartyTest { noteBoot(4); assertTrue(RescueParty.isRebootPropertySet()); + SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false)); noteBoot(5); assertTrue(RescueParty.isFactoryResetPropertySet()); } @@ -276,6 +277,7 @@ public class RescuePartyTest { noteAppCrash(4, true); assertTrue(RescueParty.isRebootPropertySet()); + SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false)); noteAppCrash(5, true); assertTrue(RescueParty.isFactoryResetPropertySet()); } @@ -429,6 +431,27 @@ public class RescuePartyTest { for (int i = 0; i < LEVEL_FACTORY_RESET; i++) { noteBoot(i + 1); } + assertFalse(RescueParty.isFactoryResetPropertySet()); + SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false)); + noteBoot(LEVEL_FACTORY_RESET + 1); + assertTrue(RescueParty.isAttemptingFactoryReset()); + assertTrue(RescueParty.isFactoryResetPropertySet()); + } + + @Test + public void testIsAttemptingFactoryResetOnlyAfterRebootCompleted() { + for (int i = 0; i < LEVEL_FACTORY_RESET; i++) { + noteBoot(i + 1); + } + int mitigationCount = LEVEL_FACTORY_RESET + 1; + assertFalse(RescueParty.isFactoryResetPropertySet()); + noteBoot(mitigationCount++); + assertFalse(RescueParty.isFactoryResetPropertySet()); + noteBoot(mitigationCount++); + assertFalse(RescueParty.isFactoryResetPropertySet()); + noteBoot(mitigationCount++); + SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false)); + noteBoot(mitigationCount + 1); assertTrue(RescueParty.isAttemptingFactoryReset()); assertTrue(RescueParty.isFactoryResetPropertySet()); } diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java new file mode 100644 index 000000000000..ea14ffbe3572 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java @@ -0,0 +1,282 @@ +/* + * 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.am; + +import static android.os.Process.myPid; +import static android.os.Process.myUid; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.app.ActivityManagerInternal; +import android.app.IApplicationThread; +import android.app.usage.UsageStatsManagerInternal; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManagerInternal; +import android.os.Binder; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.SystemClock; +import android.util.Log; + +import androidx.test.filters.MediumTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.DropBoxManagerInternal; +import com.android.server.LocalServices; +import com.android.server.am.ActivityManagerService.Injector; +import com.android.server.appop.AppOpsService; +import com.android.server.wm.ActivityTaskManagerInternal; +import com.android.server.wm.ActivityTaskManagerService; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.File; +import java.util.Arrays; + + +/** + * Tests to verify process starts are completed or timeout correctly + */ +@MediumTest +@SuppressWarnings("GuardedBy") +public class AsyncProcessStartTest { + private static final String TAG = "AsyncProcessStartTest"; + + private static final String PACKAGE = "com.foo"; + + @Rule + public final ApplicationExitInfoTest.ServiceThreadRule + mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule(); + + private Context mContext; + private HandlerThread mHandlerThread; + + @Mock + private AppOpsService mAppOpsService; + @Mock + private DropBoxManagerInternal mDropBoxManagerInt; + @Mock + private PackageManagerInternal mPackageManagerInt; + @Mock + private UsageStatsManagerInternal mUsageStatsManagerInt; + @Mock + private ActivityManagerInternal mActivityManagerInt; + @Mock + private ActivityTaskManagerInternal mActivityTaskManagerInt; + @Mock + private BatteryStatsService mBatteryStatsService; + + private ActivityManagerService mRealAms; + private ActivityManagerService mAms; + + private ProcessList mRealProcessList = new ProcessList(); + private ProcessList mProcessList; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + + mHandlerThread = new HandlerThread(TAG); + mHandlerThread.start(); + + LocalServices.removeServiceForTest(DropBoxManagerInternal.class); + LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt); + + LocalServices.removeServiceForTest(PackageManagerInternal.class); + LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt); + + LocalServices.removeServiceForTest(ActivityManagerInternal.class); + LocalServices.addService(ActivityManagerInternal.class, mActivityManagerInt); + + LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class); + LocalServices.addService(ActivityTaskManagerInternal.class, mActivityTaskManagerInt); + + doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent(); + doReturn(true).when(mActivityTaskManagerInt).attachApplication(any()); + doNothing().when(mActivityTaskManagerInt).onProcessMapped(anyInt(), any()); + + mRealAms = new ActivityManagerService( + new TestInjector(mContext), mServiceThreadRule.getThread()); + mRealAms.mActivityTaskManager = new ActivityTaskManagerService(mContext); + mRealAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper()); + mRealAms.mAtmInternal = mActivityTaskManagerInt; + mRealAms.mPackageManagerInt = mPackageManagerInt; + mRealAms.mUsageStatsService = mUsageStatsManagerInt; + mRealAms.mProcessesReady = true; + mAms = spy(mRealAms); + mRealProcessList.mService = mAms; + mProcessList = spy(mRealProcessList); + + doAnswer((invocation) -> { + Log.v(TAG, "Intercepting isProcStartValidLocked() for " + + Arrays.toString(invocation.getArguments())); + return null; + }).when(mProcessList).isProcStartValidLocked(any(), anyLong()); + } + + @After + public void tearDown() throws Exception { + mHandlerThread.quit(); + } + + private class TestInjector extends Injector { + TestInjector(Context context) { + super(context); + } + + @Override + public AppOpsService getAppOpsService(File file, Handler handler) { + return mAppOpsService; + } + + @Override + public Handler getUiHandler(ActivityManagerService service) { + return mHandlerThread.getThreadHandler(); + } + + @Override + public ProcessList getProcessList(ActivityManagerService service) { + return mRealProcessList; + } + + @Override + public BatteryStatsService getBatteryStatsService() { + return mBatteryStatsService; + } + } + + private ProcessRecord makeActiveProcessRecord(String packageName, boolean wedge) + throws Exception { + final ApplicationInfo ai = makeApplicationInfo(packageName); + return makeActiveProcessRecord(ai, wedge); + } + + private ProcessRecord makeActiveProcessRecord(ApplicationInfo ai, boolean wedge) + throws Exception { + final IApplicationThread thread = mock(IApplicationThread.class); + final IBinder threadBinder = new Binder(); + doReturn(threadBinder).when(thread).asBinder(); + doAnswer((invocation) -> { + Log.v(TAG, "Intercepting bindApplication() for " + + Arrays.toString(invocation.getArguments())); + if (!wedge) { + mRealAms.finishAttachApplication(0); + } + return null; + }).when(thread).bindApplication( + any(), any(), + any(), any(), + any(), any(), + any(), any(), + any(), + any(), anyInt(), + anyBoolean(), anyBoolean(), + anyBoolean(), anyBoolean(), any(), + any(), any(), any(), + any(), any(), + any(), any(), + any(), + anyLong(), anyLong()); + + final ProcessRecord r = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid)); + r.setPid(myPid()); + r.setStartUid(myUid()); + r.setHostingRecord(new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST)); + r.makeActive(thread, mAms.mProcessStats); + doNothing().when(r).killLocked(any(), any(), anyInt(), anyInt(), anyBoolean(), + anyBoolean()); + + return r; + } + + static ApplicationInfo makeApplicationInfo(String packageName) { + final ApplicationInfo ai = new ApplicationInfo(); + ai.packageName = packageName; + ai.processName = packageName; + ai.uid = myUid(); + return ai; + } + + /** + * Verify that we don't kill a normal process + */ + @Test + public void testNormal() throws Exception { + ProcessRecord app = startProcessAndWait(false); + + verify(app, never()).killLocked(any(), anyInt(), anyBoolean()); + } + + /** + * Verify that we kill a wedged process after the process start timeout + */ + @Test + public void testWedged() throws Exception { + ProcessRecord app = startProcessAndWait(true); + + verify(app).killLocked(any(), anyInt(), anyBoolean()); + } + + private ProcessRecord startProcessAndWait(boolean wedge) throws Exception { + final ProcessRecord app = makeActiveProcessRecord(PACKAGE, wedge); + final ApplicationInfo appInfo = makeApplicationInfo(PACKAGE); + + mProcessList.handleProcessStartedLocked(app, app.getPid(), /* usingWrapper */ false, + /* expectedStartSeq */ 0, /* procAttached */ false); + + app.getThread().bindApplication(PACKAGE, appInfo, + null, null, + null, + null, + null, null, + null, + null, 0, + false, false, + true, false, + null, + null, null, + null, + null, null, null, + null, null, + 0, 0); + + // Sleep until timeout should have triggered + SystemClock.sleep(ActivityManagerService.PROC_START_TIMEOUT + 1000); + + return app; + } +} 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/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java index dc77762795c7..8d2e1ecf0802 100644 --- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java @@ -17,6 +17,7 @@ package com.android.server.app; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.server.app.GameManagerService.CANCEL_GAME_LOADING_MODE; import static com.android.server.app.GameManagerService.WRITE_SETTINGS; import static org.junit.Assert.assertArrayEquals; @@ -1454,35 +1455,78 @@ public class GameManagerServiceTests { verify(mMockPowerManager, never()).setPowerMode(anyInt(), anyBoolean()); } - private void setGameState(boolean isLoading) { + @Test + public void testSetGameStateLoading_withNoDeviceConfig() { mockDeviceConfigNone(); mockModifyGameModeGranted(); - GameManagerService gameManagerService = - new GameManagerService(mMockContext, mTestLooper.getLooper()); - startUser(gameManagerService, USER_ID_1); + GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1); gameManagerService.setGameMode( mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1); - int testMode = GameState.MODE_NONE; + assertEquals(gameManagerService.getGameMode(mPackageName, USER_ID_1), + GameManager.GAME_MODE_PERFORMANCE); + int testMode = GameState.MODE_GAMEPLAY_INTERRUPTIBLE; int testLabel = 99; int testQuality = 123; - GameState gameState = new GameState(isLoading, testMode, testLabel, testQuality); - assertEquals(isLoading, gameState.isLoading()); + GameState gameState = new GameState(true, testMode, testLabel, testQuality); assertEquals(testMode, gameState.getMode()); assertEquals(testLabel, gameState.getLabel()); assertEquals(testQuality, gameState.getQuality()); gameManagerService.setGameState(mPackageName, gameState, USER_ID_1); mTestLooper.dispatchAll(); - verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, isLoading); + verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, true); + reset(mMockPowerManager); + assertTrue( + gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE)); + verify(mMockPowerManager, never()).setPowerMode(Mode.GAME_LOADING, false); + mTestLooper.moveTimeForward(GameManagerService.LOADING_BOOST_MAX_DURATION); + mTestLooper.dispatchAll(); + verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false); } @Test - public void testSetGameStateLoading() { - setGameState(true); + public void testSetGameStateLoading_withDeviceConfig() { + String configString = "mode=2,loadingBoost=2000"; + when(DeviceConfig.getProperty(anyString(), anyString())) + .thenReturn(configString); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1); + gameManagerService.setGameMode( + mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1); + GameState gameState = new GameState(true, GameState.MODE_GAMEPLAY_INTERRUPTIBLE, 99, 123); + gameManagerService.setGameState(mPackageName, gameState, USER_ID_1); + mTestLooper.dispatchAll(); + verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, true); + verify(mMockPowerManager, never()).setPowerMode(Mode.GAME_LOADING, false); + reset(mMockPowerManager); + assertTrue( + gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE)); + mTestLooper.moveTimeForward(2000); + mTestLooper.dispatchAll(); + verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false); } @Test public void testSetGameStateNotLoading() { - setGameState(false); + mockDeviceConfigNone(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = + new GameManagerService(mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + gameManagerService.setGameMode( + mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1); + int testMode = GameState.MODE_GAMEPLAY_UNINTERRUPTIBLE; + int testLabel = 99; + int testQuality = 123; + GameState gameState = new GameState(false, testMode, testLabel, testQuality); + assertFalse(gameState.isLoading()); + assertEquals(testMode, gameState.getMode()); + assertEquals(testLabel, gameState.getLabel()); + assertEquals(testQuality, gameState.getQuality()); + gameManagerService.setGameState(mPackageName, gameState, USER_ID_1); + mTestLooper.dispatchAll(); + verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false); + assertFalse( + gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE)); } private List<String> readGameModeInterventionList() throws Exception { 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/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java index d41ac7021077..395e6ac1017d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -161,6 +161,10 @@ public class LocalDisplayAdapterTest { when(mMockedResources.getIntArray( com.android.internal.R.array.config_autoBrightnessLevels)) .thenReturn(new int[]{}); + when(mMockedResources.obtainTypedArray(R.array.config_displayShapeArray)) + .thenReturn(mockArray); + when(mMockedResources.obtainTypedArray(R.array.config_builtInDisplayIsRoundArray)) + .thenReturn(mockArray); } @After 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/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt index dd6c7334a45a..27d0662d118e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt @@ -139,7 +139,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { .strictness(Strictness.LENIENT) .mockStatic(SystemProperties::class.java) .mockStatic(SystemConfig::class.java) - .mockStatic(SELinuxMMAC::class.java) + .mockStatic(SELinuxMMAC::class.java, Mockito.CALLS_REAL_METHODS) .mockStatic(FallbackCategoryProvider::class.java) .mockStatic(PackageManagerServiceUtils::class.java) .mockStatic(Environment::class.java) 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/mockingservicestests/src/com/android/server/utils/SlogfTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java index ae25c1bb3db8..cb59d37e46ec 100644 --- a/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java @@ -44,7 +44,7 @@ public final class SlogfTest { private MockitoSession mSession; - private final Exception mException = new Exception("D'OH!"); + private final Throwable mThrowable = new Throwable("D'OH!"); @Before public void setup() { @@ -78,10 +78,10 @@ public final class SlogfTest { } @Test - public void testV_msgAndException() { - Slogf.v(TAG, "msg", mException); + public void testV_msgAndThrowable() { + Slogf.v(TAG, "msg", mThrowable); - verify(()-> Slog.v(TAG, "msg", mException)); + verify(()-> Slog.v(TAG, "msg", mThrowable)); } @Test @@ -103,12 +103,12 @@ public final class SlogfTest { } @Test - public void testV_msgFormattedWithException_enabled() { + public void testV_msgFormattedWithThrowable_enabled() { enableLogging(Log.VERBOSE); - Slogf.v(TAG, mException, "msg in a %s", "bottle"); + Slogf.v(TAG, mThrowable, "msg in a %s", "bottle"); - verify(()-> Slog.v(TAG, "msg in a bottle", mException)); + verify(()-> Slog.v(TAG, "msg in a bottle", mThrowable)); } @Test @@ -128,10 +128,10 @@ public final class SlogfTest { } @Test - public void testD_msgAndException() { - Slogf.d(TAG, "msg", mException); + public void testD_msgAndThrowable() { + Slogf.d(TAG, "msg", mThrowable); - verify(()-> Slog.d(TAG, "msg", mException)); + verify(()-> Slog.d(TAG, "msg", mThrowable)); } @Test @@ -153,19 +153,19 @@ public final class SlogfTest { } @Test - public void testD_msgFormattedWithException_enabled() { + public void testD_msgFormattedWithThrowable_enabled() { enableLogging(Log.DEBUG); - Slogf.d(TAG, mException, "msg in a %s", "bottle"); + Slogf.d(TAG, mThrowable, "msg in a %s", "bottle"); - verify(()-> Slog.d(TAG, "msg in a bottle", mException)); + verify(()-> Slog.d(TAG, "msg in a bottle", mThrowable)); } @Test public void testD_msgFormattedWithException_disabled() { disableLogging(Log.DEBUG); - Slogf.d(TAG, mException, "msg in a %s", "bottle"); + Slogf.d(TAG, mThrowable, "msg in a %s", "bottle"); verify(()-> Slog.d(eq(TAG), any(String.class), any(Throwable.class)), never()); } @@ -178,10 +178,10 @@ public final class SlogfTest { } @Test - public void testI_msgAndException() { - Slogf.i(TAG, "msg", mException); + public void testI_msgAndThrowable() { + Slogf.i(TAG, "msg", mThrowable); - verify(()-> Slog.i(TAG, "msg", mException)); + verify(()-> Slog.i(TAG, "msg", mThrowable)); } @Test @@ -203,19 +203,19 @@ public final class SlogfTest { } @Test - public void testI_msgFormattedWithException_enabled() { + public void testI_msgFormattedWithThrowable_enabled() { enableLogging(Log.INFO); - Slogf.i(TAG, mException, "msg in a %s", "bottle"); + Slogf.i(TAG, mThrowable, "msg in a %s", "bottle"); - verify(()-> Slog.i(TAG, "msg in a bottle", mException)); + verify(()-> Slog.i(TAG, "msg in a bottle", mThrowable)); } @Test public void testI_msgFormattedWithException_disabled() { disableLogging(Log.INFO); - Slogf.i(TAG, mException, "msg in a %s", "bottle"); + Slogf.i(TAG, mThrowable, "msg in a %s", "bottle"); verify(()-> Slog.i(eq(TAG), any(String.class), any(Throwable.class)), never()); } @@ -228,17 +228,17 @@ public final class SlogfTest { } @Test - public void testW_msgAndException() { - Slogf.w(TAG, "msg", mException); + public void testW_msgAndThrowable() { + Slogf.w(TAG, "msg", mThrowable); - verify(()-> Slog.w(TAG, "msg", mException)); + verify(()-> Slog.w(TAG, "msg", mThrowable)); } @Test - public void testW_exception() { - Slogf.w(TAG, mException); + public void testW_Throwable() { + Slogf.w(TAG, mThrowable); - verify(()-> Slog.w(TAG, mException)); + verify(()-> Slog.w(TAG, mThrowable)); } @Test @@ -260,19 +260,19 @@ public final class SlogfTest { } @Test - public void testW_msgFormattedWithException_enabled() { + public void testW_msgFormattedWithThrowable_enabled() { enableLogging(Log.WARN); - Slogf.w(TAG, mException, "msg in a %s", "bottle"); + Slogf.w(TAG, mThrowable, "msg in a %s", "bottle"); - verify(()-> Slog.w(TAG, "msg in a bottle", mException)); + verify(()-> Slog.w(TAG, "msg in a bottle", mThrowable)); } @Test public void testW_msgFormattedWithException_disabled() { disableLogging(Log.WARN); - Slogf.w(TAG, mException, "msg in a %s", "bottle"); + Slogf.w(TAG, mThrowable, "msg in a %s", "bottle"); verify(()-> Slog.w(eq(TAG), any(String.class), any(Throwable.class)), never()); } @@ -285,10 +285,10 @@ public final class SlogfTest { } @Test - public void testE_msgAndException() { - Slogf.e(TAG, "msg", mException); + public void testE_msgAndThrowable() { + Slogf.e(TAG, "msg", mThrowable); - verify(()-> Slog.e(TAG, "msg", mException)); + verify(()-> Slog.e(TAG, "msg", mThrowable)); } @Test @@ -310,19 +310,19 @@ public final class SlogfTest { } @Test - public void testE_msgFormattedWithException_enabled() { + public void testE_msgFormattedWithThrowable_enabled() { enableLogging(Log.ERROR); - Slogf.e(TAG, mException, "msg in a %s", "bottle"); + Slogf.e(TAG, mThrowable, "msg in a %s", "bottle"); - verify(()-> Slog.e(TAG, "msg in a bottle", mException)); + verify(()-> Slog.e(TAG, "msg in a bottle", mThrowable)); } @Test public void testE_msgFormattedWithException_disabled() { disableLogging(Log.ERROR); - Slogf.e(TAG, mException, "msg in a %s", "bottle"); + Slogf.e(TAG, mThrowable, "msg in a %s", "bottle"); verify(()-> Slog.e(eq(TAG), any(String.class), any(Throwable.class)), never()); } @@ -335,17 +335,17 @@ public final class SlogfTest { } @Test - public void testWtf_msgAndException() { - Slogf.wtf(TAG, "msg", mException); + public void testWtf_msgAndThrowable() { + Slogf.wtf(TAG, "msg", mThrowable); - verify(()-> Slog.wtf(TAG, "msg", mException)); + verify(()-> Slog.wtf(TAG, "msg", mThrowable)); } @Test - public void testWtf_exception() { - Slogf.wtf(TAG, mException); + public void testWtf_Throwable() { + Slogf.wtf(TAG, mThrowable); - verify(()-> Slog.wtf(TAG, mException)); + verify(()-> Slog.wtf(TAG, mThrowable)); } @Test @@ -377,10 +377,10 @@ public final class SlogfTest { } @Test - public void testWtf_msgFormattedWithException() { - Slogf.wtf(TAG, mException, "msg in a %s", "bottle"); + public void testWtf_msgFormattedWithThrowable() { + Slogf.wtf(TAG, mThrowable, "msg in a %s", "bottle"); - verify(()-> Slog.wtf(TAG, "msg in a bottle", mException)); + verify(()-> Slog.wtf(TAG, "msg in a bottle", mThrowable)); } private void enableLogging(@Log.Level int level) { diff --git a/services/tests/servicestests/src/com/android/server/DockObserverTest.java b/services/tests/servicestests/src/com/android/server/DockObserverTest.java index c325778a5683..ee09074f7625 100644 --- a/services/tests/servicestests/src/com/android/server/DockObserverTest.java +++ b/services/tests/servicestests/src/com/android/server/DockObserverTest.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import android.content.Intent; import android.os.Looper; +import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableContext; import android.testing.TestableLooper; @@ -74,6 +75,11 @@ public class DockObserverTest { .isEqualTo(Intent.EXTRA_DOCK_STATE_UNDOCKED); } + void setDeviceProvisioned(boolean provisioned) { + Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, + provisioned ? 1 : 0); + } + @Before public void setUp() { if (Looper.myLooper() == null) { @@ -131,4 +137,25 @@ public class DockObserverTest { assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1\nKEY5=5", Intent.EXTRA_DOCK_STATE_HE_DESK); } + + @Test + public void testDockIntentBroadcast_deviceNotProvisioned() + throws ExecutionException, InterruptedException { + DockObserver observer = new DockObserver(mInterceptingContext); + // Set the device as not provisioned. + setDeviceProvisioned(false); + observer.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY); + + BroadcastInterceptingContext.FutureIntent futureIntent = + updateExtconDockState(observer, "DOCK=1"); + TestableLooper.get(this).processAllMessages(); + // Verify no broadcast was sent as device was not provisioned. + futureIntent.assertNotReceived(); + + // Ensure we send the broadcast when the device is provisioned. + setDeviceProvisioned(true); + TestableLooper.get(this).processAllMessages(); + assertThat(futureIntent.get().getIntExtra(Intent.EXTRA_DOCK_STATE, -1)) + .isEqualTo(Intent.EXTRA_DOCK_STATE_DESK); + } } 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/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java index af10b9dba63f..d758e71c62a2 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java @@ -52,9 +52,9 @@ import java.lang.reflect.Field; @RunWith(AndroidTestingRunner.class) public class NotificationHistoryJobServiceTest extends UiServiceTestCase { private NotificationHistoryJobService mJobService; - private JobParameters mJobParams = new JobParameters(null, - NotificationHistoryJobService.BASE_JOB_ID, null, null, null, - 0, false, false, null, null, null); + + @Mock + private JobParameters mJobParams; @Captor ArgumentCaptor<JobInfo> mJobInfoCaptor; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java index 3a6c0eb8fc2a..a83eb00dfa1f 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java @@ -44,9 +44,9 @@ import org.mockito.Mock; @RunWith(AndroidTestingRunner.class) public class ReviewNotificationPermissionsJobServiceTest extends UiServiceTestCase { private ReviewNotificationPermissionsJobService mJobService; - private JobParameters mJobParams = new JobParameters(null, - ReviewNotificationPermissionsJobService.JOB_ID, null, null, null, - 0, false, false, null, null, null); + + @Mock + private JobParameters mJobParams; @Captor ArgumentCaptor<JobInfo> mJobInfoCaptor; diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 1ab7d7e0c81b..a410eedf4c29 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -1206,6 +1206,25 @@ public class ActivityRecordTests extends WindowTestsBase { } } + @Test + public void testFinishActivityIfPossible_sendResultImmediatelyIfResumed() { + final Task task = new TaskBuilder(mSupervisor).build(); + final TaskFragment taskFragment1 = createTaskFragmentWithActivity(task); + final TaskFragment taskFragment2 = createTaskFragmentWithActivity(task); + final ActivityRecord resultToActivity = taskFragment1.getTopMostActivity(); + final ActivityRecord targetActivity = taskFragment2.getTopMostActivity(); + resultToActivity.setState(RESUMED, "test"); + targetActivity.setState(RESUMED, "test"); + targetActivity.resultTo = resultToActivity; + + clearInvocations(mAtm.getLifecycleManager()); + targetActivity.finishIfPossible(0, new Intent(), null, "test", false /* oomAdj */); + waitUntilHandlersIdle(); + + verify(resultToActivity).sendResult(anyInt(), eq(null), anyInt(), anyInt(), any(), eq(null), + anyBoolean()); + } + /** * Verify that complete finish request for non-finishing activity is invalid. */ diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java index 70b68c78f092..67334707db2a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java @@ -133,8 +133,8 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { final RoundedCorners roundedCorners = mHasRoundedCorners ? mDisplayContent.calculateRoundedCornersForRotation(mRotation) : RoundedCorners.NO_ROUNDED_CORNERS; - return new DisplayFrames(insetsState, info, - info.displayCutout, roundedCorners, new PrivacyIndicatorBounds()); + return new DisplayFrames(insetsState, info, info.displayCutout, roundedCorners, + new PrivacyIndicatorBounds(), info.displayShape); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java index d99946f0a5c4..10f2270db19e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java @@ -58,6 +58,7 @@ import android.graphics.PixelFormat; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.view.DisplayInfo; +import android.view.DisplayShape; import android.view.InsetsSource; import android.view.InsetsState; import android.view.PrivacyIndicatorBounds; @@ -321,7 +322,8 @@ public class DisplayPolicyTests extends WindowTestsBase { final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState(); mImeWindow.mAboveInsetsState.set(state); mDisplayContent.mDisplayFrames = new DisplayFrames( - state, displayInfo, NO_CUTOUT, NO_ROUNDED_CORNERS, new PrivacyIndicatorBounds()); + state, displayInfo, NO_CUTOUT, NO_ROUNDED_CORNERS, new PrivacyIndicatorBounds(), + DisplayShape.NONE); mDisplayContent.setInputMethodWindowLocked(mImeWindow); mImeWindow.mAttrs.setFitInsetsSides(Side.all() & ~Side.BOTTOM); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java index 4e796c55dda2..8fda1917d0b5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java @@ -24,7 +24,6 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; -import static android.os.Process.FIRST_APPLICATION_UID; import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; @@ -33,9 +32,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.wm.ActivityRecord.State.RESUMED; -import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED; import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION; -import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT; import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST; import static com.android.server.wm.WindowContainer.POSITION_TOP; @@ -475,23 +472,6 @@ public class TaskFragmentTest extends WindowTestsBase { doReturn(true).when(taskFragment).smallerThanMinDimension(any()); assertEquals(EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION, taskFragment.isAllowedToEmbedActivity(activity)); - - // Not allow to start activity across TaskFragments for result. - final TaskFragment newTaskFragment = new TaskFragmentBuilder(mAtm) - .setParentTask(taskFragment.getTask()) - .build(); - final ActivityRecord newActivity = new ActivityBuilder(mAtm) - .setUid(FIRST_APPLICATION_UID) - .build(); - doReturn(true).when(newTaskFragment).isAllowedToEmbedActivityInTrustedMode(any(), anyInt()); - doReturn(false).when(newTaskFragment).smallerThanMinDimension(any()); - newActivity.resultTo = activity; - assertEquals(EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT, - newTaskFragment.isAllowedToEmbedActivity(newActivity)); - - // Allow embedding if the resultTo activity is finishing. - activity.finishing = true; - assertEquals(EMBEDDING_ALLOWED, newTaskFragment.isAllowedToEmbedActivity(newActivity)); } @Test 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..94b5b93e74c2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java @@ -54,6 +54,7 @@ import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import android.view.DisplayCutout; import android.view.DisplayInfo; +import android.view.DisplayShape; import android.view.Gravity; import android.view.InsetsState; import android.view.PrivacyIndicatorBounds; @@ -165,7 +166,8 @@ public class WallpaperControllerTests extends WindowTestsBase { final DisplayInfo info = dc.computeScreenConfiguration(config, Surface.ROTATION_0); final DisplayCutout cutout = dc.calculateDisplayCutoutForRotation(Surface.ROTATION_0); final DisplayFrames displayFrames = new DisplayFrames(new InsetsState(), - info, cutout, RoundedCorners.NO_ROUNDED_CORNERS, new PrivacyIndicatorBounds()); + info, cutout, RoundedCorners.NO_ROUNDED_CORNERS, new PrivacyIndicatorBounds(), + DisplayShape.NONE); wallpaperWindow.mToken.applyFixedRotationTransform(info, displayFrames, config); // Check that the wallpaper has the same frame in landscape than in portrait @@ -369,7 +371,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..133f924047da 100644 --- a/services/usb/Android.bp +++ b/services/usb/Android.bp @@ -29,9 +29,10 @@ 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", + "android.hardware.usb.gadget-V1-java", ], } diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index 1c081c1c153d..ffdb07b27ad0 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -44,6 +44,7 @@ import android.debug.AdbManagerInternal; import android.debug.AdbNotifications; import android.debug.AdbTransportType; import android.debug.IAdbTransport; +import android.hardware.usb.IUsbOperationInternal; import android.hardware.usb.ParcelableUsbPort; import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbConfiguration; @@ -54,9 +55,7 @@ import android.hardware.usb.UsbManager; import android.hardware.usb.UsbPort; import android.hardware.usb.UsbPortStatus; import android.hardware.usb.gadget.V1_0.GadgetFunction; -import android.hardware.usb.gadget.V1_0.IUsbGadget; import android.hardware.usb.gadget.V1_0.Status; -import android.hardware.usb.gadget.V1_2.IUsbGadgetCallback; import android.hardware.usb.gadget.V1_2.UsbSpeed; import android.hidl.manager.V1_0.IServiceManager; import android.hidl.manager.V1_0.IServiceNotification; @@ -88,9 +87,12 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.SomeArgs; +import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.dump.DualDumpOutputStream; import com.android.server.FgThread; import com.android.server.LocalServices; +import com.android.server.usb.hal.gadget.UsbGadgetHal; +import com.android.server.usb.hal.gadget.UsbGadgetHalInstance; import com.android.server.utils.EventLogger; import com.android.server.wm.ActivityTaskManagerInternal; @@ -106,6 +108,7 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Scanner; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; /** * UsbDeviceManager manages USB state in device mode. @@ -216,6 +219,13 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser private static EventLogger sEventLogger; + private UsbGadgetHal mUsbGadgetHal; + + /** + * Counter for tracking UsbOperation operations. + */ + private static final AtomicInteger sUsbOperationCount = new AtomicInteger(); + static { sDenyInterfaces = new HashSet<>(); sDenyInterfaces.add(UsbConstants.USB_CLASS_AUDIO); @@ -298,15 +308,11 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser mHasUsbAccessory = pm.hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY); initRndisAddress(); + int operationId = sUsbOperationCount.incrementAndGet(); boolean halNotPresent = false; - try { - IUsbGadget.getService(true); - } catch (RemoteException e) { - Slog.e(TAG, "USB GADGET HAL present but exception thrown", e); - } catch (NoSuchElementException e) { - halNotPresent = true; - Slog.i(TAG, "USB GADGET HAL not present in the device", e); - } + + mUsbGadgetHal = UsbGadgetHalInstance.getInstance(this, null); + Slog.d(TAG, "getInstance done"); mControlFds = new HashMap<>(); FileDescriptor mtpFd = nativeOpenControl(UsbManager.USB_FUNCTION_MTP); @@ -320,7 +326,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } mControlFds.put(UsbManager.FUNCTION_PTP, ptpFd); - if (halNotPresent) { + if (mUsbGadgetHal == null) { /** * Initialze the legacy UsbHandler */ @@ -334,6 +340,8 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser alsaManager, permissionManager); } + mHandler.handlerInitDone(operationId); + if (nativeIsStartRequested()) { if (DEBUG) Slog.d(TAG, "accessory attached at boot"); startAccessoryMode(); @@ -455,6 +463,8 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser private void startAccessoryMode() { if (!mHasUsbAccessory) return; + int operationId = sUsbOperationCount.incrementAndGet(); + mAccessoryStrings = nativeGetAccessoryStrings(); boolean enableAudio = (nativeGetAudioMode() == AUDIO_MODE_SOURCE); // don't start accessory mode if our mandatory strings have not been set @@ -475,7 +485,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser ACCESSORY_REQUEST_TIMEOUT); mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_ACCESSORY_HANDSHAKE_TIMEOUT), ACCESSORY_HANDSHAKE_TIMEOUT); - setCurrentFunctions(functions); + setCurrentFunctions(functions, operationId); } } @@ -504,6 +514,20 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } } + public static void logAndPrint(int priority, IndentingPrintWriter pw, String msg) { + Slog.println(priority, TAG, msg); + if (pw != null) { + pw.println(msg); + } + } + + public static void logAndPrintException(IndentingPrintWriter pw, String msg, Exception e) { + Slog.e(TAG, msg, e); + if (pw != null) { + pw.println(msg + e); + } + } + abstract static class UsbHandler extends Handler { // current USB state @@ -608,6 +632,19 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser sendMessage(m); } + public boolean sendMessage(int what) { + removeMessages(what); + Message m = Message.obtain(this, what); + return sendMessageDelayed(m,0); + } + + public void sendMessage(int what, int operationId) { + removeMessages(what); + Message m = Message.obtain(this, what); + m.arg1 = operationId; + sendMessage(m); + } + public void sendMessage(int what, Object arg) { removeMessages(what); Message m = Message.obtain(this, what); @@ -615,6 +652,22 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser sendMessage(m); } + public void sendMessage(int what, Object arg, int operationId) { + removeMessages(what); + Message m = Message.obtain(this, what); + m.obj = arg; + m.arg1 = operationId; + sendMessage(m); + } + + public void sendMessage(int what, boolean arg, int operationId) { + removeMessages(what); + Message m = Message.obtain(this, what); + m.arg1 = (arg ? 1 : 0); + m.arg2 = operationId; + sendMessage(m); + } + public void sendMessage(int what, Object arg, boolean arg1) { removeMessages(what); Message m = Message.obtain(this, what); @@ -623,6 +676,15 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser sendMessage(m); } + public void sendMessage(int what, long arg, boolean arg1, int operationId) { + removeMessages(what); + Message m = Message.obtain(this, what); + m.obj = arg; + m.arg1 = (arg1 ? 1 : 0); + m.arg2 = operationId; + sendMessage(m); + } + public void sendMessage(int what, boolean arg1, boolean arg2) { removeMessages(what); Message m = Message.obtain(this, what); @@ -680,7 +742,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser sendMessageDelayed(msg, HOST_STATE_UPDATE_DELAY); } - private void setAdbEnabled(boolean enable) { + private void setAdbEnabled(boolean enable, int operationId) { if (DEBUG) Slog.d(TAG, "setAdbEnabled: " + enable); if (enable) { @@ -689,7 +751,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser setSystemProperty(USB_PERSISTENT_CONFIG_PROPERTY, ""); } - setEnabledFunctions(mCurrentFunctions, true); + setEnabledFunctions(mCurrentFunctions, true, operationId); updateAdbNotification(false); } @@ -701,6 +763,8 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser private void updateCurrentAccessory() { // We are entering accessory mode if we have received a request from the host // and the request has not timed out yet. + int operationId = sUsbOperationCount.incrementAndGet(); + boolean enteringAccessoryMode = hasMessages(MSG_ACCESSORY_MODE_ENTER_TIMEOUT); if (mConfigured && enteringAccessoryMode) { @@ -732,18 +796,18 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } } else { if (!enteringAccessoryMode) { - notifyAccessoryModeExit(); + notifyAccessoryModeExit(operationId); } else if (DEBUG) { Slog.v(TAG, "Debouncing accessory mode exit"); } } } - private void notifyAccessoryModeExit() { + private void notifyAccessoryModeExit(int operationId) { // make sure accessory mode is off // and restore default functions Slog.d(TAG, "exited USB accessory mode"); - setEnabledFunctions(UsbManager.FUNCTION_NONE, false); + setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId); if (mCurrentAccessory != null) { if (mBootCompleted) { @@ -869,8 +933,8 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser mMidiEnabled && mConfigured, mMidiCard, mMidiDevice); } - private void setScreenUnlockedFunctions() { - setEnabledFunctions(mScreenUnlockedFunctions, false); + private void setScreenUnlockedFunctions(int operationId) { + setEnabledFunctions(mScreenUnlockedFunctions, false, operationId); } private static class AdbTransport extends IAdbTransport.Stub { @@ -883,7 +947,8 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser @Override public void onAdbEnabled(boolean enabled, byte transportType) { if (transportType == AdbTransportType.USB) { - mHandler.sendMessage(MSG_ENABLE_ADB, enabled); + int operationId = sUsbOperationCount.incrementAndGet(); + mHandler.sendMessage(MSG_ENABLE_ADB, enabled, operationId); } } } @@ -906,6 +971,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser public void handleMessage(Message msg) { switch (msg.what) { case MSG_UPDATE_STATE: + int operationId = sUsbOperationCount.incrementAndGet(); mConnected = (msg.arg1 == 1); mConfigured = (msg.arg2 == 1); @@ -923,9 +989,9 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser // restore defaults when USB is disconnected if (!mScreenLocked && mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE) { - setScreenUnlockedFunctions(); + setScreenUnlockedFunctions(operationId); } else { - setEnabledFunctions(UsbManager.FUNCTION_NONE, false); + setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId); } } updateUsbFunctions(); @@ -1036,13 +1102,15 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser updateUsbNotification(false); break; case MSG_ENABLE_ADB: - setAdbEnabled(msg.arg1 == 1); + setAdbEnabled(msg.arg1 == 1, msg.arg2); break; case MSG_SET_CURRENT_FUNCTIONS: long functions = (Long) msg.obj; - setEnabledFunctions(functions, false); + operationId = (int) msg.arg1; + setEnabledFunctions(functions, false, operationId); break; case MSG_SET_SCREEN_UNLOCKED_FUNCTIONS: + operationId = sUsbOperationCount.incrementAndGet(); mScreenUnlockedFunctions = (Long) msg.obj; if (mSettings != null) { SharedPreferences.Editor editor = mSettings.edit(); @@ -1053,12 +1121,13 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } if (!mScreenLocked && mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE) { // If the screen is unlocked, also set current functions. - setScreenUnlockedFunctions(); + setScreenUnlockedFunctions(operationId); } else { - setEnabledFunctions(UsbManager.FUNCTION_NONE, false); + setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId); } break; case MSG_UPDATE_SCREEN_LOCK: + operationId = sUsbOperationCount.incrementAndGet(); if (msg.arg1 == 1 == mScreenLocked) { break; } @@ -1068,23 +1137,25 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } if (mScreenLocked) { if (!mConnected) { - setEnabledFunctions(UsbManager.FUNCTION_NONE, false); + setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId); } } else { if (mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE && mCurrentFunctions == UsbManager.FUNCTION_NONE) { // Set the screen unlocked functions if current function is charging. - setScreenUnlockedFunctions(); + setScreenUnlockedFunctions(operationId); } } break; case MSG_UPDATE_USER_RESTRICTIONS: + operationId = sUsbOperationCount.incrementAndGet(); // Restart the USB stack if USB transfer is enabled but no longer allowed. if (isUsbDataTransferActive(mCurrentFunctions) && !isUsbTransferAllowed()) { - setEnabledFunctions(UsbManager.FUNCTION_NONE, true); + setEnabledFunctions(UsbManager.FUNCTION_NONE, true, operationId); } break; case MSG_SYSTEM_READY: + operationId = sUsbOperationCount.incrementAndGet(); mNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); @@ -1102,17 +1173,19 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser NotificationManager.IMPORTANCE_HIGH)); } mSystemReady = true; - finishBoot(); + finishBoot(operationId); break; case MSG_LOCALE_CHANGED: updateAdbNotification(true); updateUsbNotification(true); break; case MSG_BOOT_COMPLETED: + operationId = sUsbOperationCount.incrementAndGet(); mBootCompleted = true; - finishBoot(); + finishBoot(operationId); break; case MSG_USER_SWITCHED: { + operationId = sUsbOperationCount.incrementAndGet(); if (mCurrentUser != msg.arg1) { if (DEBUG) { Slog.v(TAG, "Current user switched to " + msg.arg1); @@ -1125,16 +1198,18 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser mSettings.getString(String.format(Locale.ENGLISH, UNLOCKED_CONFIG_PREF, mCurrentUser), "")); } - setEnabledFunctions(UsbManager.FUNCTION_NONE, false); + setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId); } break; } case MSG_ACCESSORY_MODE_ENTER_TIMEOUT: { + operationId = sUsbOperationCount.incrementAndGet(); if (DEBUG) { - Slog.v(TAG, "Accessory mode enter timeout: " + mConnected); + Slog.v(TAG, "Accessory mode enter timeout: " + mConnected + + " ,operationId: " + operationId); } if (!mConnected || (mCurrentFunctions & UsbManager.FUNCTION_ACCESSORY) == 0) { - notifyAccessoryModeExit(); + notifyAccessoryModeExit(operationId); } break; } @@ -1157,7 +1232,9 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } } - protected void finishBoot() { + public abstract void handlerInitDone(int operationId); + + protected void finishBoot(int operationId) { if (mBootCompleted && mCurrentUsbFunctionsReceived && mSystemReady) { if (mPendingBootBroadcast) { updateUsbStateBroadcastIfNeeded(getAppliedFunctions(mCurrentFunctions)); @@ -1165,9 +1242,9 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } if (!mScreenLocked && mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE) { - setScreenUnlockedFunctions(); + setScreenUnlockedFunctions(operationId); } else { - setEnabledFunctions(UsbManager.FUNCTION_NONE, false); + setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId); } if (mCurrentAccessory != null) { mUsbDeviceManager.getCurrentSettings().accessoryAttached(mCurrentAccessory); @@ -1507,7 +1584,8 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser /** * Evaluates USB function policies and applies the change accordingly. */ - protected abstract void setEnabledFunctions(long functions, boolean forceRestart); + protected abstract void setEnabledFunctions(long functions, + boolean forceRestart, int operationId); public void setAccessoryUEventTime(long accessoryConnectionStartTime) { mAccessoryConnectionStartTime = accessoryConnectionStartTime; @@ -1522,6 +1600,11 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser mSendStringCount = 0; mStartAccessory = false; } + + public abstract void setCurrentUsbFunctionsCb(long functions, + int status, int mRequest, long mFunctions, boolean mChargingFunctions); + + public abstract void getUsbSpeedCb(int speed); } private static final class UsbHandlerLegacy extends UsbHandler { @@ -1540,6 +1623,11 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser private String mCurrentFunctionsStr; private boolean mUsbDataUnlocked; + /** + * Keeps track of the latest setCurrentUsbFunctions request number. + */ + private int mCurrentRequest = 0; + UsbHandlerLegacy(Looper looper, Context context, UsbDeviceManager deviceManager, UsbAlsaManager alsaManager, UsbPermissionManager permissionManager) { super(looper, context, deviceManager, alsaManager, permissionManager); @@ -1573,6 +1661,10 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } } + @Override + public void handlerInitDone(int operationId) { + } + private void readOemUsbOverrideConfig(Context context) { String[] configList = context.getResources().getStringArray( com.android.internal.R.array.config_oemUsbModeOverride); @@ -1675,11 +1767,14 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } @Override - protected void setEnabledFunctions(long usbFunctions, boolean forceRestart) { + protected void setEnabledFunctions(long usbFunctions, + boolean forceRestart, int operationId) { boolean usbDataUnlocked = isUsbDataTransferActive(usbFunctions); if (DEBUG) { - Slog.d(TAG, "setEnabledFunctions functions=" + usbFunctions + ", " - + "forceRestart=" + forceRestart + ", usbDataUnlocked=" + usbDataUnlocked); + Slog.d(TAG, "setEnabledFunctions functions=" + usbFunctions + + " ,forceRestart=" + forceRestart + + " ,usbDataUnlocked=" + usbDataUnlocked + + " ,operationId=" + operationId); } if (usbDataUnlocked != mUsbDataUnlocked) { @@ -1775,7 +1870,6 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser || !mCurrentFunctionsStr.equals(functions) || !mCurrentFunctionsApplied || forceRestart) { - Slog.i(TAG, "Setting USB config to " + functions); mCurrentFunctionsStr = functions; mCurrentOemFunctions = oemFunctions; mCurrentFunctionsApplied = false; @@ -1871,15 +1965,18 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser if (charAfter < functions.length() && functions.charAt(charAfter) != ',') return false; return true; } - } - private static final class UsbHandlerHal extends UsbHandler { + @Override + public void setCurrentUsbFunctionsCb(long functions, + int status, int mRequest, long mFunctions, boolean mChargingFunctions){ + } - /** - * Proxy object for the usb gadget hal daemon. - */ - @GuardedBy("mGadgetProxyLock") - private IUsbGadget mGadgetProxy; + @Override + public void getUsbSpeedCb(int speed){ + } + } + + private final class UsbHandlerHal extends UsbHandler { private final Object mGadgetProxyLock = new Object(); @@ -1926,33 +2023,20 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser UsbHandlerHal(Looper looper, Context context, UsbDeviceManager deviceManager, UsbAlsaManager alsaManager, UsbPermissionManager permissionManager) { super(looper, context, deviceManager, alsaManager, permissionManager); + int operationId = sUsbOperationCount.incrementAndGet(); try { - ServiceNotification serviceNotification = new ServiceNotification(); - - boolean ret = IServiceManager.getService() - .registerForNotifications(GADGET_HAL_FQ_NAME, "", serviceNotification); - if (!ret) { - Slog.e(TAG, "Failed to register usb gadget service start notification"); - return; - } synchronized (mGadgetProxyLock) { - mGadgetProxy = IUsbGadget.getService(true); - mGadgetProxy.linkToDeath(new UsbGadgetDeathRecipient(), - USB_GADGET_HAL_DEATH_COOKIE); mCurrentFunctions = UsbManager.FUNCTION_NONE; mCurrentUsbFunctionsRequested = true; mUsbSpeed = UsbSpeed.UNKNOWN; mCurrentGadgetHalVersion = UsbManager.GADGET_HAL_V1_0; - mGadgetProxy.getCurrentUsbFunctions(new UsbGadgetCallback()); + updateUsbGadgetHalVersion(); } String state = FileUtils.readTextFile(new File(STATE_PATH), 0, null).trim(); updateState(state); - updateUsbGadgetHalVersion(); } catch (NoSuchElementException e) { Slog.e(TAG, "Usb gadget hal not found", e); - } catch (RemoteException e) { - Slog.e(TAG, "Usb Gadget hal not responding", e); } catch (Exception e) { Slog.e(TAG, "Error initializing UsbHandler", e); } @@ -1965,7 +2049,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser if (cookie == USB_GADGET_HAL_DEATH_COOKIE) { Slog.e(TAG, "Usb Gadget hal service died cookie: " + cookie); synchronized (mGadgetProxyLock) { - mGadgetProxy = null; + mUsbGadgetHal = null; } } } @@ -1988,18 +2072,22 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser public void handleMessage(Message msg) { switch (msg.what) { case MSG_SET_CHARGING_FUNCTIONS: - setEnabledFunctions(UsbManager.FUNCTION_NONE, false); + int operationId = sUsbOperationCount.incrementAndGet(); + setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId); break; case MSG_SET_FUNCTIONS_TIMEOUT: - Slog.e(TAG, "Set functions timed out! no reply from usb hal"); + operationId = sUsbOperationCount.incrementAndGet(); + Slog.e(TAG, "Set functions timed out! no reply from usb hal" + + " ,operationId:" + operationId); if (msg.arg1 != 1) { // Set this since default function may be selected from Developer options - setEnabledFunctions(mScreenUnlockedFunctions, false); + setEnabledFunctions(mScreenUnlockedFunctions, false, operationId); } break; case MSG_GET_CURRENT_USB_FUNCTIONS: Slog.i(TAG, "processing MSG_GET_CURRENT_USB_FUNCTIONS"); mCurrentUsbFunctionsReceived = true; + operationId = msg.arg2; if (mCurrentUsbFunctionsRequested) { Slog.i(TAG, "updating mCurrentFunctions"); @@ -2009,91 +2097,71 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser "mCurrentFunctions:" + mCurrentFunctions + "applied:" + msg.arg1); mCurrentFunctionsApplied = msg.arg1 == 1; } - finishBoot(); + finishBoot(operationId); break; case MSG_FUNCTION_SWITCH_TIMEOUT: /** * Dont force to default when the configuration is already set to default. */ + operationId = sUsbOperationCount.incrementAndGet(); if (msg.arg1 != 1) { // Set this since default function may be selected from Developer options - setEnabledFunctions(mScreenUnlockedFunctions, false); + setEnabledFunctions(mScreenUnlockedFunctions, false, operationId); } break; case MSG_GADGET_HAL_REGISTERED: boolean preexisting = msg.arg1 == 1; + operationId = sUsbOperationCount.incrementAndGet(); synchronized (mGadgetProxyLock) { try { - mGadgetProxy = IUsbGadget.getService(); - mGadgetProxy.linkToDeath(new UsbGadgetDeathRecipient(), - USB_GADGET_HAL_DEATH_COOKIE); + mUsbGadgetHal = UsbGadgetHalInstance.getInstance(mUsbDeviceManager, + null); if (!mCurrentFunctionsApplied && !preexisting) { - setEnabledFunctions(mCurrentFunctions, false); + setEnabledFunctions(mCurrentFunctions, false, operationId); } } catch (NoSuchElementException e) { Slog.e(TAG, "Usb gadget hal not found", e); - } catch (RemoteException e) { - Slog.e(TAG, "Usb Gadget hal not responding", e); } } break; case MSG_RESET_USB_GADGET: synchronized (mGadgetProxyLock) { - if (mGadgetProxy == null) { - Slog.e(TAG, "reset Usb Gadget mGadgetProxy is null"); + if (mUsbGadgetHal == null) { + Slog.e(TAG, "reset Usb Gadget mUsbGadgetHal is null"); break; } try { - android.hardware.usb.gadget.V1_1.IUsbGadget gadgetProxy = - android.hardware.usb.gadget.V1_1.IUsbGadget - .castFrom(mGadgetProxy); - gadgetProxy.reset(); - } catch (RemoteException e) { + mUsbGadgetHal.reset(); + } catch (Exception e) { Slog.e(TAG, "reset Usb Gadget failed", e); } } break; case MSG_UPDATE_USB_SPEED: - synchronized (mGadgetProxyLock) { - if (mGadgetProxy == null) { - Slog.e(TAG, "mGadgetProxy is null"); - break; - } + operationId = sUsbOperationCount.incrementAndGet(); + if (mUsbGadgetHal == null) { + Slog.e(TAG, "mGadgetHal is null, operationId:" + operationId); + break; + } - try { - android.hardware.usb.gadget.V1_2.IUsbGadget gadgetProxy = - android.hardware.usb.gadget.V1_2.IUsbGadget - .castFrom(mGadgetProxy); - if (gadgetProxy != null) { - gadgetProxy.getUsbSpeed(new UsbGadgetCallback()); - } - } catch (RemoteException e) { - Slog.e(TAG, "get UsbSpeed failed", e); - } + try { + mUsbGadgetHal.getUsbSpeed(operationId); + } catch (Exception e) { + Slog.e(TAG, "get UsbSpeed failed", e); } break; case MSG_UPDATE_HAL_VERSION: - synchronized (mGadgetProxyLock) { - if (mGadgetProxy == null) { - Slog.e(TAG, "mGadgetProxy is null"); - break; - } - - android.hardware.usb.gadget.V1_2.IUsbGadget gadgetProxy = - android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy); - if (gadgetProxy == null) { - android.hardware.usb.gadget.V1_1.IUsbGadget gadgetProxyV1By1 = - android.hardware.usb.gadget.V1_1.IUsbGadget - .castFrom(mGadgetProxy); - if (gadgetProxyV1By1 == null) { - mCurrentGadgetHalVersion = UsbManager.GADGET_HAL_V1_0; - break; - } - mCurrentGadgetHalVersion = UsbManager.GADGET_HAL_V1_1; - break; + if (mUsbGadgetHal == null) { + Slog.e(TAG, "mUsbGadgetHal is null"); + break; + } + else { + try { + mCurrentGadgetHalVersion = mUsbGadgetHal.getGadgetHalVersion(); + } catch (RemoteException e) { + Slog.e(TAG, "update Usb gadget version failed", e); } - mCurrentGadgetHalVersion = UsbManager.GADGET_HAL_V1_2; } break; default: @@ -2101,56 +2169,31 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } } - private class UsbGadgetCallback extends IUsbGadgetCallback.Stub { - int mRequest; - long mFunctions; - boolean mChargingFunctions; - - UsbGadgetCallback() { - } - - UsbGadgetCallback(int request, long functions, - boolean chargingFunctions) { - mRequest = request; - mFunctions = functions; - mChargingFunctions = chargingFunctions; - } - - @Override - public void setCurrentUsbFunctionsCb(long functions, - int status) { - /** - * Callback called for a previous setCurrenUsbFunction - */ - if ((mCurrentRequest != mRequest) || !hasMessages(MSG_SET_FUNCTIONS_TIMEOUT) - || (mFunctions != functions)) { - return; - } + @Override + public void setCurrentUsbFunctionsCb(long functions, + int status, int mRequest, long mFunctions, boolean mChargingFunctions) { - removeMessages(MSG_SET_FUNCTIONS_TIMEOUT); - Slog.e(TAG, "notifyCurrentFunction request:" + mRequest + " status:" + status); - if (status == Status.SUCCESS) { - mCurrentFunctionsApplied = true; - } else if (!mChargingFunctions) { - Slog.e(TAG, "Setting default fuctions"); - sendEmptyMessage(MSG_SET_CHARGING_FUNCTIONS); - } + if ((mCurrentRequest != mRequest) || !hasMessages(MSG_SET_FUNCTIONS_TIMEOUT) + || (mFunctions != functions)) { + return; } - @Override - public void getCurrentUsbFunctionsCb(long functions, - int status) { - sendMessage(MSG_GET_CURRENT_USB_FUNCTIONS, functions, - status == Status.FUNCTIONS_APPLIED); + removeMessages(MSG_SET_FUNCTIONS_TIMEOUT); + Slog.i(TAG, "notifyCurrentFunction request:" + mRequest + " status:" + status); + if (status == Status.SUCCESS) { + mCurrentFunctionsApplied = true; + } else if (!mChargingFunctions) { + Slog.e(TAG, "Setting default fuctions"); + sendEmptyMessage(MSG_SET_CHARGING_FUNCTIONS); } + } - @Override - public void getUsbSpeedCb(int speed) { - mUsbSpeed = speed; - } + @Override + public void getUsbSpeedCb(int speed) { + mUsbSpeed = speed; } - private void setUsbConfig(long config, boolean chargingFunctions) { + private void setUsbConfig(long config, boolean chargingFunctions, int operationId) { if (true) Slog.d(TAG, "setUsbConfig(" + config + ") request:" + ++mCurrentRequest); /** * Cancel any ongoing requests, if present. @@ -2160,8 +2203,8 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser removeMessages(MSG_SET_CHARGING_FUNCTIONS); synchronized (mGadgetProxyLock) { - if (mGadgetProxy == null) { - Slog.e(TAG, "setUsbConfig mGadgetProxy is null"); + if (mUsbGadgetHal == null) { + Slog.e(TAG, "setUsbConfig mUsbGadgetHal is null"); return; } try { @@ -2178,10 +2221,9 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser LocalServices.getService(AdbManagerInternal.class) .stopAdbdForTransport(AdbTransportType.USB); } - UsbGadgetCallback usbGadgetCallback = new UsbGadgetCallback(mCurrentRequest, - config, chargingFunctions); - mGadgetProxy.setCurrentUsbFunctions(config, usbGadgetCallback, - SET_FUNCTIONS_TIMEOUT_MS - SET_FUNCTIONS_LEEWAY_MS); + mUsbGadgetHal.setCurrentUsbFunctions(mCurrentRequest, + config, chargingFunctions, + SET_FUNCTIONS_TIMEOUT_MS - SET_FUNCTIONS_LEEWAY_MS, operationId); sendMessageDelayed(MSG_SET_FUNCTIONS_TIMEOUT, chargingFunctions, SET_FUNCTIONS_TIMEOUT_MS); if (mConnected) { @@ -2190,17 +2232,19 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser SET_FUNCTIONS_TIMEOUT_MS + ENUMERATION_TIME_OUT_MS); } if (DEBUG) Slog.d(TAG, "timeout message queued"); - } catch (RemoteException e) { + } catch (Exception e) {//RemoteException e) { Slog.e(TAG, "Remoteexception while calling setCurrentUsbFunctions", e); } } } @Override - protected void setEnabledFunctions(long functions, boolean forceRestart) { + protected void setEnabledFunctions(long functions, boolean forceRestart, int operationId) { if (DEBUG) { - Slog.d(TAG, "setEnabledFunctions functions=" + functions + ", " - + "forceRestart=" + forceRestart); + Slog.d(TAG, "setEnabledFunctionsi " + + "functions=" + functions + + ", forceRestart=" + forceRestart + + ", operationId=" + operationId); } if (mCurrentGadgetHalVersion < UsbManager.GADGET_HAL_V1_2) { if ((functions & UsbManager.FUNCTION_NCM) != 0) { @@ -2221,7 +2265,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser functions = getAppliedFunctions(functions); // Set the new USB configuration. - setUsbConfig(functions, chargingFunctions); + setUsbConfig(functions, chargingFunctions, operationId); if (mBootCompleted && isUsbDataTransferActive(functions)) { // Start up dependent services. @@ -2229,6 +2273,11 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } } } + + @Override + public void handlerInitDone(int operationId) { + mUsbGadgetHal.getCurrentUsbFunctions(operationId); + } } /* returns the currently attached USB accessory */ @@ -2270,6 +2319,21 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser return mHandler.getGadgetHalVersion(); } + public void setCurrentUsbFunctionsCb(long functions, + int status, int mRequest, long mFunctions, boolean mChargingFunctions) { + mHandler.setCurrentUsbFunctionsCb(functions, status, + mRequest, mFunctions, mChargingFunctions); + } + + public void getCurrentUsbFunctionsCb(long functions, int status) { + mHandler.sendMessage(MSG_GET_CURRENT_USB_FUNCTIONS, functions, + status == Status.FUNCTIONS_APPLIED); + } + + public void getUsbSpeedCb(int speed) { + mHandler.getUsbSpeedCb(speed); + } + /** * Returns a dup of the control file descriptor for the given function. */ @@ -2295,7 +2359,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser * * @param functions The functions to set, or empty to set the charging function. */ - public void setCurrentFunctions(long functions) { + public void setCurrentFunctions(long functions, int operationId) { if (DEBUG) { Slog.d(TAG, "setCurrentFunctions(" + UsbManager.usbFunctionsToString(functions) + ")"); } @@ -2312,7 +2376,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } else if (functions == UsbManager.FUNCTION_ACCESSORY) { MetricsLogger.action(mContext, MetricsEvent.ACTION_USB_CONFIG_ACCESSORY); } - mHandler.sendMessage(MSG_SET_CURRENT_FUNCTIONS, functions); + mHandler.sendMessage(MSG_SET_CURRENT_FUNCTIONS, functions, operationId); } /** @@ -2340,7 +2404,8 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } private void onAdbEnabled(boolean enabled) { - mHandler.sendMessage(MSG_ENABLE_ADB, enabled); + int operationId = sUsbOperationCount.incrementAndGet(); + mHandler.sendMessage(MSG_ENABLE_ADB, enabled, operationId); } /** 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..d09f729bf4ea 100644 --- a/services/usb/java/com/android/server/usb/UsbService.java +++ b/services/usb/java/com/android/server/usb/UsbService.java @@ -622,16 +622,16 @@ public class UsbService extends IUsbManager.Stub { } @Override - public void setCurrentFunctions(long functions) { + public void setCurrentFunctions(long functions, int operationId) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); Preconditions.checkArgument(UsbManager.areSettableFunctions(functions)); Preconditions.checkState(mDeviceManager != null); - mDeviceManager.setCurrentFunctions(functions); + mDeviceManager.setCurrentFunctions(functions, operationId); } @Override - public void setCurrentFunction(String functions, boolean usbDataUnlocked) { - setCurrentFunctions(UsbManager.usbFunctionsFromString(functions)); + public void setCurrentFunction(String functions, boolean usbDataUnlocked, int operationId) { + setCurrentFunctions(UsbManager.usbFunctionsFromString(functions), operationId); } @Override @@ -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/gadget/UsbGadgetAidl.java b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetAidl.java new file mode 100644 index 000000000000..bdfe60ac07c1 --- /dev/null +++ b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetAidl.java @@ -0,0 +1,220 @@ +/* + * 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.usb.hal.gadget; + +import static android.hardware.usb.UsbManager.GADGET_HAL_V2_0; + +import static com.android.server.usb.UsbDeviceManager.logAndPrint; +import static com.android.server.usb.UsbDeviceManager.logAndPrintException; + +import android.annotation.Nullable; +import android.hardware.usb.gadget.IUsbGadget; +import android.hardware.usb.gadget.IUsbGadgetCallback; +import android.hardware.usb.UsbManager.UsbGadgetHalVersion; +import android.os.ServiceManager; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; +import android.util.LongSparseArray; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.IndentingPrintWriter; +import com.android.server.usb.UsbDeviceManager; + +import java.util.ArrayList; +import java.util.concurrent.ThreadLocalRandom; +import java.util.NoSuchElementException; +import java.util.Objects; + +/** + * Implements the methods to interact with AIDL USB HAL. + */ +public final class UsbGadgetAidl implements UsbGadgetHal { + private static final String TAG = UsbGadgetAidl.class.getSimpleName(); + private static final String USB_GADGET_AIDL_SERVICE = IUsbGadget.DESCRIPTOR + "/default"; + // Proxy object for the usb gadget hal daemon. + @GuardedBy("mGadgetProxyLock") + private IUsbGadget mGadgetProxy; + private final UsbDeviceManager mDeviceManager; + public final IndentingPrintWriter mPw; + // Mutex for all mutable shared state. + private final Object mGadgetProxyLock = new Object(); + // Callback when the UsbDevice status is changed by the kernel. + private UsbGadgetCallback mUsbGadgetCallback; + + public @UsbGadgetHalVersion int getGadgetHalVersion() throws RemoteException { + synchronized (mGadgetProxyLock) { + if (mGadgetProxy == null) { + throw new RemoteException("IUsb not initialized yet"); + } + } + Slog.i(TAG, "USB Gadget HAL AIDL version: GADGET_HAL_V2_0"); + return GADGET_HAL_V2_0; + } + + @Override + public void systemReady() { + } + + public void serviceDied() { + logAndPrint(Log.ERROR, mPw, "Usb Gadget AIDL hal service died"); + synchronized (mGadgetProxyLock) { + mGadgetProxy = null; + } + connectToProxy(null); + } + + private void connectToProxy(IndentingPrintWriter pw) { + synchronized (mGadgetProxyLock) { + if (mGadgetProxy != null) { + return; + } + + try { + mGadgetProxy = IUsbGadget.Stub.asInterface( + ServiceManager.waitForService(USB_GADGET_AIDL_SERVICE)); + } catch (NoSuchElementException e) { + logAndPrintException(pw, "connectToProxy: usb gadget hal service not found." + + " Did the service fail to start?", e); + } + } + } + + static boolean isServicePresent(IndentingPrintWriter pw) { + try { + return ServiceManager.isDeclared(USB_GADGET_AIDL_SERVICE); + } catch (NoSuchElementException e) { + logAndPrintException(pw, "connectToProxy: usb gadget Aidl hal service not found.", e); + } + + return false; + } + + public UsbGadgetAidl(UsbDeviceManager deviceManager, IndentingPrintWriter pw) { + mDeviceManager = Objects.requireNonNull(deviceManager); + mPw = pw; + connectToProxy(mPw); + } + + @Override + public void getCurrentUsbFunctions(long operationId) { + synchronized (mGadgetProxyLock) { + try { + mGadgetProxy.getCurrentUsbFunctions(new UsbGadgetCallback(), operationId); + } catch (RemoteException e) { + logAndPrintException(mPw, + "RemoteException while calling getCurrentUsbFunctions" + + ", opID:" + operationId, e); + return; + } + } + } + + @Override + public void getUsbSpeed(long operationId) { + try { + synchronized (mGadgetProxyLock) { + mGadgetProxy.getUsbSpeed(new UsbGadgetCallback(), operationId); + } + } catch (RemoteException e) { + logAndPrintException(mPw, + "RemoteException while calling getUsbSpeed" + + ", opID:" + operationId, e); + return; + } + } + + @Override + public void reset() { + try { + synchronized (mGadgetProxyLock) { + mGadgetProxy.reset(); + } + } catch (RemoteException e) { + logAndPrintException(mPw, + "RemoteException while calling getUsbSpeed", e); + return; + } + } + + @Override + public void setCurrentUsbFunctions(int mRequest, long mFunctions, + boolean mChargingFunctions, int timeout, long operationId) { + try { + mUsbGadgetCallback = new UsbGadgetCallback(mRequest, + mFunctions, mChargingFunctions); + synchronized (mGadgetProxyLock) { + mGadgetProxy.setCurrentUsbFunctions(mFunctions, mUsbGadgetCallback, + timeout, operationId); + } + } catch (RemoteException e) { + logAndPrintException(mPw, + "RemoteException while calling setCurrentUsbFunctions: " + + "mRequest=" + mRequest + + ", mFunctions=" + mFunctions + + ", mChargingFunctions=" + mChargingFunctions + + ", timeout=" + timeout + + ", opID:" + operationId, e); + return; + } + } + + private class UsbGadgetCallback extends IUsbGadgetCallback.Stub { + public int mRequest; + public long mFunctions; + public boolean mChargingFunctions; + + UsbGadgetCallback() { + } + + UsbGadgetCallback(int request, long functions, + boolean chargingFunctions) { + mRequest = request; + mFunctions = functions; + mChargingFunctions = chargingFunctions; + } + + @Override + public void setCurrentUsbFunctionsCb(long functions, + int status, long transactionId) { + mDeviceManager.setCurrentUsbFunctionsCb(functions, status, + mRequest, mFunctions, mChargingFunctions); + } + + @Override + public void getCurrentUsbFunctionsCb(long functions, + int status, long transactionId) { + mDeviceManager.getCurrentUsbFunctionsCb(functions, status); + } + + @Override + public void getUsbSpeedCb(int speed, long transactionId) { + mDeviceManager.getUsbSpeedCb(speed); + } + + @Override + public String getInterfaceHash() { + return IUsbGadgetCallback.HASH; + } + + @Override + public int getInterfaceVersion() { + return IUsbGadgetCallback.VERSION; + } + } +} + diff --git a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHal.java b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHal.java new file mode 100644 index 000000000000..267247b5b835 --- /dev/null +++ b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHal.java @@ -0,0 +1,138 @@ +/* + * 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.usb.hal.gadget; + +import android.annotation.IntDef; +import android.hardware.usb.gadget.IUsbGadgetCallback; +import android.hardware.usb.IUsbOperationInternal; +import android.hardware.usb.UsbManager.UsbHalVersion; +import android.os.RemoteException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.String; + +/** + * @hide + */ +public interface UsbGadgetHal { + /** + * Power role: This USB port can act as a source (provide power). + * @hide + */ + public static final int HAL_POWER_ROLE_SOURCE = 1; + + /** + * Power role: This USB port can act as a sink (receive power). + * @hide + */ + public static final int HAL_POWER_ROLE_SINK = 2; + + @IntDef(prefix = { "HAL_POWER_ROLE_" }, value = { + HAL_POWER_ROLE_SOURCE, + HAL_POWER_ROLE_SINK + }) + @Retention(RetentionPolicy.SOURCE) + @interface HalUsbPowerRole{} + + /** + * Data role: This USB port can act as a host (access data services). + * @hide + */ + public static final int HAL_DATA_ROLE_HOST = 1; + + /** + * Data role: This USB port can act as a device (offer data services). + * @hide + */ + public static final int HAL_DATA_ROLE_DEVICE = 2; + + @IntDef(prefix = { "HAL_DATA_ROLE_" }, value = { + HAL_DATA_ROLE_HOST, + HAL_DATA_ROLE_DEVICE + }) + @Retention(RetentionPolicy.SOURCE) + @interface HalUsbDataRole{} + + /** + * This USB port can act as a downstream facing port (host). + * + * @hide + */ + public static final int HAL_MODE_DFP = 1; + + /** + * This USB port can act as an upstream facing port (device). + * + * @hide + */ + public static final int HAL_MODE_UFP = 2; + @IntDef(prefix = { "HAL_MODE_" }, value = { + HAL_MODE_DFP, + HAL_MODE_UFP, + }) + @Retention(RetentionPolicy.SOURCE) + @interface HalUsbPortMode{} + + /** + * UsbPortManager would call this when the system is done booting. + */ + public void systemReady(); + + /** + * This function is used to query the USB functions included in the + * current USB configuration. + * + * @param transactionId Used for tracking the current request and is passed down to the HAL + * implementation as needed. + */ + public void getCurrentUsbFunctions(long transactionId); + + /** + * The function is used to query current USB speed. + * + * @param transactionId Used for tracking the current request and is passed down to the HAL + * implementation as needed. + */ + public void getUsbSpeed(long transactionId); + + /** + * This function is used to reset USB gadget driver. + * Performs USB data connection reset. The connection will disconnect and + * reconnect. + */ + public void reset(); + + /** + * Invoked to query the version of current gadget hal implementation. + */ + public @UsbHalVersion int getGadgetHalVersion() throws RemoteException; + + /** + * This function is used to set the current USB gadget configuration. + * The USB gadget needs to be torn down if a USB configuration is already + * active. + * + * @param functions list of functions defined by GadgetFunction to be + * included in the gadget composition. + * @param timeout The maximum time (in milliseconds) within which the + * IUsbGadgetCallback needs to be returned. + * @param transactionId Used for tracking the current request and is passed down to the HAL + * implementation as needed. + */ + public void setCurrentUsbFunctions(int request, long functions, + boolean chargingFunctions, int timeout, long transactionId); +} + diff --git a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHalInstance.java b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHalInstance.java new file mode 100644 index 000000000000..d268315a900f --- /dev/null +++ b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHalInstance.java @@ -0,0 +1,48 @@ +/* + * 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.usb.hal.gadget; + +import static com.android.server.usb.UsbPortManager.logAndPrint; + +import com.android.internal.util.IndentingPrintWriter; +import com.android.server.usb.hal.gadget.UsbGadgetHidl; +import com.android.server.usb.hal.gadget.UsbGadgetAidl; +import com.android.server.usb.UsbDeviceManager; + +import android.util.Log; +/** + * Helper class that queries the underlying hal layer to populate UsbPortHal instance. + */ +public final class UsbGadgetHalInstance { + + public static UsbGadgetHal getInstance(UsbDeviceManager deviceManager, + IndentingPrintWriter pw) { + + logAndPrint(Log.DEBUG, pw, "Querying USB Gadget HAL version"); + if (UsbGadgetAidl.isServicePresent(null)) { + logAndPrint(Log.INFO, pw, "USB Gadget HAL AIDL present"); + return new UsbGadgetAidl(deviceManager, pw); + } + if (UsbGadgetHidl.isServicePresent(null)) { + logAndPrint(Log.INFO, pw, "USB Gadget HAL HIDL present"); + return new UsbGadgetHidl(deviceManager, pw); + } + + logAndPrint(Log.ERROR, pw, "USB Gadget HAL AIDL/HIDL not present"); + return null; + } +} + diff --git a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java new file mode 100644 index 000000000000..3e5ecc5eddf4 --- /dev/null +++ b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java @@ -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. + */ +package com.android.server.usb.hal.gadget; + +import static android.hardware.usb.UsbManager.GADGET_HAL_NOT_SUPPORTED; +import static android.hardware.usb.UsbManager.GADGET_HAL_V1_0; +import static android.hardware.usb.UsbManager.GADGET_HAL_V1_1; +import static android.hardware.usb.UsbManager.GADGET_HAL_V1_2; + +import static com.android.server.usb.UsbDeviceManager.logAndPrint; +import static com.android.server.usb.UsbDeviceManager.logAndPrintException; + +import android.annotation.Nullable; +import android.hardware.usb.gadget.V1_0.Status; +import android.hardware.usb.gadget.V1_0.IUsbGadget; +import android.hardware.usb.gadget.V1_2.IUsbGadgetCallback; +import android.hardware.usb.gadget.V1_2.UsbSpeed; +import android.hardware.usb.UsbAccessory; +import android.hardware.usb.UsbManager; +import android.hardware.usb.UsbManager.UsbGadgetHalVersion; +import android.hardware.usb.UsbManager.UsbHalVersion; +import android.hidl.manager.V1_0.IServiceManager; +import android.hidl.manager.V1_0.IServiceNotification; +import android.os.IHwBinder; +import android.os.RemoteException; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.IndentingPrintWriter; +import com.android.server.usb.UsbDeviceManager; + +import java.util.ArrayList; +import java.util.NoSuchElementException; +import java.util.Objects; +/** + * + */ +public final class UsbGadgetHidl implements UsbGadgetHal { + // Cookie sent for usb gadget hal death notification. + private static final int USB_GADGET_HAL_DEATH_COOKIE = 2000; + // Proxy object for the usb gadget hal daemon. + @GuardedBy("mGadgetProxyLock") + private IUsbGadget mGadgetProxy; + private UsbDeviceManager mDeviceManager; + private final IndentingPrintWriter mPw; + // Mutex for all mutable shared state. + private final Object mGadgetProxyLock = new Object(); + private UsbGadgetCallback mUsbGadgetCallback; + + public @UsbGadgetHalVersion int getGadgetHalVersion() throws RemoteException { + int version; + synchronized(mGadgetProxyLock) { + if (mGadgetProxy == null) { + throw new RemoteException("IUsbGadget not initialized yet"); + } + if (android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy) != null) { + version = UsbManager.GADGET_HAL_V1_2; + } else if (android.hardware.usb.gadget.V1_1.IUsbGadget.castFrom(mGadgetProxy) != null) { + version = UsbManager.GADGET_HAL_V1_1; + } else { + version = UsbManager.GADGET_HAL_V1_0; + } + logAndPrint(Log.INFO, mPw, "USB Gadget HAL HIDL version: " + version); + return version; + } + } + + final class DeathRecipient implements IHwBinder.DeathRecipient { + private final IndentingPrintWriter mPw; + + DeathRecipient(IndentingPrintWriter pw) { + mPw = pw; + } + + @Override + public void serviceDied(long cookie) { + if (cookie == USB_GADGET_HAL_DEATH_COOKIE) { + logAndPrint(Log.ERROR, mPw, "Usb Gadget hal service died cookie: " + cookie); + synchronized (mGadgetProxyLock) { + mGadgetProxy = null; + } + } + } + } + + final class ServiceNotification extends IServiceNotification.Stub { + @Override + public void onRegistration(String fqName, String name, boolean preexisting) { + logAndPrint(Log.INFO, mPw, "Usb gadget hal service started " + fqName + " " + name); + connectToProxy(null); + } + } + + private void connectToProxy(IndentingPrintWriter pw) { + synchronized (mGadgetProxyLock) { + if (mGadgetProxy != null) { + return; + } + + try { + mGadgetProxy = IUsbGadget.getService(); + mGadgetProxy.linkToDeath(new DeathRecipient(pw), USB_GADGET_HAL_DEATH_COOKIE); + } catch (NoSuchElementException e) { + logAndPrintException(pw, "connectToProxy: usb gadget hal service not found." + + " Did the service fail to start?", e); + } catch (RemoteException e) { + logAndPrintException(pw, "connectToProxy: usb gadget hal service not responding" + , e); + } + } + } + + @Override + public void systemReady() { + } + + static boolean isServicePresent(IndentingPrintWriter pw) { + try { + IUsbGadget.getService(true); + } catch (NoSuchElementException e) { + logAndPrintException(pw, "connectToProxy: usb gadget hidl hal service not found.", e); + return false; + } catch (RemoteException e) { + logAndPrintException(pw, "IUSBGadget hal service present but failed to get service", e); + } + + return true; + } + + public UsbGadgetHidl(UsbDeviceManager deviceManager, IndentingPrintWriter pw) { + mDeviceManager = Objects.requireNonNull(deviceManager); + mPw = pw; + try { + ServiceNotification serviceNotification = new ServiceNotification(); + + boolean ret = IServiceManager.getService() + .registerForNotifications("android.hardware.usb.gadget@1.0::IUsbGadget", + "", serviceNotification); + if (!ret) { + logAndPrint(Log.ERROR, pw, "Failed to register service start notification"); + } + } catch (RemoteException e) { + logAndPrintException(pw, "Failed to register service start notification", e); + return; + } + connectToProxy(mPw); + } + + @Override + public void getCurrentUsbFunctions(long transactionId) { + try { + synchronized(mGadgetProxyLock) { + mGadgetProxy.getCurrentUsbFunctions(new UsbGadgetCallback()); + } + } catch (RemoteException e) { + logAndPrintException(mPw, + "RemoteException while calling getCurrentUsbFunctions", e); + return; + } + } + + @Override + public void getUsbSpeed(long transactionId) { + try { + synchronized(mGadgetProxyLock) { + if (android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy) != null) { + android.hardware.usb.gadget.V1_2.IUsbGadget gadgetProxy = + android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy); + gadgetProxy.getUsbSpeed(new UsbGadgetCallback()); + } + } + } catch (RemoteException e) { + logAndPrintException(mPw, "get UsbSpeed failed", e); + } + } + + @Override + public void reset() { + try { + synchronized(mGadgetProxyLock) { + if (android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy) != null) { + android.hardware.usb.gadget.V1_2.IUsbGadget gadgetProxy = + android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy); + gadgetProxy.reset(); + } + } + } catch (RemoteException e) { + logAndPrintException(mPw, + "RemoteException while calling getUsbSpeed", e); + return; + } + } + + @Override + public void setCurrentUsbFunctions(int mRequest, long mFunctions, + boolean mChargingFunctions, int timeout, long operationId) { + try { + mUsbGadgetCallback = new UsbGadgetCallback(null, mRequest, + mFunctions, mChargingFunctions); + synchronized(mGadgetProxyLock) { + mGadgetProxy.setCurrentUsbFunctions(mFunctions, mUsbGadgetCallback, timeout); + } + } catch (RemoteException e) { + logAndPrintException(mPw, + "RemoteException while calling setCurrentUsbFunctions" + + " mRequest = " + mRequest + + ", mFunctions = " + mFunctions + + ", timeout = " + timeout + + ", mChargingFunctions = " + mChargingFunctions + + ", operationId =" + operationId, e); + return; + } + } + + private class UsbGadgetCallback extends IUsbGadgetCallback.Stub { + public int mRequest; + public long mFunctions; + public boolean mChargingFunctions; + + UsbGadgetCallback() { + } + UsbGadgetCallback(IndentingPrintWriter pw, int request, + long functions, boolean chargingFunctions) { + mRequest = request; + mFunctions = functions; + mChargingFunctions = chargingFunctions; + } + + @Override + public void setCurrentUsbFunctionsCb(long functions, + int status) { + mDeviceManager.setCurrentUsbFunctionsCb(functions, status, + mRequest, mFunctions, mChargingFunctions); + } + + @Override + public void getCurrentUsbFunctionsCb(long functions, + int status) { + mDeviceManager.getCurrentUsbFunctionsCb(functions, status); + } + + @Override + public void getUsbSpeedCb(int speed) { + mDeviceManager.getUsbSpeedCb(speed); + } + } +} + 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/Annotation.java b/telephony/java/android/telephony/Annotation.java index 86b98f1cbe79..2435243f0044 100644 --- a/telephony/java/android/telephony/Annotation.java +++ b/telephony/java/android/telephony/Annotation.java @@ -5,6 +5,7 @@ import android.net.NetworkAgent; import android.net.NetworkCapabilities; import android.telecom.Connection; import android.telephony.data.ApnSetting; +import android.telephony.ims.ImsCallProfile; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -494,7 +495,7 @@ public class Annotation { PreciseCallState.PRECISE_CALL_STATE_HOLDING, PreciseCallState.PRECISE_CALL_STATE_DIALING, PreciseCallState.PRECISE_CALL_STATE_ALERTING, - PreciseCallState. PRECISE_CALL_STATE_INCOMING, + PreciseCallState.PRECISE_CALL_STATE_INCOMING, PreciseCallState.PRECISE_CALL_STATE_WAITING, PreciseCallState.PRECISE_CALL_STATE_DISCONNECTED, PreciseCallState.PRECISE_CALL_STATE_DISCONNECTING}) @@ -727,6 +728,36 @@ public class Annotation { }) public @interface ValidationStatus {} + /** + * IMS call Service types + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "SERVICE_TYPE_" }, value = { + ImsCallProfile.SERVICE_TYPE_NONE, + ImsCallProfile.SERVICE_TYPE_NORMAL, + ImsCallProfile.SERVICE_TYPE_EMERGENCY, + }) + public @interface ImsCallServiceType {} + + /** + * IMS call types + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "CALL_TYPE_" }, value = { + ImsCallProfile.CALL_TYPE_NONE, + ImsCallProfile.CALL_TYPE_VOICE_N_VIDEO, + ImsCallProfile.CALL_TYPE_VOICE, + ImsCallProfile.CALL_TYPE_VIDEO_N_VOICE, + ImsCallProfile.CALL_TYPE_VT, + ImsCallProfile.CALL_TYPE_VT_TX, + ImsCallProfile.CALL_TYPE_VT_RX, + ImsCallProfile.CALL_TYPE_VT_NODIR, + ImsCallProfile.CALL_TYPE_VS, + ImsCallProfile.CALL_TYPE_VS_TX, + ImsCallProfile.CALL_TYPE_VS_RX, + }) + public @interface ImsCallType {} + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "NET_CAPABILITY_ENTERPRISE_SUB_LEVEL" }, value = { diff --git a/telephony/java/android/telephony/CallAttributes.java b/telephony/java/android/telephony/CallAttributes.java index b7bef39aa275..1dc64a9200fc 100644 --- a/telephony/java/android/telephony/CallAttributes.java +++ b/telephony/java/android/telephony/CallAttributes.java @@ -29,8 +29,10 @@ import java.util.Objects; * Contains information about a call's attributes as passed up from the HAL. If there are multiple * ongoing calls, the CallAttributes will pertain to the call in the foreground. * @hide + * @deprecated use {@link CallState} for call information for each call. */ @SystemApi +@Deprecated public final class CallAttributes implements Parcelable { private PreciseCallState mPreciseCallState; @NetworkType diff --git a/telephony/java/android/telephony/CallState.aidl b/telephony/java/android/telephony/CallState.aidl new file mode 100644 index 000000000000..dd5af8e65921 --- /dev/null +++ b/telephony/java/android/telephony/CallState.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2018 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.telephony; + +parcelable CallState; + diff --git a/telephony/java/android/telephony/CallState.java b/telephony/java/android/telephony/CallState.java new file mode 100644 index 000000000000..51ecfb0a0be8 --- /dev/null +++ b/telephony/java/android/telephony/CallState.java @@ -0,0 +1,409 @@ +/* + * 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.telephony; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.telephony.Annotation.ImsCallServiceType; +import android.telephony.Annotation.ImsCallType; +import android.telephony.Annotation.NetworkType; +import android.telephony.Annotation.PreciseCallStates; +import android.telephony.ims.ImsCallProfile; +import android.telephony.ims.ImsCallSession; + +import java.util.Objects; + +/** + * Contains information about various states for a call. + * @hide + */ +@SystemApi +public final class CallState implements Parcelable { + + /** + * Call classifications are just used for backward compatibility of deprecated API {@link + * TelephonyCallback#CallAttributesListener#onCallAttributesChanged}, Since these will be + * removed when the deprecated API is removed, they should not be opened. + */ + /** + * Call classification is not valid. It should not be opened. + * @hide + */ + public static final int CALL_CLASSIFICATION_UNKNOWN = -1; + + /** + * Call classification indicating foreground call + * @hide + */ + public static final int CALL_CLASSIFICATION_RINGING = 0; + + /** + * Call classification indicating background call + * @hide + */ + public static final int CALL_CLASSIFICATION_FOREGROUND = 1; + + /** + * Call classification indicating ringing call + * @hide + */ + public static final int CALL_CLASSIFICATION_BACKGROUND = 2; + + /** + * Call classification Max value. + * @hide + */ + public static final int CALL_CLASSIFICATION_MAX = CALL_CLASSIFICATION_BACKGROUND + 1; + + @PreciseCallStates + private final int mPreciseCallState; + + @NetworkType + private final int mNetworkType; // TelephonyManager.NETWORK_TYPE_* ints + private final CallQuality mCallQuality; + + private final int mCallClassification; + /** + * IMS call session ID. {@link ImsCallSession#getCallId()} + */ + @Nullable + private String mImsCallId; + + /** + * IMS call service type of this call + */ + @ImsCallServiceType + private int mImsCallServiceType; + + /** + * IMS call type of this call. + */ + @ImsCallType + private int mImsCallType; + + /** + * Constructor of CallAttributes + * + * @param callState call state defined in {@link PreciseCallState} + * @param networkType network type for this call attributes + * @param callQuality call quality for this call attributes, only CallState in + * {@link PreciseCallState#PRECISE_CALL_STATE_ACTIVE} will have valid call + * quality. + * @param callClassification call classification + * @param imsCallId IMS call session ID for this call attributes + * @param imsCallServiceType IMS call service type for this call attributes + * @param imsCallType IMS call type for this call attributes + */ + private CallState(@PreciseCallStates int callState, @NetworkType int networkType, + @NonNull CallQuality callQuality, int callClassification, @Nullable String imsCallId, + @ImsCallServiceType int imsCallServiceType, @ImsCallType int imsCallType) { + this.mPreciseCallState = callState; + this.mNetworkType = networkType; + this.mCallQuality = callQuality; + this.mCallClassification = callClassification; + this.mImsCallId = imsCallId; + this.mImsCallServiceType = imsCallServiceType; + this.mImsCallType = imsCallType; + } + + @NonNull + @Override + public String toString() { + return "mPreciseCallState=" + mPreciseCallState + " mNetworkType=" + mNetworkType + + " mCallQuality=" + mCallQuality + " mCallClassification" + mCallClassification + + " mImsCallId=" + mImsCallId + " mImsCallServiceType=" + mImsCallServiceType + + " mImsCallType=" + mImsCallType; + } + + private CallState(Parcel in) { + this.mPreciseCallState = in.readInt(); + this.mNetworkType = in.readInt(); + this.mCallQuality = in.readParcelable( + CallQuality.class.getClassLoader(), CallQuality.class); + this.mCallClassification = in.readInt(); + this.mImsCallId = in.readString(); + this.mImsCallServiceType = in.readInt(); + this.mImsCallType = in.readInt(); + } + + // getters + /** + * Returns the precise call state of the call. + */ + @PreciseCallStates + public int getCallState() { + return mPreciseCallState; + } + + /** + * Returns the {@link TelephonyManager#NetworkType} of the call. + * + * @see TelephonyManager#NETWORK_TYPE_UNKNOWN + * @see TelephonyManager#NETWORK_TYPE_GPRS + * @see TelephonyManager#NETWORK_TYPE_EDGE + * @see TelephonyManager#NETWORK_TYPE_UMTS + * @see TelephonyManager#NETWORK_TYPE_CDMA + * @see TelephonyManager#NETWORK_TYPE_EVDO_0 + * @see TelephonyManager#NETWORK_TYPE_EVDO_A + * @see TelephonyManager#NETWORK_TYPE_1xRTT + * @see TelephonyManager#NETWORK_TYPE_HSDPA + * @see TelephonyManager#NETWORK_TYPE_HSUPA + * @see TelephonyManager#NETWORK_TYPE_HSPA + * @see TelephonyManager#NETWORK_TYPE_IDEN + * @see TelephonyManager#NETWORK_TYPE_EVDO_B + * @see TelephonyManager#NETWORK_TYPE_LTE + * @see TelephonyManager#NETWORK_TYPE_EHRPD + * @see TelephonyManager#NETWORK_TYPE_HSPAP + * @see TelephonyManager#NETWORK_TYPE_GSM + * @see TelephonyManager#NETWORK_TYPE_TD_SCDMA + * @see TelephonyManager#NETWORK_TYPE_IWLAN + * @see TelephonyManager#NETWORK_TYPE_LTE_CA + * @see TelephonyManager#NETWORK_TYPE_NR + */ + @NetworkType + public int getNetworkType() { + return mNetworkType; + } + + /** + * Returns the {#link CallQuality} of the call. + * @return call quality for this call attributes, only CallState in {@link + * PreciseCallState#PRECISE_CALL_STATE_ACTIVE} will have valid call quality. It will be + * null for the call which is not in {@link PreciseCallState#PRECISE_CALL_STATE_ACTIVE}. + */ + @Nullable + public CallQuality getCallQuality() { + return mCallQuality; + } + + /** + * Returns the call classification. + * @hide + */ + public int getCallClassification() { + return mCallClassification; + } + + /** + * Returns the IMS call session ID. + */ + @Nullable + public String getImsCallSessionId() { + return mImsCallId; + } + + /** + * Returns the IMS call service type. + */ + @ImsCallServiceType + public int getImsCallServiceType() { + return mImsCallServiceType; + } + + /** + * Returns the IMS call type. + */ + @ImsCallType + public int getImsCallType() { + return mImsCallType; + } + + @Override + public int hashCode() { + return Objects.hash(mPreciseCallState, mNetworkType, mCallQuality, mCallClassification, + mImsCallId, mImsCallServiceType, mImsCallType); + } + + @Override + public boolean equals(@Nullable Object o) { + if (o == null || !(o instanceof CallState) || hashCode() != o.hashCode()) { + return false; + } + + if (this == o) { + return true; + } + + CallState s = (CallState) o; + + return (mPreciseCallState == s.mPreciseCallState + && mNetworkType == s.mNetworkType + && Objects.equals(mCallQuality, s.mCallQuality) + && mCallClassification == s.mCallClassification + && Objects.equals(mImsCallId, s.mImsCallId) + && mImsCallType == s.mImsCallType + && mImsCallServiceType == s.mImsCallServiceType); + } + + /** + * {@link Parcelable#describeContents} + */ + public int describeContents() { + return 0; + } + + /** + * {@link Parcelable#writeToParcel} + */ + public void writeToParcel(@Nullable Parcel dest, int flags) { + dest.writeInt(mPreciseCallState); + dest.writeInt(mNetworkType); + dest.writeParcelable(mCallQuality, flags); + dest.writeInt(mCallClassification); + dest.writeString(mImsCallId); + dest.writeInt(mImsCallServiceType); + dest.writeInt(mImsCallType); + } + + public static final @NonNull Creator<CallState> CREATOR = new Creator() { + public CallState createFromParcel(Parcel in) { + return new CallState(in); + } + + public CallState[] newArray(int size) { + return new CallState[size]; + } + }; + + /** + * Builder of {@link CallState} + * + * <p>The example below shows how you might create a new {@code CallState}: + * + * <pre><code> + * + * CallState = new CallState.Builder() + * .setCallState(3) + * .setNetworkType({@link TelephonyManager#NETWORK_TYPE_LTE}) + * .setCallQuality({@link CallQuality}) + * .setImsCallSessionId({@link String}) + * .setImsCallServiceType({@link ImsCallProfile#SERVICE_TYPE_NORMAL}) + * .setImsCallType({@link ImsCallProfile#CALL_TYPE_VOICE}) + * .build(); + * </code></pre> + */ + public static final class Builder { + private @PreciseCallStates int mPreciseCallState; + private @NetworkType int mNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN; + private CallQuality mCallQuality = null; + private int mCallClassification = CALL_CLASSIFICATION_UNKNOWN; + private String mImsCallId; + private @ImsCallServiceType int mImsCallServiceType = ImsCallProfile.SERVICE_TYPE_NONE; + private @ImsCallType int mImsCallType = ImsCallProfile.CALL_TYPE_NONE; + + + /** + * Default constructor for the Builder. + */ + public Builder(@PreciseCallStates int preciseCallState) { + mPreciseCallState = preciseCallState; + } + + /** + * Set network type of this call. + * + * @param networkType the transport type. + * @return The same instance of the builder. + */ + @NonNull + public CallState.Builder setNetworkType(@NetworkType int networkType) { + this.mNetworkType = networkType; + return this; + } + + /** + * Set the call quality {@link CallQuality} of this call. + * + * @param callQuality call quality of active call. + * @return The same instance of the builder. + */ + @NonNull + public CallState.Builder setCallQuality(@Nullable CallQuality callQuality) { + this.mCallQuality = callQuality; + return this; + } + + /** + * Set call classification for this call. + * + * @param classification call classification type defined in this class. + * @return The same instance of the builder. + * @hide + */ + @NonNull + public CallState.Builder setCallClassification(int classification) { + this.mCallClassification = classification; + return this; + } + + /** + * Set IMS call session ID of this call. + * + * @param imsCallId IMS call session ID. + * @return The same instance of the builder. + */ + @NonNull + public CallState.Builder setImsCallSessionId(@Nullable String imsCallId) { + this.mImsCallId = imsCallId; + return this; + } + + /** + * Set IMS call service type of this call. + * + * @param serviceType IMS call service type defined in {@link ImsCallProfile}. + * @return The same instance of the builder. + */ + @NonNull + public CallState.Builder setImsCallServiceType(@ImsCallServiceType int serviceType) { + this.mImsCallServiceType = serviceType; + return this; + } + + /** + * Set IMS call type of this call. + * + * @param callType IMS call type defined in {@link ImsCallProfile}. + * @return The same instance of the builder. + */ + @NonNull + public CallState.Builder setImsCallType(@ImsCallType int callType) { + this.mImsCallType = callType; + return this; + } + + /** + * Build the {@link CallState} + * + * @return the {@link CallState} object + */ + @NonNull + public CallState build() { + return new CallState( + mPreciseCallState, + mNetworkType, + mCallQuality, + mCallClassification, + mImsCallId, + mImsCallServiceType, + mImsCallType); + } + } +} 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/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java index e6d7df34f755..1ea7fdc982a5 100644 --- a/telephony/java/android/telephony/ims/ImsCallProfile.java +++ b/telephony/java/android/telephony/ims/ImsCallProfile.java @@ -78,8 +78,9 @@ public final class ImsCallProfile implements Parcelable { public static final int SERVICE_TYPE_EMERGENCY = 2; /** - * Call types + * Call type none */ + public static final int CALL_TYPE_NONE = 0; /** * IMSPhone to support IR.92 & IR.94 (voice + video upgrade/downgrade) */ 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/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java b/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java index d133f6fbdd87..e2099e652c49 100644 --- a/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java +++ b/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java @@ -24,12 +24,15 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.hardware.usb.UsbManager; +import android.os.Binder; import android.os.RemoteException; import android.util.Log; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.concurrent.atomic.AtomicInteger; + /** * Unit tests lib for {@link android.hardware.usb.UsbManager}. */ @@ -42,6 +45,11 @@ public class UsbManagerTestLib { private UsbManager mUsbManagerMock; @Mock private android.hardware.usb.IUsbManager mMockUsbService; + /** + * Counter for tracking UsbOperation operations. + */ + private static final AtomicInteger sUsbOperationCount = new AtomicInteger(); + public UsbManagerTestLib(Context context) { MockitoAnnotations.initMocks(this); mContext = context; @@ -82,10 +90,11 @@ public class UsbManagerTestLib { } private void testSetCurrentFunctionsMock_Matched(long functions) { + int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid(); try { setCurrentFunctions(functions); - verify(mMockUsbService).setCurrentFunctions(eq(functions)); + verify(mMockUsbService).setCurrentFunctions(eq(functions), operationId); } catch (RemoteException remEx) { Log.w(TAG, "RemoteException"); } @@ -106,9 +115,10 @@ public class UsbManagerTestLib { } public void testSetCurrentFunctionsEx(long functions) throws Exception { + int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid(); setCurrentFunctions(functions); - verify(mMockUsbService).setCurrentFunctions(eq(functions)); + verify(mMockUsbService).setCurrentFunctions(eq(functions), operationId); } public void testGetCurrentFunctions_shouldMatched() { diff --git a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java index 86bcb7290d95..4103ca7c8ca8 100644 --- a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java +++ b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java @@ -98,7 +98,7 @@ public class UsbHandlerTest { } @Override - protected void setEnabledFunctions(long functions, boolean force) { + protected void setEnabledFunctions(long functions, boolean force, int operationId) { mCurrentFunctions = functions; } @@ -134,6 +134,20 @@ public class UsbHandlerTest { protected void sendStickyBroadcast(Intent intent) { mBroadcastedIntent = intent; } + + @Override + public void handlerInitDone(int operationId) { + } + + @Override + public void setCurrentUsbFunctionsCb(long functions, + int status, int mRequest, long mFunctions, boolean mChargingFunctions){ + } + + @Override + public void getUsbSpeedCb(int speed){ + } + } @Before 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..f7f2f22ca293 100644 --- a/tools/aapt2/cmd/Optimize.cpp +++ b/tools/aapt2/cmd/Optimize.cpp @@ -154,8 +154,8 @@ class Optimizer { return 1; } - if (options_.shorten_resource_paths) { - Obfuscator obfuscator(options_.table_flattener_options.shortened_path_map); + Obfuscator obfuscator(options_); + if (obfuscator.IsEnabled()) { if (!obfuscator.Consume(context_, apk->GetResourceTable())) { context_->GetDiagnostics()->Error(android::DiagMessage() << "failed shortening resource paths"); @@ -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.cpp b/tools/aapt2/format/binary/TableFlattener.cpp index f19223411232..8c594ba553a0 100644 --- a/tools/aapt2/format/binary/TableFlattener.cpp +++ b/tools/aapt2/format/binary/TableFlattener.cpp @@ -32,6 +32,7 @@ #include "format/binary/ChunkWriter.h" #include "format/binary/ResEntryWriter.h" #include "format/binary/ResourceTypeExtensions.h" +#include "optimize/Obfuscator.h" #include "trace/TraceBuffer.h" using namespace android; @@ -466,9 +467,6 @@ class PackageFlattener { // table. std::map<ConfigDescription, std::vector<FlatEntry>> config_to_entry_list_map; - // hardcoded string uses characters which make it an invalid resource name - const std::string obfuscated_resource_name = "0_resource_name_obfuscated"; - for (const ResourceTableEntryView& entry : type.entries) { if (entry.staged_id) { aliases_.insert(std::make_pair( @@ -477,30 +475,31 @@ class PackageFlattener { } uint32_t local_key_index; - ResourceName resource_name({}, type.named_type, entry.name); - if (!collapse_key_stringpool_ || - name_collapse_exemptions_.find(resource_name) != name_collapse_exemptions_.end()) { - local_key_index = (uint32_t)key_pool_.MakeRef(entry.name).index(); - } else { - // resource isn't exempt from collapse, add it as obfuscated value - if (entry.overlayable_item) { + auto onObfuscate = [this, &local_key_index, &entry](Obfuscator::Result obfuscatedResult, + const ResourceName& resource_name) { + if (obfuscatedResult == Obfuscator::Result::Keep_ExemptionList) { + local_key_index = (uint32_t)key_pool_.MakeRef(entry.name).index(); + } else if (obfuscatedResult == Obfuscator::Result::Keep_Overlayable) { // if the resource name of the specific entry is obfuscated and this // entry is in the overlayable list, the overlay can't work on this // overlayable at runtime because the name has been obfuscated in // resources.arsc during flatten operation. const OverlayableItem& item = entry.overlayable_item.value(); context_->GetDiagnostics()->Warn(android::DiagMessage(item.overlayable->source) - << "The resource name of overlayable entry " - << resource_name.to_string() << "'" - << " shouldn't be obfuscated in resources.arsc"); + << "The resource name of overlayable entry '" + << resource_name.to_string() + << "' shouldn't be obfuscated in resources.arsc"); local_key_index = (uint32_t)key_pool_.MakeRef(entry.name).index(); } else { - // TODO(b/228192695): output the entry.name and Resource id to make - // de-obfuscated possible. - local_key_index = (uint32_t)key_pool_.MakeRef(obfuscated_resource_name).index(); + local_key_index = + (uint32_t)key_pool_.MakeRef(Obfuscator::kObfuscatedResourceName).index(); } - } + }; + + Obfuscator::ObfuscateResourceName(collapse_key_stringpool_, name_collapse_exemptions_, + type.named_type, entry, onObfuscate); + // Group values by configuration. for (auto& config_value : entry.values) { config_to_entry_list_map[config_value->config].push_back( diff --git a/tools/aapt2/format/binary/TableFlattener.h b/tools/aapt2/format/binary/TableFlattener.h index 35254ba24e53..60605d2fb7fd 100644 --- a/tools/aapt2/format/binary/TableFlattener.h +++ b/tools/aapt2/format/binary/TableFlattener.h @@ -14,8 +14,13 @@ * limitations under the License. */ -#ifndef AAPT_FORMAT_BINARY_TABLEFLATTENER_H -#define AAPT_FORMAT_BINARY_TABLEFLATTENER_H +#ifndef TOOLS_AAPT2_FORMAT_BINARY_TABLEFLATTENER_H_ +#define TOOLS_AAPT2_FORMAT_BINARY_TABLEFLATTENER_H_ + +#include <map> +#include <set> +#include <string> +#include <unordered_map> #include "Resource.h" #include "ResourceTable.h" @@ -71,6 +76,9 @@ struct TableFlattenerOptions { // // This applies only to simple entries (entry->flags & ResTable_entry::FLAG_COMPLEX == 0). bool deduplicate_entry_values = false; + + // Map from original resource ids to obfuscated names. + std::unordered_map<uint32_t, std::string> id_resource_map; }; class TableFlattener : public IResourceTableConsumer { @@ -82,12 +90,12 @@ class TableFlattener : public IResourceTableConsumer { bool Consume(IAaptContext* context, ResourceTable* table) override; private: - DISALLOW_COPY_AND_ASSIGN(TableFlattener); - TableFlattenerOptions options_; android::BigBuffer* buffer_; + + DISALLOW_COPY_AND_ASSIGN(TableFlattener); }; } // namespace aapt -#endif /* AAPT_FORMAT_BINARY_TABLEFLATTENER_H */ +#endif // TOOLS_AAPT2_FORMAT_BINARY_TABLEFLATTENER_H_ 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.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp index a6d58fd38f09..0e40124aa79e 100644 --- a/tools/aapt2/format/proto/ProtoSerialize.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize.cpp @@ -18,6 +18,7 @@ #include "ValueVisitor.h" #include "androidfw/BigBuffer.h" +#include "optimize/Obfuscator.h" using android::ConfigDescription; @@ -366,21 +367,21 @@ void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table } pb_type->set_name(type.named_type.to_string()); - // hardcoded string uses characters which make it an invalid resource name - static const char* obfuscated_resource_name = "0_resource_name_obfuscated"; for (const auto& entry : type.entries) { pb::Entry* pb_entry = pb_type->add_entry(); if (entry.id) { pb_entry->mutable_entry_id()->set_id(entry.id.value()); } - ResourceName resource_name({}, type.named_type, entry.name); - if (options.collapse_key_stringpool && - options.name_collapse_exemptions.find(resource_name) == - options.name_collapse_exemptions.end()) { - pb_entry->set_name(obfuscated_resource_name); - } else { - pb_entry->set_name(entry.name); - } + auto onObfuscate = [pb_entry, &entry](Obfuscator::Result obfuscatedResult, + const ResourceName& resource_name) { + pb_entry->set_name(obfuscatedResult == Obfuscator::Result::Obfuscated + ? Obfuscator::kObfuscatedResourceName + : entry.name); + }; + + Obfuscator::ObfuscateResourceName(options.collapse_key_stringpool, + options.name_collapse_exemptions, type.named_type, entry, + onObfuscate); // Write the Visibility struct. pb::Visibility* pb_visibility = pb_entry->mutable_visibility(); 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..2533f8079c39 100644 --- a/tools/aapt2/optimize/Obfuscator.cpp +++ b/tools/aapt2/optimize/Obfuscator.cpp @@ -16,6 +16,7 @@ #include "optimize/Obfuscator.h" +#include <map> #include <set> #include <string> #include <unordered_set> @@ -32,10 +33,13 @@ static const char base64_chars[] = namespace aapt { -Obfuscator::Obfuscator(std::map<std::string, std::string>& path_map_out) : path_map_(path_map_out) { +Obfuscator::Obfuscator(OptimizeOptions& optimizeOptions) + : options_(optimizeOptions.table_flattener_options), + shorten_resource_paths_(optimizeOptions.shorten_resource_paths), + collapse_key_stringpool_(optimizeOptions.table_flattener_options.collapse_key_stringpool) { } -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 +62,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); } @@ -77,7 +81,8 @@ struct PathComparator { } }; -bool Obfuscator::Consume(IAaptContext* context, ResourceTable* table) { +static bool HandleShortenFilePaths(ResourceTable* table, + std::map<std::string, std::string>& shortened_path_map) { // used to detect collisions std::unordered_set<std::string> shortened_paths; std::set<FileReference*, PathComparator> file_refs; @@ -109,10 +114,94 @@ bool Obfuscator::Consume(IAaptContext* context, ResourceTable* table) { shortened_path = GetShortenedPath(shortened_filename, extension, collision_count); } shortened_paths.insert(shortened_path); - path_map_.insert({*file_ref->path, shortened_path}); + shortened_path_map.insert({*file_ref->path, shortened_path}); file_ref->path = table->string_pool.MakeRef(shortened_path, file_ref->path.GetContext()); } return true; } +void Obfuscator::ObfuscateResourceName( + const bool collapse_key_stringpool, const std::set<ResourceName>& name_collapse_exemptions, + const ResourceNamedType& type_name, const ResourceTableEntryView& entry, + const android::base::function_ref<void(Result obfuscatedResult, const ResourceName&)> + onObfuscate) { + ResourceName resource_name({}, type_name, entry.name); + if (!collapse_key_stringpool || + name_collapse_exemptions.find(resource_name) != name_collapse_exemptions.end()) { + onObfuscate(Result::Keep_ExemptionList, resource_name); + } else { + // resource isn't exempt from collapse, add it as obfuscated value + if (entry.overlayable_item) { + // if the resource name of the specific entry is obfuscated and this + // entry is in the overlayable list, the overlay can't work on this + // overlayable at runtime because the name has been obfuscated in + // resources.arsc during flatten operation. + onObfuscate(Result::Keep_Overlayable, resource_name); + } else { + onObfuscate(Result::Obfuscated, resource_name); + } + } +} + +static bool HandleCollapseKeyStringPool( + const ResourceTable* table, const bool collapse_key_string_pool, + const std::set<ResourceName>& name_collapse_exemptions, + std::unordered_map<uint32_t, std::string>& id_resource_map) { + if (!collapse_key_string_pool) { + return true; + } + + int entryResId = 0; + auto onObfuscate = [&entryResId, &id_resource_map](const Obfuscator::Result obfuscatedResult, + const ResourceName& resource_name) { + if (obfuscatedResult == Obfuscator::Result::Obfuscated) { + id_resource_map.insert({entryResId, resource_name.entry}); + } + }; + + for (auto& package : table->packages) { + for (auto& type : package->types) { + for (auto& entry : type->entries) { + if (!entry->id.has_value() || entry->name.empty()) { + continue; + } + entryResId = entry->id->id; + ResourceTableEntryView entry_view{ + .name = entry->name, + .id = entry->id ? entry->id.value().entry_id() : (std::optional<uint16_t>)std::nullopt, + .visibility = entry->visibility, + .allow_new = entry->allow_new, + .overlayable_item = entry->overlayable_item, + .staged_id = entry->staged_id}; + + Obfuscator::ObfuscateResourceName(collapse_key_string_pool, name_collapse_exemptions, + type->named_type, entry_view, onObfuscate); + } + } + } + + return true; +} + +bool Obfuscator::Consume(IAaptContext* context, ResourceTable* table) { + HandleCollapseKeyStringPool(table, options_.collapse_key_stringpool, + options_.name_collapse_exemptions, options_.id_resource_map); + if (shorten_resource_paths_) { + return HandleShortenFilePaths(table, options_.shortened_path_map); + } + return true; +} + +/** + * Tell the optimizer whether it's needed to dump information for de-obfuscating. + * + * There are two conditions need to dump the information for de-obfuscating. + * * the option of shortening file paths is enabled. + * * the option of collapsing resource names is enabled. + * @return true if the information needed for de-obfuscating, otherwise false + */ +bool Obfuscator::IsEnabled() const { + return shorten_resource_paths_ || collapse_key_stringpool_; +} + } // namespace aapt diff --git a/tools/aapt2/optimize/Obfuscator.h b/tools/aapt2/optimize/Obfuscator.h index 1ea32db12815..786bf8c9ed93 100644 --- a/tools/aapt2/optimize/Obfuscator.h +++ b/tools/aapt2/optimize/Obfuscator.h @@ -17,10 +17,14 @@ #ifndef TOOLS_AAPT2_OPTIMIZE_OBFUSCATOR_H_ #define TOOLS_AAPT2_OPTIMIZE_OBFUSCATOR_H_ -#include <map> +#include <set> #include <string> +#include "ResourceTable.h" +#include "android-base/function_ref.h" #include "android-base/macros.h" +#include "cmd/Optimize.h" +#include "format/binary/TableFlattener.h" #include "process/IResourceTableConsumer.h" namespace aapt { @@ -30,12 +34,26 @@ class ResourceTable; // Maps resources in the apk to shortened paths. class Obfuscator : public IResourceTableConsumer { public: - explicit Obfuscator(std::map<std::string, std::string>& path_map_out); + explicit Obfuscator(OptimizeOptions& optimizeOptions); bool Consume(IAaptContext* context, ResourceTable* table) override; + bool IsEnabled() const; + + enum class Result { Obfuscated, Keep_ExemptionList, Keep_Overlayable }; + + // hardcoded string uses characters which make it an invalid resource name + static constexpr char kObfuscatedResourceName[] = "0_resource_name_obfuscated"; + + static void ObfuscateResourceName( + const bool collapse_key_stringpool, const std::set<ResourceName>& name_collapse_exemptions, + const ResourceNamedType& type_name, const ResourceTableEntryView& entry, + const android::base::function_ref<void(Result, const ResourceName&)> onObfuscate); + private: - std::map<std::string, std::string>& path_map_; + TableFlattenerOptions& options_; + const bool shorten_resource_paths_; + const bool collapse_key_stringpool_; DISALLOW_COPY_AND_ASSIGN(Obfuscator); }; diff --git a/tools/aapt2/optimize/Obfuscator_test.cpp b/tools/aapt2/optimize/Obfuscator_test.cpp index a3339d486d4a..17d1e5262b76 100644 --- a/tools/aapt2/optimize/Obfuscator_test.cpp +++ b/tools/aapt2/optimize/Obfuscator_test.cpp @@ -16,6 +16,7 @@ #include "optimize/Obfuscator.h" +#include <map> #include <memory> #include <string> @@ -51,8 +52,9 @@ TEST(ObfuscatorTest, FileRefPathsChangedInResourceTable) { .AddString("android:string/string", "res/should/still/be/the/same.png") .Build(); - std::map<std::string, std::string> path_map; - ASSERT_TRUE(Obfuscator(path_map).Consume(context.get(), table.get())); + OptimizeOptions options{.shorten_resource_paths = true}; + std::map<std::string, std::string>& path_map = options.table_flattener_options.shortened_path_map; + ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get())); // Expect that the path map is populated ASSERT_THAT(path_map.find("res/drawables/xmlfile.xml"), Not(Eq(path_map.end()))); @@ -87,8 +89,9 @@ TEST(ObfuscatorTest, SkipColorFileRefPaths) { test::ParseConfigOrDie("mdp-v21")) .Build(); - std::map<std::string, std::string> path_map; - ASSERT_TRUE(Obfuscator(path_map).Consume(context.get(), table.get())); + OptimizeOptions options{.shorten_resource_paths = true}; + std::map<std::string, std::string>& path_map = options.table_flattener_options.shortened_path_map; + ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get())); // Expect that the path map to not contain the ColorStateList ASSERT_THAT(path_map.find("res/color/colorlist.xml"), Eq(path_map.end())); @@ -107,8 +110,9 @@ TEST(ObfuscatorTest, KeepExtensions) { .AddFileReference("android:color/pngfile", original_png_path) .Build(); - std::map<std::string, std::string> path_map; - ASSERT_TRUE(Obfuscator(path_map).Consume(context.get(), table.get())); + OptimizeOptions options{.shorten_resource_paths = true}; + std::map<std::string, std::string>& path_map = options.table_flattener_options.shortened_path_map; + ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get())); // Expect that the path map is populated ASSERT_THAT(path_map.find("res/drawable/xmlfile.xml"), Not(Eq(path_map.end()))); @@ -133,8 +137,10 @@ TEST(ObfuscatorTest, DeterministicallyHandleCollisions) { test::ResourceTableBuilder builder1; FillTable(builder1, 0, kNumResources); std::unique_ptr<ResourceTable> table1 = builder1.Build(); - std::map<std::string, std::string> expected_mapping; - ASSERT_TRUE(Obfuscator(expected_mapping).Consume(context.get(), table1.get())); + OptimizeOptions options{.shorten_resource_paths = true}; + std::map<std::string, std::string>& expected_mapping = + options.table_flattener_options.shortened_path_map; + ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table1.get())); // We are trying to ensure lack of non-determinism, it is not simple to prove // a negative, thus we must try the test a few times so that the test itself @@ -153,8 +159,10 @@ TEST(ObfuscatorTest, DeterministicallyHandleCollisions) { FillTable(builder2, 0, start_index); std::unique_ptr<ResourceTable> table2 = builder2.Build(); - std::map<std::string, std::string> actual_mapping; - ASSERT_TRUE(Obfuscator(actual_mapping).Consume(context.get(), table2.get())); + OptimizeOptions actualOptimizerOptions{.shorten_resource_paths = true}; + TableFlattenerOptions& actual_options = actualOptimizerOptions.table_flattener_options; + std::map<std::string, std::string>& actual_mapping = actual_options.shortened_path_map; + ASSERT_TRUE(Obfuscator(actualOptimizerOptions).Consume(context.get(), table2.get())); for (auto& item : actual_mapping) { ASSERT_THAT(expected_mapping[item.first], Eq(item.second)); @@ -162,4 +170,70 @@ TEST(ObfuscatorTest, DeterministicallyHandleCollisions) { } } +TEST(ObfuscatorTest, DumpIdResourceMap) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + + OverlayableItem overlayable_item(std::make_shared<Overlayable>("TestName", "overlay://theme")); + overlayable_item.policies |= PolicyFlags::PRODUCT_PARTITION; + overlayable_item.policies |= PolicyFlags::SYSTEM_PARTITION; + overlayable_item.policies |= PolicyFlags::VENDOR_PARTITION; + + std::string original_xml_path = "res/drawable/xmlfile.xml"; + std::string original_png_path = "res/drawable/pngfile.png"; + + std::string name = "com.app.test:string/overlayable"; + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddFileReference("android:color/xmlfile", original_xml_path) + .AddFileReference("android:color/pngfile", original_png_path) + .AddValue("com.app.test:color/mycolor", aapt::ResourceId(0x7f020000), + aapt::util::make_unique<aapt::BinaryPrimitive>( + uint8_t(android::Res_value::TYPE_INT_COLOR_ARGB8), 0xffaabbcc)) + .AddString("com.app.test:string/mystring", ResourceId(0x7f030000), "hi") + .AddString("com.app.test:string/in_exemption", ResourceId(0x7f030001), "Hi") + .AddString(name, ResourceId(0x7f030002), "HI") + .SetOverlayable(name, overlayable_item) + .Build(); + + OptimizeOptions options{.shorten_resource_paths = true}; + TableFlattenerOptions& flattenerOptions = options.table_flattener_options; + flattenerOptions.collapse_key_stringpool = true; + flattenerOptions.name_collapse_exemptions.insert( + ResourceName({}, ResourceType::kString, "in_exemption")); + auto& id_resource_map = flattenerOptions.id_resource_map; + ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get())); + + // Expect that the id resource name map is populated + EXPECT_THAT(id_resource_map.at(0x7f020000), Eq("mycolor")); + EXPECT_THAT(id_resource_map.at(0x7f030000), Eq("mystring")); + EXPECT_THAT(id_resource_map.find(0x7f030001), Eq(id_resource_map.end())); + EXPECT_THAT(id_resource_map.find(0x7f030002), Eq(id_resource_map.end())); +} + +TEST(ObfuscatorTest, IsEnabledWithDefaultOption) { + OptimizeOptions options; + Obfuscator obfuscatorWithDefaultOption(options); + ASSERT_THAT(obfuscatorWithDefaultOption.IsEnabled(), Eq(false)); +} + +TEST(ObfuscatorTest, IsEnabledWithShortenPathOption) { + OptimizeOptions options{.shorten_resource_paths = true}; + Obfuscator obfuscatorWithShortenPathOption(options); + ASSERT_THAT(obfuscatorWithShortenPathOption.IsEnabled(), Eq(true)); +} + +TEST(ObfuscatorTest, IsEnabledWithCollapseStringPoolOption) { + OptimizeOptions options; + options.table_flattener_options.collapse_key_stringpool = true; + Obfuscator obfuscatorWithCollapseStringPoolOption(options); + ASSERT_THAT(obfuscatorWithCollapseStringPoolOption.IsEnabled(), Eq(true)); +} + +TEST(ObfuscatorTest, IsEnabledWithShortenPathAndCollapseStringPoolOption) { + OptimizeOptions options{.shorten_resource_paths = true}; + options.table_flattener_options.collapse_key_stringpool = true; + Obfuscator obfuscatorWithCollapseStringPoolOption(options); + ASSERT_THAT(obfuscatorWithCollapseStringPoolOption.IsEnabled(), Eq(true)); +} + } // namespace aapt 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}}) + ); + `; + }, + '')); diff --git a/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt b/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt index f29d9b2a6e81..c6f6d45215fe 100644 --- a/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt +++ b/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt @@ -54,6 +54,7 @@ class ImmutabilityProcessor : AbstractProcessor() { "java.lang.Short", "java.lang.String", "java.lang.Void", + "java.util.UUID", "android.os.Parcelable.Creator", ) |