diff options
250 files changed, 6524 insertions, 2676 deletions
@@ -1,3 +1,4 @@ third_party { - license_type: NOTICE + # would be NOTICE save for libs/usb/tests/accessorytest/f_accessory.h + license_type: RESTRICTED } diff --git a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java index cc3e9c33fe42..c332a598c30b 100644 --- a/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java +++ b/apex/jobscheduler/service/java/com/android/server/AppStateTrackerImpl.java @@ -105,10 +105,6 @@ public class AppStateTrackerImpl implements AppStateTracker { @GuardedBy("mLock") final SparseBooleanArray mActiveUids = new SparseBooleanArray(); - /** UIDs that are in the foreground. */ - @GuardedBy("mLock") - final SparseBooleanArray mForegroundUids = new SparseBooleanArray(); - /** * System except-idle + user exemption list in the device idle controller. */ @@ -286,13 +282,6 @@ public class AppStateTrackerImpl implements AppStateTracker { } /** - * This is called when the foreground state changed for a UID. - */ - private void onUidForegroundStateChanged(AppStateTrackerImpl sender, int uid) { - onUidForeground(uid, sender.isUidInForeground(uid)); - } - - /** * This is called when the active/idle state changed for a UID. */ private void onUidActiveStateChanged(AppStateTrackerImpl sender, int uid) { @@ -416,14 +405,6 @@ public class AppStateTrackerImpl implements AppStateTracker { } /** - * Called when a UID comes into the foreground or the background. - * - * @see #isUidInForeground(int) - */ - public void onUidForeground(int uid, boolean foreground) { - } - - /** * Called when an ephemeral uid goes to the background, so its alarms need to be removed. */ public void removeAlarmsForUid(int uid) { @@ -460,7 +441,6 @@ public class AppStateTrackerImpl implements AppStateTracker { mExemptedBucketPackages.remove(userId, pkgName); mRunAnyRestrictedPackages.remove(Pair.create(uid, pkgName)); mActiveUids.delete(uid); - mForegroundUids.delete(uid); } break; } @@ -496,8 +476,7 @@ public class AppStateTrackerImpl implements AppStateTracker { mIActivityManager.registerUidObserver(new UidObserver(), ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_IDLE - | ActivityManager.UID_OBSERVER_ACTIVE - | ActivityManager.UID_OBSERVER_PROCSTATE, + | ActivityManager.UID_OBSERVER_ACTIVE, ActivityManager.PROCESS_STATE_UNKNOWN, null); mAppOpsService.startWatchingMode(TARGET_OP, null, new AppOpsWatcher()); @@ -698,7 +677,6 @@ public class AppStateTrackerImpl implements AppStateTracker { private final class UidObserver extends IUidObserver.Stub { @Override public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) { - mHandler.onUidStateChanged(uid, procState); } @Override @@ -769,7 +747,6 @@ public class AppStateTrackerImpl implements AppStateTracker { private class MyHandler extends Handler { private static final int MSG_UID_ACTIVE_STATE_CHANGED = 0; - private static final int MSG_UID_FG_STATE_CHANGED = 1; private static final int MSG_RUN_ANY_CHANGED = 3; private static final int MSG_ALL_UNEXEMPTED = 4; private static final int MSG_ALL_EXEMPTION_LIST_CHANGED = 5; @@ -779,7 +756,6 @@ public class AppStateTrackerImpl implements AppStateTracker { private static final int MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED = 9; private static final int MSG_EXEMPTED_BUCKET_CHANGED = 10; - private static final int MSG_ON_UID_STATE_CHANGED = 11; private static final int MSG_ON_UID_ACTIVE = 12; private static final int MSG_ON_UID_GONE = 13; private static final int MSG_ON_UID_IDLE = 14; @@ -792,10 +768,6 @@ public class AppStateTrackerImpl implements AppStateTracker { obtainMessage(MSG_UID_ACTIVE_STATE_CHANGED, uid, 0).sendToTarget(); } - public void notifyUidForegroundStateChanged(int uid) { - obtainMessage(MSG_UID_FG_STATE_CHANGED, uid, 0).sendToTarget(); - } - public void notifyRunAnyAppOpsChanged(int uid, @NonNull String packageName) { obtainMessage(MSG_RUN_ANY_CHANGED, uid, 0, packageName).sendToTarget(); } @@ -834,10 +806,6 @@ public class AppStateTrackerImpl implements AppStateTracker { obtainMessage(MSG_USER_REMOVED, userId, 0).sendToTarget(); } - public void onUidStateChanged(int uid, int procState) { - obtainMessage(MSG_ON_UID_STATE_CHANGED, uid, procState).sendToTarget(); - } - public void onUidActive(int uid) { obtainMessage(MSG_ON_UID_ACTIVE, uid, 0).sendToTarget(); } @@ -875,13 +843,6 @@ public class AppStateTrackerImpl implements AppStateTracker { mStatLogger.logDurationStat(Stats.UID_ACTIVE_STATE_CHANGED, start); return; - case MSG_UID_FG_STATE_CHANGED: - for (Listener l : cloneListeners()) { - l.onUidForegroundStateChanged(sender, msg.arg1); - } - mStatLogger.logDurationStat(Stats.UID_FG_STATE_CHANGED, start); - return; - case MSG_RUN_ANY_CHANGED: for (Listener l : cloneListeners()) { l.onRunAnyAppOpsChanged(sender, msg.arg1, (String) msg.obj); @@ -944,9 +905,6 @@ public class AppStateTrackerImpl implements AppStateTracker { handleUserRemoved(msg.arg1); return; - case MSG_ON_UID_STATE_CHANGED: - handleUidStateChanged(msg.arg1, msg.arg2); - return; case MSG_ON_UID_ACTIVE: handleUidActive(msg.arg1); return; @@ -971,20 +929,6 @@ public class AppStateTrackerImpl implements AppStateTracker { } } - public void handleUidStateChanged(int uid, int procState) { - synchronized (mLock) { - if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) { - if (removeUidFromArray(mForegroundUids, uid, false)) { - mHandler.notifyUidForegroundStateChanged(uid); - } - } else { - if (addUidToArray(mForegroundUids, uid)) { - mHandler.notifyUidForegroundStateChanged(uid); - } - } - } - } - public void handleUidActive(int uid) { synchronized (mLock) { if (addUidToArray(mActiveUids, uid)) { @@ -1007,9 +951,6 @@ public class AppStateTrackerImpl implements AppStateTracker { if (removeUidFromArray(mActiveUids, uid, remove)) { mHandler.notifyUidActiveStateChanged(uid); } - if (removeUidFromArray(mForegroundUids, uid, remove)) { - mHandler.notifyUidForegroundStateChanged(uid); - } } } } @@ -1026,7 +967,6 @@ public class AppStateTrackerImpl implements AppStateTracker { } } cleanUpArrayForUser(mActiveUids, removedUserId); - cleanUpArrayForUser(mForegroundUids, removedUserId); mExemptedBucketPackages.remove(removedUserId); } } @@ -1222,22 +1162,6 @@ public class AppStateTrackerImpl implements AppStateTracker { } /** - * @return whether a UID is in the foreground or not. - * - * Note this information is based on the UID proc state callback, meaning it's updated - * asynchronously and may subtly be stale. If the fresh data is needed, use - * {@link ActivityManagerInternal#getUidProcessState} instead. - */ - public boolean isUidInForeground(int uid) { - if (UserHandle.isCore(uid)) { - return true; - } - synchronized (mLock) { - return mForegroundUids.get(uid); - } - } - - /** * @return whether force all apps standby is enabled or not. */ public boolean isForceAllAppsStandbyEnabled() { @@ -1315,9 +1239,6 @@ public class AppStateTrackerImpl implements AppStateTracker { pw.print("Active uids: "); dumpUids(pw, mActiveUids); - pw.print("Foreground uids: "); - dumpUids(pw, mForegroundUids); - pw.print("Except-idle + user exemption list appids: "); pw.println(Arrays.toString(mPowerExemptAllAppIds)); @@ -1395,12 +1316,6 @@ public class AppStateTrackerImpl implements AppStateTracker { } } - for (int i = 0; i < mForegroundUids.size(); i++) { - if (mForegroundUids.valueAt(i)) { - proto.write(AppStateTrackerProto.FOREGROUND_UIDS, mForegroundUids.keyAt(i)); - } - } - for (int appId : mPowerExemptAllAppIds) { proto.write(AppStateTrackerProto.POWER_SAVE_EXEMPT_APP_IDS, appId); } diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index aa46cfdc5c8a..d44169d16d5d 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -90,7 +90,6 @@ import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.SparseIntArray; -import android.util.SparseLongArray; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; @@ -152,7 +151,8 @@ public class AlarmManagerService extends SystemService { static final boolean DEBUG_BG_LIMIT = localLOGV || false; static final boolean DEBUG_STANDBY = localLOGV || false; static final boolean RECORD_ALARMS_IN_HISTORY = true; - static final boolean RECORD_DEVICE_IDLE_ALARMS = false; + // TODO (b/178484639): Turn off once allow-while-idle revamp is completed. + static final boolean RECORD_DEVICE_IDLE_ALARMS = true; static final String TIMEZONE_PROPERTY = "persist.sys.timezone"; static final int TICK_HISTORY_DEPTH = 10; @@ -208,6 +208,7 @@ public class AlarmManagerService extends SystemService { new ArrayList<>(); AlarmHandler mHandler; AppWakeupHistory mAppWakeupHistory; + AppWakeupHistory mAllowWhileIdleHistory; ClockReceiver mClockReceiver; final DeliveryTracker mDeliveryTracker = new DeliveryTracker(); IBinder.DeathRecipient mListenerDeathRecipient; @@ -230,19 +231,6 @@ public class AlarmManagerService extends SystemService { */ int mSystemUiUid; - /** - * For each uid, this is the last time we dispatched an "allow while idle" alarm, - * used to determine the earliest we can dispatch the next such alarm. Times are in the - * 'elapsed' timebase. - */ - final SparseLongArray mLastAllowWhileIdleDispatch = new SparseLongArray(); - - /** - * For each uid, we store whether the last allow-while-idle alarm was dispatched while - * the uid was in foreground or not. We will use the allow_while_idle_short_time in such cases. - */ - final SparseBooleanArray mUseAllowWhileIdleShortTime = new SparseBooleanArray(); - static boolean isTimeTickAlarm(Alarm a) { return a.uid == Process.SYSTEM_UID && TIME_TICK_TAG.equals(a.listenerTag); } @@ -290,9 +278,11 @@ public class AlarmManagerService extends SystemService { private boolean mAppStandbyParole; /** - * A rolling window history of previous times when an alarm was sent to a package. + * A container to keep rolling window history of previous times when an alarm was sent to + * a package. */ - private static class AppWakeupHistory { + @VisibleForTesting + static class AppWakeupHistory { private ArrayMap<Pair<String, Integer>, LongArrayQueue> mPackageHistory = new ArrayMap<>(); private long mWindowSize; @@ -353,7 +343,6 @@ public class AlarmManagerService extends SystemService { } void dump(IndentingPrintWriter pw, long nowElapsed) { - pw.println("App Alarm history:"); pw.increaseIndent(); for (int i = 0; i < mPackageHistory.size(); i++) { final Pair<String, Integer> packageUser = mPackageHistory.keyAt(i); @@ -389,10 +378,6 @@ public class AlarmManagerService extends SystemService { @VisibleForTesting static final String KEY_MAX_INTERVAL = "max_interval"; @VisibleForTesting - static final String KEY_ALLOW_WHILE_IDLE_SHORT_TIME = "allow_while_idle_short_time"; - @VisibleForTesting - static final String KEY_ALLOW_WHILE_IDLE_LONG_TIME = "allow_while_idle_long_time"; - @VisibleForTesting static final String KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION = "allow_while_idle_whitelist_duration"; @VisibleForTesting @@ -422,11 +407,12 @@ public class AlarmManagerService extends SystemService { private static final String KEY_TIME_TICK_ALLOWED_WHILE_IDLE = "time_tick_allowed_while_idle"; + @VisibleForTesting + static final String KEY_ALLOW_WHILE_IDLE_QUOTA = "allow_while_idle_quota"; + private static final long DEFAULT_MIN_FUTURITY = 5 * 1000; private static final long DEFAULT_MIN_INTERVAL = 60 * 1000; private static final long DEFAULT_MAX_INTERVAL = 365 * DateUtils.DAY_IN_MILLIS; - private static final long DEFAULT_ALLOW_WHILE_IDLE_SHORT_TIME = DEFAULT_MIN_FUTURITY; - private static final long DEFAULT_ALLOW_WHILE_IDLE_LONG_TIME = 9 * 60 * 1000; private static final long DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION = 10 * 1000; private static final long DEFAULT_LISTENER_TIMEOUT = 5 * 1000; private static final int DEFAULT_MAX_ALARMS_PER_UID = 500; @@ -447,6 +433,9 @@ public class AlarmManagerService extends SystemService { private static final boolean DEFAULT_LAZY_BATCHING = true; private static final boolean DEFAULT_TIME_TICK_ALLOWED_WHILE_IDLE = true; + private static final int DEFAULT_ALLOW_WHILE_IDLE_QUOTA = 7; + public static final long ALLOW_WHILE_IDLE_WINDOW = 60 * 60 * 1000; // 1 hour. + // Minimum futurity of a new alarm public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY; @@ -456,12 +445,6 @@ public class AlarmManagerService extends SystemService { // Maximum alarm recurrence interval public long MAX_INTERVAL = DEFAULT_MAX_INTERVAL; - // Minimum time between ALLOW_WHILE_IDLE alarms when system is not idle. - public long ALLOW_WHILE_IDLE_SHORT_TIME = DEFAULT_ALLOW_WHILE_IDLE_SHORT_TIME; - - // Minimum time between ALLOW_WHILE_IDLE alarms when system is idling. - public long ALLOW_WHILE_IDLE_LONG_TIME = DEFAULT_ALLOW_WHILE_IDLE_LONG_TIME; - // BroadcastOptions.setTemporaryAppWhitelistDuration() to use for FLAG_ALLOW_WHILE_IDLE. public long ALLOW_WHILE_IDLE_WHITELIST_DURATION = DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION; @@ -478,6 +461,8 @@ public class AlarmManagerService extends SystemService { public boolean LAZY_BATCHING = DEFAULT_LAZY_BATCHING; public boolean TIME_TICK_ALLOWED_WHILE_IDLE = DEFAULT_TIME_TICK_ALLOWED_WHILE_IDLE; + public int ALLOW_WHILE_IDLE_QUOTA = DEFAULT_ALLOW_WHILE_IDLE_QUOTA; + private long mLastAllowWhileIdleWhitelistDuration = -1; Constants() { @@ -523,15 +508,13 @@ public class AlarmManagerService extends SystemService { MAX_INTERVAL = properties.getLong( KEY_MAX_INTERVAL, DEFAULT_MAX_INTERVAL); break; - case KEY_ALLOW_WHILE_IDLE_SHORT_TIME: - ALLOW_WHILE_IDLE_SHORT_TIME = properties.getLong( - KEY_ALLOW_WHILE_IDLE_SHORT_TIME, - DEFAULT_ALLOW_WHILE_IDLE_SHORT_TIME); - break; - case KEY_ALLOW_WHILE_IDLE_LONG_TIME: - ALLOW_WHILE_IDLE_LONG_TIME = properties.getLong( - KEY_ALLOW_WHILE_IDLE_LONG_TIME, - DEFAULT_ALLOW_WHILE_IDLE_LONG_TIME); + case KEY_ALLOW_WHILE_IDLE_QUOTA: + ALLOW_WHILE_IDLE_QUOTA = properties.getInt(KEY_ALLOW_WHILE_IDLE_QUOTA, + DEFAULT_ALLOW_WHILE_IDLE_QUOTA); + if (ALLOW_WHILE_IDLE_QUOTA <= 0) { + Slog.w(TAG, "Cannot have allow-while-idle quota lower than 1."); + ALLOW_WHILE_IDLE_QUOTA = 1; + } break; case KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION: ALLOW_WHILE_IDLE_WHITELIST_DURATION = properties.getLong( @@ -660,14 +643,11 @@ public class AlarmManagerService extends SystemService { TimeUtils.formatDuration(LISTENER_TIMEOUT, pw); pw.println(); - pw.print(KEY_ALLOW_WHILE_IDLE_SHORT_TIME); - pw.print("="); - TimeUtils.formatDuration(ALLOW_WHILE_IDLE_SHORT_TIME, pw); + pw.print("allow_while_idle_window="); + TimeUtils.formatDuration(ALLOW_WHILE_IDLE_WINDOW, pw); pw.println(); - pw.print(KEY_ALLOW_WHILE_IDLE_LONG_TIME); - pw.print("="); - TimeUtils.formatDuration(ALLOW_WHILE_IDLE_LONG_TIME, pw); + pw.print(KEY_ALLOW_WHILE_IDLE_QUOTA, ALLOW_WHILE_IDLE_QUOTA); pw.println(); pw.print(KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION); @@ -675,9 +655,8 @@ public class AlarmManagerService extends SystemService { TimeUtils.formatDuration(ALLOW_WHILE_IDLE_WHITELIST_DURATION, pw); pw.println(); - pw.print(KEY_MAX_ALARMS_PER_UID); - pw.print("="); - pw.println(MAX_ALARMS_PER_UID); + pw.print(KEY_MAX_ALARMS_PER_UID, MAX_ALARMS_PER_UID); + pw.println(); pw.print(KEY_APP_STANDBY_WINDOW); pw.print("="); @@ -685,14 +664,12 @@ public class AlarmManagerService extends SystemService { pw.println(); for (int i = 0; i < KEYS_APP_STANDBY_QUOTAS.length; i++) { - pw.print(KEYS_APP_STANDBY_QUOTAS[i]); - pw.print("="); - pw.println(APP_STANDBY_QUOTAS[i]); + pw.print(KEYS_APP_STANDBY_QUOTAS[i], APP_STANDBY_QUOTAS[i]); + pw.println(); } - pw.print(KEY_APP_STANDBY_RESTRICTED_QUOTA); - pw.print("="); - pw.println(APP_STANDBY_RESTRICTED_QUOTA); + pw.print(KEY_APP_STANDBY_RESTRICTED_QUOTA, APP_STANDBY_RESTRICTED_QUOTA); + pw.println(); pw.print(KEY_APP_STANDBY_RESTRICTED_WINDOW); pw.print("="); @@ -715,10 +692,6 @@ public class AlarmManagerService extends SystemService { proto.write(ConstantsProto.MIN_INTERVAL_DURATION_MS, MIN_INTERVAL); proto.write(ConstantsProto.MAX_INTERVAL_DURATION_MS, MAX_INTERVAL); proto.write(ConstantsProto.LISTENER_TIMEOUT_DURATION_MS, LISTENER_TIMEOUT); - proto.write(ConstantsProto.ALLOW_WHILE_IDLE_SHORT_DURATION_MS, - ALLOW_WHILE_IDLE_SHORT_TIME); - proto.write(ConstantsProto.ALLOW_WHILE_IDLE_LONG_DURATION_MS, - ALLOW_WHILE_IDLE_LONG_TIME); proto.write(ConstantsProto.ALLOW_WHILE_IDLE_WHITELIST_DURATION_MS, ALLOW_WHILE_IDLE_WHITELIST_DURATION); @@ -1268,6 +1241,7 @@ public class AlarmManagerService extends SystemService { mAlarmStore.setAlarmClockRemovalListener(mAlarmClockUpdater); mAppWakeupHistory = new AppWakeupHistory(Constants.DEFAULT_APP_STANDBY_WINDOW); + mAllowWhileIdleHistory = new AppWakeupHistory(Constants.ALLOW_WHILE_IDLE_WINDOW); mNextWakeup = mNextNonWakeup = 0; @@ -1636,25 +1610,28 @@ public class AlarmManagerService extends SystemService { return alarm.setPolicyElapsed(BATTERY_SAVER_POLICY_INDEX, nowElapsed); } - final long batterSaverPolicyElapsed; + final long batterySaverPolicyElapsed; if ((alarm.flags & (AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED)) != 0) { // Unrestricted. - batterSaverPolicyElapsed = nowElapsed; + batterySaverPolicyElapsed = nowElapsed; } else if ((alarm.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) { // Allowed but limited. - final long minDelay; - if (mUseAllowWhileIdleShortTime.get(alarm.creatorUid)) { - minDelay = mConstants.ALLOW_WHILE_IDLE_SHORT_TIME; + final int userId = UserHandle.getUserId(alarm.creatorUid); + final int quota = mConstants.ALLOW_WHILE_IDLE_QUOTA; + final int dispatchesInWindow = mAllowWhileIdleHistory.getTotalWakeupsInWindow( + alarm.sourcePackage, userId); + if (dispatchesInWindow < quota) { + // fine to go out immediately. + batterySaverPolicyElapsed = nowElapsed; } else { - minDelay = mConstants.ALLOW_WHILE_IDLE_LONG_TIME; + batterySaverPolicyElapsed = mAllowWhileIdleHistory.getNthLastWakeupForPackage( + alarm.sourcePackage, userId, quota) + Constants.ALLOW_WHILE_IDLE_WINDOW; } - final long lastDispatch = mLastAllowWhileIdleDispatch.get(alarm.creatorUid, 0); - batterSaverPolicyElapsed = (lastDispatch == 0) ? nowElapsed : lastDispatch + minDelay; } else { // Not allowed. - batterSaverPolicyElapsed = nowElapsed + INDEFINITE_DELAY; + batterySaverPolicyElapsed = nowElapsed + INDEFINITE_DELAY; } - return alarm.setPolicyElapsed(BATTERY_SAVER_POLICY_INDEX, batterSaverPolicyElapsed); + return alarm.setPolicyElapsed(BATTERY_SAVER_POLICY_INDEX, batterySaverPolicyElapsed); } /** @@ -1676,9 +1653,18 @@ public class AlarmManagerService extends SystemService { deviceIdlePolicyTime = nowElapsed; } else if ((alarm.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) { // Allowed but limited. - final long lastDispatch = mLastAllowWhileIdleDispatch.get(alarm.creatorUid, 0); - deviceIdlePolicyTime = (lastDispatch == 0) ? nowElapsed - : lastDispatch + mConstants.ALLOW_WHILE_IDLE_LONG_TIME; + final int userId = UserHandle.getUserId(alarm.creatorUid); + final int quota = mConstants.ALLOW_WHILE_IDLE_QUOTA; + final int dispatchesInWindow = mAllowWhileIdleHistory.getTotalWakeupsInWindow( + alarm.sourcePackage, userId); + if (dispatchesInWindow < quota) { + // fine to go out immediately. + deviceIdlePolicyTime = nowElapsed; + } else { + final long whenInQuota = mAllowWhileIdleHistory.getNthLastWakeupForPackage( + alarm.sourcePackage, userId, quota) + Constants.ALLOW_WHILE_IDLE_WINDOW; + deviceIdlePolicyTime = Math.min(whenInQuota, mPendingIdleUntil.getWhenElapsed()); + } } else { // Not allowed. deviceIdlePolicyTime = mPendingIdleUntil.getWhenElapsed(); @@ -1723,11 +1709,11 @@ public class AlarmManagerService extends SystemService { if (wakeupsInWindow >= quotaForBucket) { final long minElapsed; if (quotaForBucket <= 0) { - // Just keep deferring for a day till the quota changes - minElapsed = nowElapsed + MILLIS_IN_DAY; + // Just keep deferring indefinitely till the quota changes. + minElapsed = nowElapsed + INDEFINITE_DELAY; } else { // Suppose the quota for window was q, and the qth last delivery time for this - // package was t(q) then the next delivery must be after t(q) + <window_size> + // package was t(q) then the next delivery must be after t(q) + <window_size>. final long t = mAppWakeupHistory.getNthLastWakeupForPackage( sourcePackage, sourceUserId, quotaForBucket); minElapsed = t + mConstants.APP_STANDBY_WINDOW; @@ -1748,17 +1734,10 @@ public class AlarmManagerService extends SystemService { ent.uid = a.uid; ent.pkg = a.operation.getCreatorPackage(); ent.tag = a.operation.getTag(""); - ent.op = "SET"; + ent.op = "START IDLE"; ent.elapsedRealtime = mInjector.getElapsedRealtime(); ent.argRealtime = a.getWhenElapsed(); mAllowWhileIdleDispatches.add(ent); - if (mPendingIdleUntil == null) { - IdleDispatchEntry ent2 = new IdleDispatchEntry(); - ent2.uid = 0; - ent2.pkg = "START IDLE"; - ent2.elapsedRealtime = mInjector.getElapsedRealtime(); - mAllowWhileIdleDispatches.add(ent2); - } } if ((mPendingIdleUntil != a) && (mPendingIdleUntil != null)) { Slog.wtfStack(TAG, "setImplLocked: idle until changed from " + mPendingIdleUntil @@ -2182,6 +2161,7 @@ public class AlarmManagerService extends SystemService { pw.println("]"); pw.println(); + pw.println("App Alarm history:"); mAppWakeupHistory.dump(pw, nowELAPSED); if (mPendingIdleUntil != null) { @@ -2259,30 +2239,8 @@ public class AlarmManagerService extends SystemService { pw.println(); } - if (mLastAllowWhileIdleDispatch.size() > 0) { - pw.println("Last allow while idle dispatch times:"); - pw.increaseIndent(); - for (int i = 0; i < mLastAllowWhileIdleDispatch.size(); i++) { - pw.print("UID "); - final int uid = mLastAllowWhileIdleDispatch.keyAt(i); - UserHandle.formatUid(pw, uid); - pw.print(": "); - final long lastTime = mLastAllowWhileIdleDispatch.valueAt(i); - TimeUtils.formatDuration(lastTime, nowELAPSED, pw); - pw.println(); - } - pw.decreaseIndent(); - } - - pw.print("mUseAllowWhileIdleShortTime: ["); - for (int i = 0; i < mUseAllowWhileIdleShortTime.size(); i++) { - if (mUseAllowWhileIdleShortTime.valueAt(i)) { - UserHandle.formatUid(pw, mUseAllowWhileIdleShortTime.keyAt(i)); - pw.print(" "); - } - } - pw.println("]"); - pw.println(); + pw.println("Allow while idle history:"); + mAllowWhileIdleHistory.dump(pw, nowELAPSED); if (mLog.dump(pw, "Recent problems:")) { pw.println(); @@ -2533,25 +2491,6 @@ public class AlarmManagerService extends SystemService { f.dumpDebug(proto, AlarmManagerServiceDumpProto.OUTSTANDING_DELIVERIES); } - for (int i = 0; i < mLastAllowWhileIdleDispatch.size(); ++i) { - final long token = proto.start( - AlarmManagerServiceDumpProto.LAST_ALLOW_WHILE_IDLE_DISPATCH_TIMES); - final int uid = mLastAllowWhileIdleDispatch.keyAt(i); - final long lastTime = mLastAllowWhileIdleDispatch.valueAt(i); - - proto.write(AlarmManagerServiceDumpProto.LastAllowWhileIdleDispatch.UID, uid); - proto.write(AlarmManagerServiceDumpProto.LastAllowWhileIdleDispatch.TIME_MS, - lastTime); - proto.end(token); - } - - for (int i = 0; i < mUseAllowWhileIdleShortTime.size(); i++) { - if (mUseAllowWhileIdleShortTime.valueAt(i)) { - proto.write(AlarmManagerServiceDumpProto.USE_ALLOW_WHILE_IDLE_SHORT_TIME, - mUseAllowWhileIdleShortTime.keyAt(i)); - } - } - mLog.dumpDebug(proto, AlarmManagerServiceDumpProto.RECENT_PROBLEMS); final FilterStats[] topFilters = new FilterStats[10]; @@ -3049,11 +2988,6 @@ public class AlarmManagerService extends SystemService { mPendingBackgroundAlarms.removeAt(i); } } - for (int i = mLastAllowWhileIdleDispatch.size() - 1; i >= 0; i--) { - if (UserHandle.getUserId(mLastAllowWhileIdleDispatch.keyAt(i)) == userHandle) { - mLastAllowWhileIdleDispatch.removeAt(i); - } - } if (mNextWakeFromIdle != null && whichAlarms.test(mNextWakeFromIdle)) { mNextWakeFromIdle = mAlarmStore.getNextWakeFromIdleAlarm(); if (mPendingIdleUntil != null) { @@ -3215,6 +3149,16 @@ public class AlarmManagerService extends SystemService { if (mPendingIdleUntil == alarm) { mPendingIdleUntil = null; mAlarmStore.updateAlarmDeliveries(a -> adjustDeliveryTimeBasedOnDeviceIdle(a)); + if (RECORD_DEVICE_IDLE_ALARMS) { + IdleDispatchEntry ent = new IdleDispatchEntry(); + ent.uid = alarm.uid; + ent.pkg = alarm.operation.getCreatorPackage(); + ent.tag = alarm.operation.getTag(""); + ent.op = "END IDLE"; + ent.elapsedRealtime = mInjector.getElapsedRealtime(); + ent.argRealtime = alarm.getWhenElapsed(); + mAllowWhileIdleDispatches.add(ent); + } } if (mNextWakeFromIdle == alarm) { mNextWakeFromIdle = mAlarmStore.getNextWakeFromIdleAlarm(); @@ -3829,7 +3773,6 @@ public class AlarmManagerService extends SystemService { IntentFilter sdFilter = new IntentFilter(); sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); sdFilter.addAction(Intent.ACTION_USER_STOPPED); - sdFilter.addAction(Intent.ACTION_UID_REMOVED); getContext().registerReceiver(this, sdFilter); } @@ -3856,12 +3799,7 @@ public class AlarmManagerService extends SystemService { if (userHandle >= 0) { removeUserLocked(userHandle); mAppWakeupHistory.removeForUser(userHandle); - } - return; - case Intent.ACTION_UID_REMOVED: - if (uid >= 0) { - mLastAllowWhileIdleDispatch.delete(uid); - mUseAllowWhileIdleShortTime.delete(uid); + mAllowWhileIdleHistory.removeForUser(userHandle); } return; case Intent.ACTION_PACKAGE_REMOVED: @@ -3885,6 +3823,7 @@ public class AlarmManagerService extends SystemService { if (uid >= 0) { // package-removed and package-restarted case mAppWakeupHistory.removeForPackage(pkg, UserHandle.getUserId(uid)); + mAllowWhileIdleHistory.removeForPackage(pkg, UserHandle.getUserId(uid)); removeLocked(uid); } else { // external-applications-unavailable case @@ -3980,23 +3919,6 @@ public class AlarmManagerService extends SystemService { } @Override - public void onUidForeground(int uid, boolean foreground) { - synchronized (mLock) { - if (foreground) { - mUseAllowWhileIdleShortTime.put(uid, true); - if (mAlarmStore.updateAlarmDeliveries(a -> { - if (a.creatorUid != uid || (a.flags & FLAG_ALLOW_WHILE_IDLE) == 0) { - return false; - } - return adjustDeliveryTimeBasedOnBatterySaver(a); - })) { - rescheduleKernelAlarmsLocked(); - } - } - } - } - - @Override public void removeAlarmsForUid(int uid) { synchronized (mLock) { removeForStoppedLocked(uid); @@ -4273,22 +4195,23 @@ public class AlarmManagerService extends SystemService { notifyBroadcastAlarmPendingLocked(alarm.uid); } if (allowWhileIdle) { - // Record the last time this uid handled an ALLOW_WHILE_IDLE alarm. - mLastAllowWhileIdleDispatch.put(alarm.creatorUid, nowELAPSED); - if ((mAppStateTracker == null) - || mAppStateTracker.isUidInForeground(alarm.creatorUid)) { - mUseAllowWhileIdleShortTime.put(alarm.creatorUid, true); - } else { - mUseAllowWhileIdleShortTime.put(alarm.creatorUid, false); + final boolean doze = (mPendingIdleUntil != null); + final boolean batterySaver = (mAppStateTracker != null + && mAppStateTracker.isForceAllAppsStandbyEnabled()); + if (doze || batterySaver) { + // Record the last time this uid handled an ALLOW_WHILE_IDLE alarm while the + // device was in doze or battery saver. + mAllowWhileIdleHistory.recordAlarmForPackage(alarm.sourcePackage, + UserHandle.getUserId(alarm.creatorUid), nowELAPSED); + mAlarmStore.updateAlarmDeliveries(a -> { + if (a.creatorUid != alarm.creatorUid + || (a.flags & FLAG_ALLOW_WHILE_IDLE) == 0) { + return false; + } + return (doze && adjustDeliveryTimeBasedOnDeviceIdle(a)) + || (batterySaver && adjustDeliveryTimeBasedOnBatterySaver(a)); + }); } - mAlarmStore.updateAlarmDeliveries(a -> { - if (a.creatorUid != alarm.creatorUid - || (a.flags & FLAG_ALLOW_WHILE_IDLE) == 0) { - return false; - } - return adjustDeliveryTimeBasedOnDeviceIdle(a) - | adjustDeliveryTimeBasedOnBatterySaver(a); - }); if (RECORD_DEVICE_IDLE_ALARMS) { IdleDispatchEntry ent = new IdleDispatchEntry(); ent.uid = alarm.uid; @@ -4300,8 +4223,6 @@ public class AlarmManagerService extends SystemService { } } if (!isExemptFromAppStandby(alarm)) { - final Pair<String, Integer> packageUser = Pair.create(alarm.sourcePackage, - UserHandle.getUserId(alarm.creatorUid)); mAppWakeupHistory.recordAlarmForPackage(alarm.sourcePackage, UserHandle.getUserId(alarm.creatorUid), nowELAPSED); } diff --git a/core/api/current.txt b/core/api/current.txt index f49ce1fd48a5..0b81c902686b 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -24225,7 +24225,8 @@ package android.media.session { method public void setCallback(@Nullable android.media.session.MediaSession.Callback, @Nullable android.os.Handler); method public void setExtras(@Nullable android.os.Bundle); method public void setFlags(int); - method public void setMediaButtonReceiver(@Nullable android.app.PendingIntent); + method public void setMediaButtonBroadcastReceiver(@Nullable android.content.ComponentName); + method @Deprecated public void setMediaButtonReceiver(@Nullable android.app.PendingIntent); method public void setMetadata(@Nullable android.media.MediaMetadata); method public void setPlaybackState(@Nullable android.media.session.PlaybackState); method public void setPlaybackToLocal(android.media.AudioAttributes); @@ -30191,6 +30192,8 @@ package android.os { field @Deprecated public static final String RADIO; field @Deprecated public static final String SERIAL; field @NonNull public static final String SKU; + field @NonNull public static final String SOC_MANUFACTURER; + field @NonNull public static final String SOC_MODEL; field public static final String[] SUPPORTED_32_BIT_ABIS; field public static final String[] SUPPORTED_64_BIT_ABIS; field public static final String[] SUPPORTED_ABIS; @@ -31878,6 +31881,8 @@ package android.os.strictmode { } public final class UnsafeIntentLaunchViolation extends android.os.strictmode.Violation { + ctor public UnsafeIntentLaunchViolation(@NonNull android.content.Intent); + method @Nullable public android.content.Intent getIntent(); } public final class UntaggedSocketViolation extends android.os.strictmode.Violation { @@ -36957,6 +36962,7 @@ package android.security.keystore { field public static final int ORIGIN_IMPORTED = 2; // 0x2 field public static final int ORIGIN_SECURELY_IMPORTED = 8; // 0x8 field public static final int ORIGIN_UNKNOWN = 4; // 0x4 + field public static final int PURPOSE_AGREE_KEY = 64; // 0x40 field public static final int PURPOSE_DECRYPT = 2; // 0x2 field public static final int PURPOSE_ENCRYPT = 1; // 0x1 field public static final int PURPOSE_SIGN = 4; // 0x4 diff --git a/core/api/system-current.txt b/core/api/system-current.txt index b3e78ab7377a..2a2b799816c3 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -217,6 +217,7 @@ package android { field public static final String REQUEST_NETWORK_SCORES = "android.permission.REQUEST_NETWORK_SCORES"; field public static final String REQUEST_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE"; field public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD"; + field public static final String RESTART_WIFI_SUBSYSTEM = "android.permission.RESTART_WIFI_SUBSYSTEM"; field public static final String RESTORE_RUNTIME_PERMISSIONS = "android.permission.RESTORE_RUNTIME_PERMISSIONS"; field public static final String RESTRICTED_VR_ACCESS = "android.permission.RESTRICTED_VR_ACCESS"; field public static final String RETRIEVE_WINDOW_CONTENT = "android.permission.RETRIEVE_WINDOW_CONTENT"; diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 415105f88f25..bdd541a2f0ac 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -2669,6 +2669,10 @@ public class Activity extends ContextThemeWrapper dispatchActivityDestroyed(); notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_STOP); + + if (mUiTranslationController != null) { + mUiTranslationController.onActivityDestroyed(); + } } /** @@ -7877,6 +7881,10 @@ public class Activity extends ContextThemeWrapper @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) final void performCreate(Bundle icicle, PersistableBundle persistentState) { + if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) { + Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performCreate:" + + mComponent.getClassName()); + } dispatchActivityPreCreated(icicle); mCanEnterPictureInPicture = true; // initialize mIsInMultiWindowMode and mIsInPictureInPictureMode before onCreate @@ -7899,6 +7907,7 @@ public class Activity extends ContextThemeWrapper mFragments.dispatchActivityCreated(); mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions()); dispatchActivityPostCreated(icicle); + Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); } final void performNewIntent(@NonNull Intent intent) { @@ -8004,6 +8013,10 @@ public class Activity extends ContextThemeWrapper } final void performResume(boolean followedByPause, String reason) { + if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) { + Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performResume:" + + mComponent.getClassName()); + } dispatchActivityPreResumed(); performRestart(true /* start */, reason); @@ -8055,9 +8068,14 @@ public class Activity extends ContextThemeWrapper " did not call through to super.onPostResume()"); } dispatchActivityPostResumed(); + Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); } final void performPause() { + if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) { + Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performPause:" + + mComponent.getClassName()); + } dispatchActivityPrePaused(); mDoReportFullyDrawn = false; mFragments.dispatchPause(); @@ -8073,6 +8091,7 @@ public class Activity extends ContextThemeWrapper " did not call through to super.onPause()"); } dispatchActivityPostPaused(); + Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); } final void performUserLeaving() { @@ -8081,6 +8100,10 @@ public class Activity extends ContextThemeWrapper } final void performStop(boolean preserveWindow, String reason) { + if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) { + Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performStop:" + + mComponent.getClassName()); + } mDoReportFullyDrawn = false; mFragments.doLoaderStop(mChangingConfigurations /*retain*/); @@ -8126,9 +8149,14 @@ public class Activity extends ContextThemeWrapper dispatchActivityPostStopped(); } mResumed = false; + Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); } final void performDestroy() { + if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) { + Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performDestroy:" + + mComponent.getClassName()); + } dispatchActivityPreDestroyed(); mDestroyed = true; mWindow.destroy(); @@ -8141,6 +8169,7 @@ public class Activity extends ContextThemeWrapper mVoiceInteractor.detachActivity(); } dispatchActivityPostDestroyed(); + Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); } final void dispatchMultiWindowModeChanged(boolean isInMultiWindowMode, @@ -8450,9 +8479,8 @@ public class Activity extends ContextThemeWrapper } /** @hide */ - @Override @Nullable - public final View autofillClientFindViewByAutofillIdTraversal(AutofillId autofillId) { + public View findViewByAutofillIdTraversal(@NonNull AutofillId autofillId) { final ArrayList<ViewRootImpl> roots = WindowManagerGlobal.getInstance().getRootViews(getActivityToken()); for (int rootNum = 0; rootNum < roots.size(); rootNum++) { @@ -8465,12 +8493,18 @@ public class Activity extends ContextThemeWrapper } } } - return null; } /** @hide */ @Override + @Nullable + public final View autofillClientFindViewByAutofillIdTraversal(AutofillId autofillId) { + return findViewByAutofillIdTraversal(autofillId); + } + + /** @hide */ + @Override public final @NonNull boolean[] autofillClientGetViewVisibility( @NonNull AutofillId[] autofillIds) { final int autofillIdCount = autofillIds.length; diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java index d465b220f854..401f8cc13bad 100644 --- a/core/java/android/app/ActivityClient.java +++ b/core/java/android/app/ActivityClient.java @@ -26,6 +26,8 @@ import android.os.RemoteException; import android.util.Singleton; import android.view.RemoteAnimationDefinition; +import com.android.internal.policy.IKeyguardDismissCallback; + /** * Provides the activity associated operations that communicate with system. * @@ -431,6 +433,37 @@ public class ActivityClient { } } + /** + * Restart the process and activity to adopt the latest configuration for size compat mode. + * This only takes effect for visible activity because invisible background activity can be + * restarted naturally when it becomes visible. + */ + public void restartActivityProcessIfVisible(IBinder token) { + try { + getActivityClientController().restartActivityProcessIfVisible(token); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** Removes the snapshot of home task. */ + public void invalidateHomeTaskSnapshot(IBinder homeToken) { + try { + getActivityClientController().invalidateHomeTaskSnapshot(homeToken); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + void dismissKeyguard(IBinder token, IKeyguardDismissCallback callback, + CharSequence message) { + try { + getActivityClientController().dismissKeyguard(token, callback, message); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + void registerRemoteAnimations(IBinder token, RemoteAnimationDefinition definition) { try { getActivityClientController().registerRemoteAnimations(token, definition); diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl index ebf1027382c5..c2c62c11b9a6 100644 --- a/core/java/android/app/IActivityClientController.aidl +++ b/core/java/android/app/IActivityClientController.aidl @@ -25,6 +25,8 @@ import android.os.Bundle; import android.os.PersistableBundle; import android.view.RemoteAnimationDefinition; +import com.android.internal.policy.IKeyguardDismissCallback; + /** * Interface for the callback and request from an activity to system. * @@ -34,7 +36,13 @@ interface IActivityClientController { oneway void activityIdle(in IBinder token, in Configuration config, in boolean stopProfiling); oneway void activityResumed(in IBinder token); oneway void activityTopResumedStateLost(); - oneway void activityPaused(in IBinder token); + /** + * Notifies that the activity has completed paused. This call is not one-way because it can make + * consecutive launch in the same process more coherent. About the order of binder call, it + * should be fine with other one-way calls because if pause hasn't completed on the server side, + * there won't be other lifecycle changes. + */ + void activityPaused(in IBinder token); oneway void activityStopped(in IBinder token, in Bundle state, in PersistableBundle persistentState, in CharSequence description); oneway void activityDestroyed(in IBinder token); @@ -95,6 +103,27 @@ interface IActivityClientController { /** See {@link android.app.Activity#setDisablePreviewScreenshots}. */ oneway void setDisablePreviewScreenshots(in IBinder token, boolean disable); + /** + * Restarts the activity by killing its process if it is visible. If the activity is not + * visible, the activity will not be restarted immediately and just keep the activity record in + * the stack. It also resets the current override configuration so the activity will use the + * configuration according to the latest state. + * + * @param activityToken The token of the target activity to restart. + */ + void restartActivityProcessIfVisible(in IBinder activityToken); + + /** + * It should only be called from home activity to remove its outdated snapshot. The home + * snapshot is used to speed up entering home from screen off. If the content of home activity + * is significantly different from before taking the snapshot, then the home activity can use + * this method to avoid inconsistent transition. + */ + void invalidateHomeTaskSnapshot(IBinder homeToken); + + void dismissKeyguard(in IBinder token, in IKeyguardDismissCallback callback, + in CharSequence message); + /** Registers remote animations for a specific activity. */ void registerRemoteAnimations(in IBinder token, in RemoteAnimationDefinition definition); diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index b0853404a0aa..38a3e70b3742 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -72,7 +72,6 @@ import android.view.RemoteAnimationAdapter; import android.window.IWindowOrganizerController; import com.android.internal.app.IVoiceInteractor; import com.android.internal.os.IResultReceiver; -import com.android.internal.policy.IKeyguardDismissCallback; import java.util.List; @@ -85,8 +84,6 @@ import java.util.List; // TODO(b/174040395): Make this interface private to ActivityTaskManager.java and have external // caller go through that call instead. This would help us better separate and control the API // surface exposed. -// TODO(b/174041144): Move callback methods from Activity (Things that take param 'IBinder token') -// to a separate interface that is only available to the Activity. // TODO(b/174041603): Create a builder interface for things like startActivityXXX(...) to reduce // interface duplication. // TODO(b/174040691): Clean-up/remove all obsolete or unused interfaces like things that should be @@ -294,9 +291,6 @@ interface IActivityTaskManager { // Get device configuration ConfigurationInfo getDeviceConfigurationInfo(); - void dismissKeyguard(in IBinder token, in IKeyguardDismissCallback callback, - in CharSequence message); - /** Cancels the window transitions for the given task. */ void cancelTaskWindowTransition(int taskId); @@ -309,14 +303,6 @@ interface IActivityTaskManager { android.window.TaskSnapshot getTaskSnapshot(int taskId, boolean isLowResolution); /** - * It should only be called from home activity to remove its outdated snapshot. The home - * snapshot is used to speed up entering home from screen off. If the content of home activity - * is significantly different from before taking the snapshot, then the home activity can use - * this method to avoid inconsistent transition. - */ - void invalidateHomeTaskSnapshot(IBinder homeToken); - - /** * Return the user id of last resumed activity. */ int getLastResumedActivityUserId(); @@ -362,14 +348,4 @@ interface IActivityTaskManager { * Clears launch params for given packages. */ void clearLaunchParamsForPackages(in List<String> packageNames); - - /** - * Restarts the activity by killing its process if it is visible. If the activity is not - * visible, the activity will not be restarted immediately and just keep the activity record in - * the stack. It also resets the current override configuration so the activity will use the - * configuration according to the latest state. - * - * @param activityToken The token of the target activity to restart. - */ - void restartActivityProcessIfVisible(in IBinder activityToken); } diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index 545c3f70d466..b6d25cfb26ce 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -598,33 +598,29 @@ public class KeyguardManager { @SystemApi public void requestDismissKeyguard(@NonNull Activity activity, @Nullable CharSequence message, @Nullable KeyguardDismissCallback callback) { - try { - ActivityTaskManager.getService().dismissKeyguard( - activity.getActivityToken(), new IKeyguardDismissCallback.Stub() { - @Override - public void onDismissError() throws RemoteException { - if (callback != null && !activity.isDestroyed()) { - activity.mHandler.post(callback::onDismissError); - } + ActivityClient.getInstance().dismissKeyguard( + activity.getActivityToken(), new IKeyguardDismissCallback.Stub() { + @Override + public void onDismissError() throws RemoteException { + if (callback != null && !activity.isDestroyed()) { + activity.mHandler.post(callback::onDismissError); } + } - @Override - public void onDismissSucceeded() throws RemoteException { - if (callback != null && !activity.isDestroyed()) { - activity.mHandler.post(callback::onDismissSucceeded); - } + @Override + public void onDismissSucceeded() throws RemoteException { + if (callback != null && !activity.isDestroyed()) { + activity.mHandler.post(callback::onDismissSucceeded); } + } - @Override - public void onDismissCancelled() throws RemoteException { - if (callback != null && !activity.isDestroyed()) { - activity.mHandler.post(callback::onDismissCancelled); - } + @Override + public void onDismissCancelled() throws RemoteException { + if (callback != null && !activity.isDestroyed()) { + activity.mHandler.post(callback::onDismissCancelled); } - }, message); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + } + }, message); } /** diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 45f072adf801..fe8bf9d163c7 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -30,6 +30,7 @@ import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED; import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE; import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION; import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE; +import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; @@ -53,8 +54,6 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.split.DefaultSplitAssetLoader; -import android.content.pm.split.SplitAssetDependencyLoader; import android.content.pm.split.SplitAssetLoader; import android.content.res.ApkAssets; import android.content.res.AssetManager; @@ -80,6 +79,7 @@ import android.util.ArraySet; import android.util.AttributeSet; import android.util.Base64; import android.util.DisplayMetrics; +import android.util.IntArray; import android.util.Log; import android.util.PackageUtils; import android.util.Pair; @@ -118,6 +118,7 @@ import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; import java.util.Arrays; +import java.util.BitSet; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; @@ -8573,4 +8574,410 @@ public class PackageParser { this.error = error; } } + + // Duplicate the SplitAsset related classes with PackageParser.Package/ApkLite here, and + // change the original one using new Package/ApkLite. The propose is that we don't want to + // have two branches of methods in SplitAsset related classes so we can keep real classes + // clean and move all the legacy code to one place. + + /** + * A helper class that implements the dependency tree traversal for splits. Callbacks + * are implemented by subclasses to notify whether a split has already been constructed + * and is cached, and to actually create the split requested. + * + * This helper is meant to be subclassed so as to reduce the number of allocations + * needed to make use of it. + * + * All inputs and outputs are assumed to be indices into an array of splits. + * + * @hide + * @deprecated Do not use. New changes should use + * {@link android.content.pm.split.SplitDependencyLoader} instead. + */ + @Deprecated + private abstract static class SplitDependencyLoader<E extends Exception> { + private final @NonNull SparseArray<int[]> mDependencies; + + /** + * Construct a new SplitDependencyLoader. Meant to be called from the + * subclass constructor. + * @param dependencies The dependency tree of splits. + */ + protected SplitDependencyLoader(@NonNull SparseArray<int[]> dependencies) { + mDependencies = dependencies; + } + + /** + * Traverses the dependency tree and constructs any splits that are not already + * cached. This routine short-circuits and skips the creation of splits closer to the + * root if they are cached, as reported by the subclass implementation of + * {@link #isSplitCached(int)}. The construction of splits is delegated to the subclass + * implementation of {@link #constructSplit(int, int[], int)}. + * @param splitIdx The index of the split to load. 0 represents the base Application. + */ + protected void loadDependenciesForSplit(@IntRange(from = 0) int splitIdx) throws E { + // Quick check before any allocations are done. + if (isSplitCached(splitIdx)) { + return; + } + + // Special case the base, since it has no dependencies. + if (splitIdx == 0) { + final int[] configSplitIndices = collectConfigSplitIndices(0); + constructSplit(0, configSplitIndices, -1); + return; + } + + // Build up the dependency hierarchy. + final IntArray linearDependencies = new IntArray(); + linearDependencies.add(splitIdx); + + // Collect all the dependencies that need to be constructed. + // They will be listed from leaf to root. + while (true) { + // Only follow the first index into the array. The others are config splits and + // get loaded with the split. + final int[] deps = mDependencies.get(splitIdx); + if (deps != null && deps.length > 0) { + splitIdx = deps[0]; + } else { + splitIdx = -1; + } + + if (splitIdx < 0 || isSplitCached(splitIdx)) { + break; + } + + linearDependencies.add(splitIdx); + } + + // Visit each index, from right to left (root to leaf). + int parentIdx = splitIdx; + for (int i = linearDependencies.size() - 1; i >= 0; i--) { + final int idx = linearDependencies.get(i); + final int[] configSplitIndices = collectConfigSplitIndices(idx); + constructSplit(idx, configSplitIndices, parentIdx); + parentIdx = idx; + } + } + + private @NonNull int[] collectConfigSplitIndices(int splitIdx) { + // The config splits appear after the first element. + final int[] deps = mDependencies.get(splitIdx); + if (deps == null || deps.length <= 1) { + return EmptyArray.INT; + } + return Arrays.copyOfRange(deps, 1, deps.length); + } + + /** + * Subclass to report whether the split at `splitIdx` is cached and need not be constructed. + * It is assumed that if `splitIdx` is cached, any parent of `splitIdx` is also cached. + * @param splitIdx The index of the split to check for in the cache. + * @return true if the split is cached and does not need to be constructed. + */ + protected abstract boolean isSplitCached(@IntRange(from = 0) int splitIdx); + + /** + * Subclass to construct a split at index `splitIdx` with parent split `parentSplitIdx`. + * The result is expected to be cached by the subclass in its own structures. + * @param splitIdx The index of the split to construct. 0 represents the base Application. + * @param configSplitIndices The array of configuration splits to load along with this + * split. May be empty (length == 0) but never null. + * @param parentSplitIdx The index of the parent split. -1 if there is no parent. + * @throws E Subclass defined exception representing failure to construct a split. + */ + protected abstract void constructSplit(@IntRange(from = 0) int splitIdx, + @NonNull @IntRange(from = 1) int[] configSplitIndices, + @IntRange(from = -1) int parentSplitIdx) throws E; + + public static class IllegalDependencyException extends Exception { + private IllegalDependencyException(String message) { + super(message); + } + } + + private static int[] append(int[] src, int elem) { + if (src == null) { + return new int[] { elem }; + } + int[] dst = Arrays.copyOf(src, src.length + 1); + dst[src.length] = elem; + return dst; + } + + public static @NonNull SparseArray<int[]> createDependenciesFromPackage( + PackageLite pkg) + throws SplitDependencyLoader.IllegalDependencyException { + // The data structure that holds the dependencies. In PackageParser, splits are stored + // in their own array, separate from the base. We treat all paths as equals, so + // we need to insert the base as index 0, and shift all other splits. + final SparseArray<int[]> splitDependencies = new SparseArray<>(); + + // The base depends on nothing. + splitDependencies.put(0, new int[] {-1}); + + // First write out the <uses-split> dependencies. These must appear first in the + // array of ints, as is convention in this class. + for (int splitIdx = 0; splitIdx < pkg.splitNames.length; splitIdx++) { + if (!pkg.isFeatureSplits[splitIdx]) { + // Non-feature splits don't have dependencies. + continue; + } + + // Implicit dependency on the base. + final int targetIdx; + final String splitDependency = pkg.usesSplitNames[splitIdx]; + if (splitDependency != null) { + final int depIdx = Arrays.binarySearch(pkg.splitNames, splitDependency); + if (depIdx < 0) { + throw new SplitDependencyLoader.IllegalDependencyException( + "Split '" + pkg.splitNames[splitIdx] + "' requires split '" + + splitDependency + "', which is missing."); + } + targetIdx = depIdx + 1; + } else { + // Implicitly depend on the base. + targetIdx = 0; + } + splitDependencies.put(splitIdx + 1, new int[] {targetIdx}); + } + + // Write out the configForSplit reverse-dependencies. These appear after the + // <uses-split> dependencies and are considered leaves. + // + // At this point, all splits in splitDependencies have the first element in their + // array set. + for (int splitIdx = 0, size = pkg.splitNames.length; splitIdx < size; splitIdx++) { + if (pkg.isFeatureSplits[splitIdx]) { + // Feature splits are not configForSplits. + continue; + } + + // Implicit feature for the base. + final int targetSplitIdx; + final String configForSplit = pkg.configForSplit[splitIdx]; + if (configForSplit != null) { + final int depIdx = Arrays.binarySearch(pkg.splitNames, configForSplit); + if (depIdx < 0) { + throw new SplitDependencyLoader.IllegalDependencyException( + "Split '" + pkg.splitNames[splitIdx] + "' targets split '" + + configForSplit + "', which is missing."); + } + + if (!pkg.isFeatureSplits[depIdx]) { + throw new SplitDependencyLoader.IllegalDependencyException( + "Split '" + pkg.splitNames[splitIdx] + "' declares itself as " + + "configuration split for a non-feature split '" + + pkg.splitNames[depIdx] + "'"); + } + targetSplitIdx = depIdx + 1; + } else { + targetSplitIdx = 0; + } + splitDependencies.put(targetSplitIdx, + append(splitDependencies.get(targetSplitIdx), splitIdx + 1)); + } + + // Verify that there are no cycles. + final BitSet bitset = new BitSet(); + for (int i = 0, size = splitDependencies.size(); i < size; i++) { + int splitIdx = splitDependencies.keyAt(i); + + bitset.clear(); + while (splitIdx != -1) { + // Check if this split has been visited yet. + if (bitset.get(splitIdx)) { + throw new SplitDependencyLoader.IllegalDependencyException( + "Cycle detected in split dependencies."); + } + + // Mark the split so that if we visit it again, we no there is a cycle. + bitset.set(splitIdx); + + // Follow the first dependency only, the others are leaves by definition. + final int[] deps = splitDependencies.get(splitIdx); + splitIdx = deps != null ? deps[0] : -1; + } + } + return splitDependencies; + } + } + + /** + * Loads the base and split APKs into a single AssetManager. + * @hide + * @deprecated Do not use. New changes should use + * {@link android.content.pm.split.DefaultSplitAssetLoader} instead. + */ + @Deprecated + private static class DefaultSplitAssetLoader implements SplitAssetLoader { + private final String mBaseCodePath; + private final String[] mSplitCodePaths; + private final @ParseFlags int mFlags; + private AssetManager mCachedAssetManager; + + DefaultSplitAssetLoader(PackageLite pkg, @ParseFlags int flags) { + mBaseCodePath = pkg.baseCodePath; + mSplitCodePaths = pkg.splitCodePaths; + mFlags = flags; + } + + private static ApkAssets loadApkAssets(String path, @ParseFlags int flags) + throws PackageParserException { + if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(path)) { + throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, + "Invalid package file: " + path); + } + + try { + return ApkAssets.loadFromPath(path); + } catch (IOException e) { + throw new PackageParserException(INSTALL_FAILED_INVALID_APK, + "Failed to load APK at path " + path, e); + } + } + + @Override + public AssetManager getBaseAssetManager() throws PackageParserException { + if (mCachedAssetManager != null) { + return mCachedAssetManager; + } + + ApkAssets[] apkAssets = new ApkAssets[(mSplitCodePaths != null + ? mSplitCodePaths.length : 0) + 1]; + + // Load the base. + int splitIdx = 0; + apkAssets[splitIdx++] = loadApkAssets(mBaseCodePath, mFlags); + + // Load any splits. + if (!ArrayUtils.isEmpty(mSplitCodePaths)) { + for (String apkPath : mSplitCodePaths) { + apkAssets[splitIdx++] = loadApkAssets(apkPath, mFlags); + } + } + + AssetManager assets = new AssetManager(); + assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + Build.VERSION.RESOURCES_SDK_INT); + assets.setApkAssets(apkAssets, false /*invalidateCaches*/); + + mCachedAssetManager = assets; + return mCachedAssetManager; + } + + @Override + public AssetManager getSplitAssetManager(int splitIdx) throws PackageParserException { + return getBaseAssetManager(); + } + + @Override + public void close() throws Exception { + IoUtils.closeQuietly(mCachedAssetManager); + } + } + + /** + * Loads AssetManagers for splits and their dependencies. This SplitAssetLoader implementation + * is to be used when an application opts-in to isolated split loading. + * @hide + * @deprecated Do not use. New changes should use + * {@link android.content.pm.split.SplitAssetDependencyLoader} instead. + */ + @Deprecated + private static class SplitAssetDependencyLoader extends + SplitDependencyLoader<PackageParserException> implements SplitAssetLoader { + private final String[] mSplitPaths; + private final @ParseFlags int mFlags; + private final ApkAssets[][] mCachedSplitApks; + private final AssetManager[] mCachedAssetManagers; + + SplitAssetDependencyLoader(PackageLite pkg, + SparseArray<int[]> dependencies, @ParseFlags int flags) { + super(dependencies); + + // The base is inserted into index 0, so we need to shift all the splits by 1. + mSplitPaths = new String[pkg.splitCodePaths.length + 1]; + mSplitPaths[0] = pkg.baseCodePath; + System.arraycopy(pkg.splitCodePaths, 0, mSplitPaths, 1, pkg.splitCodePaths.length); + + mFlags = flags; + mCachedSplitApks = new ApkAssets[mSplitPaths.length][]; + mCachedAssetManagers = new AssetManager[mSplitPaths.length]; + } + + @Override + protected boolean isSplitCached(int splitIdx) { + return mCachedAssetManagers[splitIdx] != null; + } + + private static ApkAssets loadApkAssets(String path, @ParseFlags int flags) + throws PackageParserException { + if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(path)) { + throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, + "Invalid package file: " + path); + } + + try { + return ApkAssets.loadFromPath(path); + } catch (IOException e) { + throw new PackageParserException(PackageManager.INSTALL_FAILED_INVALID_APK, + "Failed to load APK at path " + path, e); + } + } + + private static AssetManager createAssetManagerWithAssets(ApkAssets[] apkAssets) { + final AssetManager assets = new AssetManager(); + assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + Build.VERSION.RESOURCES_SDK_INT); + assets.setApkAssets(apkAssets, false /*invalidateCaches*/); + return assets; + } + + @Override + protected void constructSplit(int splitIdx, @NonNull int[] configSplitIndices, + int parentSplitIdx) throws PackageParserException { + final ArrayList<ApkAssets> assets = new ArrayList<>(); + + // Include parent ApkAssets. + if (parentSplitIdx >= 0) { + Collections.addAll(assets, mCachedSplitApks[parentSplitIdx]); + } + + // Include this ApkAssets. + assets.add(loadApkAssets(mSplitPaths[splitIdx], mFlags)); + + // Load and include all config splits for this feature. + for (int configSplitIdx : configSplitIndices) { + assets.add(loadApkAssets(mSplitPaths[configSplitIdx], mFlags)); + } + + // Cache the results. + mCachedSplitApks[splitIdx] = assets.toArray(new ApkAssets[assets.size()]); + mCachedAssetManagers[splitIdx] = createAssetManagerWithAssets( + mCachedSplitApks[splitIdx]); + } + + @Override + public AssetManager getBaseAssetManager() throws PackageParserException { + loadDependenciesForSplit(0); + return mCachedAssetManagers[0]; + } + + @Override + public AssetManager getSplitAssetManager(int idx) throws PackageParserException { + // Since we insert the base at position 0, and PackageParser keeps splits separate from + // the base, we need to adjust the index. + loadDependenciesForSplit(idx + 1); + return mCachedAssetManagers[idx + 1]; + } + + @Override + public void close() throws Exception { + for (AssetManager assets : mCachedAssetManagers) { + IoUtils.closeQuietly(assets); + } + } + } } diff --git a/core/java/android/content/pm/dex/DexMetadataHelper.java b/core/java/android/content/pm/dex/DexMetadataHelper.java index 982fce918366..bf35c4d92d3a 100644 --- a/core/java/android/content/pm/dex/DexMetadataHelper.java +++ b/core/java/android/content/pm/dex/DexMetadataHelper.java @@ -17,11 +17,11 @@ package android.content.pm.dex; import static android.content.pm.PackageManager.INSTALL_FAILED_BAD_DEX_METADATA; -import static android.content.pm.PackageParser.APK_FILE_EXTENSION; +import static android.content.pm.parsing.ApkLiteParseUtils.APK_FILE_EXTENSION; -import android.content.pm.PackageParser; -import android.content.pm.PackageParser.PackageLite; import android.content.pm.PackageParser.PackageParserException; +import android.content.pm.parsing.ApkLiteParseUtils; +import android.content.pm.parsing.PackageLite; import android.util.ArrayMap; import android.util.jar.StrictJarFile; @@ -87,7 +87,7 @@ public class DexMetadataHelper { * NOTE: involves I/O checks. */ private static Map<String, String> getPackageDexMetadata(PackageLite pkg) { - return buildPackageApkToDexMetadataMap(pkg.getAllCodePaths()); + return buildPackageApkToDexMetadataMap(pkg.getAllApkPaths()); } /** @@ -125,7 +125,7 @@ public class DexMetadataHelper { * @throws IllegalArgumentException if the code path is not an .apk. */ public static String buildDexMetadataPathForApk(String codePath) { - if (!PackageParser.isApkPath(codePath)) { + if (!ApkLiteParseUtils.isApkPath(codePath)) { throw new IllegalStateException( "Corrupted package. Code path is not an apk " + codePath); } @@ -140,7 +140,7 @@ public class DexMetadataHelper { * extension (e.g. 'foo.dm' will match 'foo' or 'foo.apk'). */ private static String buildDexMetadataPathForFile(File targetFile) { - return PackageParser.isApkFile(targetFile) + return ApkLiteParseUtils.isApkFile(targetFile) ? buildDexMetadataPathForApk(targetFile.getPath()) : targetFile.getPath() + DEX_METADATA_FILE_EXTENSION; } @@ -179,7 +179,7 @@ public class DexMetadataHelper { public static void validateDexPaths(String[] paths) { ArrayList<String> apks = new ArrayList<>(); for (int i = 0; i < paths.length; i++) { - if (PackageParser.isApkPath(paths[i])) { + if (ApkLiteParseUtils.isApkPath(paths[i])) { apks.add(paths[i]); } } diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java index 51b81b60a825..a3c2cbc56652 100644 --- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java +++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java @@ -66,6 +66,8 @@ public class ApkLiteParseUtils { private static final int PARSE_DEFAULT_INSTALL_LOCATION = PackageInfo.INSTALL_LOCATION_UNSPECIFIED; + private static final Comparator<String> sSplitNameComparator = new SplitNameComparator(); + public static final String APK_FILE_EXTENSION = ".apk"; /** @@ -79,7 +81,7 @@ public class ApkLiteParseUtils { * * @see PackageParser#parsePackage(File, int) */ - public static ParseResult<PackageParser.PackageLite> parsePackageLite(ParseInput input, + public static ParseResult<PackageLite> parsePackageLite(ParseInput input, File packageFile, int flags) { if (packageFile.isDirectory()) { return parseClusterPackageLite(input, packageFile, flags); @@ -88,26 +90,32 @@ public class ApkLiteParseUtils { } } - public static ParseResult<PackageParser.PackageLite> parseMonolithicPackageLite( - ParseInput input, File packageFile, int flags) { + /** + * Parse lightweight details about a single APK files. + */ + public static ParseResult<PackageLite> parseMonolithicPackageLite(ParseInput input, + File packageFile, int flags) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite"); try { - ParseResult<PackageParser.ApkLite> result = parseApkLite(input, packageFile, flags); + final ParseResult<ApkLite> result = parseApkLite(input, packageFile, flags); if (result.isError()) { return input.error(result); } - final PackageParser.ApkLite baseApk = result.getResult(); + final ApkLite baseApk = result.getResult(); final String packagePath = packageFile.getAbsolutePath(); return input.success( - new PackageParser.PackageLite(packagePath, baseApk.codePath, baseApk, null, + new PackageLite(packagePath, baseApk.getPath(), baseApk, null, null, null, null, null, null)); } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } } - public static ParseResult<PackageParser.PackageLite> parseClusterPackageLite(ParseInput input, + /** + * Parse lightweight details about a directory of APKs. + */ + public static ParseResult<PackageLite> parseClusterPackageLite(ParseInput input, File packageDir, int flags) { final File[] files = packageDir.listFiles(); if (ArrayUtils.isEmpty(files)) { @@ -122,39 +130,39 @@ public class ApkLiteParseUtils { String packageName = null; int versionCode = 0; - final ArrayMap<String, PackageParser.ApkLite> apks = new ArrayMap<>(); + final ArrayMap<String, ApkLite> apks = new ArrayMap<>(); Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite"); try { for (File file : files) { - if (PackageParser.isApkFile(file)) { - ParseResult<PackageParser.ApkLite> result = parseApkLite(input, file, flags); + if (isApkFile(file)) { + final ParseResult<ApkLite> result = parseApkLite(input, file, flags); if (result.isError()) { return input.error(result); } - final PackageParser.ApkLite lite = result.getResult(); + final ApkLite lite = result.getResult(); // Assert that all package names and version codes are // consistent with the first one we encounter. if (packageName == null) { - packageName = lite.packageName; - versionCode = lite.versionCode; + packageName = lite.getPackageName(); + versionCode = lite.getVersionCode(); } else { - if (!packageName.equals(lite.packageName)) { + if (!packageName.equals(lite.getPackageName())) { return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST, - "Inconsistent package " + lite.packageName + " in " + file + "Inconsistent package " + lite.getPackageName() + " in " + file + "; expected " + packageName); } - if (versionCode != lite.versionCode) { + if (versionCode != lite.getVersionCode()) { return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST, - "Inconsistent version " + lite.versionCode + " in " + file + "Inconsistent version " + lite.getVersionCode() + " in " + file + "; expected " + versionCode); } } // Assert that each split is defined only oncuses-static-libe - if (apks.put(lite.splitName, lite) != null) { + if (apks.put(lite.getSplitName(), lite) != null) { return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST, - "Split name " + lite.splitName + "Split name " + lite.getSplitName() + " defined more than once; most recent was " + file); } } @@ -163,7 +171,7 @@ public class ApkLiteParseUtils { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } - final PackageParser.ApkLite baseApk = apks.remove(null); + final ApkLite baseApk = apks.remove(null); return composePackageLiteFromApks(input, packageDir, baseApk, apks); } @@ -176,9 +184,8 @@ public class ApkLiteParseUtils { * @param splitApks Parsed split APKs * @return PackageLite */ - public static ParseResult<PackageParser.PackageLite> composePackageLiteFromApks( - ParseInput input, File packageDir, PackageParser.ApkLite baseApk, - ArrayMap<String, PackageParser.ApkLite> splitApks) { + public static ParseResult<PackageLite> composePackageLiteFromApks(ParseInput input, + File packageDir, ApkLite baseApk, ArrayMap<String, ApkLite> splitApks) { return composePackageLiteFromApks(input, packageDir, baseApk, splitApks, false); } @@ -192,9 +199,9 @@ public class ApkLiteParseUtils { * @param apkRenamed Indicate whether the APKs are renamed after parsed. * @return PackageLite */ - public static ParseResult<PackageParser.PackageLite> composePackageLiteFromApks( - ParseInput input, File packageDir, PackageParser.ApkLite baseApk, - ArrayMap<String, PackageParser.ApkLite> splitApks, boolean apkRenamed) { + public static ParseResult<PackageLite> composePackageLiteFromApks( + ParseInput input, File packageDir, ApkLite baseApk, + ArrayMap<String, ApkLite> splitApks, boolean apkRenamed) { if (baseApk == null) { return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST, "Missing base APK in " + packageDir); @@ -217,26 +224,25 @@ public class ApkLiteParseUtils { splitRevisionCodes = new int[size]; splitNames = splitApks.keySet().toArray(splitNames); - Arrays.sort(splitNames, PackageParser.sSplitNameComparator); + Arrays.sort(splitNames, sSplitNameComparator); for (int i = 0; i < size; i++) { - final PackageParser.ApkLite apk = splitApks.get(splitNames[i]); - usesSplitNames[i] = apk.usesSplitName; - isFeatureSplits[i] = apk.isFeatureSplit; - configForSplits[i] = apk.configForSplit; + final ApkLite apk = splitApks.get(splitNames[i]); + usesSplitNames[i] = apk.getUsesSplitName(); + isFeatureSplits[i] = apk.isFeatureSplit(); + configForSplits[i] = apk.getConfigForSplit(); splitCodePaths[i] = apkRenamed ? new File(packageDir, - splitNameToFileName(apk)).getAbsolutePath() : apk.codePath; - splitRevisionCodes[i] = apk.revisionCode; + splitNameToFileName(apk)).getAbsolutePath() : apk.getPath(); + splitRevisionCodes[i] = apk.getRevisionCode(); } } final String codePath = packageDir.getAbsolutePath(); final String baseCodePath = apkRenamed ? new File(packageDir, - splitNameToFileName(baseApk)).getAbsolutePath() : baseApk.codePath; + splitNameToFileName(baseApk)).getAbsolutePath() : baseApk.getPath(); return input.success( - new PackageParser.PackageLite(codePath, baseCodePath, baseApk, splitNames, - isFeatureSplits, usesSplitNames, configForSplits, splitCodePaths, - splitRevisionCodes)); + new PackageLite(codePath, baseCodePath, baseApk, splitNames, isFeatureSplits, + usesSplitNames, configForSplits, splitCodePaths, splitRevisionCodes)); } /** @@ -245,9 +251,9 @@ public class ApkLiteParseUtils { * @param apk Parsed APK * @return The canonical file name */ - public static String splitNameToFileName(@NonNull PackageParser.ApkLite apk) { + public static String splitNameToFileName(@NonNull ApkLite apk) { Objects.requireNonNull(apk); - final String fileName = apk.splitName == null ? "base" : "split_" + apk.splitName; + final String fileName = apk.getSplitName() == null ? "base" : "split_" + apk.getSplitName(); return fileName + APK_FILE_EXTENSION; } @@ -257,10 +263,9 @@ public class ApkLiteParseUtils { * * @param apkFile path to a single APK * @param flags optional parse flags, such as - * {@link PackageParser#PARSE_COLLECT_CERTIFICATES} + * {@link ParsingPackageUtils#PARSE_COLLECT_CERTIFICATES} */ - public static ParseResult<PackageParser.ApkLite> parseApkLite(ParseInput input, File apkFile, - int flags) { + public static ParseResult<ApkLite> parseApkLite(ParseInput input, File apkFile, int flags) { return parseApkLiteInner(input, apkFile, null, null, flags); } @@ -271,14 +276,14 @@ public class ApkLiteParseUtils { * @param fd already open file descriptor of an apk file * @param debugPathName arbitrary text name for this file, for debug output * @param flags optional parse flags, such as - * {@link PackageParser#PARSE_COLLECT_CERTIFICATES} + * {@link ParsingPackageUtils#PARSE_COLLECT_CERTIFICATES} */ - public static ParseResult<PackageParser.ApkLite> parseApkLite(ParseInput input, + public static ParseResult<ApkLite> parseApkLite(ParseInput input, FileDescriptor fd, String debugPathName, int flags) { return parseApkLiteInner(input, null, fd, debugPathName, flags); } - private static ParseResult<PackageParser.ApkLite> parseApkLiteInner(ParseInput input, + private static ParseResult<ApkLite> parseApkLiteInner(ParseInput input, File apkFile, FileDescriptor fd, String debugPathName, int flags) { final String apkPath = fd != null ? debugPathName : apkFile.getAbsolutePath(); @@ -294,11 +299,11 @@ public class ApkLiteParseUtils { "Failed to parse " + apkPath, e); } - parser = apkAssets.openXml(PackageParser.ANDROID_MANIFEST_FILENAME); + parser = apkAssets.openXml(ParsingPackageUtils.ANDROID_MANIFEST_FILENAME); final PackageParser.SigningDetails signingDetails; - if ((flags & PackageParser.PARSE_COLLECT_CERTIFICATES) != 0) { - final boolean skipVerify = (flags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0; + if ((flags & ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES) != 0) { + final boolean skipVerify = (flags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0; Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates"); try { ParseResult<PackageParser.SigningDetails> result = @@ -335,9 +340,8 @@ public class ApkLiteParseUtils { } } - private static ParseResult<PackageParser.ApkLite> parseApkLite(ParseInput input, - String codePath, XmlPullParser parser, AttributeSet attrs, - PackageParser.SigningDetails signingDetails) + private static ParseResult<ApkLite> parseApkLite(ParseInput input, String codePath, + XmlPullParser parser, AttributeSet attrs, PackageParser.SigningDetails signingDetails) throws IOException, XmlPullParserException { ParseResult<Pair<String, String>> result = parsePackageSplitNames(input, parser, attrs); if (result.isError()) { @@ -421,12 +425,12 @@ public class ApkLiteParseUtils { continue; } - if (PackageParser.TAG_PACKAGE_VERIFIER.equals(parser.getName())) { + if (ParsingPackageUtils.TAG_PACKAGE_VERIFIER.equals(parser.getName())) { final VerifierInfo verifier = parseVerifier(attrs); if (verifier != null) { verifiers.add(verifier); } - } else if (PackageParser.TAG_APPLICATION.equals(parser.getName())) { + } else if (ParsingPackageUtils.TAG_APPLICATION.equals(parser.getName())) { for (int i = 0; i < attrs.getAttributeCount(); ++i) { final String attr = attrs.getAttributeName(i); switch (attr) { @@ -464,7 +468,7 @@ public class ApkLiteParseUtils { continue; } - if (PackageParser.TAG_PROFILEABLE.equals(parser.getName())) { + if (ParsingPackageUtils.TAG_PROFILEABLE.equals(parser.getName())) { for (int i = 0; i < attrs.getAttributeCount(); ++i) { final String attr = attrs.getAttributeName(i); if ("shell".equals(attr)) { @@ -474,7 +478,7 @@ public class ApkLiteParseUtils { } } } - } else if (PackageParser.TAG_OVERLAY.equals(parser.getName())) { + } else if (ParsingPackageUtils.TAG_OVERLAY.equals(parser.getName())) { for (int i = 0; i < attrs.getAttributeCount(); ++i) { final String attr = attrs.getAttributeName(i); if ("requiredSystemPropertyName".equals(attr)) { @@ -489,7 +493,7 @@ public class ApkLiteParseUtils { overlayPriority = attrs.getAttributeIntValue(i, 0); } } - } else if (PackageParser.TAG_USES_SPLIT.equals(parser.getName())) { + } else if (ParsingPackageUtils.TAG_USES_SPLIT.equals(parser.getName())) { if (usesSplitName != null) { Slog.w(TAG, "Only one <uses-split> permitted. Ignoring others."); continue; @@ -500,7 +504,7 @@ public class ApkLiteParseUtils { return input.error(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, "<uses-split> tag requires 'android:name' attribute"); } - } else if (PackageParser.TAG_USES_SDK.equals(parser.getName())) { + } else if (ParsingPackageUtils.TAG_USES_SDK.equals(parser.getName())) { for (int i = 0; i < attrs.getAttributeCount(); ++i) { final String attr = attrs.getAttributeName(i); if ("targetSdkVersion".equals(attr)) { @@ -526,8 +530,8 @@ public class ApkLiteParseUtils { } return input.success( - new PackageParser.ApkLite(codePath, packageSplit.first, packageSplit.second, - isFeatureSplit, configForSplit, usesSplitName, isSplitRequired, versionCode, + new ApkLite(codePath, packageSplit.first, packageSplit.second, isFeatureSplit, + configForSplit, usesSplitName, isSplitRequired, versionCode, versionCodeMajor, revisionCode, installLocation, verifiers, signingDetails, coreApp, debuggable, profilableByShell, multiArch, use32bitAbi, useEmbeddedDex, extractNativeLibs, isolatedSplits, targetPackage, @@ -546,7 +550,7 @@ public class ApkLiteParseUtils { return input.error(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, "No start tag found"); } - if (!parser.getName().equals(PackageParser.TAG_MANIFEST)) { + if (!parser.getName().equals(ParsingPackageUtils.TAG_MANIFEST)) { return input.error(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, "No <manifest> tag"); } @@ -625,4 +629,24 @@ public class ApkLiteParseUtils { } } } + + /** + * Check if the given file is an APK file. + * + * @param file the file to check. + * @return {@code true} if the given file is an APK file. + */ + public static boolean isApkFile(File file) { + return isApkPath(file.getName()); + } + + /** + * Check if the given path ends with APK file extension. + * + * @param path the path to check. + * @return {@code true} if the given path ends with APK file extension. + */ + public static boolean isApkPath(String path) { + return path.endsWith(APK_FILE_EXTENSION); + } } diff --git a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java index f8fd4a539334..b7365b3eaf61 100644 --- a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java +++ b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java @@ -385,7 +385,7 @@ public class PackageInfoWithoutStateUtils { } // CompatibilityMode is global state. - if (!PackageParser.sCompatibilityModeEnabled) { + if (!ParsingPackageUtils.sCompatibilityModeEnabled) { ai.disableCompatibilityMode(); } diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java index 38d3940ea517..51ec297df9e4 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java +++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java @@ -335,7 +335,7 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { private int fullBackupContent; private int iconRes; - private int installLocation = PackageParser.PARSE_DEFAULT_INSTALL_LOCATION; + private int installLocation = ParsingPackageUtils.PARSE_DEFAULT_INSTALL_LOCATION; private int labelRes; private int largestWidthLimitDp; private int logo; @@ -1013,7 +1013,8 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { // TODO(b/135203078): See ParsingPackageImpl#getHiddenApiEnforcementPolicy // appInfo.mHiddenApiPolicy // appInfo.hiddenUntilInstalled - appInfo.icon = (PackageParser.sUseRoundIcon && roundIconRes != 0) ? roundIconRes : iconRes; + appInfo.icon = + (ParsingPackageUtils.sUseRoundIcon && roundIconRes != 0) ? roundIconRes : iconRes; appInfo.iconRes = iconRes; appInfo.roundIconRes = roundIconRes; appInfo.installLocation = installLocation; diff --git a/core/java/android/content/pm/parsing/ParsingPackageRead.java b/core/java/android/content/pm/parsing/ParsingPackageRead.java index 13ae7a28360e..a102e828744e 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageRead.java +++ b/core/java/android/content/pm/parsing/ParsingPackageRead.java @@ -66,7 +66,7 @@ public interface ParsingPackageRead extends Parcelable { /** * The names of packages to adopt ownership of permissions from, parsed under - * {@link PackageParser#TAG_ADOPT_PERMISSIONS}. + * {@link ParsingPackageUtils#TAG_ADOPT_PERMISSIONS}. * @see R.styleable#AndroidManifestOriginalPackage_name */ @NonNull @@ -105,7 +105,7 @@ public interface ParsingPackageRead extends Parcelable { /** * For use with {@link com.android.server.pm.KeySetManagerService}. Parsed in - * {@link PackageParser#TAG_KEY_SETS}. + * {@link ParsingPackageUtils#TAG_KEY_SETS}. * @see R.styleable#AndroidManifestKeySet * @see R.styleable#AndroidManifestPublicKey */ @@ -816,7 +816,7 @@ public interface ParsingPackageRead extends Parcelable { /** * For use with {@link com.android.server.pm.KeySetManagerService}. Parsed in - * {@link PackageParser#TAG_KEY_SETS}. + * {@link ParsingPackageUtils#TAG_KEY_SETS}. * @see R.styleable#AndroidManifestUpgradeKeySet */ @NonNull diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java index 494b3cc1c28f..b054304bbe74 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java +++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java @@ -189,6 +189,12 @@ public class ParsingPackageUtils { PackageInfo.INSTALL_LOCATION_UNSPECIFIED; public static final int PARSE_DEFAULT_TARGET_SANDBOX = 1; + /** If set to true, we will only allow package files that exactly match + * the DTD. Otherwise, we try to get as much from the package as we + * can without failing. This should normally be set to false, to + * support extensions to the DTD in future versions. */ + public static final boolean RIGID_PARSER = false; + public static final int PARSE_MUST_BE_APK = 1 << 0; public static final int PARSE_IGNORE_PROCESSES = 1 << 1; public static final int PARSE_EXTERNAL_STORAGE = 1 << 3; @@ -220,7 +226,7 @@ public class ParsingPackageUtils { */ @NonNull public static ParseResult<ParsingPackage> parseDefaultOneTime(File file, - @PackageParser.ParseFlags int parseFlags, boolean collectCertificates) { + @ParseFlags int parseFlags, boolean collectCertificates) { ParseInput input = ParseTypeImpl.forDefaultParsing().reset(); return parseDefault(input, file, parseFlags, collectCertificates); } @@ -232,7 +238,7 @@ public class ParsingPackageUtils { */ @NonNull public static ParseResult<ParsingPackage> parseDefault(ParseInput input, File file, - @PackageParser.ParseFlags int parseFlags, boolean collectCertificates) { + @ParseFlags int parseFlags, boolean collectCertificates) { ParseResult<ParsingPackage> result; ParsingPackageUtils parser = new ParsingPackageUtils(false, null, null, new Callback() { @@ -334,14 +340,14 @@ public class ParsingPackageUtils { */ private ParseResult<ParsingPackage> parseClusterPackage(ParseInput input, File packageDir, int flags) { - ParseResult<PackageParser.PackageLite> liteResult = + final ParseResult<PackageLite> liteResult = ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, 0); if (liteResult.isError()) { return input.error(liteResult); } - final PackageParser.PackageLite lite = liteResult.getResult(); - if (mOnlyCoreApps && !lite.coreApp) { + final PackageLite lite = liteResult.getResult(); + if (mOnlyCoreApps && !lite.isCoreApp()) { return input.error(INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED, "Not a coreApp: " + packageDir); } @@ -349,7 +355,7 @@ public class ParsingPackageUtils { // Build the split dependency tree. SparseArray<int[]> splitDependencies = null; final SplitAssetLoader assetLoader; - if (lite.isolatedSplits && !ArrayUtils.isEmpty(lite.splitNames)) { + if (lite.isIsolatedSplits() && !ArrayUtils.isEmpty(lite.getSplitNames())) { try { splitDependencies = SplitAssetDependencyLoader.createDependenciesFromPackage(lite); assetLoader = new SplitAssetDependencyLoader(lite, splitDependencies, flags); @@ -362,22 +368,22 @@ public class ParsingPackageUtils { try { final AssetManager assets = assetLoader.getBaseAssetManager(); - final File baseApk = new File(lite.baseCodePath); - ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk, - lite.codePath, assets, flags); + final File baseApk = new File(lite.getBaseApkPath()); + final ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk, + lite.getPath(), assets, flags); if (result.isError()) { return input.error(result); } ParsingPackage pkg = result.getResult(); - if (!ArrayUtils.isEmpty(lite.splitNames)) { + if (!ArrayUtils.isEmpty(lite.getSplitNames())) { pkg.asSplit( - lite.splitNames, - lite.splitCodePaths, - lite.splitRevisionCodes, + lite.getSplitNames(), + lite.getSplitApkPaths(), + lite.getSplitRevisionCodes(), splitDependencies ); - final int num = lite.splitNames.length; + final int num = lite.getSplitNames().length; for (int i = 0; i < num; i++) { final AssetManager splitAssets = assetLoader.getSplitAssetManager(i); @@ -385,11 +391,11 @@ public class ParsingPackageUtils { } } - pkg.setUse32BitAbi(lite.use32bitAbi); + pkg.setUse32BitAbi(lite.isUse32bitAbi()); return input.success(pkg); } catch (PackageParserException e) { return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, - "Failed to load assets: " + lite.baseCodePath, e); + "Failed to load assets: " + lite.getBaseApkPath(), e); } finally { IoUtils.closeQuietly(assetLoader); } @@ -403,21 +409,21 @@ public class ParsingPackageUtils { */ private ParseResult<ParsingPackage> parseMonolithicPackage(ParseInput input, File apkFile, int flags) throws PackageParserException { - ParseResult<PackageParser.PackageLite> liteResult = + final ParseResult<PackageLite> liteResult = ApkLiteParseUtils.parseMonolithicPackageLite(input, apkFile, flags); if (liteResult.isError()) { return input.error(liteResult); } - final PackageParser.PackageLite lite = liteResult.getResult(); - if (mOnlyCoreApps && !lite.coreApp) { + final PackageLite lite = liteResult.getResult(); + if (mOnlyCoreApps && !lite.isCoreApp()) { return input.error(INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED, "Not a coreApp: " + apkFile); } final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags); try { - ParseResult<ParsingPackage> result = parseBaseApk(input, + final ParseResult<ParsingPackage> result = parseBaseApk(input, apkFile, apkFile.getCanonicalPath(), assetLoader.getBaseAssetManager(), flags); @@ -426,7 +432,7 @@ public class ParsingPackageUtils { } return input.success(result.getResult() - .setUse32BitAbi(lite.use32bitAbi)); + .setUse32BitAbi(lite.isUse32bitAbi())); } catch (IOException e) { return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, "Failed to get path: " + apkFile, e); @@ -440,12 +446,12 @@ public class ParsingPackageUtils { final String apkPath = apkFile.getAbsolutePath(); String volumeUuid = null; - if (apkPath.startsWith(PackageParser.MNT_EXPAND)) { - final int end = apkPath.indexOf('/', PackageParser.MNT_EXPAND.length()); - volumeUuid = apkPath.substring(PackageParser.MNT_EXPAND.length(), end); + if (apkPath.startsWith(MNT_EXPAND)) { + final int end = apkPath.indexOf('/', MNT_EXPAND.length()); + volumeUuid = apkPath.substring(MNT_EXPAND.length(), end); } - if (PackageParser.DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath); + if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath); final int cookie = assets.findCookieForPath(apkPath); if (cookie == 0) { @@ -454,7 +460,7 @@ public class ParsingPackageUtils { } try (XmlResourceParser parser = assets.openXmlResourceParser(cookie, - PackageParser.ANDROID_MANIFEST_FILENAME)) { + ANDROID_MANIFEST_FILENAME)) { final Resources res = new Resources(assets, mDisplayMetrics, null); ParseResult<ParsingPackage> result = parseBaseApk(input, apkPath, codePath, res, @@ -495,7 +501,7 @@ public class ParsingPackageUtils { pkg.setVolumeUuid(volumeUuid); - if ((flags & PackageParser.PARSE_COLLECT_CERTIFICATES) != 0) { + if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) { pkg.setSigningDetails(getSigningDetails(pkg, false)); } else { pkg.setSigningDetails(SigningDetails.UNKNOWN); @@ -512,7 +518,7 @@ public class ParsingPackageUtils { ParsingPackage pkg, int splitIndex, AssetManager assets, int flags) { final String apkPath = pkg.getSplitCodePaths()[splitIndex]; - if (PackageParser.DEBUG_JAR) Slog.d(TAG, "Scanning split APK: " + apkPath); + if (DEBUG_JAR) Slog.d(TAG, "Scanning split APK: " + apkPath); // This must always succeed, as the path has been added to the AssetManager before. final int cookie = assets.findCookieForPath(apkPath); @@ -521,7 +527,7 @@ public class ParsingPackageUtils { "Failed adding asset path: " + apkPath); } try (XmlResourceParser parser = assets.openXmlResourceParser(cookie, - PackageParser.ANDROID_MANIFEST_FILENAME)) { + ANDROID_MANIFEST_FILENAME)) { Resources res = new Resources(assets, mDisplayMetrics, null); ParseResult<ParsingPackage> parseResult = parseSplitApk(input, pkg, res, parser, flags, splitIndex); @@ -620,9 +626,9 @@ public class ParsingPackageUtils { final ParseResult result; String tagName = parser.getName(); - if (PackageParser.TAG_APPLICATION.equals(tagName)) { + if (TAG_APPLICATION.equals(tagName)) { if (foundApp) { - if (PackageParser.RIGID_PARSER) { + if (RIGID_PARSER) { result = input.error("<manifest> has more than one <application>"); } else { Slog.w(TAG, "<manifest> has more than one <application>"); @@ -701,7 +707,7 @@ public class ParsingPackageUtils { ParseResult<ParsedActivity> activityResult = ParsedActivityUtils.parseActivityOrReceiver(mSeparateProcesses, pkg, res, - parser, flags, PackageParser.sUseRoundIcon, input); + parser, flags, sUseRoundIcon, input); if (activityResult.isSuccess()) { ParsedActivity activity = activityResult.getResult(); if (isActivity) { @@ -716,7 +722,7 @@ public class ParsingPackageUtils { case "service": ParseResult<ParsedService> serviceResult = ParsedServiceUtils.parseService( mSeparateProcesses, pkg, res, parser, flags, - PackageParser.sUseRoundIcon, input); + sUseRoundIcon, input); if (serviceResult.isSuccess()) { ParsedService service = serviceResult.getResult(); pkg.addService(service); @@ -727,7 +733,7 @@ public class ParsingPackageUtils { case "provider": ParseResult<ParsedProvider> providerResult = ParsedProviderUtils.parseProvider(mSeparateProcesses, pkg, res, parser, - flags, PackageParser.sUseRoundIcon, input); + flags, sUseRoundIcon, input); if (providerResult.isSuccess()) { ParsedProvider provider = providerResult.getResult(); pkg.addProvider(provider); @@ -737,7 +743,7 @@ public class ParsingPackageUtils { break; case "activity-alias": activityResult = ParsedActivityUtils.parseActivityAlias(pkg, res, parser, - PackageParser.sUseRoundIcon, input); + sUseRoundIcon, input); if (activityResult.isSuccess()) { ParsedActivity activity = activityResult.getResult(); pkg.addActivity(activity); @@ -815,12 +821,12 @@ public class ParsingPackageUtils { return sharedUserResult; } - pkg.setInstallLocation(anInteger(PackageParser.PARSE_DEFAULT_INSTALL_LOCATION, + pkg.setInstallLocation(anInteger(PARSE_DEFAULT_INSTALL_LOCATION, R.styleable.AndroidManifest_installLocation, sa)) - .setTargetSandboxVersion(anInteger(PackageParser.PARSE_DEFAULT_TARGET_SANDBOX, + .setTargetSandboxVersion(anInteger(PARSE_DEFAULT_TARGET_SANDBOX, R.styleable.AndroidManifest_targetSandboxVersion, sa)) /* Set the global "on SD card" flag */ - .setExternalStorage((flags & PackageParser.PARSE_EXTERNAL_STORAGE) != 0); + .setExternalStorage((flags & PARSE_EXTERNAL_STORAGE) != 0); boolean foundApp = false; final int depth = parser.getDepth(); @@ -836,9 +842,9 @@ public class ParsingPackageUtils { final ParseResult result; // <application> has special logic, so it's handled outside the general method - if (PackageParser.TAG_APPLICATION.equals(tagName)) { + if (TAG_APPLICATION.equals(tagName)) { if (foundApp) { - if (PackageParser.RIGID_PARSER) { + if (RIGID_PARSER) { result = input.error("<manifest> has more than one <application>"); } else { Slog.w(TAG, "<manifest> has more than one <application>"); @@ -897,51 +903,51 @@ public class ParsingPackageUtils { ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags) throws IOException, XmlPullParserException { switch (tag) { - case PackageParser.TAG_OVERLAY: + case TAG_OVERLAY: return parseOverlay(input, pkg, res, parser); - case PackageParser.TAG_KEY_SETS: + case TAG_KEY_SETS: return parseKeySets(input, pkg, res, parser); case "feature": // TODO moltmann: Remove - case PackageParser.TAG_ATTRIBUTION: + case TAG_ATTRIBUTION: return parseAttribution(input, pkg, res, parser); - case PackageParser.TAG_PERMISSION_GROUP: + case TAG_PERMISSION_GROUP: return parsePermissionGroup(input, pkg, res, parser); - case PackageParser.TAG_PERMISSION: + case TAG_PERMISSION: return parsePermission(input, pkg, res, parser); - case PackageParser.TAG_PERMISSION_TREE: + case TAG_PERMISSION_TREE: return parsePermissionTree(input, pkg, res, parser); - case PackageParser.TAG_USES_PERMISSION: - case PackageParser.TAG_USES_PERMISSION_SDK_M: - case PackageParser.TAG_USES_PERMISSION_SDK_23: + case TAG_USES_PERMISSION: + case TAG_USES_PERMISSION_SDK_M: + case TAG_USES_PERMISSION_SDK_23: return parseUsesPermission(input, pkg, res, parser); - case PackageParser.TAG_USES_CONFIGURATION: + case TAG_USES_CONFIGURATION: return parseUsesConfiguration(input, pkg, res, parser); - case PackageParser.TAG_USES_FEATURE: + case TAG_USES_FEATURE: return parseUsesFeature(input, pkg, res, parser); - case PackageParser.TAG_FEATURE_GROUP: + case TAG_FEATURE_GROUP: return parseFeatureGroup(input, pkg, res, parser); - case PackageParser.TAG_USES_SDK: + case TAG_USES_SDK: return parseUsesSdk(input, pkg, res, parser); - case PackageParser.TAG_SUPPORT_SCREENS: + case TAG_SUPPORT_SCREENS: return parseSupportScreens(input, pkg, res, parser); - case PackageParser.TAG_PROTECTED_BROADCAST: + case TAG_PROTECTED_BROADCAST: return parseProtectedBroadcast(input, pkg, res, parser); - case PackageParser.TAG_INSTRUMENTATION: + case TAG_INSTRUMENTATION: return parseInstrumentation(input, pkg, res, parser); - case PackageParser.TAG_ORIGINAL_PACKAGE: + case TAG_ORIGINAL_PACKAGE: return parseOriginalPackage(input, pkg, res, parser); - case PackageParser.TAG_ADOPT_PERMISSIONS: + case TAG_ADOPT_PERMISSIONS: return parseAdoptPermissions(input, pkg, res, parser); - case PackageParser.TAG_USES_GL_TEXTURE: - case PackageParser.TAG_COMPATIBLE_SCREENS: - case PackageParser.TAG_SUPPORTS_INPUT: - case PackageParser.TAG_EAT_COMMENT: + case TAG_USES_GL_TEXTURE: + case TAG_COMPATIBLE_SCREENS: + case TAG_SUPPORTS_INPUT: + case TAG_EAT_COMMENT: // Just skip this tag XmlUtils.skipCurrentTag(parser); return input.success(pkg); - case PackageParser.TAG_RESTRICT_UPDATE: + case TAG_RESTRICT_UPDATE: return parseRestrictUpdateHash(flags, input, pkg, res, parser); - case PackageParser.TAG_QUERIES: + case TAG_QUERIES: return parseQueries(input, pkg, res, parser); default: return ParsingUtils.unknownTag("<manifest>", pkg, parser, input); @@ -1125,7 +1131,7 @@ public class ParsingPackageUtils { ParsingPackage pkg, Resources res, XmlResourceParser parser) throws XmlPullParserException, IOException { ParseResult<ParsedPermissionGroup> result = ParsedPermissionUtils.parsePermissionGroup( - pkg, res, parser, PackageParser.sUseRoundIcon, input); + pkg, res, parser, sUseRoundIcon, input); if (result.isError()) { return input.error(result); } @@ -1136,7 +1142,7 @@ public class ParsingPackageUtils { ParsingPackage pkg, Resources res, XmlResourceParser parser) throws XmlPullParserException, IOException { ParseResult<ParsedPermission> result = ParsedPermissionUtils.parsePermission( - pkg, res, parser, PackageParser.sUseRoundIcon, input); + pkg, res, parser, sUseRoundIcon, input); if (result.isError()) { return input.error(result); } @@ -1147,7 +1153,7 @@ public class ParsingPackageUtils { ParsingPackage pkg, Resources res, XmlResourceParser parser) throws XmlPullParserException, IOException { ParseResult<ParsedPermission> result = ParsedPermissionUtils.parsePermissionTree( - pkg, res, parser, PackageParser.sUseRoundIcon, input); + pkg, res, parser, sUseRoundIcon, input); if (result.isError()) { return input.error(result); } @@ -1405,7 +1411,7 @@ public class ParsingPackageUtils { private static ParseResult<ParsingPackage> parseUsesSdk(ParseInput input, ParsingPackage pkg, Resources res, XmlResourceParser parser) throws IOException, XmlPullParserException { - if (PackageParser.SDK_VERSION > 0) { + if (SDK_VERSION > 0) { TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesSdk); try { int minVers = 1; @@ -1440,7 +1446,7 @@ public class ParsingPackageUtils { } ParseResult<Integer> targetSdkVersionResult = computeTargetSdkVersion( - targetVers, targetCode, PackageParser.SDK_CODENAMES, input); + targetVers, targetCode, SDK_CODENAMES, input); if (targetSdkVersionResult.isError()) { return input.error(targetSdkVersionResult); } @@ -1454,7 +1460,7 @@ public class ParsingPackageUtils { } ParseResult<Integer> minSdkVersionResult = computeMinSdkVersion(minVers, minCode, - PackageParser.SDK_VERSION, PackageParser.SDK_CODENAMES, input); + SDK_VERSION, SDK_CODENAMES, input); if (minSdkVersionResult.isError()) { return input.error(minSdkVersionResult); } @@ -1637,7 +1643,7 @@ public class ParsingPackageUtils { private static ParseResult<ParsingPackage> parseRestrictUpdateHash(int flags, ParseInput input, ParsingPackage pkg, Resources res, XmlResourceParser parser) { - if ((flags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0) { + if ((flags & PARSE_IS_SYSTEM_DIR) != 0) { TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestRestrictUpdate); try { final String hash = sa.getNonConfigurationString( @@ -1846,7 +1852,7 @@ public class ParsingPackageUtils { return input.error("Empty class name in package " + pkgName); } - if (PackageParser.DEBUG_BACKUP) { + if (DEBUG_BACKUP) { Slog.v(TAG, "android:backupAgent = " + backupAgentName + " from " + pkgName + "+" + backupAgent); } @@ -1870,7 +1876,7 @@ public class ParsingPackageUtils { fullBackupContent = v.resourceId; if (v.resourceId == 0) { - if (PackageParser.DEBUG_BACKUP) { + if (DEBUG_BACKUP) { Slog.v(TAG, "fullBackupContent specified as boolean=" + (v.data == 0 ? "false" : "true")); } @@ -1880,7 +1886,7 @@ public class ParsingPackageUtils { pkg.setFullBackupContent(fullBackupContent); } - if (PackageParser.DEBUG_BACKUP) { + if (DEBUG_BACKUP) { Slog.v(TAG, "fullBackupContent=" + fullBackupContent + " for " + pkgName); } } @@ -1994,7 +2000,7 @@ public class ParsingPackageUtils { case "receiver": ParseResult<ParsedActivity> activityResult = ParsedActivityUtils.parseActivityOrReceiver(mSeparateProcesses, pkg, - res, parser, flags, PackageParser.sUseRoundIcon, input); + res, parser, flags, sUseRoundIcon, input); if (activityResult.isSuccess()) { ParsedActivity activity = activityResult.getResult(); @@ -2012,7 +2018,7 @@ public class ParsingPackageUtils { case "service": ParseResult<ParsedService> serviceResult = ParsedServiceUtils.parseService(mSeparateProcesses, pkg, res, parser, - flags, PackageParser.sUseRoundIcon, input); + flags, sUseRoundIcon, input); if (serviceResult.isSuccess()) { ParsedService service = serviceResult.getResult(); hasServiceOrder |= (service.getOrder() != 0); @@ -2024,7 +2030,7 @@ public class ParsingPackageUtils { case "provider": ParseResult<ParsedProvider> providerResult = ParsedProviderUtils.parseProvider(mSeparateProcesses, pkg, res, parser, - flags, PackageParser.sUseRoundIcon, input); + flags, sUseRoundIcon, input); if (providerResult.isSuccess()) { pkg.addProvider(providerResult.getResult()); } @@ -2033,7 +2039,7 @@ public class ParsingPackageUtils { break; case "activity-alias": activityResult = ParsedActivityUtils.parseActivityAlias(pkg, res, - parser, PackageParser.sUseRoundIcon, input); + parser, sUseRoundIcon, input); if (activityResult.isSuccess()) { ParsedActivity activity = activityResult.getResult(); hasActivityOrder |= (activity.getOrder() != 0); @@ -2505,8 +2511,7 @@ public class ParsingPackageUtils { private static void setMaxAspectRatio(ParsingPackage pkg) { // Default to (1.86) 16.7:9 aspect ratio for pre-O apps and unset for O and greater. // NOTE: 16.7:9 was the max aspect ratio Android devices can support pre-O per the CDD. - float maxAspectRatio = pkg.getTargetSdkVersion() < O - ? PackageParser.DEFAULT_PRE_O_MAX_ASPECT_RATIO : 0; + float maxAspectRatio = pkg.getTargetSdkVersion() < O ? DEFAULT_PRE_O_MAX_ASPECT_RATIO : 0; float packageMaxAspectRatio = pkg.getMaxAspectRatio(); if (packageMaxAspectRatio != 0) { @@ -2514,10 +2519,8 @@ public class ParsingPackageUtils { maxAspectRatio = packageMaxAspectRatio; } else { Bundle appMetaData = pkg.getMetaData(); - if (appMetaData != null && appMetaData.containsKey( - PackageParser.METADATA_MAX_ASPECT_RATIO)) { - maxAspectRatio = appMetaData.getFloat(PackageParser.METADATA_MAX_ASPECT_RATIO, - maxAspectRatio); + if (appMetaData != null && appMetaData.containsKey(METADATA_MAX_ASPECT_RATIO)) { + maxAspectRatio = appMetaData.getFloat(METADATA_MAX_ASPECT_RATIO, maxAspectRatio); } } @@ -2536,8 +2539,7 @@ public class ParsingPackageUtils { // process the meta data here since this method is called at the end of processing // the application and all meta data is guaranteed. final float activityAspectRatio = activity.getMetaData() != null - ? activity.getMetaData().getFloat(PackageParser.METADATA_MAX_ASPECT_RATIO, - maxAspectRatio) + ? activity.getMetaData().getFloat(METADATA_MAX_ASPECT_RATIO, maxAspectRatio) : maxAspectRatio; activity.setMaxAspectRatio(activity.getResizeMode(), activityAspectRatio); @@ -2565,7 +2567,7 @@ public class ParsingPackageUtils { private void setSupportsSizeChanges(ParsingPackage pkg) { final Bundle appMetaData = pkg.getMetaData(); final boolean supportsSizeChanges = appMetaData != null - && appMetaData.getBoolean(PackageParser.METADATA_SUPPORTS_SIZE_CHANGES, false); + && appMetaData.getBoolean(METADATA_SUPPORTS_SIZE_CHANGES, false); List<ParsedActivity> activities = pkg.getActivities(); int activitiesSize = activities.size(); @@ -2573,7 +2575,7 @@ public class ParsingPackageUtils { ParsedActivity activity = activities.get(index); if (supportsSizeChanges || (activity.getMetaData() != null && activity.getMetaData().getBoolean( - PackageParser.METADATA_SUPPORTS_SIZE_CHANGES, false))) { + METADATA_SUPPORTS_SIZE_CHANGES, false))) { activity.setSupportsSizeChanges(true); } } @@ -2674,7 +2676,7 @@ public class ParsingPackageUtils { ParsingPackage pkg, Resources res, XmlResourceParser parser) throws XmlPullParserException, IOException { ParseResult<ParsedInstrumentation> result = ParsedInstrumentationUtils.parseInstrumentation( - pkg, res, parser, PackageParser.sUseRoundIcon, input); + pkg, res, parser, sUseRoundIcon, input); if (result.isError()) { return input.error(result); } @@ -2860,7 +2862,7 @@ public class ParsingPackageUtils { } else if (v.type == TypedValue.TYPE_FLOAT) { property = new Property(name, v.getFloat(), packageName, className); } else { - if (!PackageParser.RIGID_PARSER) { + if (!RIGID_PARSER) { Slog.w(TAG, tagName + " only supports string, integer, float, color, " + "boolean, and resource reference types: " diff --git a/core/java/android/content/pm/parsing/component/ComponentParseUtils.java b/core/java/android/content/pm/parsing/component/ComponentParseUtils.java index d65f8ffc989a..0403a25f5ea1 100644 --- a/core/java/android/content/pm/parsing/component/ComponentParseUtils.java +++ b/core/java/android/content/pm/parsing/component/ComponentParseUtils.java @@ -22,9 +22,9 @@ import android.annotation.AttrRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Intent; -import android.content.pm.PackageParser; import android.content.pm.PackageUserState; import android.content.pm.parsing.ParsingPackage; +import android.content.pm.parsing.ParsingPackageUtils; import android.content.pm.parsing.ParsingUtils; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; @@ -77,7 +77,7 @@ public class ComponentParseUtils { @NonNull public static ParseResult<String> buildProcessName(@NonNull String pkg, String defProc, CharSequence procSeq, int flags, String[] separateProcesses, ParseInput input) { - if ((flags & PackageParser.PARSE_IGNORE_PROCESSES) != 0 && !"system".contentEquals( + if ((flags & ParsingPackageUtils.PARSE_IGNORE_PROCESSES) != 0 && !"system".contentEquals( procSeq)) { return input.success(defProc != null ? defProc : pkg); } diff --git a/core/java/android/content/pm/parsing/component/ParsedActivity.java b/core/java/android/content/pm/parsing/component/ParsedActivity.java index 19150283d71e..2ea24f71371b 100644 --- a/core/java/android/content/pm/parsing/component/ParsedActivity.java +++ b/core/java/android/content/pm/parsing/component/ParsedActivity.java @@ -28,7 +28,6 @@ import android.app.ActivityTaskManager; import android.content.ComponentName; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; -import android.content.pm.PackageParser; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; @@ -127,7 +126,7 @@ public class ParsedActivity extends ParsedMainComponent { activity.launchMode = ActivityInfo.LAUNCH_MULTIPLE; activity.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NONE; activity.maxRecents = ActivityTaskManager.getDefaultAppRecentsLimitStatic(); - activity.configChanges = PackageParser.getActivityConfigChanges(0, 0); + activity.configChanges = ParsedActivityUtils.getActivityConfigChanges(0, 0); activity.softInputMode = 0; activity.persistableMode = ActivityInfo.PERSIST_NEVER; activity.screenOrientation = SCREEN_ORIENTATION_UNSPECIFIED; diff --git a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java index f96bd5400cd2..f821e081fc66 100644 --- a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java +++ b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java @@ -23,8 +23,8 @@ import android.annotation.NonNull; import android.app.ActivityTaskManager; import android.content.Intent; import android.content.pm.ActivityInfo; -import android.content.pm.PackageParser; import android.content.pm.parsing.ParsingPackage; +import android.content.pm.parsing.ParsingPackageUtils; import android.content.pm.parsing.ParsingUtils; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseInput.DeferredError; @@ -67,6 +67,12 @@ public class ParsedActivityUtils { SAFE_BROADCASTS.add(Intent.ACTION_BOOT_COMPLETED); } + /** + * Bit mask of all the valid bits that can be set in recreateOnConfigChanges. + */ + private static final int RECREATE_ON_CONFIG_CHANGES_MASK = + ActivityInfo.CONFIG_MCC | ActivityInfo.CONFIG_MNC; + @NonNull @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public static ParseResult<ParsedActivity> parseActivityOrReceiver(String[] separateProcesses, @@ -153,7 +159,7 @@ public class ParsedActivityUtils { activity.rotationAnimation = sa.getInt(R.styleable.AndroidManifestActivity_rotationAnimation, WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED); activity.softInputMode = sa.getInt(R.styleable.AndroidManifestActivity_windowSoftInputMode, 0); - activity.configChanges = PackageParser.getActivityConfigChanges( + activity.configChanges = getActivityConfigChanges( sa.getInt(R.styleable.AndroidManifestActivity_configChanges, 0), sa.getInt(R.styleable.AndroidManifestActivity_recreateOnConfigChanges, 0)); @@ -345,7 +351,7 @@ public class ParsedActivityUtils { if (intent != null) { activity.order = Math.max(intent.getOrder(), activity.order); activity.addIntent(intent); - if (PackageParser.LOG_UNSAFE_BROADCASTS && isReceiver + if (LOG_UNSAFE_BROADCASTS && isReceiver && pkg.getTargetSdkVersion() >= Build.VERSION_CODES.O) { int actionCount = intent.countActions(); for (int i = 0; i < actionCount; i++) { @@ -354,7 +360,7 @@ public class ParsedActivityUtils { continue; } - if (!PackageParser.SAFE_BROADCASTS.contains(action)) { + if (!SAFE_BROADCASTS.contains(action)) { Slog.w(TAG, "Broadcast " + action + " may never be delivered to " + pkg.getPackageName() + " as requested at: " @@ -532,7 +538,7 @@ public class ParsedActivityUtils { ParsedActivity activity, ParseInput input) { // There isn't a metadata for us to fall back. Whatever is in layout is correct. if (activity.metaData == null || !activity.metaData.containsKey( - PackageParser.METADATA_ACTIVITY_WINDOW_LAYOUT_AFFINITY)) { + ParsingPackageUtils.METADATA_ACTIVITY_WINDOW_LAYOUT_AFFINITY)) { return input.success(activity.windowLayout); } @@ -542,7 +548,7 @@ public class ParsedActivityUtils { } String windowLayoutAffinity = activity.metaData.getString( - PackageParser.METADATA_ACTIVITY_WINDOW_LAYOUT_AFFINITY); + ParsingPackageUtils.METADATA_ACTIVITY_WINDOW_LAYOUT_AFFINITY); ActivityInfo.WindowLayout layout = activity.windowLayout; if (layout == null) { layout = new ActivityInfo.WindowLayout(-1 /* width */, -1 /* widthFraction */, @@ -553,4 +559,14 @@ public class ParsedActivityUtils { } return input.success(layout); } + + /** + * @param configChanges The bit mask of configChanges fetched from AndroidManifest.xml. + * @param recreateOnConfigChanges The bit mask recreateOnConfigChanges fetched from + * AndroidManifest.xml. + * @hide + */ + static int getActivityConfigChanges(int configChanges, int recreateOnConfigChanges) { + return configChanges | ((~recreateOnConfigChanges) & RECREATE_ON_CONFIG_CHANGES_MASK); + } } diff --git a/core/java/android/content/pm/parsing/component/ParsedIntentInfoUtils.java b/core/java/android/content/pm/parsing/component/ParsedIntentInfoUtils.java index 368dcfde3267..939e77f6067c 100644 --- a/core/java/android/content/pm/parsing/component/ParsedIntentInfoUtils.java +++ b/core/java/android/content/pm/parsing/component/ParsedIntentInfoUtils.java @@ -21,6 +21,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageParser; import android.content.pm.parsing.ParsingPackage; +import android.content.pm.parsing.ParsingPackageUtils; import android.content.pm.parsing.ParsingUtils; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; @@ -65,7 +66,7 @@ public class ParsedIntentInfoUtils { } } - if (PackageParser.sUseRoundIcon) { + if (ParsingPackageUtils.sUseRoundIcon) { intentInfo.icon = sa.getResourceId( R.styleable.AndroidManifestIntentFilter_roundIcon, 0); } @@ -141,7 +142,7 @@ public class ParsedIntentInfoUtils { intentInfo.hasDefault = intentInfo.hasCategory(Intent.CATEGORY_DEFAULT); - if (PackageParser.DEBUG_PARSER) { + if (DEBUG) { final StringBuilder cats = new StringBuilder("Intent d="); cats.append(intentInfo.isHasDefault()); cats.append(", cat="); diff --git a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java index 9e3a8f48996c..f3caf603921f 100644 --- a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java +++ b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java @@ -18,9 +18,11 @@ package android.content.pm.split; import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK; -import android.content.pm.PackageParser; import android.content.pm.PackageParser.PackageParserException; -import android.content.pm.PackageParser.ParseFlags; +import android.content.pm.parsing.ApkLiteParseUtils; +import android.content.pm.parsing.PackageLite; +import android.content.pm.parsing.ParsingPackageUtils; +import android.content.pm.parsing.ParsingPackageUtils.ParseFlags; import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.os.Build; @@ -36,20 +38,21 @@ import java.io.IOException; * @hide */ public class DefaultSplitAssetLoader implements SplitAssetLoader { - private final String mBaseCodePath; - private final String[] mSplitCodePaths; + private final String mBaseApkPath; + private final String[] mSplitApkPaths; private final @ParseFlags int mFlags; private AssetManager mCachedAssetManager; - public DefaultSplitAssetLoader(PackageParser.PackageLite pkg, @ParseFlags int flags) { - mBaseCodePath = pkg.baseCodePath; - mSplitCodePaths = pkg.splitCodePaths; + public DefaultSplitAssetLoader(PackageLite pkg, @ParseFlags int flags) { + mBaseApkPath = pkg.getBaseApkPath(); + mSplitApkPaths = pkg.getSplitApkPaths(); mFlags = flags; } private static ApkAssets loadApkAssets(String path, @ParseFlags int flags) throws PackageParserException { - if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(path)) { + if ((flags & ParsingPackageUtils.PARSE_MUST_BE_APK) != 0 + && !ApkLiteParseUtils.isApkPath(path)) { throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, "Invalid package file: " + path); } @@ -68,16 +71,16 @@ public class DefaultSplitAssetLoader implements SplitAssetLoader { return mCachedAssetManager; } - ApkAssets[] apkAssets = new ApkAssets[(mSplitCodePaths != null - ? mSplitCodePaths.length : 0) + 1]; + ApkAssets[] apkAssets = new ApkAssets[(mSplitApkPaths != null + ? mSplitApkPaths.length : 0) + 1]; // Load the base. int splitIdx = 0; - apkAssets[splitIdx++] = loadApkAssets(mBaseCodePath, mFlags); + apkAssets[splitIdx++] = loadApkAssets(mBaseApkPath, mFlags); // Load any splits. - if (!ArrayUtils.isEmpty(mSplitCodePaths)) { - for (String apkPath : mSplitCodePaths) { + if (!ArrayUtils.isEmpty(mSplitApkPaths)) { + for (String apkPath : mSplitApkPaths) { apkAssets[splitIdx++] = loadApkAssets(apkPath, mFlags); } } diff --git a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java index 58eaabfa62f2..523ca405eec7 100644 --- a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java +++ b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java @@ -19,9 +19,11 @@ import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK; import android.annotation.NonNull; import android.content.pm.PackageManager; -import android.content.pm.PackageParser; import android.content.pm.PackageParser.PackageParserException; -import android.content.pm.PackageParser.ParseFlags; +import android.content.pm.parsing.ApkLiteParseUtils; +import android.content.pm.parsing.PackageLite; +import android.content.pm.parsing.ParsingPackageUtils; +import android.content.pm.parsing.ParsingPackageUtils.ParseFlags; import android.content.res.ApkAssets; import android.content.res.AssetManager; import android.os.Build; @@ -45,14 +47,14 @@ public class SplitAssetDependencyLoader extends SplitDependencyLoader<PackagePar private final ApkAssets[][] mCachedSplitApks; private final AssetManager[] mCachedAssetManagers; - public SplitAssetDependencyLoader(PackageParser.PackageLite pkg, + public SplitAssetDependencyLoader(PackageLite pkg, SparseArray<int[]> dependencies, @ParseFlags int flags) { super(dependencies); // The base is inserted into index 0, so we need to shift all the splits by 1. - mSplitPaths = new String[pkg.splitCodePaths.length + 1]; - mSplitPaths[0] = pkg.baseCodePath; - System.arraycopy(pkg.splitCodePaths, 0, mSplitPaths, 1, pkg.splitCodePaths.length); + mSplitPaths = new String[pkg.getSplitApkPaths().length + 1]; + mSplitPaths[0] = pkg.getBaseApkPath(); + System.arraycopy(pkg.getSplitApkPaths(), 0, mSplitPaths, 1, pkg.getSplitApkPaths().length); mFlags = flags; mCachedSplitApks = new ApkAssets[mSplitPaths.length][]; @@ -66,7 +68,8 @@ public class SplitAssetDependencyLoader extends SplitDependencyLoader<PackagePar private static ApkAssets loadApkAssets(String path, @ParseFlags int flags) throws PackageParserException { - if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(path)) { + if ((flags & ParsingPackageUtils.PARSE_MUST_BE_APK) != 0 + && !ApkLiteParseUtils.isApkPath(path)) { throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, "Invalid package file: " + path); } diff --git a/core/java/android/content/pm/split/SplitDependencyLoader.java b/core/java/android/content/pm/split/SplitDependencyLoader.java index 358654692c9e..3e681327e0c0 100644 --- a/core/java/android/content/pm/split/SplitDependencyLoader.java +++ b/core/java/android/content/pm/split/SplitDependencyLoader.java @@ -17,7 +17,7 @@ package android.content.pm.split; import android.annotation.IntRange; import android.annotation.NonNull; -import android.content.pm.PackageParser; +import android.content.pm.parsing.PackageLite; import android.util.IntArray; import android.util.SparseArray; @@ -149,10 +149,19 @@ public abstract class SplitDependencyLoader<E extends Exception> { return dst; } - public static @NonNull SparseArray<int[]> createDependenciesFromPackage( - PackageParser.PackageLite pkg) throws IllegalDependencyException { - // The data structure that holds the dependencies. In PackageParser, splits are stored - // in their own array, separate from the base. We treat all paths as equals, so + /** + * Build the split dependency tree by the given package + * + * @param pkg The package to retrieve the dependency tree + * @return The dependency tree of splits + * @throws IllegalDependencyException if the requires split is missing, targets split is + * missing, it declares itself as configuration split for a non-feature split, or + * cycle detected in split dependencies. + */ + public static @NonNull SparseArray<int[]> createDependenciesFromPackage(PackageLite pkg) + throws IllegalDependencyException { + // The data structure that holds the dependencies. In ParsingPackageUtils, splits are + // stored in their own array, separate from the base. We treat all paths as equals, so // we need to insert the base as index 0, and shift all other splits. final SparseArray<int[]> splitDependencies = new SparseArray<>(); @@ -161,19 +170,19 @@ public abstract class SplitDependencyLoader<E extends Exception> { // First write out the <uses-split> dependencies. These must appear first in the // array of ints, as is convention in this class. - for (int splitIdx = 0; splitIdx < pkg.splitNames.length; splitIdx++) { - if (!pkg.isFeatureSplits[splitIdx]) { + for (int splitIdx = 0; splitIdx < pkg.getSplitNames().length; splitIdx++) { + if (!pkg.getIsFeatureSplits()[splitIdx]) { // Non-feature splits don't have dependencies. continue; } // Implicit dependency on the base. final int targetIdx; - final String splitDependency = pkg.usesSplitNames[splitIdx]; + final String splitDependency = pkg.getUsesSplitNames()[splitIdx]; if (splitDependency != null) { - final int depIdx = Arrays.binarySearch(pkg.splitNames, splitDependency); + final int depIdx = Arrays.binarySearch(pkg.getSplitNames(), splitDependency); if (depIdx < 0) { - throw new IllegalDependencyException("Split '" + pkg.splitNames[splitIdx] + throw new IllegalDependencyException("Split '" + pkg.getSplitNames()[splitIdx] + "' requires split '" + splitDependency + "', which is missing."); } targetIdx = depIdx + 1; @@ -188,26 +197,26 @@ public abstract class SplitDependencyLoader<E extends Exception> { // dependencies and are considered leaves. // // At this point, all splits in splitDependencies have the first element in their array set. - for (int splitIdx = 0; splitIdx < pkg.splitNames.length; splitIdx++) { - if (pkg.isFeatureSplits[splitIdx]) { + for (int splitIdx = 0, size = pkg.getSplitNames().length; splitIdx < size; splitIdx++) { + if (pkg.getIsFeatureSplits()[splitIdx]) { // Feature splits are not configForSplits. continue; } // Implicit feature for the base. final int targetSplitIdx; - final String configForSplit = pkg.configForSplit[splitIdx]; + final String configForSplit = pkg.getConfigForSplit()[splitIdx]; if (configForSplit != null) { - final int depIdx = Arrays.binarySearch(pkg.splitNames, configForSplit); + final int depIdx = Arrays.binarySearch(pkg.getSplitNames(), configForSplit); if (depIdx < 0) { - throw new IllegalDependencyException("Split '" + pkg.splitNames[splitIdx] + throw new IllegalDependencyException("Split '" + pkg.getSplitNames()[splitIdx] + "' targets split '" + configForSplit + "', which is missing."); } - if (!pkg.isFeatureSplits[depIdx]) { - throw new IllegalDependencyException("Split '" + pkg.splitNames[splitIdx] + if (!pkg.getIsFeatureSplits()[depIdx]) { + throw new IllegalDependencyException("Split '" + pkg.getSplitNames()[splitIdx] + "' declares itself as configuration split for a non-feature split '" - + pkg.splitNames[depIdx] + "'"); + + pkg.getSplitNames()[depIdx] + "'"); } targetSplitIdx = depIdx + 1; } else { diff --git a/core/java/android/graphics/fonts/FontManager.java b/core/java/android/graphics/fonts/FontManager.java index ea6cf2f44be9..eca56b375dd4 100644 --- a/core/java/android/graphics/fonts/FontManager.java +++ b/core/java/android/graphics/fonts/FontManager.java @@ -16,6 +16,7 @@ package android.graphics.fonts; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; @@ -28,6 +29,8 @@ import android.util.Log; import com.android.internal.graphics.fonts.IFontManager; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** @@ -41,6 +44,116 @@ public class FontManager { private static final String TAG = "FontManager"; private final @NonNull IFontManager mIFontManager; + /** @hide */ + @IntDef(prefix = "ERROR_CODE_", + value = { ERROR_CODE_OK, ERROR_CODE_FAILED_TO_WRITE_FONT_FILE, + ERROR_CODE_VERIFICATION_FAILURE, ERROR_CODE_FONT_NAME_MISMATCH, + ERROR_CODE_INVALID_FONT_FILE, ERROR_CODE_MISSING_POST_SCRIPT_NAME, + ERROR_CODE_DOWNGRADING, ERROR_CODE_FAILED_TO_CREATE_CONFIG_FILE, + ERROR_CODE_FONT_UPDATER_DISABLED }) + @Retention(RetentionPolicy.SOURCE) + public @interface ErrorCode {} + + /** + * Indicates an operation has processed successfully. + * @hide + */ + public static final int ERROR_CODE_OK = 0; + + /** + * Indicates a failure of writing font files. + * @hide + */ + public static final int ERROR_CODE_FAILED_TO_WRITE_FONT_FILE = -1; + + /** + * Indicates a failure of fs-verity setup. + * @hide + */ + public static final int ERROR_CODE_VERIFICATION_FAILURE = -2; + + /** + * Indicates a failure of verifying the font name with PostScript name. + * @hide + */ + public static final int ERROR_CODE_FONT_NAME_MISMATCH = -3; + + /** + * Indicates a failure of placing fonts due to unexpected font contents. + * @hide + */ + public static final int ERROR_CODE_INVALID_FONT_FILE = -4; + + /** + * Indicates a failure due to missing PostScript name in name table. + * @hide + */ + public static final int ERROR_CODE_MISSING_POST_SCRIPT_NAME = -5; + + /** + * Indicates a failure of placing fonts due to downgrading. + * @hide + */ + public static final int ERROR_CODE_DOWNGRADING = -6; + + /** + * Indicates a failure of writing system font configuration XML file. + * @hide + */ + public static final int ERROR_CODE_FAILED_TO_CREATE_CONFIG_FILE = -7; + + /** + * Indicates a failure due to disabled font updater. + * @hide + */ + public static final int ERROR_CODE_FONT_UPDATER_DISABLED = -8; + + /** + * Indicates a failure of opening font file. + * + * This error code is only used with the shell command interaction. + * + * @hide + */ + public static final int ERROR_CODE_FAILED_TO_OPEN_FONT_FILE = -10001; + + /** + * Indicates a failure of opening signature file. + * + * This error code is only used with the shell command interaction. + * + * @hide + */ + public static final int ERROR_CODE_FAILED_TO_OPEN_SIGNATURE_FILE = -10002; + + /** + * Indicates a failure of invalid shell command arguments. + * + * This error code is only used with the shell command interaction. + * + * @hide + */ + public static final int ERROR_CODE_INVALID_SHELL_ARGUMENT = -10003; + + /** + * Indicates a failure of reading signature file. + * + * This error code is only used with the shell command interaction. + * + * @hide + */ + public static final int ERROR_CODE_INVALID_SIGNATURE_FILE = -10004; + + /** + * Indicates a failure due to exceeding allowed signature file size (8kb). + * + * This error code is only used with the shell command interaction. + * + * @hide + */ + public static final int ERROR_CODE_SIGNATURE_TOO_LARGE = -10005; + + private FontManager(@NonNull IFontManager iFontManager) { mIFontManager = iFontManager; } diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index 5a03adee4eab..95f1d124b9a1 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -465,4 +465,46 @@ public abstract class DisplayManagerInternal { public interface DisplayTransactionListener { void onDisplayTransaction(Transaction t); } + + /** + * Called when there are changes to {@link com.android.server.display.DisplayGroup + * DisplayGroups}. + */ + public interface DisplayGroupListener { + /** + * A new display group with the provided {@code groupId} was added. + * + * <ol> + * <li>The {@code groupId} is applied to all appropriate {@link Display displays}. + * <li>This method is called. + * <li>{@link android.hardware.display.DisplayManager.DisplayListener DisplayListeners} + * are informed of any corresponding changes. + * </ol> + */ + void onDisplayGroupAdded(int groupId); + + /** + * The display group with the provided {@code groupId} was removed. + * + * <ol> + * <li>All affected {@link Display displays} have their group IDs updated appropriately. + * <li>{@link android.hardware.display.DisplayManager.DisplayListener DisplayListeners} + * are informed of any corresponding changes. + * <li>This method is called. + * </ol> + */ + void onDisplayGroupRemoved(int groupId); + + /** + * The display group with the provided {@code groupId} has changed. + * + * <ol> + * <li>All affected {@link Display displays} have their group IDs updated appropriately. + * <li>{@link android.hardware.display.DisplayManager.DisplayListener DisplayListeners} + * are informed of any corresponding changes. + * <li>This method is called. + * </ol> + */ + void onDisplayGroupChanged(int groupId); + } } diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java index e8298218e456..46141e0d0c1e 100644 --- a/core/java/android/net/Network.java +++ b/core/java/android/net/Network.java @@ -22,6 +22,7 @@ import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Parcel; +import android.os.ParcelFileDescriptor; import android.os.Parcelable; import android.system.ErrnoException; import android.system.Os; @@ -381,7 +382,13 @@ public class Network implements Parcelable { // Query a property of the underlying socket to ensure that the socket's file descriptor // exists, is available to bind to a network and is not closed. socket.getReuseAddress(); - bindSocket(socket.getFileDescriptor$()); + final ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(socket); + bindSocket(pfd.getFileDescriptor()); + // ParcelFileDescriptor.fromSocket() creates a dup of the original fd. The original and the + // dup share the underlying socket in the kernel. The socket is never truly closed until the + // last fd pointing to the socket being closed. So close the dup one after binding the + // socket to control the lifetime of the dup fd. + pfd.close(); } /** @@ -393,7 +400,13 @@ public class Network implements Parcelable { // Query a property of the underlying socket to ensure that the socket's file descriptor // exists, is available to bind to a network and is not closed. socket.getReuseAddress(); - bindSocket(socket.getFileDescriptor$()); + final ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket); + bindSocket(pfd.getFileDescriptor()); + // ParcelFileDescriptor.fromSocket() creates a dup of the original fd. The original and the + // dup share the underlying socket in the kernel. The socket is never truly closed until the + // last fd pointing to the socket being closed. So close the dup one after binding the + // socket to control the lifetime of the dup fd. + pfd.close(); } /** diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index 0a895b98f9fd..3843b9ab93c2 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -204,6 +204,7 @@ public final class NetworkCapabilities implements Parcelable { NET_CAPABILITY_TEMPORARILY_NOT_METERED, NET_CAPABILITY_OEM_PRIVATE, NET_CAPABILITY_VEHICLE_INTERNAL, + NET_CAPABILITY_NOT_VCN_MANAGED, }) public @interface NetCapability { } @@ -399,8 +400,16 @@ public final class NetworkCapabilities implements Parcelable { @SystemApi public static final int NET_CAPABILITY_VEHICLE_INTERNAL = 27; + /** + * Indicates that this network is not managed by a Virtual Carrier Network (VCN). + * + * TODO(b/177299683): Add additional clarifying javadoc. + * @hide + */ + public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28; + private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS; - private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_VEHICLE_INTERNAL; + private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_NOT_VCN_MANAGED; /** * Network capabilities that are expected to be mutable, i.e., can change while a particular @@ -417,7 +426,8 @@ public final class NetworkCapabilities implements Parcelable { | (1 << NET_CAPABILITY_NOT_CONGESTED) | (1 << NET_CAPABILITY_NOT_SUSPENDED) | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY) - | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED); + | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED) + | (1 << NET_CAPABILITY_NOT_VCN_MANAGED); /** * Network capabilities that are not allowed in NetworkRequests. This exists because the @@ -426,16 +436,21 @@ public final class NetworkCapabilities implements Parcelable { * can get into a cycle where the NetworkFactory endlessly churns out NetworkAgents that then * get immediately torn down because they do not have the requested capability. */ + // Note that as a historical exception, the TRUSTED and NOT_VCN_MANAGED capabilities + // are mutable but requestable. Factories are responsible for not getting + // in an infinite loop about these. private static final long NON_REQUESTABLE_CAPABILITIES = - MUTABLE_CAPABILITIES & ~(1 << NET_CAPABILITY_TRUSTED); + MUTABLE_CAPABILITIES + & ~(1 << NET_CAPABILITY_TRUSTED) + & ~(1 << NET_CAPABILITY_NOT_VCN_MANAGED); /** * Capabilities that are set by default when the object is constructed. */ private static final long DEFAULT_CAPABILITIES = - (1 << NET_CAPABILITY_NOT_RESTRICTED) | - (1 << NET_CAPABILITY_TRUSTED) | - (1 << NET_CAPABILITY_NOT_VPN); + (1 << NET_CAPABILITY_NOT_RESTRICTED) + | (1 << NET_CAPABILITY_TRUSTED) + | (1 << NET_CAPABILITY_NOT_VPN); /** * Capabilities that suggest that a network is restricted. @@ -495,7 +510,8 @@ public final class NetworkCapabilities implements Parcelable { | (1 << NET_CAPABILITY_NOT_VPN) | (1 << NET_CAPABILITY_NOT_ROAMING) | (1 << NET_CAPABILITY_NOT_CONGESTED) - | (1 << NET_CAPABILITY_NOT_SUSPENDED); + | (1 << NET_CAPABILITY_NOT_SUSPENDED) + | (1 << NET_CAPABILITY_NOT_VCN_MANAGED); /** * Adds the given capability to this {@code NetworkCapability} instance. @@ -1982,6 +1998,7 @@ public final class NetworkCapabilities implements Parcelable { case NET_CAPABILITY_TEMPORARILY_NOT_METERED: return "TEMPORARILY_NOT_METERED"; case NET_CAPABILITY_OEM_PRIVATE: return "OEM_PRIVATE"; case NET_CAPABILITY_VEHICLE_INTERNAL: return "NET_CAPABILITY_VEHICLE_INTERNAL"; + case NET_CAPABILITY_NOT_VCN_MANAGED: return "NOT_VCN_MANAGED"; default: return Integer.toString(capability); } } diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java index 66b99b9ba319..c4d1b09a5c20 100644 --- a/core/java/android/net/NetworkRequest.java +++ b/core/java/android/net/NetworkRequest.java @@ -353,7 +353,9 @@ public class NetworkRequest implements Parcelable { * NetworkSpecifier. */ public Builder setNetworkSpecifier(NetworkSpecifier networkSpecifier) { - MatchAllNetworkSpecifier.checkNotMatchAllNetworkSpecifier(networkSpecifier); + if (networkSpecifier instanceof MatchAllNetworkSpecifier) { + throw new IllegalArgumentException("A MatchAllNetworkSpecifier is not permitted"); + } mNetworkCapabilities.setNetworkSpecifier(networkSpecifier); return this; } diff --git a/core/java/android/net/ProxyInfo.java b/core/java/android/net/ProxyInfo.java index 950d39369425..9c9fed102828 100644 --- a/core/java/android/net/ProxyInfo.java +++ b/core/java/android/net/ProxyInfo.java @@ -355,7 +355,7 @@ public class ProxyInfo implements Parcelable { port = in.readInt(); } String exclList = in.readString(); - String[] parsedExclList = in.readStringArray(); + String[] parsedExclList = in.createStringArray(); ProxyInfo proxyProperties = new ProxyInfo(host, port, exclList, parsedExclList); return proxyProperties; } diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index e50494615f0c..74df1b2b9194 100755 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -27,6 +27,7 @@ import android.app.ActivityThread; import android.app.Application; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; +import android.sysprop.SocProperties; import android.sysprop.TelephonyProperties; import android.text.TextUtils; import android.util.Slog; @@ -88,6 +89,14 @@ public class Build { /** The end-user-visible name for the end product. */ public static final String MODEL = getString("ro.product.model"); + /** The manufacturer of the device's primary system-on-chip. */ + @NonNull + public static final String SOC_MANUFACTURER = SocProperties.soc_manufacturer().orElse(UNKNOWN); + + /** The model name of the device's primary system-on-chip. */ + @NonNull + public static final String SOC_MODEL = SocProperties.soc_model().orElse(UNKNOWN); + /** The system bootloader version number. */ public static final String BOOTLOADER = getString("ro.bootloader"); diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java index 1c1f5c034cd9..102f52566590 100644 --- a/core/java/android/os/Bundle.java +++ b/core/java/android/os/Bundle.java @@ -45,6 +45,7 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { @VisibleForTesting static final int FLAG_ALLOW_FDS = 1 << 10; + /** An unmodifiable {@code Bundle} that is always {@link #isEmpty() empty}. */ public static final Bundle EMPTY; /** diff --git a/core/java/android/os/PersistableBundle.java b/core/java/android/os/PersistableBundle.java index 5e3a34d8848a..e5e9b5f6f53c 100644 --- a/core/java/android/os/PersistableBundle.java +++ b/core/java/android/os/PersistableBundle.java @@ -47,6 +47,8 @@ import java.util.ArrayList; public final class PersistableBundle extends BaseBundle implements Cloneable, Parcelable, XmlUtils.WriteMapCallback { private static final String TAG_PERSISTABLEMAP = "pbundle_as_map"; + + /** An unmodifiable {@code PersistableBundle} that is always {@link #isEmpty() empty}. */ public static final PersistableBundle EMPTY; static { diff --git a/core/java/android/os/strictmode/UnsafeIntentLaunchViolation.java b/core/java/android/os/strictmode/UnsafeIntentLaunchViolation.java index 891fb59326df..f0f3cef656f0 100644 --- a/core/java/android/os/strictmode/UnsafeIntentLaunchViolation.java +++ b/core/java/android/os/strictmode/UnsafeIntentLaunchViolation.java @@ -17,10 +17,13 @@ package android.os.strictmode; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.PendingIntent; import android.content.Intent; import android.net.Uri; +import java.util.Objects; + /** * Violation raised when your app launches an {@link Intent} which originated * from outside your app. @@ -46,8 +49,20 @@ import android.net.Uri; * not protected, your app is likely vulnerable to malicious apps. */ public final class UnsafeIntentLaunchViolation extends Violation { - /** @hide */ + private transient Intent mIntent; + public UnsafeIntentLaunchViolation(@NonNull Intent intent) { super("Launch of unsafe intent: " + intent); + mIntent = Objects.requireNonNull(intent); + } + + /** + * Return the {@link Intent} which caused this violation to be raised. Note + * that this value is not available if this violation has been serialized + * since intents cannot be serialized. + */ + @SuppressWarnings("IntentBuilderName") + public @Nullable Intent getIntent() { + return mIntent; } } diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java index db55e1c7afda..b1b29254783c 100644 --- a/core/java/android/permission/PermissionUsageHelper.java +++ b/core/java/android/permission/PermissionUsageHelper.java @@ -88,10 +88,8 @@ public class PermissionUsageHelper { private static final long DEFAULT_RECENT_TIME_MS = 30000L; private static boolean shouldShowIndicators() { - return true; - // TODO ntmyren: remove true set when device config is configured correctly - //DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, - //PROPERTY_CAMERA_MIC_ICONS_ENABLED, true); + return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, + PROPERTY_CAMERA_MIC_ICONS_ENABLED, true); } private static boolean shouldShowLocationIndicator() { @@ -142,7 +140,7 @@ public class PermissionUsageHelper { } private Context mContext; - private Map<UserHandle, Context> mUserContexts; + private ArrayMap<UserHandle, Context> mUserContexts; private PackageManager mPkgManager; private AppOpsManager mAppOpsManager; @@ -154,7 +152,8 @@ public class PermissionUsageHelper { mContext = context; mPkgManager = context.getPackageManager(); mAppOpsManager = context.getSystemService(AppOpsManager.class); - mUserContexts = Map.of(Process.myUserHandle(), mContext); + mUserContexts = new ArrayMap<>(); + mUserContexts.put(Process.myUserHandle(), mContext); } private Context getUserContext(UserHandle user) { diff --git a/core/java/android/rotationresolver/OWNERS b/core/java/android/rotationresolver/OWNERS new file mode 100644 index 000000000000..81b6f05a1658 --- /dev/null +++ b/core/java/android/rotationresolver/OWNERS @@ -0,0 +1 @@ +include /core/java/android/rotationresolver/OWNERS diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java index 017f40521a81..f994d2930cd9 100644 --- a/core/java/android/security/keymaster/KeymasterDefs.java +++ b/core/java/android/security/keymaster/KeymasterDefs.java @@ -177,6 +177,7 @@ public final class KeymasterDefs { public static final int KM_PURPOSE_SIGN = KeyPurpose.SIGN; public static final int KM_PURPOSE_VERIFY = KeyPurpose.VERIFY; public static final int KM_PURPOSE_WRAP = KeyPurpose.WRAP_KEY; + public static final int KM_PURPOSE_AGREE_KEY = KeyPurpose.AGREE_KEY; // Key formats. public static final int KM_KEY_FORMAT_X509 = KeyFormat.X509; diff --git a/core/java/android/service/rotationresolver/OWNERS b/core/java/android/service/rotationresolver/OWNERS new file mode 100644 index 000000000000..e381d17b57eb --- /dev/null +++ b/core/java/android/service/rotationresolver/OWNERS @@ -0,0 +1,10 @@ +# Bug component: 814982 + +asalo@google.com +augale@google.com +bquezada@google.com +eejiang@google.com +payamp@google.com +siddikap@google.com +svetoslavganov@google.com +tgadh@google.com diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java index 82d7399c86e0..53fe1ba9c4b4 100644 --- a/core/java/android/text/FontConfig.java +++ b/core/java/android/text/FontConfig.java @@ -53,6 +53,8 @@ import java.util.List; public final class FontConfig implements Parcelable { private final @NonNull List<FontFamily> mFamilies; private final @NonNull List<Alias> mAliases; + private final long mLastModifiedDate; + private final int mConfigVersion; /** * Construct a FontConfig instance. @@ -62,9 +64,12 @@ public final class FontConfig implements Parcelable { * * @hide Only system server can create this instance and passed via IPC. */ - public FontConfig(@NonNull List<FontFamily> families, @NonNull List<Alias> aliases) { + public FontConfig(@NonNull List<FontFamily> families, @NonNull List<Alias> aliases, + long lastModifiedDate, @IntRange(from = 0) int configVersion) { mFamilies = families; mAliases = aliases; + mLastModifiedDate = lastModifiedDate; + mConfigVersion = configVersion; } /** @@ -88,6 +93,26 @@ public final class FontConfig implements Parcelable { } /** + * Returns the last modified date as Java epoch seconds. + * + * If there is no update, this return 0. + * @hide + */ + public long getLastModifiedDate() { + return mLastModifiedDate; + } + + /** + * Returns the monotonically increasing config version value. + * + * The config version is reset to 0 when the system is restarted. + * @hide + */ + public @IntRange(from = 0) int getConfigVersion() { + return mConfigVersion; + } + + /** * Returns the ordered list of families included in the system fonts. * @deprecated Use getFontFamilies instead. * @hide @@ -107,6 +132,8 @@ public final class FontConfig implements Parcelable { public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeParcelableList(mFamilies, flags); dest.writeParcelableList(mAliases, flags); + dest.writeLong(mLastModifiedDate); + dest.writeInt(mConfigVersion); } public static final @NonNull Creator<FontConfig> CREATOR = new Creator<FontConfig>() { @@ -116,7 +143,9 @@ public final class FontConfig implements Parcelable { FontFamily.class.getClassLoader()); List<Alias> aliases = source.readParcelableList(new ArrayList<>(), Alias.class.getClassLoader()); - return new FontConfig(families, aliases); + long lastModifiedDate = source.readLong(); + int configVersion = source.readInt(); + return new FontConfig(families, aliases, lastModifiedDate, configVersion); } @Override diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java index 02edb7ed50a5..696271c69717 100644 --- a/core/java/android/util/apk/ApkSignatureVerifier.java +++ b/core/java/android/util/apk/ApkSignatureVerifier.java @@ -27,6 +27,7 @@ import android.content.pm.PackageParser; import android.content.pm.PackageParser.PackageParserException; import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion; import android.content.pm.Signature; +import android.content.pm.parsing.ParsingPackageUtils; import android.os.Build; import android.os.Trace; import android.util.jar.StrictJarFile; @@ -361,7 +362,7 @@ public class ApkSignatureVerifier { // Gather certs from AndroidManifest.xml, which every APK must have, as an optimization // to not need to verify the whole APK when verifyFUll == false. final ZipEntry manifestEntry = jarFile.findEntry( - PackageParser.ANDROID_MANIFEST_FILENAME); + ParsingPackageUtils.ANDROID_MANIFEST_FILENAME); if (manifestEntry == null) { throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST, "Package " + apkPath + " has no manifest"); @@ -370,7 +371,7 @@ public class ApkSignatureVerifier { if (ArrayUtils.isEmpty(lastCerts)) { throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, "Package " + apkPath + " has no certificates at entry " - + PackageParser.ANDROID_MANIFEST_FILENAME); + + ParsingPackageUtils.ANDROID_MANIFEST_FILENAME); } lastSigs = convertToSignatures(lastCerts); @@ -383,7 +384,7 @@ public class ApkSignatureVerifier { final String entryName = entry.getName(); if (entryName.startsWith("META-INF/")) continue; - if (entryName.equals(PackageParser.ANDROID_MANIFEST_FILENAME)) continue; + if (entryName.equals(ParsingPackageUtils.ANDROID_MANIFEST_FILENAME)) continue; toVerify.add(entry); } diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 0e5fb2cad08f..c664ccba4ca7 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -121,6 +121,19 @@ public final class Display { public static final int INVALID_DISPLAY = -1; /** + * The default display group id, which is the display group id of the primary display assuming + * there is one. + * @hide + */ + public static final int DEFAULT_DISPLAY_GROUP = 0; + + /** + * Invalid display group id. + * @hide + */ + public static final int INVALID_DISPLAY_GROUP = -1; + + /** * Display flag: Indicates that the display supports compositing content * that is stored in protected graphics buffers. * <p> diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index fc42cd07950e..d200a328773b 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -66,6 +66,11 @@ public final class DisplayInfo implements Parcelable { public int displayId; /** + * Display Group identifier. + */ + public int displayGroupId; + + /** * Display address, or null if none. * Interpretation varies by display type. */ @@ -331,6 +336,7 @@ public final class DisplayInfo implements Parcelable { && flags == other.flags && type == other.type && displayId == other.displayId + && displayGroupId == other.displayGroupId && Objects.equals(address, other.address) && Objects.equals(deviceProductInfo, other.deviceProductInfo) && Objects.equals(uniqueId, other.uniqueId) @@ -376,6 +382,7 @@ public final class DisplayInfo implements Parcelable { flags = other.flags; type = other.type; displayId = other.displayId; + displayGroupId = other.displayGroupId; address = other.address; deviceProductInfo = other.deviceProductInfo; name = other.name; @@ -418,6 +425,7 @@ public final class DisplayInfo implements Parcelable { flags = source.readInt(); type = source.readInt(); displayId = source.readInt(); + displayGroupId = source.readInt(); address = source.readParcelable(null); deviceProductInfo = source.readParcelable(null); name = source.readString8(); @@ -468,6 +476,7 @@ public final class DisplayInfo implements Parcelable { dest.writeInt(this.flags); dest.writeInt(type); dest.writeInt(displayId); + dest.writeInt(displayGroupId); dest.writeParcelable(address, flags); dest.writeParcelable(deviceProductInfo, flags); dest.writeString8(name); @@ -547,16 +556,17 @@ public final class DisplayInfo implements Parcelable { * Returns the id of the "default" mode with the given refresh rate, or {@code 0} if no suitable * mode could be found. */ - public int findDefaultModeByRefreshRate(float refreshRate) { + @Nullable + public Display.Mode findDefaultModeByRefreshRate(float refreshRate) { Display.Mode[] modes = supportedModes; Display.Mode defaultMode = getDefaultMode(); for (int i = 0; i < modes.length; i++) { if (modes[i].matches( defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), refreshRate)) { - return modes[i].getModeId(); + return modes[i]; } } - return 0; + return null; } /** @@ -661,6 +671,8 @@ public final class DisplayInfo implements Parcelable { sb.append(name); sb.append("\", displayId "); sb.append(displayId); + sb.append("\", displayGroupId "); + sb.append(displayGroupId); sb.append(flagsToString(flags)); sb.append(", real "); sb.append(logicalWidth); diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index d68e9032c19d..bf377b0bcfd7 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -106,9 +106,7 @@ public class InsetsState implements Parcelable { public static final int ITYPE_NAVIGATION_BAR = 1; public static final int ITYPE_CAPTION_BAR = 2; - // The always visible types are visible to all windows regardless of the z-order. - public static final int FIRST_ALWAYS_VISIBLE_TYPE = 3; - public static final int ITYPE_TOP_GESTURES = FIRST_ALWAYS_VISIBLE_TYPE; + public static final int ITYPE_TOP_GESTURES = 3; public static final int ITYPE_BOTTOM_GESTURES = 4; public static final int ITYPE_LEFT_GESTURES = 5; public static final int ITYPE_RIGHT_GESTURES = 6; @@ -119,16 +117,15 @@ public class InsetsState implements Parcelable { public static final int ITYPE_LEFT_MANDATORY_GESTURES = 9; public static final int ITYPE_RIGHT_MANDATORY_GESTURES = 10; - public static final int ITYPE_LEFT_DISPLAY_CUTOUT = 11; - public static final int ITYPE_TOP_DISPLAY_CUTOUT = 12; - public static final int ITYPE_RIGHT_DISPLAY_CUTOUT = 13; - public static final int ITYPE_BOTTOM_DISPLAY_CUTOUT = 14; - public static final int LAST_ALWAYS_VISIBLE_TYPE = ITYPE_BOTTOM_DISPLAY_CUTOUT; + public static final int ITYPE_LEFT_TAPPABLE_ELEMENT = 11; + public static final int ITYPE_TOP_TAPPABLE_ELEMENT = 12; + public static final int ITYPE_RIGHT_TAPPABLE_ELEMENT = 13; + public static final int ITYPE_BOTTOM_TAPPABLE_ELEMENT = 14; - public static final int ITYPE_LEFT_TAPPABLE_ELEMENT = 15; - public static final int ITYPE_TOP_TAPPABLE_ELEMENT = 16; - public static final int ITYPE_RIGHT_TAPPABLE_ELEMENT = 17; - public static final int ITYPE_BOTTOM_TAPPABLE_ELEMENT = 18; + public static final int ITYPE_LEFT_DISPLAY_CUTOUT = 15; + public static final int ITYPE_TOP_DISPLAY_CUTOUT = 16; + public static final int ITYPE_RIGHT_DISPLAY_CUTOUT = 17; + public static final int ITYPE_BOTTOM_DISPLAY_CUTOUT = 18; /** Input method window. */ public static final int ITYPE_IME = 19; @@ -185,18 +182,6 @@ public class InsetsState implements Parcelable { } /** - * Mirror the always visible sources from the other state. They will share the same object for - * the always visible types. - * - * @param other the state to mirror the mirrored sources from. - */ - public void mirrorAlwaysVisibleInsetsSources(InsetsState other) { - for (int type = FIRST_ALWAYS_VISIBLE_TYPE; type <= LAST_ALWAYS_VISIBLE_TYPE; type++) { - mSources[type] = other.mSources[type]; - } - } - - /** * Calculates {@link WindowInsets} based on the current source configuration. * * @param frame The frame to calculate the insets relative to. diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index a2777fe985d5..24bc30874318 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -218,6 +218,15 @@ public class Surface implements Parcelable { public static final int FRAME_RATE_COMPATIBILITY_FIXED_SOURCE = 1; /** + * This surface belongs to an app on the High Refresh Rate Deny list, and needs the display + * to operate at the exact frame rate. + * + * This is used internally by the platform and should not be used by apps. + * @hide + */ + public static final int FRAME_RATE_COMPATIBILITY_EXACT = 100; + + /** * Create an empty surface, which will later be filled in by readFromParcel(). * @hide */ diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 5140c09dc323..90c8e17a6984 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -2307,7 +2307,9 @@ public final class InputMethodManager { public void removeImeSurface(IBinder windowToken) { synchronized (mH) { try { - mService.removeImeSurfaceFromWindow(windowToken); + final Completable.Void value = Completable.createVoid(); + mService.removeImeSurfaceFromWindow(windowToken, ResultCallbacks.of(value)); + Completable.getResult(value); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -3239,7 +3241,9 @@ public final class InputMethodManager { @Deprecated public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) { try { - mService.setAdditionalInputMethodSubtypes(imiId, subtypes); + final Completable.Void value = Completable.createVoid(); + mService.setAdditionalInputMethodSubtypes(imiId, subtypes, ResultCallbacks.of(value)); + Completable.getResult(value); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/view/translation/Translator.java b/core/java/android/view/translation/Translator.java index 675f32b19d17..22c3e57ecc95 100644 --- a/core/java/android/view/translation/Translator.java +++ b/core/java/android/view/translation/Translator.java @@ -28,6 +28,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; +import android.service.translation.ITranslationCallback; import android.util.Log; import com.android.internal.annotations.GuardedBy; @@ -36,9 +37,11 @@ import com.android.internal.util.SyncResultReceiver; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.List; import java.util.Objects; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; /** * The {@link Translator} for translation, defined by a source and a dest {@link TranslationSpec}. @@ -295,4 +298,49 @@ public class Translator { } // TODO: add methods for UI-toolkit case. + /** @hide */ + public void requestUiTranslate(@NonNull List<TranslationRequest> requests, + @NonNull Consumer<TranslationResponse> responseCallback) { + if (mDirectServiceBinder == null) { + Log.wtf(TAG, "Translator created without proper initialization."); + return; + } + final android.service.translation.TranslationRequest request = + new android.service.translation.TranslationRequest + .Builder(getNextRequestId(), mSourceSpec, mDestSpec, requests) + .build(); + final ITranslationCallback callback = + new TranslationResponseCallbackImpl(responseCallback); + try { + mDirectServiceBinder.onTranslationRequest(request, mId, callback, null); + } catch (RemoteException e) { + Log.w(TAG, "RemoteException calling flushRequest"); + } + } + + private static class TranslationResponseCallbackImpl extends ITranslationCallback.Stub { + + private final WeakReference<Consumer<TranslationResponse>> mResponseCallback; + + TranslationResponseCallbackImpl(Consumer<TranslationResponse> responseCallback) { + mResponseCallback = new WeakReference<>(responseCallback); + } + + @Override + public void onTranslationComplete(TranslationResponse response) throws RemoteException { + provideTranslationResponse(response); + } + + @Override + public void onError() throws RemoteException { + provideTranslationResponse(null); + } + + private void provideTranslationResponse(TranslationResponse response) { + final Consumer<TranslationResponse> responseCallback = mResponseCallback.get(); + if (responseCallback != null) { + responseCallback.accept(response); + } + } + } } diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java index a810c2e6fb41..fa4614628102 100644 --- a/core/java/android/view/translation/UiTranslationController.java +++ b/core/java/android/view/translation/UiTranslationController.java @@ -16,35 +16,274 @@ package android.view.translation; +import static android.view.translation.UiTranslationManager.STATE_UI_TRANSLATION_FINISHED; +import static android.view.translation.UiTranslationManager.STATE_UI_TRANSLATION_PAUSED; +import static android.view.translation.UiTranslationManager.STATE_UI_TRANSLATION_RESUMED; +import static android.view.translation.UiTranslationManager.STATE_UI_TRANSLATION_STARTED; + +import android.annotation.NonNull; +import android.annotation.WorkerThread; import android.app.Activity; import android.content.Context; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Process; +import android.util.ArrayMap; +import android.util.Log; +import android.util.Pair; +import android.view.View; import android.view.autofill.AutofillId; +import android.view.translation.UiTranslationManager.UiTranslationState; + +import com.android.internal.util.function.pooled.PooledLambda; +import java.lang.ref.WeakReference; +import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; /** - * A controller to manage the ui translation requests. + * A controller to manage the ui translation requests for the {@link Activity}. * * @hide */ public class UiTranslationController { private static final String TAG = "UiTranslationController"; - + @NonNull private final Activity mActivity; - + @NonNull private final Context mContext; + @NonNull + private final Object mLock = new Object(); + + // Each Translator is distinguished by sourceSpec and desSepc. + @NonNull + private final ArrayMap<Pair<TranslationSpec, TranslationSpec>, Translator> mTranslators; + @NonNull + private final ArrayMap<AutofillId, WeakReference<View>> mViews; + @NonNull + private final HandlerThread mWorkerThread; + @NonNull + private final Handler mWorkerHandler; public UiTranslationController(Activity activity, Context context) { mActivity = activity; mContext = context; + mViews = new ArrayMap<>(); + mTranslators = new ArrayMap<>(); + + mWorkerThread = + new HandlerThread("UiTranslationController_" + mActivity.getComponentName(), + Process.THREAD_PRIORITY_FOREGROUND); + mWorkerThread.start(); + mWorkerHandler = mWorkerThread.getThreadHandler(); } /** * Update the Ui translation state. */ - public void updateUiTranslationState(int state, TranslationSpec sourceSpec, + public void updateUiTranslationState(@UiTranslationState int state, TranslationSpec sourceSpec, TranslationSpec destSpec, List<AutofillId> views) { - // Implement it. Deal with the each states + if (!mActivity.isResumed()) { + return; + } + switch (state) { + case STATE_UI_TRANSLATION_STARTED: + final Pair<TranslationSpec, TranslationSpec> specs = + new Pair<>(sourceSpec, destSpec); + if (!mTranslators.containsKey(specs)) { + mWorkerHandler.sendMessage(PooledLambda.obtainMessage( + UiTranslationController::createTranslatorAndStart, + UiTranslationController.this, sourceSpec, destSpec, views)); + } else { + onUiTranslationStarted(mTranslators.get(specs), views); + } + break; + case STATE_UI_TRANSLATION_PAUSED: + runForEachView((view) -> view.onPauseUiTranslation(), STATE_UI_TRANSLATION_PAUSED); + break; + case STATE_UI_TRANSLATION_RESUMED: + runForEachView((view) -> view.onRestoreUiTranslation(), + STATE_UI_TRANSLATION_PAUSED); + break; + case STATE_UI_TRANSLATION_FINISHED: + destroyTranslators(); + runForEachView((view) -> view.onFinishUiTranslation(), STATE_UI_TRANSLATION_PAUSED); + break; + default: + Log.w(TAG, "onAutoTranslationStateChange(): unknown state: " + state); + } + } + + /** + * Called when the Activity is destroyed. + */ + public void onActivityDestroyed() { + synchronized (mLock) { + mViews.clear(); + destroyTranslators(); + mWorkerThread.quitSafely(); + } + } + + /** + * The method is used by {@link Translator}, it will be called when the translation is done. The + * translation result can be get from here. + */ + public void onTranslationCompleted(TranslationResponse response) { + if (response == null || response.getTranslationStatus() + != TranslationResponse.TRANSLATION_STATUS_SUCCESS) { + Log.w(TAG, "Fail result from TranslationService, response: " + response); + return; + } + final List<TranslationRequest> translatedResult = response.getTranslations(); + onTranslationCompleted(translatedResult); + } + + private void onTranslationCompleted(List<TranslationRequest> translatedResult) { + if (!mActivity.isResumed()) { + return; + } + final int resultCount = translatedResult.size(); + synchronized (mLock) { + for (int i = 0; i < resultCount; i++) { + final TranslationRequest request = translatedResult.get(i); + final AutofillId autofillId = request.getAutofillId(); + if (autofillId == null) { + continue; + } + final View view = mViews.get(autofillId).get(); + if (view == null) { + Log.w(TAG, "onTranslationCompleted: the Veiew for autofill id " + autofillId + + " may be gone."); + continue; + } + mActivity.runOnUiThread(() -> view.onTranslationComplete(request)); + } + } + } + + /** + * Called when there is an ui translation request comes to request view translation. + */ + @WorkerThread + private void createTranslatorAndStart(TranslationSpec sourceSpec, TranslationSpec destSpec, + List<AutofillId> views) { + // Create Translator + final Translator translator = createTranslatorIfNeeded(sourceSpec, destSpec); + if (translator == null) { + Log.w(TAG, "Can not create Translator for sourceSpec:" + sourceSpec + " destSpec:" + + destSpec); + return; + } + onUiTranslationStarted(translator, views); + } + + @WorkerThread + private void sendTranslationRequest(Translator translator, + ArrayList<TranslationRequest> requests) { + translator.requestUiTranslate(requests, this::onTranslationCompleted); + } + + /** + * Called when there is an ui translation request comes to request view translation. + */ + private void onUiTranslationStarted(Translator translator, List<AutofillId> views) { + synchronized (mLock) { + if (views == null || views.size() == 0) { + throw new IllegalArgumentException("Invalid empty views: " + views); + } + // Find Views collect the translation data + // TODO(b/178084101): try to optimize, e.g. to this in a single traversal + final int viewCounts = views.size(); + final ArrayList<TranslationRequest> requests = new ArrayList<>(); + for (int i = 0; i < viewCounts; i++) { + final AutofillId viewAutofillId = views.get(i); + final View view = mActivity.findViewByAutofillIdTraversal(viewAutofillId); + if (view == null) { + Log.w(TAG, "Can not find the View for autofill id= " + viewAutofillId); + continue; + } + mViews.put(viewAutofillId, new WeakReference<>(view)); + mActivity.runOnUiThread(() -> { + final TranslationRequest translationRequest = view.onCreateTranslationRequest(); + if (translationRequest != null + && translationRequest.getTranslationText().length() > 0) { + requests.add(translationRequest); + } + if (requests.size() == viewCounts) { + Log.v(TAG, "onUiTranslationStarted: send " + requests.size() + " request."); + mWorkerHandler.sendMessage(PooledLambda.obtainMessage( + UiTranslationController::sendTranslationRequest, + UiTranslationController.this, translator, requests)); + } + }); + } + } + } + + private void runForEachView(Consumer<View> action, @UiTranslationState int state) { + synchronized (mLock) { + mActivity.runOnUiThread(() -> { + final int viewCounts = mViews.size(); + for (int i = 0; i < viewCounts; i++) { + final View view = mViews.valueAt(i).get(); + if (view == null) { + Log.w(TAG, "The View for autofill id " + mViews.keyAt(i) + + " may be gone for state " + stateToString(state)); + continue; + } + action.accept(view); + } + if (state == STATE_UI_TRANSLATION_FINISHED) { + mViews.clear(); + } + }); + } + } + + private Translator createTranslatorIfNeeded( + TranslationSpec sourceSpec, TranslationSpec destSpec) { + final TranslationManager tm = mContext.getSystemService(TranslationManager.class); + if (tm == null) { + Log.e(TAG, "Can not find TranslationManager when trying to create translator."); + return null; + } + final Translator translator = tm.createTranslator(sourceSpec, destSpec); + if (translator != null) { + final Pair<TranslationSpec, TranslationSpec> specs = new Pair<>(sourceSpec, destSpec); + mTranslators.put(specs, translator); + } + return translator; + } + + private void destroyTranslators() { + synchronized (mLock) { + final int count = mTranslators.size(); + for (int i = 0; i < count; i++) { + Translator translator = mTranslators.valueAt(i); + translator.destroy(); + } + mTranslators.clear(); + } + } + + /** + * Returns a string representation of the state. + */ + public static String stateToString(@UiTranslationState int state) { + switch (state) { + case STATE_UI_TRANSLATION_STARTED: + return "UI_TRANSLATION_STARTED"; + case STATE_UI_TRANSLATION_PAUSED: + return "UI_TRANSLATION_PAUSED"; + case STATE_UI_TRANSLATION_RESUMED: + return "UI_TRANSLATION_RESUMED"; + case STATE_UI_TRANSLATION_FINISHED: + return "UI_TRANSLATION_FINISHED"; + default: + return "Unknown state (" + state + ")"; + } } } diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index 7a30ee2376ac..55f8c400022f 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -134,7 +134,7 @@ interface IBatteryStats { void noteWifiBatchedScanStartedFromSource(in WorkSource ws, int csph); void noteWifiBatchedScanStoppedFromSource(in WorkSource ws); void noteWifiRadioPowerState(int powerState, long timestampNs, int uid); - void noteNetworkInterfaceType(String iface, int type); + void noteNetworkInterfaceForTransports(String iface, in int[] transportTypes); void noteNetworkStatsEnabled(); void noteDeviceIdleMode(int mode, String activeReason, int activeUid); void setBatteryState(int status, int health, int plugType, int level, int temp, int volt, diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java index bbfd07b64ccf..c74c39a434fa 100644 --- a/core/java/com/android/internal/content/NativeLibraryHelper.java +++ b/core/java/com/android/internal/content/NativeLibraryHelper.java @@ -27,9 +27,10 @@ import static android.system.OsConstants.S_IXOTH; import android.content.Context; import android.content.pm.PackageManager; -import android.content.pm.PackageParser; -import android.content.pm.PackageParser.PackageLite; -import android.content.pm.PackageParser.PackageParserException; +import android.content.pm.parsing.ApkLiteParseUtils; +import android.content.pm.parsing.PackageLite; +import android.content.pm.parsing.result.ParseResult; +import android.content.pm.parsing.result.ParseTypeImpl; import android.os.Build; import android.os.IBinder; import android.os.SELinux; @@ -86,17 +87,19 @@ public class NativeLibraryHelper { final boolean debuggable; public static Handle create(File packageFile) throws IOException { - try { - final PackageLite lite = PackageParser.parsePackageLite(packageFile, 0); - return create(lite); - } catch (PackageParserException e) { - throw new IOException("Failed to parse package: " + packageFile, e); + final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing(); + final ParseResult<PackageLite> ret = ApkLiteParseUtils.parsePackageLite(input.reset(), + packageFile, /* flags */ 0); + if (ret.isError()) { + throw new IOException("Failed to parse package: " + packageFile, + ret.getException()); } + return create(ret.getResult()); } public static Handle create(PackageLite lite) throws IOException { - return create(lite.getAllCodePaths(), lite.multiArch, lite.extractNativeLibs, - lite.debuggable); + return create(lite.getAllApkPaths(), lite.isMultiArch(), lite.isExtractNativeLibs(), + lite.isDebuggable()); } public static Handle create(List<String> codePaths, boolean multiArch, @@ -122,14 +125,14 @@ public class NativeLibraryHelper { public static Handle createFd(PackageLite lite, FileDescriptor fd) throws IOException { final long[] apkHandles = new long[1]; - final String path = lite.baseCodePath; + final String path = lite.getBaseApkPath(); apkHandles[0] = nativeOpenApkFd(fd, path); if (apkHandles[0] == 0) { throw new IOException("Unable to open APK " + path + " from fd " + fd); } - return new Handle(new String[]{path}, apkHandles, lite.multiArch, - lite.extractNativeLibs, lite.debuggable); + return new Handle(new String[]{path}, apkHandles, lite.isMultiArch(), + lite.isExtractNativeLibs(), lite.isDebuggable()); } Handle(String[] apkPaths, long[] apkHandles, boolean multiArch, diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java index 2b78be383d43..c2f20526f8fd 100644 --- a/core/java/com/android/internal/content/PackageHelper.java +++ b/core/java/com/android/internal/content/PackageHelper.java @@ -24,8 +24,8 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller.SessionParams; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.PackageParser.PackageLite; import android.content.pm.dex.DexMetadataHelper; +import android.content.pm.parsing.PackageLite; import android.os.Environment; import android.os.IBinder; import android.os.RemoteException; @@ -84,8 +84,8 @@ public class PackageHelper { /** * A group of external dependencies used in - * {@link #resolveInstallVolume(Context, String, int, long)}. It can be backed by real values - * from the system or mocked ones for testing purposes. + * {@link #resolveInstallVolume(Context, String, int, long, TestableInterface)}. + * It can be backed by real values from the system or mocked ones for testing purposes. */ public static abstract class TestableInterface { abstract public StorageManager getStorageManager(Context context); @@ -447,7 +447,7 @@ public class PackageHelper { long sizeBytes = 0; // Include raw APKs, and possibly unpacked resources - for (String codePath : pkg.getAllCodePaths()) { + for (String codePath : pkg.getAllApkPaths()) { final File codeFile = new File(codePath); sizeBytes += codeFile.length(); } diff --git a/core/java/com/android/internal/content/om/OverlayScanner.java b/core/java/com/android/internal/content/om/OverlayScanner.java index a85cf56068cd..6b5cb8d9b850 100644 --- a/core/java/com/android/internal/content/om/OverlayScanner.java +++ b/core/java/com/android/internal/content/om/OverlayScanner.java @@ -20,7 +20,10 @@ import static com.android.internal.content.om.OverlayConfig.TAG; import android.annotation.NonNull; import android.annotation.Nullable; -import android.content.pm.PackageParser; +import android.content.pm.parsing.ApkLite; +import android.content.pm.parsing.ApkLiteParseUtils; +import android.content.pm.parsing.result.ParseResult; +import android.content.pm.parsing.result.ParseTypeImpl; import android.util.ArrayMap; import android.util.Log; @@ -124,15 +127,17 @@ public class OverlayScanner { /** Extracts information about the overlay from its manifest. */ @VisibleForTesting public ParsedOverlayInfo parseOverlayManifest(File overlayApk) { - try { - final PackageParser.ApkLite apkLite = PackageParser.parseApkLite(overlayApk, 0); - return apkLite.targetPackageName == null ? null : - new ParsedOverlayInfo(apkLite.packageName, apkLite.targetPackageName, - apkLite.targetSdkVersion, apkLite.overlayIsStatic, - apkLite.overlayPriority, new File(apkLite.codePath)); - } catch (PackageParser.PackageParserException e) { - Log.w(TAG, "Got exception loading overlay.", e); + final ParseTypeImpl input = ParseTypeImpl.forParsingWithoutPlatformCompat(); + final ParseResult<ApkLite> ret = ApkLiteParseUtils.parseApkLite(input.reset(), + overlayApk, /* flags */ 0); + if (ret.isError()) { + Log.w(TAG, "Got exception loading overlay.", ret.getException()); return null; } + final ApkLite apkLite = ret.getResult(); + return apkLite.getTargetPackageName() == null ? null : + new ParsedOverlayInfo(apkLite.getPackageName(), apkLite.getTargetPackageName(), + apkLite.getTargetSdkVersion(), apkLite.isOverlayIsStatic(), + apkLite.getOverlayPriority(), new File(apkLite.getPath())); } } diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java index 4a24358ad5d5..a21c68b4f01a 100644 --- a/core/java/com/android/internal/os/BatteryStatsHelper.java +++ b/core/java/com/android/internal/os/BatteryStatsHelper.java @@ -23,7 +23,6 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.Resources; import android.hardware.SensorManager; -import android.net.ConnectivityManager; import android.os.BatteryStats; import android.os.BatteryStats.Uid; import android.os.Build; @@ -37,6 +36,7 @@ import android.os.SELinux; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; +import android.telephony.TelephonyManager; import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.Log; @@ -120,12 +120,11 @@ public class BatteryStatsHelper { private double mMaxDrainedPower; public static boolean checkWifiOnly(Context context) { - ConnectivityManager cm = (ConnectivityManager) context.getSystemService( - Context.CONNECTIVITY_SERVICE); - if (cm == null) { + final TelephonyManager tm = context.getSystemService(TelephonyManager.class); + if (tm == null) { return false; } - return !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE); + return !tm.isDataCapable(); } @UnsupportedAppUsage diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 37b621d781d1..d475c658a3a0 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -16,6 +16,8 @@ package com.android.internal.os; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.os.BatteryStatsManager.NUM_WIFI_STATES; import static android.os.BatteryStatsManager.NUM_WIFI_SUPPL_STATES; @@ -36,7 +38,6 @@ import android.content.IntentFilter; import android.database.ContentObserver; import android.hardware.usb.UsbManager; import android.location.GnssSignalQuality; -import android.net.ConnectivityManager; import android.net.INetworkStatsService; import android.net.NetworkStats; import android.net.Uri; @@ -111,6 +112,7 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.XmlUtils; +import com.android.net.module.util.NetworkCapabilitiesUtils; import libcore.util.EmptyArray; @@ -6711,11 +6713,12 @@ public class BatteryStatsImpl extends BatteryStats { } /** @hide */ - public void noteNetworkInterfaceType(String iface, int networkType) { + public void noteNetworkInterfaceForTransports(String iface, int[] transportTypes) { if (TextUtils.isEmpty(iface)) return; + final int displayTransport = NetworkCapabilitiesUtils.getDisplayTransport(transportTypes); synchronized (mModemNetworkLock) { - if (ConnectivityManager.isNetworkTypeMobile(networkType)) { + if (displayTransport == TRANSPORT_CELLULAR) { mModemIfaces = includeInStringArray(mModemIfaces, iface); if (DEBUG) Slog.d(TAG, "Note mobile iface " + iface + ": " + mModemIfaces); } else { @@ -6725,7 +6728,7 @@ public class BatteryStatsImpl extends BatteryStats { } synchronized (mWifiNetworkLock) { - if (ConnectivityManager.isNetworkTypeWifi(networkType)) { + if (displayTransport == TRANSPORT_WIFI) { mWifiIfaces = includeInStringArray(mWifiIfaces, iface); if (DEBUG) Slog.d(TAG, "Note wifi iface " + iface + ": " + mWifiIfaces); } else { diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java index 1e9801f5ef30..30cd94c21169 100644 --- a/core/java/com/android/internal/util/LatencyTracker.java +++ b/core/java/com/android/internal/util/LatencyTracker.java @@ -106,7 +106,6 @@ public class LatencyTracker { private static LatencyTracker sLatencyTracker; private final SparseLongArray mStartRtc = new SparseLongArray(); - private final Context mContext; private volatile int mSamplingInterval; private volatile boolean mEnabled; @@ -114,15 +113,14 @@ public class LatencyTracker { if (sLatencyTracker == null) { synchronized (LatencyTracker.class) { if (sLatencyTracker == null) { - sLatencyTracker = new LatencyTracker(context); + sLatencyTracker = new LatencyTracker(); } } } return sLatencyTracker; } - public LatencyTracker(Context context) { - mContext = context; + private LatencyTracker() { mEnabled = DEFAULT_ENABLED; mSamplingInterval = DEFAULT_SAMPLING_INTERVAL; @@ -173,8 +171,8 @@ public class LatencyTracker { } } - private String getTraceNameOfAcion(int action) { - return "L<" + getNameOfAction(action) + ">"; + private static String getTraceNameOfAction(int action) { + return "L<" + getNameOfAction(STATSD_ACTION[action]) + ">"; } public static boolean isEnabled(Context ctx) { @@ -194,7 +192,7 @@ public class LatencyTracker { if (!isEnabled()) { return; } - Trace.asyncTraceBegin(Trace.TRACE_TAG_APP, getTraceNameOfAcion(action), 0); + Trace.asyncTraceBegin(Trace.TRACE_TAG_APP, getTraceNameOfAction(action), 0); mStartRtc.put(action, SystemClock.elapsedRealtime()); } @@ -213,7 +211,7 @@ public class LatencyTracker { return; } mStartRtc.delete(action); - Trace.asyncTraceEnd(Trace.TRACE_TAG_APP, getTraceNameOfAcion(action), 0); + Trace.asyncTraceEnd(Trace.TRACE_TAG_APP, getTraceNameOfAction(action), 0); logAction(action, (int) (endRtc - startRtc)); } @@ -236,7 +234,7 @@ public class LatencyTracker { * @param writeToStatsLog Whether to write the measured latency to FrameworkStatsLog. */ public static void logActionDeprecated(int action, int duration, boolean writeToStatsLog) { - Log.i(TAG, "action=" + action + " latency=" + duration); + Log.i(TAG, getNameOfAction(STATSD_ACTION[action]) + " latency=" + duration); EventLog.writeEvent(EventLogTags.SYSUI_LATENCY, action, duration); if (writeToStatsLog) { diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index b42404fb6d56..892c5a53527b 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -41,22 +41,24 @@ interface IInputMethodManager { int untrustedDisplayId); // TODO: Use ParceledListSlice instead - void getInputMethodList(int userId, in IInputMethodInfoListResultCallback resultCallback); + oneway void getInputMethodList(int userId, + in IInputMethodInfoListResultCallback resultCallback); // TODO: Use ParceledListSlice instead - void getEnabledInputMethodList(int userId, + oneway void getEnabledInputMethodList(int userId, in IInputMethodInfoListResultCallback resultCallback); - void getEnabledInputMethodSubtypeList(in String imiId, boolean allowsImplicitlySelectedSubtypes, + oneway void getEnabledInputMethodSubtypeList(in String imiId, + boolean allowsImplicitlySelectedSubtypes, in IInputMethodSubtypeListResultCallback resultCallback); - void getLastInputMethodSubtype(in IInputMethodSubtypeResultCallback resultCallback); + oneway void getLastInputMethodSubtype(in IInputMethodSubtypeResultCallback resultCallback); - void showSoftInput(in IInputMethodClient client, IBinder windowToken, int flags, + oneway void showSoftInput(in IInputMethodClient client, IBinder windowToken, int flags, in ResultReceiver resultReceiver, in IBooleanResultCallback resultCallback); - void hideSoftInput(in IInputMethodClient client, IBinder windowToken, int flags, + oneway void hideSoftInput(in IInputMethodClient client, IBinder windowToken, int flags, in ResultReceiver resultReceiver, in IBooleanResultCallback resultCallback); // If windowToken is null, this just does startInput(). Otherwise this reports that a window // has gained focus, and if 'attribute' is non-null then also does startInput. // @NonNull - void startInputOrWindowGainedFocus( + oneway void startInputOrWindowGainedFocus( /* @StartInputReason */ int startInputReason, in IInputMethodClient client, in IBinder windowToken, /* @StartInputFlags */ int startInputFlags, @@ -66,29 +68,31 @@ interface IInputMethodManager { int unverifiedTargetSdkVersion, in IInputBindResultResultCallback inputBindResult); - void showInputMethodPickerFromClient(in IInputMethodClient client, + oneway void showInputMethodPickerFromClient(in IInputMethodClient client, int auxiliarySubtypeMode, in IVoidResultCallback resultCallback); - void showInputMethodPickerFromSystem(in IInputMethodClient client, int auxiliarySubtypeMode, - int displayId, in IVoidResultCallback resultCallback); - void showInputMethodAndSubtypeEnablerFromClient(in IInputMethodClient client, String topId, + oneway void showInputMethodPickerFromSystem(in IInputMethodClient client, + int auxiliarySubtypeMode, int displayId, in IVoidResultCallback resultCallback); + oneway void showInputMethodAndSubtypeEnablerFromClient(in IInputMethodClient client, + String topId, in IVoidResultCallback resultCallback); + oneway void isInputMethodPickerShownForTest(in IBooleanResultCallback resultCallback); + oneway void getCurrentInputMethodSubtype(in IInputMethodSubtypeResultCallback resultCallback); + oneway void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes, in IVoidResultCallback resultCallback); - void isInputMethodPickerShownForTest(in IBooleanResultCallback resultCallback); - void getCurrentInputMethodSubtype(in IInputMethodSubtypeResultCallback resultCallback); - void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes); // This is kept due to @UnsupportedAppUsage. // TODO(Bug 113914148): Consider removing this. - void getInputMethodWindowVisibleHeight(IIntResultCallback resultCallback); + oneway void getInputMethodWindowVisibleHeight(IIntResultCallback resultCallback); - void reportActivityView(in IInputMethodClient parentClient, int childDisplayId, + oneway void reportActivityView(in IInputMethodClient parentClient, int childDisplayId, in float[] matrixValues, in IVoidResultCallback resultCallback); oneway void reportPerceptible(in IBinder windowToken, boolean perceptible); /** Remove the IME surface. Requires INTERNAL_SYSTEM_WINDOW permission. */ - void removeImeSurface(); + oneway void removeImeSurface(in IVoidResultCallback resultCallback); /** Remove the IME surface. Requires passing the currently focused window. */ - void removeImeSurfaceFromWindow(in IBinder windowToken); + oneway void removeImeSurfaceFromWindow(in IBinder windowToken, + in IVoidResultCallback resultCallback); void startProtoDump(in byte[] protoDump, int source, String where); - void isImeTraceEnabled(in IBooleanResultCallback resultCallback); + oneway void isImeTraceEnabled(in IBooleanResultCallback resultCallback); // Starts an ime trace. void startImeTrace(); diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto index c359a7da8137..c882431c9366 100644 --- a/core/proto/android/server/windowmanagerservice.proto +++ b/core/proto/android/server/windowmanagerservice.proto @@ -307,6 +307,7 @@ message TaskProto { optional bool animating_bounds = 26 [deprecated = true]; optional float minimize_amount = 27; optional bool created_by_organizer = 28; + optional string affinity = 29; } /* represents ActivityRecordProto */ diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 397552969c31..396f95446bf6 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1744,6 +1744,12 @@ <permission android:name="android.permission.REQUEST_NETWORK_SCORES" android:protectionLevel="signature|setup" /> + <!-- Allows applications to restart the Wi-Fi subsystem. + @SystemApi + <p>Not for use by third-party applications. @hide --> + <permission android:name="android.permission.RESTART_WIFI_SUBSYSTEM" + android:protectionLevel="signature|setup" /> + <!-- @SystemApi @hide Allows applications to toggle airplane mode. <p>Not for use by third-party or privileged applications. --> @@ -5906,7 +5912,7 @@ <!-- AOSP configures a default secondary LocationTimeZoneProvider that uses an on-device data set from the com.android.geotz APEX. --> - <service android:name="com.android.timezone.geotz.provider.OfflineLocationTimeZoneProviderService" + <service android:name="com.android.timezone.location.provider.OfflineLocationTimeZoneProviderService" android:enabled="@bool/config_enableSecondaryLocationTimeZoneProvider" android:permission="android.permission.BIND_TIME_ZONE_PROVIDER_SERVICE" android:exported="false"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 996fbb366506..98b36c5c9cbf 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -298,10 +298,10 @@ <item>@string/crossSimFormat_spn_cross_sim_calling</item> </string-array> - <!-- Spn during Cross-SIM Calling: "<operator> " [CHAR LIMIT=NONE] --> + <!-- Spn during Backup Calling: "<operator> " [CHAR LIMIT=NONE] --> <string name="crossSimFormat_spn"><xliff:g id="spn" example="Operator">%s</xliff:g></string> - <!-- Spn during Cross SIM Calling: "<operator> Cross-SIM Calling" [CHAR LIMIT=NONE] --> - <string name="crossSimFormat_spn_cross_sim_calling"><xliff:g id="spn" example="Operator">%s</xliff:g> Cross-SIM Calling</string> + <!-- Spn during Backup Calling: "<operator> Backup Calling" [CHAR LIMIT=NONE] --> + <string name="crossSimFormat_spn_cross_sim_calling"><xliff:g id="spn" example="Operator">%s</xliff:g> Backup Calling</string> <!-- {0} is one of "bearerServiceCode*" diff --git a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java index 65ea2a8373aa..3df2e90241ff 100644 --- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java +++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java @@ -171,7 +171,8 @@ public class TypefaceSystemFallbackTest { FontConfig fontConfig; try { fontConfig = FontListParser.parse( - TEST_FONTS_XML, TEST_FONT_DIR, oemXmlPath, TEST_OEM_DIR, updatableFontMap); + TEST_FONTS_XML, TEST_FONT_DIR, oemXmlPath, TEST_OEM_DIR, updatableFontMap, 0, + 0); } catch (IOException | XmlPullParserException e) { throw new RuntimeException(e); } @@ -199,7 +200,7 @@ public class TypefaceSystemFallbackTest { FontConfig fontConfig; try { fontConfig = FontListParser.parse( - SYSTEM_FONTS_XML, SYSTEM_FONT_DIR, null, TEST_OEM_DIR, null); + SYSTEM_FONTS_XML, SYSTEM_FONT_DIR, null, TEST_OEM_DIR, null, 0, 0); } catch (IOException | XmlPullParserException e) { throw new RuntimeException(e); } diff --git a/core/tests/coretests/src/android/text/FontFallbackSetup.java b/core/tests/coretests/src/android/text/FontFallbackSetup.java index e301037d01f1..90a6ca3cd1d1 100644 --- a/core/tests/coretests/src/android/text/FontFallbackSetup.java +++ b/core/tests/coretests/src/android/text/FontFallbackSetup.java @@ -79,7 +79,7 @@ public class FontFallbackSetup implements AutoCloseable { FontConfig fontConfig; try { - fontConfig = FontListParser.parse(testFontsXml, mTestFontsDir, null, null, null); + fontConfig = FontListParser.parse(testFontsXml, mTestFontsDir, null, null, null, 0, 0); } catch (IOException | XmlPullParserException e) { throw new RuntimeException(e); } diff --git a/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java index 4230066f32a1..66a8379177c2 100644 --- a/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java @@ -21,7 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import android.net.ConnectivityManager; +import android.net.NetworkCapabilities; import android.net.NetworkStats; import android.os.BatteryConsumer; import android.os.Process; @@ -78,7 +78,8 @@ public class MobileRadioPowerCalculatorTest { 8_000_000_000L, APP_UID, 8000, 8000); // Note established network - stats.noteNetworkInterfaceType("cellular", ConnectivityManager.TYPE_MOBILE); + stats.noteNetworkInterfaceForTransports("cellular", + new int[] { NetworkCapabilities.TRANSPORT_CELLULAR }); // Note application network activity NetworkStats networkStats = new NetworkStats(10000, 1) diff --git a/data/etc/com.android.emergency.xml b/data/etc/com.android.emergency.xml index 28f99dd1d018..734561ceeb01 100644 --- a/data/etc/com.android.emergency.xml +++ b/data/etc/com.android.emergency.xml @@ -19,5 +19,6 @@ <!-- Required to place emergency calls from emergency info screen. --> <permission name="android.permission.CALL_PRIVILEGED"/> <permission name="android.permission.MANAGE_USERS"/> + <permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/> </privapp-permissions> </permissions> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 03f89185b5f4..f84d947b51ed 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -354,6 +354,7 @@ applications that come with the platform <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> <permission name="android.permission.MOVE_PACKAGE"/> <!-- Needed for test only --> + <permission name="android.permission.RESTART_WIFI_SUBSYSTEM"/> <permission name="android.permission.NETWORK_AIRPLANE_MODE"/> <permission name="android.permission.OBSERVE_APP_USAGE"/> <permission name="android.permission.NETWORK_SCAN"/> diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java index c8ff95d15626..bb795cd99e6e 100644 --- a/graphics/java/android/graphics/FontListParser.java +++ b/graphics/java/android/graphics/FontListParser.java @@ -51,7 +51,8 @@ public class FontListParser { XmlPullParser parser = Xml.newPullParser(); parser.setInput(in, null); parser.nextTag(); - return readFamilies(parser, "/system/fonts/", new FontCustomizationParser.Result(), null); + return readFamilies(parser, "/system/fonts/", new FontCustomizationParser.Result(), null, + 0, 0); } /** @@ -71,7 +72,9 @@ public class FontListParser { @NonNull String systemFontDir, @Nullable String oemCustomizationXmlPath, @Nullable String productFontDir, - @Nullable Map<String, File> updatableFontMap + @Nullable Map<String, File> updatableFontMap, + long lastModifiedDate, + int configVersion ) throws IOException, XmlPullParserException { FontCustomizationParser.Result oemCustomization; if (oemCustomizationXmlPath != null) { @@ -90,7 +93,8 @@ public class FontListParser { XmlPullParser parser = Xml.newPullParser(); parser.setInput(is, null); parser.nextTag(); - return readFamilies(parser, systemFontDir, oemCustomization, updatableFontMap); + return readFamilies(parser, systemFontDir, oemCustomization, updatableFontMap, + lastModifiedDate, configVersion); } } @@ -98,7 +102,9 @@ public class FontListParser { @NonNull XmlPullParser parser, @NonNull String fontDir, @NonNull FontCustomizationParser.Result customization, - @Nullable Map<String, File> updatableFontMap) + @Nullable Map<String, File> updatableFontMap, + long lastModifiedDate, + int configVersion) throws XmlPullParserException, IOException { List<FontConfig.FontFamily> families = new ArrayList<>(); List<FontConfig.Alias> aliases = new ArrayList<>(customization.getAdditionalAliases()); @@ -126,7 +132,7 @@ public class FontListParser { } families.addAll(oemNamedFamilies.values()); - return new FontConfig(families, aliases); + return new FontConfig(families, aliases, lastModifiedDate, configVersion); } /** diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java index a41215f9cae1..c166e12fc6bf 100644 --- a/graphics/java/android/graphics/fonts/SystemFonts.java +++ b/graphics/java/android/graphics/fonts/SystemFonts.java @@ -240,10 +240,12 @@ public final class SystemFonts { * @hide */ public static @NonNull FontConfig getSystemFontConfig( - @Nullable Map<String, File> updatableFontMap + @Nullable Map<String, File> updatableFontMap, + long lastModifiedDate, + int configVersion ) { return getSystemFontConfigInternal(FONTS_XML, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR, - updatableFontMap); + updatableFontMap, lastModifiedDate, configVersion); } /** @@ -251,7 +253,8 @@ public final class SystemFonts { * @hide */ public static @NonNull FontConfig getSystemPreinstalledFontConfig() { - return getSystemFontConfigInternal(FONTS_XML, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR, null); + return getSystemFontConfigInternal(FONTS_XML, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR, null, + 0, 0); } /* package */ static @NonNull FontConfig getSystemFontConfigInternal( @@ -259,17 +262,19 @@ public final class SystemFonts { @NonNull String systemFontDir, @Nullable String oemXml, @Nullable String productFontDir, - @Nullable Map<String, File> updatableFontMap + @Nullable Map<String, File> updatableFontMap, + long lastModifiedDate, + int configVersion ) { try { return FontListParser.parse(fontsXml, systemFontDir, oemXml, productFontDir, - updatableFontMap); + updatableFontMap, lastModifiedDate, configVersion); } catch (IOException e) { Log.e(TAG, "Failed to open/read system font configurations.", e); - return new FontConfig(Collections.emptyList(), Collections.emptyList()); + return new FontConfig(Collections.emptyList(), Collections.emptyList(), 0, 0); } catch (XmlPullParserException e) { Log.e(TAG, "Failed to parse the system font configuration.", e); - return new FontConfig(Collections.emptyList(), Collections.emptyList()); + return new FontConfig(Collections.emptyList(), Collections.emptyList(), 0, 0); } } diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java index 5928540b19bf..014d6882be8d 100644 --- a/keystore/java/android/security/keystore/KeyProperties.java +++ b/keystore/java/android/security/keystore/KeyProperties.java @@ -67,6 +67,7 @@ public abstract class KeyProperties { PURPOSE_SIGN, PURPOSE_VERIFY, PURPOSE_WRAP_KEY, + PURPOSE_AGREE_KEY, }) public @interface PurposeEnum {} @@ -96,6 +97,11 @@ public abstract class KeyProperties { public static final int PURPOSE_WRAP_KEY = 1 << 5; /** + * Purpose of key: creating a shared ECDH secret through key agreement. + */ + public static final int PURPOSE_AGREE_KEY = 1 << 6; + + /** * @hide */ public static abstract class Purpose { @@ -113,6 +119,8 @@ public abstract class KeyProperties { return KeymasterDefs.KM_PURPOSE_VERIFY; case PURPOSE_WRAP_KEY: return KeymasterDefs.KM_PURPOSE_WRAP; + case PURPOSE_AGREE_KEY: + return KeymasterDefs.KM_PURPOSE_AGREE_KEY; default: throw new IllegalArgumentException("Unknown purpose: " + purpose); } @@ -130,6 +138,8 @@ public abstract class KeyProperties { return PURPOSE_VERIFY; case KeymasterDefs.KM_PURPOSE_WRAP: return PURPOSE_WRAP_KEY; + case KeymasterDefs.KM_PURPOSE_AGREE_KEY: + return PURPOSE_AGREE_KEY; default: throw new IllegalArgumentException("Unknown purpose: " + purpose); } diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java new file mode 100644 index 000000000000..fc963a88c4d1 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore2; + +import android.hardware.security.keymint.Algorithm; +import android.hardware.security.keymint.KeyParameter; +import android.hardware.security.keymint.KeyPurpose; +import android.hardware.security.keymint.Tag; +import android.security.KeyStoreException; +import android.security.KeyStoreOperation; +import android.security.keystore.KeyStoreCryptoOperation; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.ProviderException; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; +import java.util.ArrayList; +import java.util.List; + +import javax.crypto.KeyAgreementSpi; +import javax.crypto.SecretKey; +import javax.crypto.ShortBufferException; +import javax.crypto.spec.SecretKeySpec; + +/** + * {@link KeyAgreementSpi} which provides an ECDH implementation backed by Android KeyStore. + * + * @hide + */ +public class AndroidKeyStoreKeyAgreementSpi extends KeyAgreementSpi + implements KeyStoreCryptoOperation { + + private static final String TAG = "AndroidKeyStoreKeyAgreementSpi"; + + /** + * ECDH implementation. + * + * @hide + */ + public static class ECDH extends AndroidKeyStoreKeyAgreementSpi { + public ECDH() { + super(Algorithm.EC); + } + } + + private final int mKeymintAlgorithm; + + // Fields below are populated by engineInit and should be preserved after engineDoFinal. + private AndroidKeyStorePrivateKey mKey; + private PublicKey mOtherPartyKey; + + // Fields below are reset when engineDoFinal succeeds. + private KeyStoreOperation mOperation; + private long mOperationHandle; + + protected AndroidKeyStoreKeyAgreementSpi(int keymintAlgorithm) { + resetAll(); + + mKeymintAlgorithm = keymintAlgorithm; + } + + @Override + protected void engineInit(Key key, SecureRandom random) throws InvalidKeyException { + resetAll(); + + if (key == null) { + throw new InvalidKeyException("key == null"); + } else if (!(key instanceof AndroidKeyStorePrivateKey)) { + throw new InvalidKeyException( + "Only Android KeyStore private keys supported. Key: " + key); + } + // Checking the correct KEY_PURPOSE and algorithm is done by the Keymint implementation in + // ensureKeystoreOperationInitialized() below. + mKey = (AndroidKeyStorePrivateKey) key; + + boolean success = false; + try { + ensureKeystoreOperationInitialized(); + success = true; + } finally { + if (!success) { + resetAll(); + } + } + } + + @Override + protected void engineInit(Key key, AlgorithmParameterSpec params, SecureRandom random) + throws InvalidKeyException, InvalidAlgorithmParameterException { + if (params != null) { + throw new InvalidAlgorithmParameterException( + "Unsupported algorithm parameters: " + params); + } + engineInit(key, random); + } + + @Override + protected Key engineDoPhase(Key key, boolean lastPhase) + throws InvalidKeyException, IllegalStateException { + ensureKeystoreOperationInitialized(); + + if (key == null) { + throw new InvalidKeyException("key == null"); + } else if (!(key instanceof PublicKey)) { + throw new InvalidKeyException("Only public keys supported. Key: " + key); + } else if (!lastPhase) { + throw new IllegalStateException( + "Only one other party supported. lastPhase must be set to true."); + } else if (mOtherPartyKey != null) { + throw new IllegalStateException( + "Only one other party supported. doPhase() must only be called exactly once."); + } + // The other party key will be passed as part of the doFinal() call, to prevent an + // additional IPC. + mOtherPartyKey = (PublicKey) key; + + return null; // No intermediate key + } + + @Override + protected byte[] engineGenerateSecret() throws IllegalStateException { + try { + ensureKeystoreOperationInitialized(); + } catch (InvalidKeyException e) { + throw new IllegalStateException("Not initialized", e); + } + + if (mOtherPartyKey == null) { + throw new IllegalStateException("Other party key not provided. Call doPhase() first."); + } + byte[] otherPartyKeyEncoded = mOtherPartyKey.getEncoded(); + + try { + return mOperation.finish(otherPartyKeyEncoded, null); + } catch (KeyStoreException e) { + throw new ProviderException("Keystore operation failed", e); + } finally { + resetWhilePreservingInitState(); + } + } + + @Override + protected SecretKey engineGenerateSecret(String algorithm) + throws IllegalStateException, NoSuchAlgorithmException, InvalidKeyException { + byte[] generatedSecret = engineGenerateSecret(); + + return new SecretKeySpec(generatedSecret, algorithm); + } + + @Override + protected int engineGenerateSecret(byte[] sharedSecret, int offset) + throws IllegalStateException, ShortBufferException { + byte[] generatedSecret = engineGenerateSecret(); + + if (generatedSecret.length > sharedSecret.length - offset) { + throw new ShortBufferException("Needed: " + generatedSecret.length); + } + System.arraycopy(generatedSecret, 0, sharedSecret, offset, generatedSecret.length); + return generatedSecret.length; + } + + @Override + public long getOperationHandle() { + return mOperationHandle; + } + + @Override + protected void finalize() throws Throwable { + try { + resetAll(); + } finally { + super.finalize(); + } + } + + private void resetWhilePreservingInitState() { + KeyStoreCryptoOperationUtils.abortOperation(mOperation); + mOperationHandle = 0; + mOperation = null; + mOtherPartyKey = null; + } + + private void resetAll() { + resetWhilePreservingInitState(); + mKey = null; + } + + private void ensureKeystoreOperationInitialized() + throws InvalidKeyException, IllegalStateException { + if (mKey == null) { + throw new IllegalStateException("Not initialized"); + } + if (mOperation != null) { + return; + } + + // We don't need to explicitly pass in any other parameters here, as they're part of the + // private key that is available to Keymint. + List<KeyParameter> parameters = new ArrayList<>(); + parameters.add(KeyStore2ParameterUtils.makeEnum( + Tag.PURPOSE, KeyPurpose.AGREE_KEY + )); + + try { + mOperation = + mKey.getSecurityLevel().createOperation(mKey.getKeyIdDescriptor(), parameters); + } catch (KeyStoreException keyStoreException) { + // If necessary, throw an exception due to KeyStore operation having failed. + InvalidKeyException e = + KeyStoreCryptoOperationUtils.getInvalidKeyException(mKey, keyStoreException); + if (e != null) { + throw e; + } + } + + // Set the operation handle. This will be a random number, or the operation challenge if + // user authentication is required. If we got a challenge we check if the authorization can + // possibly succeed. + mOperationHandle = + KeyStoreCryptoOperationUtils.getOrMakeOperationChallenge(mOperation, mKey); + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java index 403da189262d..164bc8669525 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java @@ -94,6 +94,9 @@ public class AndroidKeyStoreProvider extends Provider { put("KeyGenerator.DESede", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$DESede"); } + // javax.crypto.KeyAgreement + put("KeyAgreement.ECDH", PACKAGE_NAME + ".AndroidKeyStoreKeyAgreementSpi$ECDH"); + // java.security.SecretKeyFactory putSecretKeyFactoryImpl("AES"); if (supports3DES) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt index 5cd660a2caa5..7ea4689be7e2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt @@ -472,11 +472,6 @@ class PhysicsAnimator<T> private constructor (target: T) { * animator is under test. */ internal fun startInternal() { - if (!Looper.getMainLooper().isCurrentThread) { - Log.e(TAG, "Animations can only be started on the main thread. If you are seeing " + - "this message in a test, call PhysicsAnimatorTestUtils#prepareForTest in " + - "your test setup.") - } val target = weakTarget.get() if (target == null) { Log.w(TAG, "Trying to animate a GC-ed object.") diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java index 39441281f670..a2cd683f2fdd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java @@ -42,10 +42,11 @@ import android.view.animation.PathInterpolator; import androidx.annotation.BinderThread; import androidx.annotation.VisibleForTesting; +import com.android.internal.inputmethod.Completable; +import com.android.internal.inputmethod.ResultCallbacks; import com.android.internal.view.IInputMethodManager; import java.util.ArrayList; -import java.util.Objects; import java.util.concurrent.Executor; /** @@ -207,23 +208,21 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } protected void insetsChanged(InsetsState insetsState) { - mMainExecutor.execute(() -> { - if (mInsetsState.equals(insetsState)) { - return; - } + if (mInsetsState.equals(insetsState)) { + return; + } - mImeShowing = insetsState.getSourceOrDefaultVisibility(InsetsState.ITYPE_IME); + mImeShowing = insetsState.getSourceOrDefaultVisibility(InsetsState.ITYPE_IME); - final InsetsSource newSource = insetsState.getSource(InsetsState.ITYPE_IME); - final Rect newFrame = newSource.getFrame(); - final Rect oldFrame = mInsetsState.getSource(InsetsState.ITYPE_IME).getFrame(); + final InsetsSource newSource = insetsState.getSource(InsetsState.ITYPE_IME); + final Rect newFrame = newSource.getFrame(); + final Rect oldFrame = mInsetsState.getSource(InsetsState.ITYPE_IME).getFrame(); - mInsetsState.set(insetsState, true /* copySources */); - if (mImeShowing && !newFrame.equals(oldFrame) && newSource.isVisible()) { - if (DEBUG) Slog.d(TAG, "insetsChanged when IME showing, restart animation"); - startAnimation(mImeShowing, true /* forceRestart */); - } - }); + mInsetsState.set(insetsState, true /* copySources */); + if (mImeShowing && !newFrame.equals(oldFrame) && newSource.isVisible()) { + if (DEBUG) Slog.d(TAG, "insetsChanged when IME showing, restart animation"); + startAnimation(mImeShowing, true /* forceRestart */); + } } @VisibleForTesting @@ -236,27 +235,25 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged continue; } if (activeControl.getType() == InsetsState.ITYPE_IME) { - mMainExecutor.execute(() -> { - final Point lastSurfacePosition = mImeSourceControl != null - ? mImeSourceControl.getSurfacePosition() : null; - final boolean positionChanged = - !activeControl.getSurfacePosition().equals(lastSurfacePosition); - final boolean leashChanged = - !haveSameLeash(mImeSourceControl, activeControl); - mImeSourceControl = activeControl; - if (mAnimation != null) { - if (positionChanged) { - startAnimation(mImeShowing, true /* forceRestart */); - } - } else { - if (leashChanged) { - applyVisibilityToLeash(); - } - if (!mImeShowing) { - removeImeSurface(); - } + final Point lastSurfacePosition = mImeSourceControl != null + ? mImeSourceControl.getSurfacePosition() : null; + final boolean positionChanged = + !activeControl.getSurfacePosition().equals(lastSurfacePosition); + final boolean leashChanged = + !haveSameLeash(mImeSourceControl, activeControl); + mImeSourceControl = activeControl; + if (mAnimation != null) { + if (positionChanged) { + startAnimation(mImeShowing, true /* forceRestart */); + } + } else { + if (leashChanged) { + applyVisibilityToLeash(); + } + if (!mImeShowing) { + removeImeSurface(); } - }); + } } } } @@ -281,7 +278,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged return; } if (DEBUG) Slog.d(TAG, "Got showInsets for ime"); - mMainExecutor.execute(() -> startAnimation(true /* show */, false /* forceRestart */)); + startAnimation(true /* show */, false /* forceRestart */); } @@ -290,7 +287,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged return; } if (DEBUG) Slog.d(TAG, "Got hideInsets for ime"); - mMainExecutor.execute(() -> startAnimation(false /* show */, false /* forceRestart */)); + startAnimation(false /* show */, false /* forceRestart */); } public void topFocusedWindowChanged(String packageName) { @@ -506,7 +503,9 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged try { // Remove the IME surface to make the insets invisible for // non-client controlled insets. - imms.removeImeSurface(); + final Completable.Void value = Completable.createVoid(); + imms.removeImeSurface(ResultCallbacks.of(value)); + Completable.getResult(value); } catch (RemoteException e) { Slog.e(TAG, "Failed to remove IME surface.", e); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java index d37e628b9a51..1149cceb1068 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ShellExecutor.java @@ -17,10 +17,15 @@ package com.android.wm.shell.common; import android.os.Looper; +import android.os.SystemClock; +import android.os.Trace; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; +import java.util.function.BooleanSupplier; +import java.util.function.Predicate; +import java.util.function.Supplier; /** * Super basic Executor interface that adds support for delayed execution and removing callbacks. @@ -65,17 +70,17 @@ public interface ShellExecutor extends Executor { /** * See {@link android.os.Handler#postDelayed(Runnable, long)}. */ - void executeDelayed(Runnable r, long delayMillis); + void executeDelayed(Runnable runnable, long delayMillis); /** * See {@link android.os.Handler#removeCallbacks}. */ - void removeCallbacks(Runnable r); + void removeCallbacks(Runnable runnable); /** * See {@link android.os.Handler#hasCallbacks(Runnable)}. */ - boolean hasCallback(Runnable r); + boolean hasCallback(Runnable runnable); /** * Returns the looper that this executor is running on. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java index 1f07542c9a27..d14c3e3c0dd4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java @@ -35,54 +35,17 @@ import java.util.function.Consumer; @ExternalThread public interface Pip { /** - * Closes PIP (PIPed activity and PIP system UI). - */ - default void closePip() { - } - - /** - * Dump the current state and information if need. - * - * @param pw The stream to dump information to. - */ - default void dump(PrintWriter pw) { - } - - /** * Expand PIP, it's possible that specific request to activate the window via Alt-tab. */ default void expandPip() { } /** - * Get the touch handler which manages all the touch handling for PIP on the Phone, - * including moving, dismissing and expanding the PIP. (Do not use in TV) - * - * @return - */ - default @Nullable PipTouchHandler getPipTouchHandler() { - return null; - } - - /** * Hides the PIP menu. */ default void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) {} /** - * Returns {@code true} if PIP is shown. - */ - default boolean isPipShown() { - return false; - } - - /** - * Moves the PIPed activity to the fullscreen and closes PIP system UI. - */ - default void movePipToFullscreen() { - } - - /** * Called when configuration is changed. */ default void onConfigurationChanged(Configuration newConfig) { @@ -101,12 +64,6 @@ public interface Pip { } /** - * Registers the session listener for the current user. - */ - default void registerSessionListenerForCurrentUser() { - } - - /** * Called when SysUI state changed. * * @param isSysUiStateValid Is SysUI state valid or not. @@ -116,19 +73,9 @@ public interface Pip { } /** - * Resize the Pip to the appropriate size for the input state. - * - * @param state In Pip state also used to determine the new size for the Pip. - */ - default void resizePinnedStack(int state) { - } - - /** - * Resumes resizing operation on the Pip that was previously suspended. - * - * @param reason The reason resizing operations on the Pip was suspended. + * Registers the session listener for the current user. */ - default void resumePipResizing(int reason) { + default void registerSessionListenerForCurrentUser() { } /** @@ -162,14 +109,6 @@ public interface Pip { default void showPictureInPictureMenu() {} /** - * Suspends resizing operation on the Pip until {@link #resumePipResizing} is called. - * - * @param reason The reason for suspending resizing operations on the Pip. - */ - default void suspendPipResizing(int reason) { - } - - /** * Called by Launcher when swiping an auto-pip enabled Activity to home starts * @param componentName {@link ComponentName} represents the Activity entering PiP * @param activityInfo {@link ActivityInfo} tied to the Activity @@ -199,4 +138,12 @@ public interface Pip { * PiP and the Back-from-Edge gesture. */ default void setPipExclusionBoundsChangeListener(Consumer<Rect> listener) { } + + /** + * Dump the current state and information if need. + * + * @param pw The stream to dump information to. + */ + default void dump(PrintWriter pw) { + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java index d96d4d0a6a3c..1a4616c5f591 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java @@ -31,6 +31,7 @@ import android.media.MediaMetadata; import android.media.session.MediaController; import android.media.session.MediaSessionManager; import android.media.session.PlaybackState; +import android.os.Handler; import android.os.UserHandle; import androidx.annotation.Nullable; @@ -74,6 +75,7 @@ public class PipMediaController { } private final Context mContext; + private final Handler mMainHandler; private final MediaSessionManager mMediaSessionManager; private MediaController mMediaController; @@ -118,15 +120,16 @@ public class PipMediaController { private final ArrayList<ActionListener> mActionListeners = new ArrayList<>(); private final ArrayList<MetadataListener> mMetadataListeners = new ArrayList<>(); - public PipMediaController(Context context) { + public PipMediaController(Context context, Handler mainHandler) { mContext = context; + mMainHandler = mainHandler; IntentFilter mediaControlFilter = new IntentFilter(); mediaControlFilter.addAction(ACTION_PLAY); mediaControlFilter.addAction(ACTION_PAUSE); mediaControlFilter.addAction(ACTION_NEXT); mediaControlFilter.addAction(ACTION_PREV); - mContext.registerReceiver(mPlayPauseActionReceiver, mediaControlFilter, - UserHandle.USER_ALL); + mContext.registerReceiverForAllUsers(mPlayPauseActionReceiver, mediaControlFilter, + null /* permission */, mainHandler); createMediaActions(); mMediaSessionManager = context.getSystemService(MediaSessionManager.class); @@ -245,7 +248,7 @@ public class PipMediaController { public void registerSessionListenerForCurrentUser() { mMediaSessionManager.removeOnActiveSessionsChangedListener(mSessionsChangedListener); mMediaSessionManager.addOnActiveSessionsChangedListener(mSessionsChangedListener, null, - UserHandle.CURRENT, null); + UserHandle.CURRENT, mMainHandler); } /** @@ -277,7 +280,7 @@ public class PipMediaController { } mMediaController = controller; if (controller != null) { - controller.registerCallback(mPlaybackChangedListener); + controller.registerCallback(mPlaybackChangedListener, mMainHandler); } notifyActionsChanged(); notifyMetadataChanged(getMediaMetadata()); 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 1279cd36724b..b80f285c3eb4 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 @@ -50,9 +50,7 @@ import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Rect; -import android.os.Handler; import android.os.IBinder; -import android.os.Looper; import android.os.RemoteException; import android.util.Log; import android.util.Rational; @@ -64,14 +62,14 @@ import android.window.WindowContainerTransaction; import android.window.WindowContainerTransactionCallback; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.os.SomeArgs; +import com.android.internal.jank.InteractionJankMonitor; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.pip.phone.PipMotionHelper; -import com.android.wm.shell.pip.phone.PipUpdateThread; - import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; @@ -98,12 +96,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, private static final String TAG = PipTaskOrganizer.class.getSimpleName(); private static final boolean DEBUG = false; - private static final int MSG_RESIZE_IMMEDIATE = 1; - private static final int MSG_RESIZE_ANIMATE = 2; - private static final int MSG_OFFSET_ANIMATE = 3; - private static final int MSG_FINISH_RESIZE = 4; - private static final int MSG_RESIZE_USER = 5; - // Not a complete set of states but serves what we want right now. private enum State { UNDEFINED(0), @@ -135,8 +127,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } } - private final Handler mMainHandler; - private final Handler mUpdateHandler; private final PipBoundsState mPipBoundsState; private final PipBoundsAlgorithm mPipBoundsAlgorithm; private final @NonNull PipMenuController mPipMenuController; @@ -148,6 +138,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, private final Map<IBinder, Configuration> mInitialState = new HashMap<>(); private final Optional<LegacySplitScreen> mSplitScreenOptional; protected final ShellTaskOrganizer mTaskOrganizer; + protected final ShellExecutor mMainExecutor; // These callbacks are called on the update thread private final PipAnimationController.PipAnimationCallback mPipAnimationCallback = @@ -183,68 +174,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } }; - @SuppressWarnings("unchecked") - private final Handler.Callback mUpdateCallbacks = (msg) -> { - SomeArgs args = (SomeArgs) msg.obj; - Consumer<Rect> updateBoundsCallback = (Consumer<Rect>) args.arg1; - switch (msg.what) { - case MSG_RESIZE_IMMEDIATE: { - Rect toBounds = (Rect) args.arg2; - resizePip(toBounds); - if (updateBoundsCallback != null) { - updateBoundsCallback.accept(toBounds); - } - break; - } - case MSG_RESIZE_ANIMATE: { - Rect currentBounds = (Rect) args.arg2; - Rect toBounds = (Rect) args.arg3; - Rect sourceHintRect = (Rect) args.arg4; - float startingAngle = (float) args.arg5; - int duration = args.argi2; - animateResizePip(currentBounds, toBounds, sourceHintRect, - args.argi1 /* direction */, duration, startingAngle); - if (updateBoundsCallback != null) { - updateBoundsCallback.accept(toBounds); - } - break; - } - case MSG_OFFSET_ANIMATE: { - Rect originalBounds = (Rect) args.arg2; - final int offset = args.argi1; - final int duration = args.argi2; - offsetPip(originalBounds, 0 /* xOffset */, offset, duration); - Rect toBounds = new Rect(originalBounds); - toBounds.offset(0, offset); - if (updateBoundsCallback != null) { - updateBoundsCallback.accept(toBounds); - } - break; - } - case MSG_FINISH_RESIZE: { - SurfaceControl.Transaction tx = (SurfaceControl.Transaction) args.arg2; - Rect toBounds = (Rect) args.arg3; - finishResize(tx, toBounds, args.argi1 /* direction */, -1); - if (updateBoundsCallback != null) { - updateBoundsCallback.accept(toBounds); - } - break; - } - case MSG_RESIZE_USER: { - Rect startBounds = (Rect) args.arg2; - Rect toBounds = (Rect) args.arg3; - float degrees = (float) args.arg4; - userResizePip(startBounds, toBounds, degrees); - if (updateBoundsCallback != null) { - updateBoundsCallback.accept(toBounds); - } - break; - } - } - args.recycle(); - return true; - }; - private ActivityManager.RunningTaskInfo mTaskInfo; private WindowContainerToken mToken; private SurfaceControl mLeash; @@ -276,9 +205,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, Optional<LegacySplitScreen> splitScreenOptional, @NonNull DisplayController displayController, @NonNull PipUiEventLogger pipUiEventLogger, - @NonNull ShellTaskOrganizer shellTaskOrganizer) { - mMainHandler = new Handler(Looper.getMainLooper()); - mUpdateHandler = new Handler(PipUpdateThread.get().getLooper(), mUpdateCallbacks); + @NonNull ShellTaskOrganizer shellTaskOrganizer, + @ShellMainThread ShellExecutor mainExecutor) { mPipBoundsState = pipBoundsState; mPipBoundsAlgorithm = boundsHandler; mPipMenuController = pipMenuController; @@ -290,12 +218,13 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new; mSplitScreenOptional = splitScreenOptional; mTaskOrganizer = shellTaskOrganizer; - mTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_PIP); - displayController.addDisplayWindowListener(this); - } + mMainExecutor = mainExecutor; - public Handler getUpdateHandler() { - return mUpdateHandler; + // TODO: Can be removed once wm components are created on the shell-main thread + mMainExecutor.execute(() -> { + mTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_PIP); + }); + displayController.addDisplayWindowListener(this); } public Rect getCurrentOrAnimatingBounds() { @@ -428,15 +357,17 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mTaskOrganizer.applySyncTransaction(wct, new WindowContainerTransactionCallback() { @Override public void onTransactionReady(int id, SurfaceControl.Transaction t) { - t.apply(); - // Make sure to grab the latest source hint rect as it could have been updated - // right after applying the windowing mode change. - final Rect sourceHintRect = getValidSourceHintRect(mPictureInPictureParams, - destinationBounds); - scheduleAnimateResizePip(mPipBoundsState.getBounds(), destinationBounds, - 0 /* startingAngle */, sourceHintRect, direction, animationDurationMs, - null /* updateBoundsCallback */); - mState = State.EXITING_PIP; + mMainExecutor.execute(() -> { + t.apply(); + // Make sure to grab the latest source hint rect as it could have been + // updated right after applying the windowing mode change. + final Rect sourceHintRect = getValidSourceHintRect(mPictureInPictureParams, + destinationBounds); + scheduleAnimateResizePip(mPipBoundsState.getBounds(), destinationBounds, + 0 /* startingAngle */, sourceHintRect, direction, + animationDurationMs, null /* updateBoundsCallback */); + mState = State.EXITING_PIP; + }); } }); } @@ -465,12 +396,12 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } // removePipImmediately is expected when the following animation finishes. - mUpdateHandler.post(() -> mPipAnimationController + mPipAnimationController .getAnimator(mLeash, mPipBoundsState.getBounds(), 1f, 0f) .setTransitionDirection(TRANSITION_DIRECTION_REMOVE_STACK) .setPipAnimationCallback(mPipAnimationCallback) .setDuration(mEnterExitAnimationDuration) - .start()); + .start(); mInitialState.remove(mToken.asBinder()); mState = State.EXITING_PIP; } @@ -579,12 +510,12 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, tx.setAlpha(mLeash, 0f); tx.apply(); applyEnterPipSyncTransaction(destinationBounds, () -> { - mUpdateHandler.post(() -> mPipAnimationController + mPipAnimationController .getAnimator(mLeash, destinationBounds, 0f, 1f) .setTransitionDirection(TRANSITION_DIRECTION_TO_PIP) .setPipAnimationCallback(mPipAnimationCallback) .setDuration(durationMs) - .start()); + .start(); // mState is set right after the animation is kicked off to block any resize // requests such as offsetPip that may have been called prior to the transition. mState = State.ENTERING_PIP; @@ -599,13 +530,16 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED); wct.setBounds(mToken, destinationBounds); wct.scheduleFinishEnterPip(mToken, destinationBounds); + // TODO: Migrate to SyncTransactionQueue mTaskOrganizer.applySyncTransaction(wct, new WindowContainerTransactionCallback() { @Override public void onTransactionReady(int id, SurfaceControl.Transaction t) { - t.apply(); - if (runnable != null) { - runnable.run(); - } + mMainExecutor.execute(() -> { + t.apply(); + if (runnable != null) { + runnable.run(); + } + }); } }); } @@ -621,12 +555,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mState = State.ENTERING_PIP; } final Rect pipBounds = mPipBoundsState.getBounds(); - runOnMainHandler(() -> { - for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) { - final PipTransitionCallback callback = mPipTransitionCallbacks.get(i); - callback.onPipTransitionStarted(componentName, direction, pipBounds); - } - }); + for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) { + final PipTransitionCallback callback = mPipTransitionCallbacks.get(i); + callback.onPipTransitionStarted(componentName, direction, pipBounds); + } } private void sendOnPipTransitionFinished( @@ -634,29 +566,17 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, if (direction == TRANSITION_DIRECTION_TO_PIP) { mState = State.ENTERED_PIP; } - runOnMainHandler(() -> { - for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) { - final PipTransitionCallback callback = mPipTransitionCallbacks.get(i); - callback.onPipTransitionFinished(mTaskInfo.baseActivity, direction); - } - }); + for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) { + final PipTransitionCallback callback = mPipTransitionCallbacks.get(i); + callback.onPipTransitionFinished(mTaskInfo.baseActivity, direction); + } } private void sendOnPipTransitionCancelled( @PipAnimationController.TransitionDirection int direction) { - runOnMainHandler(() -> { - for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) { - final PipTransitionCallback callback = mPipTransitionCallbacks.get(i); - callback.onPipTransitionCanceled(mTaskInfo.baseActivity, direction); - } - }); - } - - private void runOnMainHandler(Runnable r) { - if (Looper.getMainLooper() == Looper.myLooper()) { - r.run(); - } else { - mMainHandler.post(r); + for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) { + final PipTransitionCallback callback = mPipTransitionCallbacks.get(i); + callback.onPipTransitionCanceled(mTaskInfo.baseActivity, direction); } } @@ -872,15 +792,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, return; } - SomeArgs args = SomeArgs.obtain(); - args.arg1 = updateBoundsCallback; - args.arg2 = currentBounds; - args.arg3 = destinationBounds; - args.arg4 = sourceHintRect; - args.arg5 = startingAngle; - args.argi1 = direction; - args.argi2 = durationMs; - mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_RESIZE_ANIMATE, args)); + animateResizePip(currentBounds, destinationBounds, sourceHintRect, direction, durationMs, + startingAngle); + if (updateBoundsCallback != null) { + updateBoundsCallback.accept(destinationBounds); + } } /** @@ -888,10 +804,24 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, * {@link WindowContainerTransaction} until {@link #scheduleFinishResizePip} is called. */ public void scheduleResizePip(Rect toBounds, Consumer<Rect> updateBoundsCallback) { - SomeArgs args = SomeArgs.obtain(); - args.arg1 = updateBoundsCallback; - args.arg2 = toBounds; - mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_RESIZE_IMMEDIATE, args)); + // Could happen when exitPip + if (mToken == null || mLeash == null) { + Log.w(TAG, "Abort animation, invalid leash"); + return; + } + mPipBoundsState.setBounds(toBounds); + final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); + mSurfaceTransactionHelper + .crop(tx, mLeash, toBounds) + .round(tx, mLeash, mState.isInPip()); + if (mPipMenuController.isMenuVisible()) { + mPipMenuController.resizePipMenu(mLeash, tx, toBounds); + } else { + tx.apply(); + } + if (updateBoundsCallback != null) { + updateBoundsCallback.accept(toBounds); + } } /** @@ -909,12 +839,27 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, */ public void scheduleUserResizePip(Rect startBounds, Rect toBounds, float degrees, Consumer<Rect> updateBoundsCallback) { - SomeArgs args = SomeArgs.obtain(); - args.arg1 = updateBoundsCallback; - args.arg2 = startBounds; - args.arg3 = toBounds; - args.arg4 = degrees; - mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_RESIZE_USER, args)); + // Could happen when exitPip + if (mToken == null || mLeash == null) { + Log.w(TAG, "Abort animation, invalid leash"); + return; + } + + if (startBounds.isEmpty() || toBounds.isEmpty()) { + Log.w(TAG, "Attempted to user resize PIP to or from empty bounds, aborting."); + return; + } + + final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); + mSurfaceTransactionHelper.scale(tx, mLeash, startBounds, toBounds, degrees); + if (mPipMenuController.isMenuVisible()) { + mPipMenuController.movePipMenu(mLeash, tx, toBounds); + } else { + tx.apply(); + } + if (updateBoundsCallback != null) { + updateBoundsCallback.accept(toBounds); + } } /** @@ -948,13 +893,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, return; } - SomeArgs args = SomeArgs.obtain(); - args.arg1 = updateBoundsCallback; - args.arg2 = createFinishResizeSurfaceTransaction( - destinationBounds); - args.arg3 = destinationBounds; - args.argi1 = direction; - mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_FINISH_RESIZE, args)); + finishResize(createFinishResizeSurfaceTransaction(destinationBounds), destinationBounds, + direction, -1); + if (updateBoundsCallback != null) { + updateBoundsCallback.accept(destinationBounds); + } } private SurfaceControl.Transaction createFinishResizeSurfaceTransaction( @@ -979,20 +922,15 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, Log.d(TAG, "skip scheduleOffsetPip, entering pip deferred"); return; } - SomeArgs args = SomeArgs.obtain(); - args.arg1 = updateBoundsCallback; - args.arg2 = originalBounds; - // offset would be zero if triggered from screen rotation. - args.argi1 = offset; - args.argi2 = duration; - mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_OFFSET_ANIMATE, args)); + offsetPip(originalBounds, 0 /* xOffset */, offset, duration); + Rect toBounds = new Rect(originalBounds); + toBounds.offset(0, offset); + if (updateBoundsCallback != null) { + updateBoundsCallback.accept(toBounds); + } } private void offsetPip(Rect originalBounds, int xOffset, int yOffset, int durationMs) { - if (Looper.myLooper() != mUpdateHandler.getLooper()) { - throw new RuntimeException("Callers should call scheduleOffsetPip() instead of this " - + "directly"); - } if (mTaskInfo == null) { Log.w(TAG, "mTaskInfo is not set"); return; @@ -1003,62 +941,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, TRANSITION_DIRECTION_SAME, durationMs, 0); } - private void resizePip(Rect destinationBounds) { - if (Looper.myLooper() != mUpdateHandler.getLooper()) { - throw new RuntimeException("Callers should call scheduleResizePip() instead of this " - + "directly"); - } - // Could happen when exitPip - if (mToken == null || mLeash == null) { - Log.w(TAG, "Abort animation, invalid leash"); - return; - } - mPipBoundsState.setBounds(destinationBounds); - final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); - mSurfaceTransactionHelper - .crop(tx, mLeash, destinationBounds) - .round(tx, mLeash, mState.isInPip()); - if (mPipMenuController.isMenuVisible()) { - runOnMainHandler(() -> - mPipMenuController.resizePipMenu(mLeash, tx, destinationBounds)); - } else { - tx.apply(); - } - } - - private void userResizePip(Rect startBounds, Rect destinationBounds, float degrees) { - if (Looper.myLooper() != mUpdateHandler.getLooper()) { - throw new RuntimeException("Callers should call scheduleUserResizePip() instead of " - + "this directly"); - } - // Could happen when exitPip - if (mToken == null || mLeash == null) { - Log.w(TAG, "Abort animation, invalid leash"); - return; - } - - if (startBounds.isEmpty() || destinationBounds.isEmpty()) { - Log.w(TAG, "Attempted to user resize PIP to or from empty bounds, aborting."); - return; - } - - final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); - mSurfaceTransactionHelper.scale(tx, mLeash, startBounds, destinationBounds, degrees); - if (mPipMenuController.isMenuVisible()) { - runOnMainHandler(() -> - mPipMenuController.movePipMenu(mLeash, tx, destinationBounds)); - } else { - tx.apply(); - } - } - private void finishResize(SurfaceControl.Transaction tx, Rect destinationBounds, @PipAnimationController.TransitionDirection int direction, @PipAnimationController.AnimationType int type) { - if (Looper.myLooper() != mUpdateHandler.getLooper()) { - throw new RuntimeException("Callers should call scheduleResizePip() instead of this " - + "directly"); - } mPipBoundsState.setBounds(destinationBounds); if (direction == TRANSITION_DIRECTION_REMOVE_STACK) { removePipImmediately(); @@ -1097,7 +982,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mSurfaceTransactionHelper.scale(t, snapshotSurface, snapshotSrc, snapshotDest); t.apply(); - mUpdateHandler.post(() -> { + mMainExecutor.execute(() -> { // Start animation to fade out the snapshot. final ValueAnimator animator = ValueAnimator.ofFloat(1.0f, 0.0f); animator.setDuration(mEnterExitAnimationDuration); @@ -1129,10 +1014,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } private void finishResizeForMenu(Rect destinationBounds) { - runOnMainHandler(() -> { - mPipMenuController.movePipMenu(null, null, destinationBounds); - mPipMenuController.updateMenuBounds(destinationBounds); - }); + mPipMenuController.movePipMenu(null, null, destinationBounds); + mPipMenuController.updateMenuBounds(destinationBounds); } private void prepareFinishResizeTransaction(Rect destinationBounds, @@ -1185,10 +1068,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, private void animateResizePip(Rect currentBounds, Rect destinationBounds, Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction, int durationMs, float startingAngle) { - if (Looper.myLooper() != mUpdateHandler.getLooper()) { - throw new RuntimeException("Callers should call scheduleAnimateResizePip() instead of " - + "this directly"); - } // Could happen when exitPip if (mToken == null || mLeash == null) { Log.w(TAG, "Abort animation, invalid leash"); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java index 5db8f3d7ef40..8bf1b468cb7d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java @@ -30,6 +30,7 @@ import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.RectF; import android.os.Debug; +import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; @@ -39,6 +40,7 @@ import android.view.SyncRtSurfaceTransactionApplier; import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; import android.view.WindowManagerGlobal; +import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipMediaController.ActionListener; @@ -97,6 +99,8 @@ public class PhonePipMenuController implements PipMenuController { private final RectF mTmpDestinationRectF = new RectF(); private final Context mContext; private final PipMediaController mMediaController; + private final ShellExecutor mMainExecutor; + private final Handler mMainHandler; private final ArrayList<Listener> mListeners = new ArrayList<>(); private final SystemWindows mSystemWindows; @@ -116,11 +120,14 @@ public class PhonePipMenuController implements PipMenuController { } }; - public PhonePipMenuController(Context context, - PipMediaController mediaController, SystemWindows systemWindows) { + public PhonePipMenuController(Context context, PipMediaController mediaController, + SystemWindows systemWindows, ShellExecutor mainExecutor, + Handler mainHandler) { mContext = context; mMediaController = mediaController; mSystemWindows = systemWindows; + mMainExecutor = mainExecutor; + mMainHandler = mainHandler; } public boolean isMenuVisible() { @@ -156,7 +163,7 @@ public class PhonePipMenuController implements PipMenuController { if (mPipMenuView != null) { detachPipMenuView(); } - mPipMenuView = new PipMenuView(mContext, this); + mPipMenuView = new PipMenuView(mContext, this, mMainExecutor, mMainHandler); mSystemWindows.addView(mPipMenuView, getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */), 0, SHELL_ROOT_LAYER_PIP); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAppOpsListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAppOpsListener.java index 2cd010796799..d97d2d6ebb4f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAppOpsListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAppOpsListener.java @@ -21,22 +21,20 @@ import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE; import android.app.AppOpsManager; import android.app.AppOpsManager.OnOpChangedListener; -import android.app.IActivityManager; import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager.NameNotFoundException; -import android.os.Handler; import android.util.Pair; +import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.pip.PipUtils; public class PipAppOpsListener { private static final String TAG = PipAppOpsListener.class.getSimpleName(); private Context mContext; - private Handler mHandler; - private IActivityManager mActivityManager; + private ShellExecutor mMainExecutor; private AppOpsManager mAppOpsManager; private Callback mCallback; @@ -53,7 +51,7 @@ public class PipAppOpsListener { if (appInfo.packageName.equals(topPipActivityInfo.first.getPackageName()) && mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid, packageName) != MODE_ALLOWED) { - mHandler.post(() -> mCallback.dismissPip()); + mMainExecutor.execute(() -> mCallback.dismissPip()); } } } catch (NameNotFoundException e) { @@ -63,11 +61,9 @@ public class PipAppOpsListener { } }; - public PipAppOpsListener(Context context, IActivityManager activityManager, - Callback callback) { + public PipAppOpsListener(Context context, Callback callback, ShellExecutor mainExecutor) { mContext = context; - mHandler = new Handler(mContext.getMainLooper()); - mActivityManager = activityManager; + mMainExecutor = mainExecutor; mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); mCallback = callback; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index fa880848a9c2..cefeb93998f9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -71,11 +71,11 @@ import java.util.function.Consumer; /** * Manages the picture-in-picture (PIP) UI and states for Phones. */ -public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallback { +public class PipController implements PipTaskOrganizer.PipTransitionCallback { private static final String TAG = "PipController"; private Context mContext; - private ShellExecutor mMainExecutor; + protected ShellExecutor mMainExecutor; private DisplayController mDisplayController; private PipInputConsumer mPipInputConsumer; private WindowManagerShellWrapper mWindowManagerShellWrapper; @@ -84,6 +84,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac private PipBoundsAlgorithm mPipBoundsAlgorithm; private PipBoundsState mPipBoundsState; private PipTouchHandler mTouchHandler; + protected final PipImpl mImpl = new PipImpl(); private final DisplayInfo mTmpDisplayInfo = new DisplayInfo(); private final Rect mTmpInsetBounds = new Rect(); @@ -204,6 +205,28 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac } } + + /** + * Instantiates {@link PipController}, returns {@code null} if the feature not supported. + */ + @Nullable + public static Pip create(Context context, DisplayController displayController, + PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm, + PipBoundsState pipBoundsState, PipMediaController pipMediaController, + PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer, + PipTouchHandler pipTouchHandler, WindowManagerShellWrapper windowManagerShellWrapper, + TaskStackListenerImpl taskStackListener, ShellExecutor mainExecutor) { + if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) { + Slog.w(TAG, "Device doesn't support Pip feature"); + return null; + } + + return new PipController(context, displayController, pipAppOpsListener, pipBoundsAlgorithm, + pipBoundsState, pipMediaController, phonePipMenuController, pipTaskOrganizer, + pipTouchHandler, windowManagerShellWrapper, taskStackListener, mainExecutor) + .mImpl; + } + protected PipController(Context context, DisplayController displayController, PipAppOpsListener pipAppOpsListener, @@ -235,7 +258,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac mTouchHandler = pipTouchHandler; mAppOpsListener = pipAppOpsListener; mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(), - INPUT_CONSUMER_PIP); + INPUT_CONSUMER_PIP, mainExecutor); mPipTaskOrganizer.registerPipTransitionCallback(this); mPipTaskOrganizer.registerOnDisplayIdChangeCallback((int displayId) -> { final DisplayInfo newDisplayInfo = new DisplayInfo(); @@ -288,7 +311,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac if (taskInfo != null) { // If SystemUI restart, and it already existed a pinned stack, // register the pip input consumer to ensure touch can send to it. - mPipInputConsumer.registerInputConsumer(true /* withSfVsync */); + mPipInputConsumer.registerInputConsumer(); } } catch (RemoteException | UnsupportedOperationException e) { Log.e(TAG, "Failed to register pinned stack listener", e); @@ -301,12 +324,10 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac @Override public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { - mMainExecutor.execute(() -> { - mTouchHandler.onActivityPinned(); - mMediaController.onActivityPinned(); - mAppOpsListener.onActivityPinned(packageName); - }); - mPipInputConsumer.registerInputConsumer(true /* withSfVsync */); + mTouchHandler.onActivityPinned(); + mMediaController.onActivityPinned(); + mAppOpsListener.onActivityPinned(packageName); + mPipInputConsumer.registerInputConsumer(); } @Override @@ -314,10 +335,8 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac final Pair<ComponentName, Integer> topPipActivityInfo = PipUtils.getTopPipActivity(mContext); final ComponentName topActivity = topPipActivityInfo.first; - mMainExecutor.execute(() -> { - mTouchHandler.onActivityUnpinned(topActivity); - mAppOpsListener.onActivityUnpinned(); - }); + mTouchHandler.onActivityUnpinned(topActivity); + mAppOpsListener.onActivityUnpinned(); mPipInputConsumer.unregisterInputConsumer(); } @@ -333,60 +352,46 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac }); } - @Override - public void onConfigurationChanged(Configuration newConfig) { - mMainExecutor.execute(() -> { - mPipBoundsAlgorithm.onConfigurationChanged(mContext); - mTouchHandler.onConfigurationChanged(); - mPipBoundsState.onConfigurationChanged(); - }); + private void onConfigurationChanged(Configuration newConfig) { + mPipBoundsAlgorithm.onConfigurationChanged(mContext); + mTouchHandler.onConfigurationChanged(); + mPipBoundsState.onConfigurationChanged(); } - @Override - public void onDensityOrFontScaleChanged() { - mMainExecutor.execute(() -> { - mPipTaskOrganizer.onDensityOrFontScaleChanged(mContext); - }); + private void onDensityOrFontScaleChanged() { + mPipTaskOrganizer.onDensityOrFontScaleChanged(mContext); } - @Override - public void onOverlayChanged() { - mMainExecutor.execute(() -> { - mPipBoundsState.setDisplayLayout(new DisplayLayout(mContext, mContext.getDisplay())); - updateMovementBounds(null /* toBounds */, - false /* fromRotation */, false /* fromImeAdjustment */, - false /* fromShelfAdjustment */, - null /* windowContainerTransaction */); - }); + private void onOverlayChanged() { + mPipBoundsState.setDisplayLayout(new DisplayLayout(mContext, mContext.getDisplay())); + updateMovementBounds(null /* toBounds */, + false /* fromRotation */, false /* fromImeAdjustment */, + false /* fromShelfAdjustment */, + null /* windowContainerTransaction */); } - @Override - public void registerSessionListenerForCurrentUser() { + private void registerSessionListenerForCurrentUser() { mMediaController.registerSessionListenerForCurrentUser(); } - @Override - public void onSystemUiStateChanged(boolean isValidState, int flag) { + private void onSystemUiStateChanged(boolean isValidState, int flag) { mTouchHandler.onSystemUiStateChanged(isValidState); } /** * Expands the PIP. */ - @Override public void expandPip() { mTouchHandler.getMotionHelper().expandLeavePip(false /* skipAnimation */); } - @Override - public PipTouchHandler getPipTouchHandler() { + private PipTouchHandler getPipTouchHandler() { return mTouchHandler; } /** * Hides the PIP menu. */ - @Override public void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) { mMenuController.hideMenu(onStartCallback, onEndCallback); } @@ -408,9 +413,8 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac /** * Sets both shelf visibility and its height. */ - @Override - public void setShelfHeight(boolean visible, int height) { - mMainExecutor.execute(() -> setShelfHeightLocked(visible, height)); + private void setShelfHeight(boolean visible, int height) { + setShelfHeightLocked(visible, height); } private void setShelfHeightLocked(boolean visible, int height) { @@ -418,18 +422,15 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac mPipBoundsState.setShelfVisibility(visible, shelfHeight); } - @Override - public void setPinnedStackAnimationType(int animationType) { - mMainExecutor.execute(() -> mPipTaskOrganizer.setOneShotAnimationType(animationType)); + private void setPinnedStackAnimationType(int animationType) { + mPipTaskOrganizer.setOneShotAnimationType(animationType); } - @Override - public void setPinnedStackAnimationListener(Consumer<Boolean> callback) { - mMainExecutor.execute(() -> mPinnedStackAnimationRecentsCallback = callback); + private void setPinnedStackAnimationListener(Consumer<Boolean> callback) { + mPinnedStackAnimationRecentsCallback = callback; } - @Override - public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo, + private Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo, PictureInPictureParams pictureInPictureParams, int launcherRotation, int shelfHeight) { setShelfHeightLocked(shelfHeight > 0 /* visible */, shelfHeight); @@ -438,11 +439,19 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac pictureInPictureParams); } - @Override - public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) { + private void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) { mPipTaskOrganizer.stopSwipePipToHome(componentName, destinationBounds); } + /** + * Set a listener to watch out for PiP bounds. This is mostly used by SystemUI's + * Back-gesture handler, to avoid conflicting with PiP when it's stashed. + */ + private void setPipExclusionBoundsChangeListener( + Consumer<Rect> pipExclusionBoundsChangeListener) { + mTouchHandler.setPipExclusionBoundsChangeListener(pipExclusionBoundsChangeListener); + } + @Override public void onPipTransitionStarted(ComponentName activity, int direction, Rect pipBounds) { if (isOutPipDirection(direction)) { @@ -468,16 +477,6 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac } } - /** - * Set a listener to watch out for PiP bounds. This is mostly used by SystemUI's - * Back-gesture handler, to avoid conflicting with PiP when it's stashed. - */ - @Override - public void setPipExclusionBoundsChangeListener( - Consumer<Rect> pipExclusionBoundsChangeListener) { - mTouchHandler.setPipExclusionBoundsChangeListener(pipExclusionBoundsChangeListener); - } - @Override public void onPipTransitionFinished(ComponentName activity, int direction) { onPipTransitionFinishedOrCanceled(direction); @@ -607,8 +606,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac } } - @Override - public void dump(PrintWriter pw) { + private void dump(PrintWriter pw) { final String innerPrefix = " "; pw.println(TAG); mMenuController.dump(pw, innerPrefix); @@ -619,23 +617,123 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac mPipInputConsumer.dump(pw, innerPrefix); } - /** - * Instantiates {@link PipController}, returns {@code null} if the feature not supported. - */ - @Nullable - public static PipController create(Context context, DisplayController displayController, - PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm, - PipBoundsState pipBoundsState, PipMediaController pipMediaController, - PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer, - PipTouchHandler pipTouchHandler, WindowManagerShellWrapper windowManagerShellWrapper, - TaskStackListenerImpl taskStackListener, ShellExecutor mainExecutor) { - if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) { - Slog.w(TAG, "Device doesn't support Pip feature"); - return null; + private class PipImpl implements Pip { + @Override + public void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) { + mMainExecutor.execute(() -> { + PipController.this.hidePipMenu(onStartCallback, onEndCallback); + }); } - return new PipController(context, displayController, pipAppOpsListener, pipBoundsAlgorithm, - pipBoundsState, pipMediaController, phonePipMenuController, pipTaskOrganizer, - pipTouchHandler, windowManagerShellWrapper, taskStackListener, mainExecutor); + @Override + public void expandPip() { + mMainExecutor.execute(() -> { + PipController.this.expandPip(); + }); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + mMainExecutor.execute(() -> { + PipController.this.onConfigurationChanged(newConfig); + }); + } + + @Override + public void onDensityOrFontScaleChanged() { + mMainExecutor.execute(() -> { + PipController.this.onDensityOrFontScaleChanged(); + }); + } + + @Override + public void onOverlayChanged() { + mMainExecutor.execute(() -> { + PipController.this.onOverlayChanged(); + }); + } + + @Override + public void onSystemUiStateChanged(boolean isSysUiStateValid, int flag) { + mMainExecutor.execute(() -> { + PipController.this.onSystemUiStateChanged(isSysUiStateValid, flag); + }); + } + + @Override + public void registerSessionListenerForCurrentUser() { + mMainExecutor.execute(() -> { + PipController.this.registerSessionListenerForCurrentUser(); + }); + } + + @Override + public void setShelfHeight(boolean visible, int height) { + mMainExecutor.execute(() -> { + PipController.this.setShelfHeight(visible, height); + }); + } + + @Override + public void setPinnedStackAnimationListener(Consumer<Boolean> callback) { + mMainExecutor.execute(() -> { + PipController.this.setPinnedStackAnimationListener(callback); + }); + } + + @Override + public void setPinnedStackAnimationType(int animationType) { + mMainExecutor.execute(() -> { + PipController.this.setPinnedStackAnimationType(animationType); + }); + } + + @Override + public void setPipExclusionBoundsChangeListener(Consumer<Rect> listener) { + mMainExecutor.execute(() -> { + PipController.this.setPipExclusionBoundsChangeListener(listener); + }); + } + + @Override + public void showPictureInPictureMenu() { + mMainExecutor.execute(() -> { + PipController.this.showPictureInPictureMenu(); + }); + } + + @Override + public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo, + PictureInPictureParams pictureInPictureParams, int launcherRotation, + int shelfHeight) { + Rect[] result = new Rect[1]; + try { + mMainExecutor.executeBlocking(() -> { + result[0] = PipController.this.startSwipePipToHome(componentName, activityInfo, + pictureInPictureParams, launcherRotation, shelfHeight); + }); + } catch (InterruptedException e) { + Slog.e(TAG, "Failed to start swipe pip to home"); + } + return result[0]; + } + + @Override + public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) { + mMainExecutor.execute(() -> { + PipController.this.stopSwipePipToHome(componentName, destinationBounds); + }); + } + + @Override + public void dump(PrintWriter pw) { + try { + mMainExecutor.executeBlocking(() -> { + PipController.this.dump(pw); + }); + } catch (InterruptedException e) { + Slog.e(TAG, "Failed to dump PipController in 2s"); + } + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java index bebe5f965251..d9a7bdb2eca6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java @@ -37,9 +37,12 @@ import androidx.dynamicanimation.animation.SpringForce; import com.android.wm.shell.R; import com.android.wm.shell.animation.PhysicsAnimator; import com.android.wm.shell.common.DismissCircleView; +import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.magnetictarget.MagnetizedObject; import com.android.wm.shell.pip.PipUiEventLogger; +import java.util.concurrent.TimeUnit; + import kotlin.Unit; /** @@ -57,36 +60,32 @@ public class PipDismissTargetHandler { * MagnetizedObject wrapper for PIP. This allows the magnetic target library to locate and move * PIP. */ - private final MagnetizedObject<Rect> mMagnetizedPip; + private MagnetizedObject<Rect> mMagnetizedPip; /** * Container for the dismiss circle, so that it can be animated within the container via * translation rather than within the WindowManager via slow layout animations. */ - private final ViewGroup mTargetViewContainer; + private ViewGroup mTargetViewContainer; /** Circle view used to render the dismiss target. */ - private final DismissCircleView mTargetView; + private DismissCircleView mTargetView; /** * MagneticTarget instance wrapping the target view and allowing us to set its magnetic radius. */ - private final MagnetizedObject.MagneticTarget mMagneticTarget; + private MagnetizedObject.MagneticTarget mMagneticTarget; - /** PhysicsAnimator instance for animating the dismiss target in/out. */ - private final PhysicsAnimator<View> mMagneticTargetAnimator; + /** + * PhysicsAnimator instance for animating the dismiss target in/out. + */ + private PhysicsAnimator<View> mMagneticTargetAnimator; /** Default configuration to use for springing the dismiss target in/out. */ private final PhysicsAnimator.SpringConfig mTargetSpringConfig = new PhysicsAnimator.SpringConfig( SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY); - /** - * Runnable that can be posted delayed to show the target. This needs to be saved as a member - * variable so we can pass it to removeCallbacks. - */ - private Runnable mShowTargetAction = this::showDismissTargetMaybe; - // Allow dragging the PIP to a location to close it private final boolean mEnableDismissDragToEdge; @@ -96,74 +95,76 @@ public class PipDismissTargetHandler { private final PipMotionHelper mMotionHelper; private final PipUiEventLogger mPipUiEventLogger; private final WindowManager mWindowManager; - private final Handler mHandler; + private final ShellExecutor mMainExecutor; public PipDismissTargetHandler(Context context, PipUiEventLogger pipUiEventLogger, - PipMotionHelper motionHelper, Handler handler) { + PipMotionHelper motionHelper, ShellExecutor mainExecutor) { mContext = context; mPipUiEventLogger = pipUiEventLogger; mMotionHelper = motionHelper; - mHandler = handler; + mMainExecutor = mainExecutor; mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); Resources res = context.getResources(); mEnableDismissDragToEdge = res.getBoolean(R.bool.config_pipEnableDismissDragToEdge); mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height); - mTargetView = new DismissCircleView(context); - mTargetViewContainer = new FrameLayout(context); - mTargetViewContainer.setBackgroundDrawable( - context.getDrawable(R.drawable.floating_dismiss_gradient_transition)); - mTargetViewContainer.setClipChildren(false); - mTargetViewContainer.addView(mTargetView); - - mMagnetizedPip = mMotionHelper.getMagnetizedPip(); - mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0); - updateMagneticTargetSize(); - - mMagnetizedPip.setAnimateStuckToTarget( - (target, velX, velY, flung, after) -> { + mMainExecutor.execute(() -> { + mTargetView = new DismissCircleView(context); + mTargetViewContainer = new FrameLayout(context); + mTargetViewContainer.setBackgroundDrawable( + context.getDrawable(R.drawable.floating_dismiss_gradient_transition)); + mTargetViewContainer.setClipChildren(false); + mTargetViewContainer.addView(mTargetView); + + mMagnetizedPip = mMotionHelper.getMagnetizedPip(); + mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0); + updateMagneticTargetSize(); + + mMagnetizedPip.setAnimateStuckToTarget( + (target, velX, velY, flung, after) -> { + if (mEnableDismissDragToEdge) { + mMotionHelper.animateIntoDismissTarget(target, velX, velY, flung, + after); + } + return Unit.INSTANCE; + }); + mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() { + @Override + public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) { + // Show the dismiss target, in case the initial touch event occurred within + // the magnetic field radius. if (mEnableDismissDragToEdge) { - mMotionHelper.animateIntoDismissTarget(target, velX, velY, flung, after); + showDismissTargetMaybe(); } - return Unit.INSTANCE; - }); - mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() { - @Override - public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) { - // Show the dismiss target, in case the initial touch event occurred within the - // magnetic field radius. - if (mEnableDismissDragToEdge) { - showDismissTargetMaybe(); } - } - @Override - public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target, - float velX, float velY, boolean wasFlungOut) { - if (wasFlungOut) { - mMotionHelper.flingToSnapTarget(velX, velY, null /* endAction */); - hideDismissTargetMaybe(); - } else { - mMotionHelper.setSpringingToTouch(true); + @Override + public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target, + float velX, float velY, boolean wasFlungOut) { + if (wasFlungOut) { + mMotionHelper.flingToSnapTarget(velX, velY, null /* endAction */); + hideDismissTargetMaybe(); + } else { + mMotionHelper.setSpringingToTouch(true); + } } - } - @Override - public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) { - mMotionHelper.notifyDismissalPending(); + @Override + public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) { + mMainExecutor.executeDelayed(() -> { + mMotionHelper.notifyDismissalPending(); + mMotionHelper.animateDismiss(); + hideDismissTargetMaybe(); - handler.post(() -> { - mMotionHelper.animateDismiss(); - hideDismissTargetMaybe(); - }); + mPipUiEventLogger.log( + PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_DRAG_TO_REMOVE); + }, 0); + } + }); - mPipUiEventLogger.log( - PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_DRAG_TO_REMOVE); - } + mMagneticTargetAnimator = PhysicsAnimator.getInstance(mTargetView); }); - - mMagneticTargetAnimator = PhysicsAnimator.getInstance(mTargetView); } /** @@ -200,7 +201,6 @@ public class PipDismissTargetHandler { /** Adds the magnetic target view to the WindowManager so it's ready to be animated in. */ public void createOrUpdateDismissTarget() { if (!mTargetViewContainer.isAttachedToWindow()) { - mHandler.removeCallbacks(mShowTargetAction); mMagneticTargetAnimator.cancel(); mTargetViewContainer.setVisibility(View.INVISIBLE); @@ -270,7 +270,6 @@ public class PipDismissTargetHandler { return; } - mHandler.removeCallbacks(mShowTargetAction); mMagneticTargetAnimator .spring(DynamicAnimation.TRANSLATION_Y, mTargetViewContainer.getHeight(), @@ -286,8 +285,6 @@ public class PipDismissTargetHandler { * Removes the dismiss target and cancels any pending callbacks to show it. */ public void cleanUpDismissTarget() { - mHandler.removeCallbacks(mShowTargetAction); - if (mTargetViewContainer.isAttachedToWindow()) { mWindowManager.removeViewImmediate(mTargetViewContainer); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java index 0c64c8ca9b64..7a634c3eef78 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java @@ -29,6 +29,8 @@ import android.view.IWindowManager; import android.view.InputChannel; import android.view.InputEvent; +import com.android.wm.shell.common.ShellExecutor; + import java.io.PrintWriter; /** @@ -81,6 +83,7 @@ public class PipInputConsumer { private final IWindowManager mWindowManager; private final IBinder mToken; private final String mName; + private final ShellExecutor mMainExecutor; private InputEventReceiver mInputEventReceiver; private InputListener mListener; @@ -89,10 +92,12 @@ public class PipInputConsumer { /** * @param name the name corresponding to the input consumer that is defined in the system. */ - public PipInputConsumer(IWindowManager windowManager, String name) { + public PipInputConsumer(IWindowManager windowManager, String name, + ShellExecutor mainExecutor) { mWindowManager = windowManager; mToken = new Binder(); mName = name; + mMainExecutor = mainExecutor; } /** @@ -107,9 +112,11 @@ public class PipInputConsumer { */ public void setRegistrationListener(RegistrationListener listener) { mRegistrationListener = listener; - if (mRegistrationListener != null) { - mRegistrationListener.onRegistrationChanged(mInputEventReceiver != null); - } + mMainExecutor.execute(() -> { + if (mRegistrationListener != null) { + mRegistrationListener.onRegistrationChanged(mInputEventReceiver != null); + } + }); } /** @@ -125,14 +132,6 @@ public class PipInputConsumer { * Registers the input consumer. */ public void registerInputConsumer() { - registerInputConsumer(false); - } - - /** - * Registers the input consumer. - * @param withSfVsync the flag set using sf vsync signal or no - */ - public void registerInputConsumer(boolean withSfVsync) { if (mInputEventReceiver != null) { return; } @@ -144,11 +143,15 @@ public class PipInputConsumer { } catch (RemoteException e) { Log.e(TAG, "Failed to create input consumer", e); } - mInputEventReceiver = new InputEventReceiver(inputChannel, Looper.myLooper(), - withSfVsync ? Choreographer.getSfInstance() : Choreographer.getInstance()); - if (mRegistrationListener != null) { - mRegistrationListener.onRegistrationChanged(true /* isRegistered */); - } + mMainExecutor.execute(() -> { + // Choreographer.getSfInstance() must be called on the thread that the input event + // receiver should be receiving events + mInputEventReceiver = new InputEventReceiver(inputChannel, + mMainExecutor.getLooper(), Choreographer.getSfInstance()); + if (mRegistrationListener != null) { + mRegistrationListener.onRegistrationChanged(true /* isRegistered */); + } + }); } /** @@ -166,9 +169,11 @@ public class PipInputConsumer { } mInputEventReceiver.dispose(); mInputEventReceiver = null; - if (mRegistrationListener != null) { - mRegistrationListener.onRegistrationChanged(false /* isRegistered */); - } + mMainExecutor.execute(() -> { + if (mRegistrationListener != null) { + mRegistrationListener.onRegistrationChanged(false /* isRegistered */); + } + }); } public void dump(PrintWriter pw, String prefix) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java index 2e10fc93cafb..2e515ee91d3a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java @@ -63,6 +63,7 @@ import android.widget.LinearLayout; import com.android.wm.shell.R; import com.android.wm.shell.animation.Interpolators; +import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.pip.PipUtils; import java.util.ArrayList; @@ -116,7 +117,8 @@ public class PipMenuView extends FrameLayout { } }; - private Handler mHandler = new Handler(); + private ShellExecutor mMainExecutor; + private Handler mMainHandler; private final Runnable mHideMenuRunnable = this::hideMenu; @@ -127,10 +129,13 @@ public class PipMenuView extends FrameLayout { protected View mTopEndContainer; protected PipMenuIconsAlgorithm mPipMenuIconsAlgorithm; - public PipMenuView(Context context, PhonePipMenuController controller) { + public PipMenuView(Context context, PhonePipMenuController controller, + ShellExecutor mainExecutor, Handler mainHandler) { super(context, null, 0); mContext = context; mController = controller; + mMainExecutor = mainExecutor; + mMainHandler = mainHandler; mAccessibilityManager = context.getSystemService(AccessibilityManager.class); inflate(context, R.layout.pip_menu, this); @@ -412,17 +417,15 @@ public class PipMenuView extends FrameLayout { d.setTint(Color.WHITE); actionView.setImageDrawable(d); } - }, mHandler); + }, mMainHandler); actionView.setContentDescription(action.getContentDescription()); if (action.isEnabled()) { actionView.setOnClickListener(v -> { - mHandler.post(() -> { - try { - action.getActionIntent().send(); - } catch (CanceledException e) { - Log.w(TAG, "Failed to send action", e); - } - }); + try { + action.getActionIntent().send(); + } catch (CanceledException e) { + Log.w(TAG, "Failed to send action", e); + } }); } actionView.setEnabled(action.isEnabled()); @@ -480,13 +483,13 @@ public class PipMenuView extends FrameLayout { } private void cancelDelayedHide() { - mHandler.removeCallbacks(mHideMenuRunnable); + mMainExecutor.removeCallbacks(mHideMenuRunnable); } private void repostDelayedHide(int delay) { int recommendedTimeout = mAccessibilityManager.getRecommendedTimeoutMillis(delay, FLAG_CONTENT_ICONS | FLAG_CONTENT_CONTROLS); - mHandler.removeCallbacks(mHideMenuRunnable); - mHandler.postDelayed(mHideMenuRunnable, recommendedTimeout); + mMainExecutor.removeCallbacks(mHideMenuRunnable); + mMainExecutor.executeDelayed(mHideMenuRunnable, recommendedTimeout); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java index 8c8f5c6d4940..4df7cef12f63 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java @@ -40,6 +40,7 @@ import androidx.dynamicanimation.animation.SpringForce; import com.android.wm.shell.animation.FloatProperties; import com.android.wm.shell.animation.PhysicsAnimator; import com.android.wm.shell.common.FloatingContentCoordinator; +import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.magnetictarget.MagnetizedObject; import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipSnapAlgorithm; @@ -74,8 +75,6 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private PhonePipMenuController mMenuController; private PipSnapAlgorithm mSnapAlgorithm; - private final Handler mMainHandler = new Handler(Looper.getMainLooper()); - /** The region that all of PIP must stay within. */ private final Rect mFloatingAllowedArea = new Rect(); @@ -130,10 +129,8 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY); private final Consumer<Rect> mUpdateBoundsCallback = (Rect newBounds) -> { - mMainHandler.post(() -> { - mMenuController.updateMenuLayout(newBounds); - mPipBoundsState.setBounds(newBounds); - }); + mMenuController.updateMenuLayout(newBounds); + mPipBoundsState.setBounds(newBounds); }; /** @@ -174,7 +171,8 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer, PhonePipMenuController menuController, - PipSnapAlgorithm snapAlgorithm, FloatingContentCoordinator floatingContentCoordinator) { + PipSnapAlgorithm snapAlgorithm, FloatingContentCoordinator floatingContentCoordinator, + ShellExecutor mainExecutor) { mContext = context; mPipTaskOrganizer = pipTaskOrganizer; mPipBoundsState = pipBoundsState; @@ -184,8 +182,12 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, mPipTaskOrganizer.registerPipTransitionCallback(mPipTransitionCallback); mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance( mPipBoundsState.getMotionBoundsState().getBoundsInMotion()); - mTemporaryBoundsPhysicsAnimator.setCustomAnimationHandler( - mSfAnimationHandlerThreadLocal.get()); + + // Need to get the shell main thread sf vsync animation handler + mainExecutor.execute(() -> { + mTemporaryBoundsPhysicsAnimator.setCustomAnimationHandler( + mSfAnimationHandlerThreadLocal.get()); + }); mResizePipUpdateListener = (target, values) -> { if (mPipBoundsState.getMotionBoundsState().isInMotion()) { @@ -256,10 +258,8 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, mPipBoundsState.getMotionBoundsState().setBoundsInMotion(toBounds); mPipTaskOrganizer.scheduleUserResizePip(getBounds(), toBounds, (Rect newBounds) -> { - mMainHandler.post(() -> { mMenuController.updateMenuLayout(newBounds); - }); - }); + }); } } else { // If PIP is 'catching up' after being stuck in the dismiss target, update the animation @@ -326,11 +326,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, } cancelPhysicsAnimation(); mMenuController.hideMenuWithoutResize(); - mPipTaskOrganizer.getUpdateHandler().post(() -> { - mPipTaskOrganizer.exitPip(skipAnimation - ? 0 - : LEAVE_PIP_DURATION); - }); + mPipTaskOrganizer.exitPip(skipAnimation ? 0 : LEAVE_PIP_DURATION); } /** @@ -393,7 +389,8 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, .spring(FloatProperties.RECT_WIDTH, getBounds().width(), mSpringConfig) .spring(FloatProperties.RECT_HEIGHT, getBounds().height(), mSpringConfig) .flingThenSpring( - FloatProperties.RECT_X, velocityX, isStash ? mStashConfigX : mFlingConfigX, + FloatProperties.RECT_X, velocityX, + isStash ? mStashConfigX : mFlingConfigX, mSpringConfig, true /* flingMustReachMinOrMax */) .flingThenSpring( FloatProperties.RECT_Y, velocityY, mFlingConfigY, mSpringConfig); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java index 762b73800c23..41cc59d138fa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java @@ -29,7 +29,6 @@ import android.graphics.PointF; import android.graphics.Rect; import android.graphics.Region; import android.hardware.input.InputManager; -import android.os.Handler; import android.os.Looper; import android.provider.DeviceConfig; import android.view.BatchedInputEventReceiver; @@ -45,6 +44,7 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.policy.TaskResizingAlgorithm; import com.android.wm.shell.R; +import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipBoundsState; @@ -52,7 +52,7 @@ import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipUiEventLogger; import java.io.PrintWriter; -import java.util.concurrent.Executor; +import java.util.function.Consumer; import java.util.function.Function; /** @@ -73,7 +73,7 @@ public class PipResizeGestureHandler { private final PhonePipMenuController mPhonePipMenuController; private final PipUiEventLogger mPipUiEventLogger; private final int mDisplayId; - private final Executor mMainExecutor; + private final ShellExecutor mMainExecutor; private final Region mTmpRegion = new Region(); private final PointF mDownPoint = new PointF(); @@ -91,7 +91,6 @@ public class PipResizeGestureHandler { private final Rect mDisplayBounds = new Rect(); private final Function<Rect, Rect> mMovementBoundsSupplier; private final Runnable mUpdateMovementBoundsRunnable; - private final Handler mHandler; private int mDelta; private float mTouchSlop; @@ -119,10 +118,10 @@ public class PipResizeGestureHandler { PipBoundsState pipBoundsState, PipMotionHelper motionHelper, PipTaskOrganizer pipTaskOrganizer, Function<Rect, Rect> movementBoundsSupplier, Runnable updateMovementBoundsRunnable, PipUiEventLogger pipUiEventLogger, - PhonePipMenuController menuActivityController) { + PhonePipMenuController menuActivityController, ShellExecutor mainExecutor) { mContext = context; mDisplayId = context.getDisplayId(); - mMainExecutor = context.getMainExecutor(); + mMainExecutor = mainExecutor; mPipBoundsAlgorithm = pipBoundsAlgorithm; mPipBoundsState = pipBoundsState; mMotionHelper = motionHelper; @@ -131,7 +130,6 @@ public class PipResizeGestureHandler { mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable; mPhonePipMenuController = menuActivityController; mPipUiEventLogger = pipUiEventLogger; - mHandler = new Handler(Looper.getMainLooper()); context.getDisplay().getRealSize(mMaxSize); reloadResources(); @@ -140,7 +138,8 @@ public class PipResizeGestureHandler { DeviceConfig.NAMESPACE_SYSTEMUI, PIP_PINCH_RESIZE, /* defaultValue = */ false); - DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, mMainExecutor, + DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, + mMainExecutor, new DeviceConfig.OnPropertiesChangedListener() { @Override public void onPropertiesChanged(DeviceConfig.Properties properties) { @@ -213,8 +212,8 @@ public class PipResizeGestureHandler { // Register input event receiver mInputMonitor = InputManager.getInstance().monitorGestureInput( "pip-resize", mDisplayId); - mInputEventReceiver = new SysUiInputEventReceiver( - mInputMonitor.getInputChannel(), Looper.getMainLooper()); + mInputEventReceiver = new PipResizeInputEventReceiver( + mInputMonitor.getInputChannel(), mMainExecutor.getLooper()); } } @@ -523,7 +522,7 @@ public class PipResizeGestureHandler { private void finishResize() { if (!mLastResizeBounds.isEmpty()) { - final Runnable callback = () -> { + final Consumer<Rect> callback = (rect) -> { mUserResizeBounds.set(mLastResizeBounds); mMotionHelper.synchronizePinnedStackBounds(); mUpdateMovementBoundsRunnable.run(); @@ -537,16 +536,10 @@ public class PipResizeGestureHandler { mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, mPipBoundsAlgorithm.getSnapFraction(mPipBoundsState.getBounds())); mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds, - PINCH_RESIZE_SNAP_DURATION, mAngle, - (Rect rect) -> { - mHandler.post(callback); - }); + PINCH_RESIZE_SNAP_DURATION, -mAngle, callback); } else { mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds, - PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE, - (Rect bounds) -> { - mHandler.post(callback); - }); + PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE, callback); } mPipUiEventLogger.log( PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_RESIZE); @@ -593,8 +586,8 @@ public class PipResizeGestureHandler { pw.println(innerPrefix + "mThresholdCrossed=" + mThresholdCrossed); } - class SysUiInputEventReceiver extends BatchedInputEventReceiver { - SysUiInputEventReceiver(InputChannel channel, Looper looper) { + class PipResizeInputEventReceiver extends BatchedInputEventReceiver { + PipResizeInputEventReceiver(InputChannel channel, Looper looper) { super(channel, looper, Choreographer.getSfInstance()); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index 75d674e4ceea..c9f5ae28175c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -167,18 +167,20 @@ public class PipTouchHandler { mGesture = new DefaultPipTouchGesture(); mMotionHelper = new PipMotionHelper(mContext, pipBoundsState, pipTaskOrganizer, mMenuController, mPipBoundsAlgorithm.getSnapAlgorithm(), - floatingContentCoordinator); + floatingContentCoordinator, mainExecutor); mPipResizeGestureHandler = new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState, mMotionHelper, pipTaskOrganizer, this::getMovementBounds, - this::updateMovementBounds, pipUiEventLogger, menuController); + this::updateMovementBounds, pipUiEventLogger, menuController, + mainExecutor); mPipDismissTargetHandler = new PipDismissTargetHandler(context, pipUiEventLogger, - mMotionHelper, mHandler); - mTouchState = new PipTouchState(ViewConfiguration.get(context), mHandler, + mMotionHelper, mainExecutor); + mTouchState = new PipTouchState(ViewConfiguration.get(context), () -> mMenuController.showMenuWithDelay(MENU_STATE_FULL, mPipBoundsState.getBounds(), true /* allowMenuTimeout */, willResizeMenu(), shouldShowResizeHandle()), - menuController::hideMenu); + menuController::hideMenu, + mainExecutor); Resources res = context.getResources(); mEnableResize = res.getBoolean(R.bool.config_pipEnableResizeForMenu); @@ -196,7 +198,7 @@ public class PipTouchHandler { PIP_STASHING, /* defaultValue = */ true); DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, - context.getMainExecutor(), + mainExecutor, properties -> { if (properties.getKeyset().contains(PIP_STASHING)) { mEnableStash = properties.getBoolean( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java index 5f2327ce98d6..53303ff2b679 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java @@ -25,6 +25,7 @@ import android.view.VelocityTracker; import android.view.ViewConfiguration; import com.android.internal.annotations.VisibleForTesting; +import com.android.wm.shell.common.ShellExecutor; import java.io.PrintWriter; @@ -39,7 +40,7 @@ public class PipTouchState { public static final long DOUBLE_TAP_TIMEOUT = 200; static final long HOVER_EXIT_TIMEOUT = 50; - private final Handler mHandler; + private final ShellExecutor mMainExecutor; private final ViewConfiguration mViewConfig; private final Runnable mDoubleTapTimeoutCallback; private final Runnable mHoverExitTimeoutCallback; @@ -67,12 +68,12 @@ public class PipTouchState { private int mActivePointerId; private int mLastTouchDisplayId = Display.INVALID_DISPLAY; - public PipTouchState(ViewConfiguration viewConfig, Handler handler, - Runnable doubleTapTimeoutCallback, Runnable hoverExitTimeoutCallback) { + public PipTouchState(ViewConfiguration viewConfig, Runnable doubleTapTimeoutCallback, + Runnable hoverExitTimeoutCallback, ShellExecutor mainExecutor) { mViewConfig = viewConfig; - mHandler = handler; mDoubleTapTimeoutCallback = doubleTapTimeoutCallback; mHoverExitTimeoutCallback = hoverExitTimeoutCallback; + mMainExecutor = mainExecutor; } /** @@ -116,7 +117,7 @@ public class PipTouchState { mIsDragging = false; mLastDownTouchTime = mDownTouchTime; if (mDoubleTapTimeoutCallback != null) { - mHandler.removeCallbacks(mDoubleTapTimeoutCallback); + mMainExecutor.removeCallbacks(mDoubleTapTimeoutCallback); } break; } @@ -324,8 +325,8 @@ public class PipTouchState { public void scheduleDoubleTapTimeoutCallback() { if (mIsWaitingForDoubleTap) { long delay = getDoubleTapTimeoutCallbackDelay(); - mHandler.removeCallbacks(mDoubleTapTimeoutCallback); - mHandler.postDelayed(mDoubleTapTimeoutCallback, delay); + mMainExecutor.removeCallbacks(mDoubleTapTimeoutCallback); + mMainExecutor.executeDelayed(mDoubleTapTimeoutCallback, delay); } } @@ -342,17 +343,17 @@ public class PipTouchState { */ public void removeDoubleTapTimeoutCallback() { mIsWaitingForDoubleTap = false; - mHandler.removeCallbacks(mDoubleTapTimeoutCallback); + mMainExecutor.removeCallbacks(mDoubleTapTimeoutCallback); } @VisibleForTesting public void scheduleHoverExitTimeoutCallback() { - mHandler.removeCallbacks(mHoverExitTimeoutCallback); - mHandler.postDelayed(mHoverExitTimeoutCallback, HOVER_EXIT_TIMEOUT); + mMainExecutor.removeCallbacks(mHoverExitTimeoutCallback); + mMainExecutor.executeDelayed(mHoverExitTimeoutCallback, HOVER_EXIT_TIMEOUT); } void removeHoverExitTimeoutCallback() { - mHandler.removeCallbacks(mHoverExitTimeoutCallback); + mMainExecutor.removeCallbacks(mHoverExitTimeoutCallback); } void addMovementToVelocityTracker(MotionEvent event) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipUpdateThread.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipUpdateThread.java deleted file mode 100644 index d686cac3457b..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipUpdateThread.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.pip.phone; - -import android.os.Handler; -import android.os.HandlerThread; - -/** - * Similar to {@link com.android.internal.os.BackgroundThread}, this is a shared singleton - * foreground thread for each process for updating PIP. - */ -public final class PipUpdateThread extends HandlerThread { - private static PipUpdateThread sInstance; - private static Handler sHandler; - - private PipUpdateThread() { - super("pip"); - } - - private static void ensureThreadLocked() { - if (sInstance == null) { - sInstance = new PipUpdateThread(); - sInstance.start(); - sHandler = new Handler(sInstance.getLooper()); - } - } - - /** - * @return the static update thread instance - */ - public static PipUpdateThread get() { - synchronized (PipUpdateThread.class) { - ensureThreadLocked(); - return sInstance; - } - } - /** - * @return the static update thread handler instance - */ - public static Handler getHandler() { - synchronized (PipUpdateThread.class) { - ensureThreadLocked(); - return sHandler; - } - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java index 8bc60f9dbcd9..61cf22b7164b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java @@ -36,6 +36,7 @@ import android.view.DisplayInfo; import com.android.wm.shell.R; import com.android.wm.shell.WindowManagerShellWrapper; +import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.pip.PinnedStackListenerForwarder; @@ -51,7 +52,7 @@ import java.lang.annotation.RetentionPolicy; /** * Manages the picture-in-picture (PIP) UI and states. */ -public class TvPipController implements Pip, PipTaskOrganizer.PipTransitionCallback, +public class TvPipController implements PipTaskOrganizer.PipTransitionCallback, TvPipMenuController.Delegate, TvPipNotificationController.Delegate { private static final String TAG = "TvPipController"; static final boolean DEBUG = true; @@ -90,13 +91,15 @@ public class TvPipController implements Pip, PipTaskOrganizer.PipTransitionCallb private final PipMediaController mPipMediaController; private final TvPipNotificationController mPipNotificationController; private final TvPipMenuController mTvPipMenuController; + private final ShellExecutor mMainExecutor; + private final TvPipImpl mImpl = new TvPipImpl(); private @State int mState = STATE_NO_PIP; private int mPinnedTaskId = NONEXISTENT_TASK_ID; private int mResizeAnimationDuration; - public TvPipController( + public static Pip create( Context context, PipBoundsState pipBoundsState, PipBoundsAlgorithm pipBoundsAlgorithm, @@ -105,8 +108,34 @@ public class TvPipController implements Pip, PipTaskOrganizer.PipTransitionCallb PipMediaController pipMediaController, TvPipNotificationController pipNotificationController, TaskStackListenerImpl taskStackListener, - WindowManagerShellWrapper wmShell) { + WindowManagerShellWrapper wmShell, + ShellExecutor mainExecutor) { + return new TvPipController( + context, + pipBoundsState, + pipBoundsAlgorithm, + pipTaskOrganizer, + tvPipMenuController, + pipMediaController, + pipNotificationController, + taskStackListener, + wmShell, + mainExecutor).mImpl; + } + + private TvPipController( + Context context, + PipBoundsState pipBoundsState, + PipBoundsAlgorithm pipBoundsAlgorithm, + PipTaskOrganizer pipTaskOrganizer, + TvPipMenuController tvPipMenuController, + PipMediaController pipMediaController, + TvPipNotificationController pipNotificationController, + TaskStackListenerImpl taskStackListener, + WindowManagerShellWrapper wmShell, + ShellExecutor mainExecutor) { mContext = context; + mMainExecutor = mainExecutor; mPipBoundsState = pipBoundsState; mPipBoundsState.setDisplayInfo(getDisplayInfo()); @@ -129,8 +158,7 @@ public class TvPipController implements Pip, PipTaskOrganizer.PipTransitionCallb registerWmShellPinnedStackListener(wmShell); } - @Override - public void onConfigurationChanged(Configuration newConfig) { + private void onConfigurationChanged(Configuration newConfig) { if (DEBUG) Log.d(TAG, "onConfigurationChanged(), state=" + stateToName(mState)); if (isPipShown()) { @@ -145,8 +173,7 @@ public class TvPipController implements Pip, PipTaskOrganizer.PipTransitionCallb /** * Returns {@code true} if Pip is shown. */ - @Override - public boolean isPipShown() { + private boolean isPipShown() { return mState != STATE_NO_PIP; } @@ -211,8 +238,7 @@ public class TvPipController implements Pip, PipTaskOrganizer.PipTransitionCallb * @param state the to determine the Pip bounds. IMPORTANT: should always match the current * state of the Controller. */ - @Override - public void resizePinnedStack(@State int state) { + private void resizePinnedStack(@State int state) { if (state != mState) { throw new IllegalArgumentException("The passed state should match the current state!"); } @@ -240,8 +266,7 @@ public class TvPipController implements Pip, PipTaskOrganizer.PipTransitionCallb mPipTaskOrganizer.scheduleAnimateResizePip(newBounds, mResizeAnimationDuration, null); } - @Override - public void registerSessionListenerForCurrentUser() { + private void registerSessionListenerForCurrentUser() { mPipMediaController.registerSessionListenerForCurrentUser(); } @@ -418,4 +443,20 @@ public class TvPipController implements Pip, PipTaskOrganizer.PipTransitionCallb throw new IllegalArgumentException("Unknown state " + state); } } + + private class TvPipImpl implements Pip { + @Override + public void onConfigurationChanged(Configuration newConfig) { + mMainExecutor.execute(() -> { + TvPipController.this.onConfigurationChanged(newConfig); + }); + } + + @Override + public void registerSessionListenerForCurrentUser() { + mMainExecutor.execute(() -> { + TvPipController.this.registerSessionListenerForCurrentUser(); + }); + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java index 470ab0c9c0e3..ee41b41a743d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java @@ -24,6 +24,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ParceledListSlice; +import android.os.Handler; import android.util.Log; import android.view.SurfaceControl; @@ -47,6 +48,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis private final Context mContext; private final SystemWindows mSystemWindows; private final PipBoundsState mPipBoundsState; + private final Handler mMainHandler; private Delegate mDelegate; private SurfaceControl mLeash; @@ -56,10 +58,12 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis private final List<RemoteAction> mAppActions = new ArrayList<>(); public TvPipMenuController(Context context, PipBoundsState pipBoundsState, - SystemWindows systemWindows, PipMediaController pipMediaController) { + SystemWindows systemWindows, PipMediaController pipMediaController, + Handler mainHandler) { mContext = context; mPipBoundsState = pipBoundsState; mSystemWindows = systemWindows; + mMainHandler = mainHandler; // We need to "close" the menu the platform call for all the system dialogs to close (for // example, on the Home button press). @@ -69,8 +73,9 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis hideMenu(); } }; - context.registerReceiver(closeSystemDialogsBroadcastReceiver, - new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); + context.registerReceiverForAllUsers(closeSystemDialogsBroadcastReceiver, + new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), null /* permission */, + mainHandler); pipMediaController.addActionListener(this::onMediaActionsChanged); } @@ -199,9 +204,9 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis return; } if (!mAppActions.isEmpty()) { - mMenuView.setAdditionalActions(mAppActions); + mMenuView.setAdditionalActions(mAppActions, mMainHandler); } else { - mMenuView.setAdditionalActions(mMediaActions); + mMenuView.setAdditionalActions(mMediaActions, mMainHandler); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java index e08ca52fd2ca..d6cd9ea13ca1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java @@ -49,7 +49,7 @@ import java.util.List; /** * A View that represents Pip Menu on TV. It's responsible for displaying 2 ever-present Pip Menu * actions: Fullscreen and Close, but could also display "additional" actions, that may be set via - * a {@link #setAdditionalActions(List)} call. + * a {@link #setAdditionalActions(List, Handler)} call. */ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { private static final String TAG = "TvPipMenuView"; @@ -57,7 +57,6 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { private static final float DISABLED_ACTION_ALPHA = 0.54f; - private final Handler mUiThreadHandler; private final Animator mFadeInAnimation; private final Animator mFadeOutAnimation; @Nullable private Listener mListener; @@ -80,7 +79,6 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { public TvPipMenuView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - mUiThreadHandler = new Handler(Looper.getMainLooper()); inflate(context, R.layout.tv_pip_menu, this); @@ -132,7 +130,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { } } - void setAdditionalActions(List<RemoteAction> actions) { + void setAdditionalActions(List<RemoteAction> actions, Handler mainHandler) { if (DEBUG) Log.d(TAG, "setAdditionalActions()"); // Make sure we exactly as many additional buttons as we have actions to display. @@ -176,7 +174,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { action.getIcon().loadDrawableAsync(mContext, drawable -> { drawable.setTint(Color.WHITE); button.setImageDrawable(drawable); - }, mUiThreadHandler); + }, mainHandler); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java index ce4b60893367..a47483144fef 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java @@ -27,6 +27,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.media.MediaMetadata; +import android.os.Handler; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; @@ -60,6 +61,7 @@ public class TvPipNotificationController { private final NotificationManager mNotificationManager; private final Notification.Builder mNotificationBuilder; private final ActionBroadcastReceiver mActionBroadcastReceiver; + private final Handler mMainHandler; private Delegate mDelegate; private String mDefaultTitle; @@ -70,10 +72,12 @@ public class TvPipNotificationController { private String mMediaTitle; private Bitmap mArt; - public TvPipNotificationController(Context context, PipMediaController pipMediaController) { + public TvPipNotificationController(Context context, PipMediaController pipMediaController, + Handler mainHandler) { mContext = context; mPackageManager = context.getPackageManager(); mNotificationManager = context.getSystemService(NotificationManager.class); + mMainHandler = mainHandler; mNotificationBuilder = new Notification.Builder(context, NOTIFICATION_CHANNEL) .setLocalOnly(true) @@ -219,7 +223,8 @@ public class TvPipNotificationController { void register() { if (mRegistered) return; - mContext.registerReceiver(this, mIntentFilter, UserHandle.USER_ALL); + mContext.registerReceiverForAllUsers(this, mIntentFilter, null /* permission */, + mMainHandler); mRegistered = true; } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreenTest.kt index ed305a21f444..e79820f520dd 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreenTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreenTest.kt @@ -67,7 +67,8 @@ class NonResizableDismissInLegacySplitScreenTest( visibleLayersShownMoreThanOneConsecutiveEntry( listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName, nonResizeableApp.defaultWindowName, LETTER_BOX_NAME, - TOAST_NAME, LIVE_WALLPAPER_PACKAGE_NAME) + TOAST_NAME, LIVE_WALLPAPER_PACKAGE_NAME), + bugId = 178447631 ) } windowManagerTrace { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreenTest.kt index 88dab51f9b9f..280af5d708c9 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreenTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreenTest.kt @@ -68,7 +68,7 @@ class NonResizableLaunchInLegacySplitScreenTest( listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName, nonResizeableApp.defaultWindowName, LETTER_BOX_NAME, TOAST_NAME, LIVE_WALLPAPER_PACKAGE_NAME), - enabled = false + bugId = 178447631 ) } windowManagerTrace { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipOrientationTest.kt index 292d0efeaeea..5e0760ceeda7 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipOrientationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipOrientationTest.kt @@ -127,7 +127,7 @@ class PipOrientationTest( hasVisibleRegion(pipApp.defaultWindowName, startingBounds) isInvisible(testApp.defaultWindowName) } - end("testApp layer covers fullscreen") { + end("testApp layer covers fullscreen", enabled = false) { hasVisibleRegion(testApp.defaultWindowName, endingBounds) } navBarLayerIsAlwaysVisible(bugId = 140855415) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java index 9eb13fb1c5e7..5f5c30bb6207 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java @@ -46,7 +46,7 @@ public class TestShellExecutor implements ShellExecutor { @Override public boolean hasCallback(Runnable r) { - return !mRunnables.isEmpty(); + return mRunnables.contains(r); } @Override @@ -55,8 +55,8 @@ public class TestShellExecutor implements ShellExecutor { } public void flushAll() { - for (int i = mRunnables.size() - 1; i >= 0; --i) { - mRunnables.get(i).run(); + for (Runnable r : mRunnables) { + r.run(); } mRunnables.clear(); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java index 5e0d51809d44..4cedc483fc21 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java @@ -19,11 +19,13 @@ package com.android.wm.shell.common; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.InsetsState.ITYPE_IME; import static android.view.Surface.ROTATION_0; +import static android.view.WindowInsets.Type.ime; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; @@ -40,18 +42,22 @@ import com.android.internal.view.IInputMethodManager; import org.junit.Before; import org.junit.Test; +import java.util.concurrent.Executor; + @SmallTest public class DisplayImeControllerTest { private SurfaceControl.Transaction mT; private DisplayImeController.PerDisplay mPerDisplay; private IInputMethodManager mMock; + private Executor mExecutor; @Before public void setUp() throws Exception { mT = mock(SurfaceControl.Transaction.class); mMock = mock(IInputMethodManager.class); - mPerDisplay = new DisplayImeController(null, null, Runnable::run, new TransactionPool() { + mExecutor = spy(Runnable::run); + mPerDisplay = new DisplayImeController(null, null, mExecutor, new TransactionPool() { @Override public SurfaceControl.Transaction acquire() { return mT; @@ -65,10 +71,36 @@ public class DisplayImeControllerTest { public IInputMethodManager getImms() { return mMock; } + @Override + void removeImeSurface() { } }.new PerDisplay(DEFAULT_DISPLAY, ROTATION_0); } @Test + public void insetsControlChanged_schedulesNoWorkOnExecutor() { + mPerDisplay.insetsControlChanged(insetsStateWithIme(false), insetsSourceControl()); + verifyZeroInteractions(mExecutor); + } + + @Test + public void insetsChanged_schedulesNoWorkOnExecutor() { + mPerDisplay.insetsChanged(insetsStateWithIme(false)); + verifyZeroInteractions(mExecutor); + } + + @Test + public void showInsets_schedulesNoWorkOnExecutor() { + mPerDisplay.showInsets(ime(), true); + verifyZeroInteractions(mExecutor); + } + + @Test + public void hideInsets_schedulesNoWorkOnExecutor() { + mPerDisplay.hideInsets(ime(), true); + verifyZeroInteractions(mExecutor); + } + + @Test public void reappliesVisibilityToChangedLeash() { verifyZeroInteractions(mT); mPerDisplay.mImeShowing = true; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java index b5d10d71e693..245858d14b49 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java @@ -43,7 +43,9 @@ import android.window.WindowContainerToken; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.pip.phone.PhonePipMenuController; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; @@ -71,6 +73,7 @@ public class PipTaskOrganizerTest extends ShellTestCase { @Mock private PipUiEventLogger mMockPipUiEventLogger; @Mock private Optional<LegacySplitScreen> mMockOptionalSplitScreen; @Mock private ShellTaskOrganizer mMockShellTaskOrganizer; + private TestShellExecutor mMainExecutor; private PipBoundsState mPipBoundsState; private ComponentName mComponent1; @@ -82,10 +85,12 @@ public class PipTaskOrganizerTest extends ShellTestCase { mComponent1 = new ComponentName(mContext, "component1"); mComponent2 = new ComponentName(mContext, "component2"); mPipBoundsState = new PipBoundsState(mContext); + mMainExecutor = new TestShellExecutor(); mSpiedPipTaskOrganizer = spy(new PipTaskOrganizer(mContext, mPipBoundsState, mMockPipBoundsAlgorithm, mMockPhonePipMenuController, mMockPipSurfaceTransactionHelper, mMockOptionalSplitScreen, mMockdDisplayController, - mMockPipUiEventLogger, mMockShellTaskOrganizer)); + mMockPipUiEventLogger, mMockShellTaskOrganizer, mMainExecutor)); + mMainExecutor.flushAll(); preparePipTaskOrg(); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchStateTest.java index 000f7e8b2e85..0d4d1269f767 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchStateTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchStateTest.java @@ -24,18 +24,15 @@ import static android.view.MotionEvent.ACTION_UP; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import android.os.Handler; -import android.os.Looper; import android.os.SystemClock; import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; -import android.testing.TestableLooper.RunWithLooper; import android.view.MotionEvent; import android.view.ViewConfiguration; import androidx.test.filters.SmallTest; import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.TestShellExecutor; import org.junit.Before; import org.junit.Test; @@ -45,23 +42,22 @@ import java.util.concurrent.CountDownLatch; @RunWith(AndroidTestingRunner.class) @SmallTest -@RunWithLooper public class PipTouchStateTest extends ShellTestCase { private PipTouchState mTouchState; private CountDownLatch mDoubleTapCallbackTriggeredLatch; private CountDownLatch mHoverExitCallbackTriggeredLatch; + private TestShellExecutor mShellMainExecutor; @Before public void setUp() throws Exception { + mShellMainExecutor = new TestShellExecutor(); mDoubleTapCallbackTriggeredLatch = new CountDownLatch(1); mHoverExitCallbackTriggeredLatch = new CountDownLatch(1); mTouchState = new PipTouchState(ViewConfiguration.get(getContext()), - Handler.createAsync(Looper.myLooper()), () -> { - mDoubleTapCallbackTriggeredLatch.countDown(); - }, () -> { - mHoverExitCallbackTriggeredLatch.countDown(); - }); + mDoubleTapCallbackTriggeredLatch::countDown, + mHoverExitCallbackTriggeredLatch::countDown, + mShellMainExecutor); assertFalse(mTouchState.isDoubleTap()); assertFalse(mTouchState.isWaitingForDoubleTap()); } @@ -91,9 +87,7 @@ public class PipTouchStateTest extends ShellTestCase { assertTrue(mTouchState.getDoubleTapTimeoutCallbackDelay() == 10); mTouchState.scheduleDoubleTapTimeoutCallback(); - // TODO: Remove this sleep. Its only being added because it speeds up this test a bit. - Thread.sleep(15); - TestableLooper.get(this).processAllMessages(); + mShellMainExecutor.flushAll(); assertTrue(mDoubleTapCallbackTriggeredLatch.getCount() == 0); } @@ -128,17 +122,13 @@ public class PipTouchStateTest extends ShellTestCase { @Test public void testHoverExitTimeout_timeoutCallbackCalled() throws Exception { mTouchState.scheduleHoverExitTimeoutCallback(); - - // TODO: Remove this sleep. Its only being added because it speeds up this test a bit. - Thread.sleep(50); - TestableLooper.get(this).processAllMessages(); + mShellMainExecutor.flushAll(); assertTrue(mHoverExitCallbackTriggeredLatch.getCount() == 0); } @Test public void testHoverExitTimeout_timeoutCallbackNotCalled() throws Exception { mTouchState.scheduleHoverExitTimeoutCallback(); - TestableLooper.get(this).processAllMessages(); assertTrue(mHoverExitCallbackTriggeredLatch.getCount() == 1); } @@ -147,14 +137,12 @@ public class PipTouchStateTest extends ShellTestCase { mTouchState.scheduleHoverExitTimeoutCallback(); mTouchState.onTouchEvent(createMotionEvent(ACTION_BUTTON_PRESS, SystemClock.uptimeMillis(), 0, 0)); - - // TODO: Remove this sleep. Its only being added because it speeds up this test a bit. - Thread.sleep(50); - TestableLooper.get(this).processAllMessages(); + mShellMainExecutor.flushAll(); assertTrue(mHoverExitCallbackTriggeredLatch.getCount() == 1); } private MotionEvent createMotionEvent(int action, long eventTime, float x, float y) { return MotionEvent.obtain(0, eventTime, action, x, y, 0); } + } diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl index 77f7b54368f8..31fb8d03c4a0 100644 --- a/media/java/android/media/session/ISession.aidl +++ b/media/java/android/media/session/ISession.aidl @@ -16,6 +16,7 @@ package android.media.session; import android.app.PendingIntent; +import android.content.ComponentName; import android.content.pm.ParceledListSlice; import android.media.AudioAttributes; import android.media.MediaMetadata; @@ -35,6 +36,7 @@ interface ISession { void setFlags(int flags); void setActive(boolean active); void setMediaButtonReceiver(in PendingIntent mbr); + void setMediaButtonBroadcastReceiver(in ComponentName broadcastReceiver); void setLaunchPendingIntent(in PendingIntent pi); void destroySession(); diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java index 14b236890e4a..24118b086c24 100644 --- a/media/java/android/media/session/MediaSession.java +++ b/media/java/android/media/session/MediaSession.java @@ -23,6 +23,7 @@ import android.annotation.SystemApi; import android.app.Activity; import android.app.PendingIntent; import android.compat.annotation.UnsupportedAppUsage; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.media.AudioAttributes; @@ -131,6 +132,7 @@ public final class MediaSession { public @interface SessionFlags { } private final Object mLock = new Object(); + private Context mContext; private final int mMaxBitmapSize; private final Token mSessionToken; @@ -194,6 +196,7 @@ public final class MediaSession { + "parcelables"); } + mContext = context; mMaxBitmapSize = context.getResources().getDimensionPixelSize( com.android.internal.R.dimen.config_mediaMetadataBitmapMaxSize); mCbStub = new CallbackStub(this); @@ -277,7 +280,10 @@ public final class MediaSession { * * @param mbr The {@link PendingIntent} to send the media button event to. * @see PendingIntent#getActivity + * + * @deprecated Use {@link #setMediaButtonBroadcastReceiver(ComponentName)} instead. */ + @Deprecated public void setMediaButtonReceiver(@Nullable PendingIntent mbr) { try { mBinder.setMediaButtonReceiver(mbr); @@ -287,6 +293,32 @@ public final class MediaSession { } /** + * Set the component name of the manifest-declared {@link android.content.BroadcastReceiver} + * class that should receive media buttons. This allows restarting playback after the session + * has been stopped. If your app is started in this way an {@link Intent#ACTION_MEDIA_BUTTON} + * intent will be sent to the broadcast receiver. + * <p> + * Note: The given {@link android.content.BroadcastReceiver} should belong to the same package + * as the context that was given when creating {@link MediaSession}. + * + * @param broadcastReceiver the component name of the BroadcastReceiver class + */ + public void setMediaButtonBroadcastReceiver(@Nullable ComponentName broadcastReceiver) { + try { + if (broadcastReceiver != null) { + if (!TextUtils.equals(broadcastReceiver.getPackageName(), + mContext.getPackageName())) { + throw new IllegalArgumentException("broadcastReceiver should belong to the same" + + " package as the context given when creating MediaSession."); + } + } + mBinder.setMediaButtonBroadcastReceiver(broadcastReceiver); + } catch (RemoteException e) { + Log.wtf(TAG, "Failure in setMediaButtonBroadcastReceiver.", e); + } + } + + /** * Set any flags for the session. * * @param flags The flags to set for this session. diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java index c6c7142c3bd0..935cb372029d 100755 --- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java @@ -25,7 +25,10 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; -import android.content.pm.PackageParser; +import android.content.pm.parsing.ApkLiteParseUtils; +import android.content.pm.parsing.PackageLite; +import android.content.pm.parsing.result.ParseResult; +import android.content.pm.parsing.result.ParseTypeImpl; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; @@ -143,16 +146,21 @@ public class InstallInstalling extends AlertActivity { File file = new File(mPackageURI.getPath()); try { - PackageParser.PackageLite pkg = PackageParser.parsePackageLite(file, 0); - params.setAppPackageName(pkg.packageName); - params.setInstallLocation(pkg.installLocation); - params.setSize( - PackageHelper.calculateInstalledSize(pkg, false, params.abiOverride)); - } catch (PackageParser.PackageParserException e) { - Log.e(LOG_TAG, "Cannot parse package " + file + ". Assuming defaults."); - Log.e(LOG_TAG, - "Cannot calculate installed size " + file + ". Try only apk size."); - params.setSize(file.length()); + final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing(); + final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite( + input.reset(), file, /* flags */ 0); + if (result.isError()) { + Log.e(LOG_TAG, "Cannot parse package " + file + ". Assuming defaults."); + Log.e(LOG_TAG, + "Cannot calculate installed size " + file + ". Try only apk size."); + params.setSize(file.length()); + } else { + final PackageLite pkg = result.getResult(); + params.setAppPackageName(pkg.getPackageName()); + params.setInstallLocation(pkg.getInstallLocation()); + params.setSize( + PackageHelper.calculateInstalledSize(pkg, params.abiOverride)); + } } catch (IOException e) { Log.e(LOG_TAG, "Cannot calculate installed size " + file + ". Try only apk size."); diff --git a/packages/SettingsLib/SettingsTheme/res/layout/preference_category_settings.xml b/packages/SettingsLib/SettingsTheme/res/layout/preference_category_settings.xml index 4b1b255a3744..4a1b089970c7 100644 --- a/packages/SettingsLib/SettingsTheme/res/layout/preference_category_settings.xml +++ b/packages/SettingsLib/SettingsTheme/res/layout/preference_category_settings.xml @@ -19,14 +19,13 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingLeft="24dp" - android:paddingStart="24dp" android:paddingRight="?android:attr/listPreferredItemPaddingRight" android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" android:background="?android:attr/selectableItemBackground" android:baselineAligned="false" android:layout_marginTop="16dp" - android:gravity="center_vertical"> + android:gravity="center_vertical" + style="@style/PreferenceCategoryStartMargin"> <RelativeLayout android:layout_width="0dp" @@ -57,6 +56,5 @@ android:textColor="?android:attr/textColorSecondary" android:maxLines="10" style="@style/PreferenceSummaryTextStyle"/> - </RelativeLayout> </LinearLayout>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values-sw360dp/styles.xml b/packages/SettingsLib/SettingsTheme/res/values-sw360dp/styles.xml new file mode 100644 index 000000000000..4f402565787c --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/values-sw360dp/styles.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources> + <style name="PreferenceCategoryStartMargin"> + <item name="android:paddingLeft">24dp</item> + <item name="android:paddingStart">24dp</item> + </style> +</resources> diff --git a/packages/SettingsLib/SettingsTheme/res/values/styles.xml b/packages/SettingsLib/SettingsTheme/res/values/styles.xml index 6b285d5b727e..a6623b01fede 100644 --- a/packages/SettingsLib/SettingsTheme/res/values/styles.xml +++ b/packages/SettingsLib/SettingsTheme/res/values/styles.xml @@ -22,4 +22,9 @@ <!-- 0.8 Spacing, 0.8/11 = 0.072727273 --> <item name="android:letterSpacing">0.072727273</item> </style> + + <style name="PreferenceCategoryStartMargin"> + <item name="android:paddingLeft">?android:attr/listPreferredItemPaddingLeft</item> + <item name="android:paddingStart">?android:attr/listPreferredItemPaddingStart</item> + </style> </resources> diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml index fbb01bef0e8b..84dacfdaa2e4 100644 --- a/packages/SettingsProvider/res/values/defaults.xml +++ b/packages/SettingsProvider/res/values/defaults.xml @@ -239,7 +239,7 @@ <bool name="def_aware_lock_enabled">false</bool> <!-- Default for setting for Settings.Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED --> - <bool name="def_hdmiControlAutoDeviceOff">false</bool> + <bool name="def_hdmiControlAutoDeviceOff">true</bool> <!-- Default for Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED --> <bool name="def_swipe_bottom_to_notification_enabled">true</bool> diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 5d8fc0b6bffc..7eab559cb9b9 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -300,6 +300,9 @@ <!-- Permission needed to test mainline permission module rollback --> <uses-permission android:name="android.permission.UPGRADE_RUNTIME_PERMISSIONS" /> + <!-- Permission needed to restart WiFi Subsystem --> + <uses-permission android:name="android.permission.RESTART_WIFI_SUBSYSTEM" /> + <!-- Permission needed to read wifi network credentials for CtsNetTestCases --> <uses-permission android:name="android.permission.NETWORK_AIRPLANE_MODE" /> diff --git a/packages/SystemUI/res/layout/global_screenshot_preview.xml b/packages/SystemUI/res/layout/global_screenshot_preview.xml index 5262407ffef9..100213bdfd8c 100644 --- a/packages/SystemUI/res/layout/global_screenshot_preview.xml +++ b/packages/SystemUI/res/layout/global_screenshot_preview.xml @@ -25,7 +25,7 @@ android:layout_marginBottom="@dimen/screenshot_offset_y" android:scaleType="fitEnd" android:elevation="@dimen/screenshot_preview_elevation" - android:visibility="gone" + android:visibility="invisible" android:background="@drawable/screenshot_rounded_corners" android:adjustViewBounds="true" android:contentDescription="@string/screenshot_edit_label" diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 0a17828bda8e..09710d7e3fad 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -351,7 +351,7 @@ <bool name="config_showNotificationGear">true</bool> <!-- Whether or not a background should be drawn behind a notification. --> - <bool name="config_drawNotificationBackground">false</bool> + <bool name="config_drawNotificationBackground">true</bool> <!-- Whether or the notifications can be shown and dismissed with a drag. --> <bool name="config_enableNotificationShadeDrag">true</bool> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 79cb236ff34a..e5109307c4f4 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -155,7 +155,7 @@ <dimen name="notification_max_heads_up_height_increased">188dp</dimen> <!-- Side padding on the lockscreen on the side of notifications --> - <dimen name="notification_side_paddings">16dp</dimen> + <dimen name="notification_side_paddings">4dp</dimen> <!-- padding between the heads up and the statusbar --> <dimen name="heads_up_status_bar_padding">8dp</dimen> @@ -177,7 +177,10 @@ <dimen name="notification_min_interaction_height">40dp</dimen> <!-- Radius for notifications corners without adjacent notifications --> - <dimen name="notification_corner_radius">28dp</dimen> + <dimen name="notification_corner_radius">8dp</dimen> + + <!-- Radius for notifications corners with adjacent notifications --> + <dimen name="notification_corner_radius_small">0dp</dimen> <!-- the padding of the shelf icon container --> <dimen name="shelf_icon_container_padding">13dp</dimen> @@ -619,7 +622,7 @@ <dimen name="z_distance_between_notifications">0.5dp</dimen> <!-- The height of the divider between the individual notifications. --> - <dimen name="notification_divider_height">2dp</dimen> + <dimen name="notification_divider_height">1dp</dimen> <!-- The corner radius of the shadow behind the notification. --> <dimen name="notification_shadow_radius">0dp</dimen> @@ -632,7 +635,7 @@ <dimen name="notification_children_container_divider_height">0.5dp</dimen> <!-- The horizontal margin of the content in the notification shade --> - <dimen name="notification_shade_content_margin_horizontal">16dp</dimen> + <dimen name="notification_shade_content_margin_horizontal">4dp</dimen> <!-- The top margin for the notification children container in its non-expanded form. --> <dimen name="notification_children_container_margin_top"> diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml index 905a575d3bb8..49f91096543a 100644 --- a/packages/SystemUI/res/values/flags.xml +++ b/packages/SystemUI/res/values/flags.xml @@ -16,10 +16,12 @@ --> <resources> - <bool name="are_flags_overrideable">true</bool> + <bool name="are_flags_overrideable">false</bool> <bool name="flag_notification_pipeline2">false</bool> <bool name="flag_notification_pipeline2_rendering">false</bool> + <bool name="flag_notif_updates">false</bool> + <bool name="flag_shade_is_opaque">false</bool> <!-- b/171917882 --> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java index e5c4bf32db3a..9164137feb41 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java @@ -24,6 +24,7 @@ import static android.app.ActivityTaskManager.getService; import android.annotation.NonNull; import android.app.Activity; +import android.app.ActivityClient; import android.app.ActivityManager; import android.app.ActivityManager.RecentTaskInfo; import android.app.ActivityManager.RunningTaskInfo; @@ -140,8 +141,9 @@ public class ActivityManagerWrapper { */ public void invalidateHomeTaskSnapshot(final Activity homeActivity) { try { - getService().invalidateHomeTaskSnapshot(homeActivity.getActivityToken()); - } catch (RemoteException e) { + ActivityClient.getInstance().invalidateHomeTaskSnapshot( + homeActivity.getActivityToken()); + } catch (Throwable e) { Log.w(TAG, "Failed to invalidate home snapshot", e); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index 2d972e04bd1d..6eb54c25a440 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -192,7 +192,7 @@ public class KeyguardClockSwitch extends RelativeLayout { statusAreaLP.removeRule(RelativeLayout.ALIGN_PARENT_START); statusAreaLP.removeRule(RelativeLayout.START_OF); statusAreaLP.addRule(RelativeLayout.BELOW, R.id.clock_view); - statusAreaLP.width = ViewGroup.LayoutParams.WRAP_CONTENT; + statusAreaLP.width = ViewGroup.LayoutParams.MATCH_PARENT; } requestLayout(); diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java index 8ebcb20fe63f..756d6107570a 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java @@ -156,9 +156,14 @@ public class NumPadKey extends ViewGroup { } @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - mAnimator.updateMargin((ViewGroup.MarginLayoutParams) getLayoutParams()); + public void setLayoutParams(ViewGroup.LayoutParams params) { + mAnimator.updateMargin((ViewGroup.MarginLayoutParams) params); + + super.setLayoutParams(params); + } + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); measureChildren(widthMeasureSpec, heightMeasureSpec); diff --git a/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java b/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java index 02f34ac3dec0..5384ddfd18bf 100644 --- a/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java +++ b/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java @@ -16,7 +16,7 @@ package com.android.systemui; -import android.app.ActivityTaskManager; +import android.app.ActivityClient; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.Color; @@ -27,7 +27,6 @@ import android.graphics.drawable.RippleDrawable; import android.hardware.display.DisplayManager; import android.inputmethodservice.InputMethodService; import android.os.IBinder; -import android.os.RemoteException; import android.util.Log; import android.util.SparseArray; import android.view.Display; @@ -237,12 +236,7 @@ public class SizeCompatModeActivityController extends SystemUI implements Comman @Override public void onClick(View v) { - try { - ActivityTaskManager.getService().restartActivityProcessIfVisible( - mLastActivityToken); - } catch (RemoteException e) { - Log.w(TAG, "Unable to restart activity", e); - } + ActivityClient.getInstance().restartActivityProcessIfVisible(mLastActivityToken); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java index 5fc2e5361689..40fe7b17f37d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimation.java @@ -47,4 +47,9 @@ public abstract class UdfpsAnimation extends Drawable { (int) sensorRect.right - margin, (int) sensorRect.bottom - margin); } + + @Override + public void setAlpha(int alpha) { + mFingerprintDrawable.setAlpha(alpha); + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java index 58ebadeb3f77..52662ae93376 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationEnroll.java @@ -28,6 +28,8 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.internal.graphics.ColorUtils; +import com.android.settingslib.Utils; import com.android.systemui.R; /** @@ -36,8 +38,11 @@ import com.android.systemui.R; public class UdfpsAnimationEnroll extends UdfpsAnimation { private static final String TAG = "UdfpsAnimationEnroll"; + private static final float SHADOW_RADIUS = 5.f; + @Nullable private RectF mSensorRect; @NonNull private final Paint mSensorPaint; + private final int mNotificationShadeColor; UdfpsAnimationEnroll(@NonNull Context context) { super(context); @@ -45,8 +50,11 @@ public class UdfpsAnimationEnroll extends UdfpsAnimation { mSensorPaint = new Paint(0 /* flags */); mSensorPaint.setAntiAlias(true); mSensorPaint.setColor(Color.WHITE); - mSensorPaint.setShadowLayer(UdfpsView.SENSOR_SHADOW_RADIUS, 0, 0, Color.BLACK); + mSensorPaint.setShadowLayer(SHADOW_RADIUS, 0, 0, Color.BLACK); mSensorPaint.setStyle(Paint.Style.FILL); + + mNotificationShadeColor = Utils.getColorAttr(context, + android.R.attr.colorBackgroundFloating).getDefaultColor(); } @Override @@ -74,7 +82,14 @@ public class UdfpsAnimationEnroll extends UdfpsAnimation { @Override public void setAlpha(int alpha) { - + super.setAlpha(alpha); + + // Gradually fade into the notification shade color. This needs to be done because the + // UDFPS view is drawn on a layer on top of the notification shade + final float percent = alpha / 255.f; + mSensorPaint.setColor(ColorUtils.blendARGB(mNotificationShadeColor, Color.WHITE, percent)); + mSensorPaint.setShadowLayer(SHADOW_RADIUS, 0, 0, + ColorUtils.blendARGB(mNotificationShadeColor, Color.BLACK, percent)); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationFpmOther.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationFpmOther.java index 7563e739c93a..efc864ade5ff 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationFpmOther.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationFpmOther.java @@ -44,11 +44,6 @@ public class UdfpsAnimationFpmOther extends UdfpsAnimation { } @Override - public void setAlpha(int alpha) { - - } - - @Override public void setColorFilter(@Nullable ColorFilter colorFilter) { } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java index 3e46a656ecd5..501de9df575b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationKeyguard.java @@ -99,11 +99,6 @@ public class UdfpsAnimationKeyguard extends UdfpsAnimation implements DozeReceiv } @Override - public void setAlpha(int alpha) { - - } - - @Override public void setColorFilter(@Nullable ColorFilter colorFilter) { } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 001730a7e4fe..b373cff489f6 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -47,9 +47,11 @@ import androidx.annotation.Nullable; import com.android.internal.BrightnessSynchronizer; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.R; +import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.DozeReceiver; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.settings.SystemSettings; @@ -70,7 +72,8 @@ import javax.inject.Inject; * {@code sensorId} parameters. */ @SuppressWarnings("deprecation") -class UdfpsController implements DozeReceiver { +@SysUISingleton +public class UdfpsController implements DozeReceiver { private static final String TAG = "UdfpsController"; // Gamma approximation for the sRGB color space. private static final float DISPLAY_GAMMA = 2.2f; @@ -176,7 +179,7 @@ class UdfpsController implements DozeReceiver { }; @Inject - UdfpsController(@NonNull Context context, + public UdfpsController(@NonNull Context context, @Main Resources resources, LayoutInflater inflater, @Nullable FingerprintManager fingerprintManager, @@ -184,7 +187,8 @@ class UdfpsController implements DozeReceiver { WindowManager windowManager, SystemSettings systemSettings, @NonNull StatusBarStateController statusBarStateController, - @Main DelayableExecutor fgExecutor) { + @Main DelayableExecutor fgExecutor, + @NonNull ScrimController scrimController) { mContext = context; // The fingerprint manager is queried for UDFPS before this class is constructed, so the // fingerprint manager should never be null. @@ -220,6 +224,8 @@ class UdfpsController implements DozeReceiver { mHbmSupported = !TextUtils.isEmpty(mHbmPath); mView.setHbmSupported(mHbmSupported); + scrimController.addScrimChangedListener(mView); + statusBarStateController.addCallback(mView); // This range only consists of the minimum and maximum values, which only cover // non-high-brightness mode. @@ -460,7 +466,7 @@ class UdfpsController implements DozeReceiver { onFingerUp(); } - private void onFingerDown(int x, int y, float minor, float major) { + protected void onFingerDown(int x, int y, float minor, float major) { if (mHbmSupported) { try { FileWriter fw = new FileWriter(mHbmPath); @@ -478,7 +484,7 @@ class UdfpsController implements DozeReceiver { mView.showScrimAndDot(); } - private void onFingerUp() { + protected void onFingerUp() { mFingerprintManager.onPointerUp(mSensorProps.sensorId); // Hiding the scrim before disabling HBM results in less noticeable flicker. mView.hideScrimAndDot(); @@ -517,4 +523,8 @@ class UdfpsController implements DozeReceiver { } return normalizedBacklight; } + + protected UdfpsView getView() { + return mView; + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java index 983206e7984a..96ecc7bdb017 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java @@ -16,6 +16,10 @@ package com.android.systemui.biometrics; +import static com.android.systemui.statusbar.StatusBarState.FULLSCREEN_USER_SWITCHER; +import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; +import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; @@ -23,6 +27,7 @@ import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; +import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.RectF; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; @@ -30,20 +35,24 @@ import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.view.Surface; -import android.view.View; +import android.view.SurfaceHolder; +import android.view.SurfaceView; import android.view.ViewTreeObserver; import com.android.systemui.R; import com.android.systemui.doze.DozeReceiver; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.phone.ScrimController; /** * A full screen view with a configurable illumination dot and scrim. */ -public class UdfpsView extends View implements DozeReceiver { +public class UdfpsView extends SurfaceView implements DozeReceiver, + StatusBarStateController.StateListener, ScrimController.ScrimChangedListener { private static final String TAG = "UdfpsView"; // Values in pixels. - public static final float SENSOR_SHADOW_RADIUS = 2.0f; + private static final float SENSOR_SHADOW_RADIUS = 2.0f; private static final int DEBUG_TEXT_SIZE_PX = 32; @@ -70,11 +79,37 @@ public class UdfpsView extends View implements DozeReceiver { private boolean mShowScrimAndDot; private boolean mIsHbmSupported; @Nullable private String mDebugMessage; + private int mStatusBarState; + private boolean mNotificationShadeExpanded; + private int mNotificationPanelAlpha; // Runnable that will be run after the illumination dot and scrim are shown. // The runnable is reset to null after it's executed once. @Nullable private Runnable mRunAfterShowingScrimAndDot; + @NonNull private final SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() { + @Override + public void surfaceCreated(@NonNull SurfaceHolder holder) { + Log.d(TAG, "Surface created"); + // SurfaceView sets this to true by default. We must set it to false to allow + // onDraw to be called + setWillNotDraw(false); + } + + @Override + public void surfaceChanged(@NonNull SurfaceHolder holder, int format, + int width, int height) { + + } + + @Override + public void surfaceDestroyed(@NonNull SurfaceHolder holder) { + Log.d(TAG, "Surface destroyed"); + // Must not draw when the surface is destroyed + setWillNotDraw(true); + } + }; + public UdfpsView(Context context, AttributeSet attrs) { super(context, attrs); @@ -91,6 +126,8 @@ public class UdfpsView extends View implements DozeReceiver { a.recycle(); } + getHolder().addCallback(mSurfaceCallback); + getHolder().setFormat(PixelFormat.TRANSLUCENT); mScrimRect = new Rect(); mScrimPaint = new Paint(0 /* flags */); @@ -136,6 +173,22 @@ public class UdfpsView extends View implements DozeReceiver { } } + @Override + public void onExpandedChanged(boolean isExpanded) { + mNotificationShadeExpanded = isExpanded; + } + + @Override + public void onStateChanged(int newState) { + mStatusBarState = newState; + } + + @Override + public void onAlphaChanged(float alpha) { + mNotificationPanelAlpha = (int) (alpha * 255); + postInvalidate(); + } + // The "h" and "w" are the display's height and width relative to its current rotation. protected void updateSensorRect(int h, int w) { // mSensorProps coordinates assume portrait mode. @@ -147,10 +200,12 @@ public class UdfpsView extends View implements DozeReceiver { // Transform mSensorRect if the device is in landscape mode. switch (mContext.getDisplay().getRotation()) { case Surface.ROTATION_90: + //noinspection SuspiciousNameCombination mSensorRect.set(mSensorRect.top, h - mSensorRect.right, mSensorRect.bottom, h - mSensorRect.left); break; case Surface.ROTATION_270: + //noinspection SuspiciousNameCombination mSensorRect.set(w - mSensorRect.bottom, mSensorRect.left, w - mSensorRect.top, mSensorRect.right); break; @@ -222,6 +277,8 @@ public class UdfpsView extends View implements DozeReceiver { canvas.drawOval(mSensorRect, mSensorPaint); } else { if (mUdfpsAnimation != null) { + final int alpha = shouldPauseAuth() ? 255 - mNotificationPanelAlpha : 255; + mUdfpsAnimation.setAlpha(alpha); mUdfpsAnimation.draw(canvas); } } @@ -260,7 +317,19 @@ public class UdfpsView extends View implements DozeReceiver { return x > (cx - rx * mSensorTouchAreaCoefficient) && x < (cx + rx * mSensorTouchAreaCoefficient) && y > (cy - ry * mSensorTouchAreaCoefficient) - && y < (cy + ry * mSensorTouchAreaCoefficient); + && y < (cy + ry * mSensorTouchAreaCoefficient) + && !shouldPauseAuth(); + } + + /** + * States where UDFPS should temporarily not be authenticating. Instead of completely stopping + * authentication which would cause the UDFPS icons to abruptly disappear, do it here by not + * sending onFingerDown and smoothly animating away. + */ + private boolean shouldPauseAuth() { + return (mNotificationShadeExpanded && mStatusBarState != KEYGUARD) + || mStatusBarState == SHADE_LOCKED + || mStatusBarState == FULLSCREEN_USER_SWITCHER; } void setScrimAlpha(int alpha) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index b71036c8fbc5..4431b6974b7b 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -49,7 +49,6 @@ import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.media.MediaActionSound; import android.net.Uri; -import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -67,6 +66,7 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.SurfaceControl; import android.view.View; +import android.view.ViewTreeObserver; import android.view.Window; import android.view.WindowInsets; import android.view.WindowManager; @@ -80,13 +80,15 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.policy.PhoneWindow; import com.android.settingslib.applications.InterestingConfigChanges; import com.android.systemui.R; -import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition; +import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback; import com.android.systemui.util.DeviceConfigProxy; import java.util.List; import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.function.Consumer; import java.util.function.Supplier; @@ -173,7 +175,7 @@ public class ScreenshotController { private final UiEventLogger mUiEventLogger; private final ImageExporter mImageExporter; private final Executor mMainExecutor; - private final Executor mBgExecutor; + private final ExecutorService mBgExecutor; private final WindowManager mWindowManager; private final WindowManager.LayoutParams mWindowLayoutParams; @@ -182,17 +184,14 @@ public class ScreenshotController { private final ScrollCaptureClient mScrollCaptureClient; private final DeviceConfigProxy mConfigProxy; private final PhoneWindow mWindow; - private final View mDecorView; private final DisplayManager mDisplayManager; - private final Binder mWindowToken; private ScreenshotView mScreenshotView; private Bitmap mScreenBitmap; private SaveImageInBackgroundTask mSaveInBgTask; private Animator mScreenshotAnimation; - - private Runnable mOnCompleteRunnable; + private RequestCallback mCurrentRequestCallback; private final Handler mScreenshotHandler = new Handler(Looper.getMainLooper()) { @Override @@ -229,15 +228,14 @@ public class ScreenshotController { UiEventLogger uiEventLogger, DeviceConfigProxy configProxy, ImageExporter imageExporter, - @Main Executor mainExecutor, - @Background Executor bgExecutor) { + @Main Executor mainExecutor) { mScreenshotSmartActions = screenshotSmartActions; mNotificationsController = screenshotNotificationsController; mScrollCaptureClient = scrollCaptureClient; mUiEventLogger = uiEventLogger; mImageExporter = imageExporter; mMainExecutor = mainExecutor; - mBgExecutor = bgExecutor; + mBgExecutor = Executors.newSingleThreadExecutor(); mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class)); final Context displayContext = context.createDisplayContext(getDefaultDisplay()); @@ -247,9 +245,6 @@ public class ScreenshotController { mAccessibilityManager = AccessibilityManager.getInstance(mContext); mConfigProxy = configProxy; - mWindowToken = new Binder("ScreenshotController"); - mScrollCaptureClient.setHostWindowToken(mWindowToken); - // Setup the window that we are going to use mWindowLayoutParams = new WindowManager.LayoutParams( MATCH_PARENT, MATCH_PARENT, /* xpos */ 0, /* ypos */ 0, TYPE_SCREENSHOT, @@ -263,7 +258,6 @@ public class ScreenshotController { mWindowLayoutParams.setTitle("ScreenshotAnimation"); mWindowLayoutParams.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - mWindowLayoutParams.token = mWindowToken; // This is needed to let touches pass through outside the touchable areas mWindowLayoutParams.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; @@ -272,8 +266,8 @@ public class ScreenshotController { mWindow.requestFeature(Window.FEATURE_NO_TITLE); mWindow.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS); mWindow.setBackgroundDrawableResource(android.R.color.transparent); - mDecorView = mWindow.getDecorView(); + mConfigChanges.applyNewConfig(context.getResources()); reloadAssets(); // Setup the Camera shutter sound @@ -281,9 +275,8 @@ public class ScreenshotController { mCameraSound.load(MediaActionSound.SHUTTER_CLICK); } - void takeScreenshotFullscreen(Consumer<Uri> finisher, Runnable onComplete) { - mOnCompleteRunnable = onComplete; - + void takeScreenshotFullscreen(Consumer<Uri> finisher, RequestCallback requestCallback) { + mCurrentRequestCallback = requestCallback; DisplayMetrics displayMetrics = new DisplayMetrics(); getDefaultDisplay().getRealMetrics(displayMetrics); takeScreenshotInternal( @@ -293,16 +286,14 @@ public class ScreenshotController { void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds, Insets visibleInsets, int taskId, int userId, ComponentName topComponent, - Consumer<Uri> finisher, Runnable onComplete) { + Consumer<Uri> finisher, RequestCallback requestCallback) { // TODO: use task Id, userId, topComponent for smart handler - mOnCompleteRunnable = onComplete; if (screenshot == null) { Log.e(TAG, "Got null bitmap from screenshot message"); mNotificationsController.notifyScreenshotError( R.string.screenshot_failed_to_capture_text); - finisher.accept(null); - mOnCompleteRunnable.run(); + requestCallback.reportError(); return; } @@ -312,17 +303,19 @@ public class ScreenshotController { visibleInsets = Insets.NONE; screenshotScreenBounds.set(0, 0, screenshot.getWidth(), screenshot.getHeight()); } + mCurrentRequestCallback = requestCallback; saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, showFlash); } /** * Displays a screenshot selector */ - void takeScreenshotPartial(final Consumer<Uri> finisher, Runnable onComplete) { - dismissScreenshot(true); - mOnCompleteRunnable = onComplete; + void takeScreenshotPartial(final Consumer<Uri> finisher, RequestCallback requestCallback) { + mScreenshotView.reset(); + mCurrentRequestCallback = requestCallback; - mWindowManager.addView(mScreenshotView, mWindowLayoutParams); + attachWindow(); + mWindow.setContentView(mScreenshotView); mScreenshotView.takePartialScreenshot( rect -> takeScreenshotInternal(finisher, rect)); @@ -343,9 +336,9 @@ public class ScreenshotController { } return; } - mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT); + cancelTimeout(); if (immediate) { - resetScreenshotView(); + finishDismiss(); } else { mScreenshotView.animateDismissal(); } @@ -360,6 +353,8 @@ public class ScreenshotController { */ void releaseContext() { mContext.release(); + mCameraSound.release(); + mBgExecutor.shutdownNow(); } /** @@ -369,12 +364,8 @@ public class ScreenshotController { if (DEBUG_UI) { Log.d(TAG, "reloadAssets()"); } - boolean wasAttached = mDecorView.isAttachedToWindow(); - if (wasAttached) { - if (DEBUG_WINDOW) { - Log.d(TAG, "Removing screenshot window"); - } - mWindowManager.removeView(mDecorView); + if (mScreenshotView != null && mScreenshotView.isAttachedToWindow()) { + mWindow.clearContentView(); // Is there a simpler way to say "remove screenshotView?" } // respect the display cutout in landscape (since we'd otherwise overlap) but not portrait @@ -382,12 +373,6 @@ public class ScreenshotController { mWindowLayoutParams.setFitInsetsTypes( orientation == ORIENTATION_PORTRAIT ? 0 : WindowInsets.Type.displayCutout()); - // ignore system bar insets for the purpose of window layout - mDecorView.setOnApplyWindowInsetsListener((v, insets) -> v.onApplyWindowInsets( - new WindowInsets.Builder(insets) - .setInsets(WindowInsets.Type.all(), Insets.NONE) - .build())); - // Inflate the screenshot layout mScreenshotView = (ScreenshotView) LayoutInflater.from(mContext).inflate(R.layout.global_screenshot, null); @@ -399,12 +384,18 @@ public class ScreenshotController { @Override public void onDismiss() { - resetScreenshotView(); + finishDismiss(); } }); + // ignore system bar insets for the purpose of window layout + mScreenshotView.setOnApplyWindowInsetsListener((v, insets) -> v.onApplyWindowInsets( + new WindowInsets.Builder(insets) + .setInsets(WindowInsets.Type.all(), Insets.NONE) + .build())); + // TODO(159460485): Remove this when focus is handled properly in the system - mDecorView.setOnTouchListener((v, event) -> { + mScreenshotView.setOnTouchListener((v, event) -> { if (event.getActionMasked() == MotionEvent.ACTION_OUTSIDE) { if (DEBUG_INPUT) { Log.d(TAG, "onTouch: ACTION_OUTSIDE"); @@ -426,8 +417,10 @@ public class ScreenshotController { return false; }); - // view is added to window manager in startAnimation - mWindow.setContentView(mScreenshotView, mWindowLayoutParams); + if (DEBUG_WINDOW) { + Log.d(TAG, "adding OnComputeInternalInsetsListener"); + } + mScreenshotView.getViewTreeObserver().addOnComputeInternalInsetsListener(mScreenshotView); } /** @@ -464,14 +457,9 @@ public class ScreenshotController { Log.e(TAG, "takeScreenshotInternal: Screenshot bitmap was null"); mNotificationsController.notifyScreenshotError( R.string.screenshot_failed_to_capture_text); - if (DEBUG_CALLBACK) { - Log.d(TAG, "Supplying null to Consumer<Uri>"); - } - finisher.accept(null); - if (DEBUG_CALLBACK) { - Log.d(TAG, "Calling mOnCompleteRunnable.run()"); + if (mCurrentRequestCallback != null) { + mCurrentRequestCallback.reportError(); } - mOnCompleteRunnable.run(); return; } @@ -488,6 +476,13 @@ public class ScreenshotController { mAccessibilityManager.sendAccessibilityEvent(event); } + if (mConfigChanges.applyNewConfig(mContext.getResources())) { + if (DEBUG_UI) { + Log.d(TAG, "saveScreenshot: reloading assets"); + } + reloadAssets(); + } + if (mScreenshotView.isAttachedToWindow()) { // if we didn't already dismiss for another reason if (!mScreenshotView.isDismissing()) { @@ -514,33 +509,101 @@ public class ScreenshotController { mScreenBitmap.setHasAlpha(false); mScreenBitmap.prepareToDraw(); - if (mConfigChanges.applyNewConfig(mContext.getResources())) { - if (DEBUG_UI) { - Log.d(TAG, "saveScreenshot: reloading assets"); - } - reloadAssets(); - } + saveScreenshotInWorkerThread(finisher, this::showUiOnActionsReady); // The window is focusable by default setWindowFocusable(true); - // Start the post-screenshot animation - startAnimation(finisher, screenRect, screenInsets, showFlash); - if (mConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.SCREENSHOT_SCROLLING_ENABLED, false)) { - mScrollCaptureClient.request(DEFAULT_DISPLAY, (connection) -> - mScreenshotView.showScrollChip(() -> - runScrollCapture(connection, - () -> mScreenshotHandler.post( - () -> dismissScreenshot(false))))); + View decorView = mWindow.getDecorView(); + + // Wait until this window is attached to request because it is + // the reference used to locate the target window (below). + withWindowAttached(() -> { + mScrollCaptureClient.setHostWindowToken(decorView.getWindowToken()); + mScrollCaptureClient.request(DEFAULT_DISPLAY, + /* onConnection */ + (connection) -> mScreenshotHandler.post(() -> + mScreenshotView.showScrollChip(() -> + /* onClick */ + runScrollCapture(connection)))); + }); + } + + + attachWindow(); + if (DEBUG_WINDOW) { + Log.d(TAG, "setContentView: " + mScreenshotView); } + mScreenshotView.getViewTreeObserver().addOnPreDrawListener( + new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + if (DEBUG_WINDOW) { + Log.d(TAG, "onPreDraw: startAnimation"); + } + mScreenshotView.getViewTreeObserver().removeOnPreDrawListener(this); + startAnimation(screenRect, showFlash); + return true; + } + }); + mScreenshotView.setScreenshot(mScreenBitmap, screenInsets); + setContentView(mScreenshotView); + cancelTimeout(); // restarted after animation } - private void runScrollCapture(ScrollCaptureClient.Connection connection, Runnable andThen) { + private void withWindowAttached(Runnable action) { + View decorView = mWindow.getDecorView(); + if (decorView.isAttachedToWindow()) { + action.run(); + } else { + decorView.getViewTreeObserver().addOnWindowAttachListener( + new ViewTreeObserver.OnWindowAttachListener() { + @Override + public void onWindowAttached() { + decorView.getViewTreeObserver().removeOnWindowAttachListener(this); + action.run(); + } + + @Override + public void onWindowDetached() { } + }); + + } + } + + private void setContentView(View contentView) { + mWindow.setContentView(contentView); + } + + private void attachWindow() { + View decorView = mWindow.getDecorView(); + if (decorView.isAttachedToWindow()) { + return; + } + if (DEBUG_WINDOW) { + Log.d(TAG, "attachWindow"); + } + mWindowManager.addView(decorView, mWindowLayoutParams); + decorView.requestApplyInsets(); + } + + void removeWindow() { + final View decorView = mWindow.peekDecorView(); + if (decorView != null && decorView.isAttachedToWindow()) { + if (DEBUG_WINDOW) { + Log.d(TAG, "Removing screenshot window"); + } + mWindowManager.removeViewImmediate(decorView); + } + } + + private void runScrollCapture(ScrollCaptureClient.Connection connection) { + cancelTimeout(); ScrollCaptureController controller = new ScrollCaptureController(mContext, connection, mMainExecutor, mBgExecutor, mImageExporter); - controller.run(andThen); + controller.start(/* onDismiss */ () -> dismissScreenshot(false)); } /** @@ -549,11 +612,11 @@ public class ScreenshotController { */ private void saveScreenshotAndToast(Consumer<Uri> finisher) { // Play the shutter sound to notify that we've taken a screenshot - mScreenshotHandler.post(() -> { - mCameraSound.play(MediaActionSound.SHUTTER_CLICK); - }); + mCameraSound.play(MediaActionSound.SHUTTER_CLICK); - saveScreenshotInWorkerThread(finisher, imageData -> { + saveScreenshotInWorkerThread( + /* onComplete */ finisher, + /* actionsReadyListener */ imageData -> { if (DEBUG_CALLBACK) { Log.d(TAG, "returning URI to finisher (Consumer<URI>): " + imageData.uri); } @@ -573,55 +636,35 @@ public class ScreenshotController { /** * Starts the animation after taking the screenshot */ - private void startAnimation(final Consumer<Uri> finisher, Rect screenRect, Insets screenInsets, - boolean showFlash) { - mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT); - mScreenshotHandler.post(() -> { - if (!mScreenshotView.isAttachedToWindow()) { - if (DEBUG_WINDOW) { - Log.d(TAG, "Adding screenshot window"); - } - mWindowManager.addView(mWindow.getDecorView(), mWindowLayoutParams); - } - - mScreenshotView.prepareForAnimation(mScreenBitmap, screenInsets); - - mScreenshotHandler.post(() -> { - if (DEBUG_WINDOW) { - Log.d(TAG, "adding OnComputeInternalInsetsListener"); - } - mScreenshotView.getViewTreeObserver().addOnComputeInternalInsetsListener( - mScreenshotView); - - mScreenshotAnimation = - mScreenshotView.createScreenshotDropInAnimation(screenRect, showFlash); + private void startAnimation(Rect screenRect, boolean showFlash) { + if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) { + mScreenshotAnimation.cancel(); + } - saveScreenshotInWorkerThread(finisher, this::showUiOnActionsReady); + mScreenshotAnimation = + mScreenshotView.createScreenshotDropInAnimation(screenRect, showFlash); - // Play the shutter sound to notify that we've taken a screenshot - mCameraSound.play(MediaActionSound.SHUTTER_CLICK); + // Play the shutter sound to notify that we've taken a screenshot + mCameraSound.play(MediaActionSound.SHUTTER_CLICK); - if (DEBUG_ANIM) { - Log.d(TAG, "starting post-screenshot animation"); - } - mScreenshotAnimation.start(); - }); - }); + if (DEBUG_ANIM) { + Log.d(TAG, "starting post-screenshot animation"); + } + mScreenshotAnimation.start(); } /** Reset screenshot view and then call onCompleteRunnable */ - private void resetScreenshotView() { + private void finishDismiss() { if (DEBUG_UI) { - Log.d(TAG, "resetScreenshotView"); - } - if (mScreenshotView.isAttachedToWindow()) { - if (DEBUG_WINDOW) { - Log.d(TAG, "Removing screenshot window"); - } - mWindowManager.removeView(mDecorView); + Log.d(TAG, "finishDismiss"); } + cancelTimeout(); + removeWindow(); mScreenshotView.reset(); - mOnCompleteRunnable.run(); + if (mCurrentRequestCallback != null) { + mCurrentRequestCallback.onFinish(); + mCurrentRequestCallback = null; + } } /** @@ -645,8 +688,12 @@ public class ScreenshotController { mSaveInBgTask.execute(); } - private void resetTimeout() { + private void cancelTimeout() { mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT); + } + + private void resetTimeout() { + cancelTimeout(); AccessibilityManager accessibilityManager = (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE); @@ -705,7 +752,7 @@ public class ScreenshotController { @Override public void hideSharedElements() { - resetScreenshotView(); + finishDismiss(); } @Override @@ -753,13 +800,21 @@ public class ScreenshotController { if (DEBUG_WINDOW) { Log.d(TAG, "setWindowFocusable: " + focusable); } + int flags = mWindowLayoutParams.flags; if (focusable) { mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; } else { mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; } - if (mDecorView.isAttachedToWindow()) { - mWindowManager.updateViewLayout(mDecorView, mWindowLayoutParams); + if (mWindowLayoutParams.flags == flags) { + if (DEBUG_WINDOW) { + Log.d(TAG, "setWindowFocusable: skipping, already " + focusable); + } + return; + } + final View decorView = mWindow.peekDecorView(); + if (decorView != null && decorView.isAttachedToWindow()) { + mWindowManager.updateViewLayout(decorView, mWindowLayoutParams); } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java index 211f5072bd1a..bf86b68893c8 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java @@ -316,21 +316,21 @@ public class ScreenshotView extends FrameLayout implements mScreenshotSelectorView.requestFocus(); } - void prepareForAnimation(Bitmap bitmap, Insets screenInsets) { + void setScreenshot(Bitmap bitmap, Insets screenInsets) { mScreenshotPreview.setImageDrawable(createScreenDrawable(mResources, bitmap, screenInsets)); - // make static preview invisible (from gone) so we can query its location on screen - mScreenshotPreview.setVisibility(View.INVISIBLE); } AnimatorSet createScreenshotDropInAnimation(Rect bounds, boolean showFlash) { - mScreenshotPreview.setLayerType(View.LAYER_TYPE_HARDWARE, null); - mScreenshotPreview.buildLayer(); + if (DEBUG_ANIM) { + Log.d(TAG, "createAnim: bounds=" + bounds + " showFlash=" + showFlash); + } Rect previewBounds = new Rect(); mScreenshotPreview.getBoundsOnScreen(previewBounds); - int[] previewLocation = new int[2]; - mScreenshotPreview.getLocationInWindow(previewLocation); + Rect targetPosition = new Rect(); + mScreenshotPreview.getHitRect(targetPosition); + // ratio of preview width, end vs. start size float cornerScale = mCornerSizeX / (mOrientationPortrait ? bounds.width() : bounds.height()); final float currentScale = 1 / cornerScale; @@ -358,8 +358,13 @@ public class ScreenshotView extends FrameLayout implements // animate from the current location, to the static preview location final PointF startPos = new PointF(bounds.centerX(), bounds.centerY()); - final PointF finalPos = new PointF(previewLocation[0] + previewBounds.width() / 2f, - previewLocation[1] + previewBounds.height() / 2f); + final PointF finalPos = new PointF(targetPosition.exactCenterX(), + targetPosition.exactCenterY()); + + if (DEBUG_ANIM) { + Log.d(TAG, "toCorner: startPos=" + startPos); + Log.d(TAG, "toCorner: finalPos=" + finalPos); + } ValueAnimator toCorner = ValueAnimator.ofFloat(0, 1); toCorner.setDuration(SCREENSHOT_TO_CORNER_Y_DURATION_MS); @@ -427,7 +432,7 @@ public class ScreenshotView extends FrameLayout implements @Override public void onAnimationEnd(Animator animation) { if (DEBUG_ANIM) { - Log.d(TAG, "drop-in animation completed"); + Log.d(TAG, "drop-in animation ended"); } mDismissButton.setOnClickListener(view -> { if (DEBUG_INPUT) { @@ -653,13 +658,12 @@ public class ScreenshotView extends FrameLayout implements getViewTreeObserver().removeOnComputeInternalInsetsListener(this); // Clear any references to the bitmap mScreenshotPreview.setImageDrawable(null); + mScreenshotPreview.setVisibility(View.INVISIBLE); mPendingSharedTransition = false; mActionsContainerBackground.setVisibility(View.GONE); mActionsContainer.setVisibility(View.GONE); mBackgroundProtection.setAlpha(0f); mDismissButton.setVisibility(View.GONE); - mScreenshotPreview.setVisibility(View.GONE); - mScreenshotPreview.setLayerType(View.LAYER_TYPE_NONE, null); mScreenshotStatic.setTranslationX(0); mScreenshotPreview.setTranslationY(0); mScreenshotPreview.setContentDescription( diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java index 54b1b2c8c54c..825c85769e03 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java @@ -72,7 +72,7 @@ public class ScrollCaptureController { * * @param after action to take after the flow is complete */ - public void run(final Runnable after) { + public void start(final Runnable after) { mCaptureTime = ZonedDateTime.now(); mRequestId = UUID.randomUUID(); mConnection.start((session) -> startCapture(session, after)); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 144ad39a32aa..daa9d099de86 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -25,6 +25,7 @@ import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS; import static com.android.systemui.screenshot.LogConfig.DEBUG_SERVICE; import static com.android.systemui.screenshot.LogConfig.logTag; +import android.annotation.MainThread; import android.app.Service; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -59,7 +60,8 @@ import javax.inject.Inject; public class TakeScreenshotService extends Service { private static final String TAG = logTag(TakeScreenshotService.class); - private final ScreenshotController mScreenshot; + private ScreenshotController mScreenshot; + private final UserManager mUserManager; private final UiEventLogger mUiEventLogger; private final ScreenshotNotificationsController mNotificationsController; @@ -79,6 +81,15 @@ public class TakeScreenshotService extends Service { } }; + /** Informs about coarse grained state of the Controller. */ + interface RequestCallback { + /** Respond to the current request indicating the screenshot request failed.*/ + void reportError(); + + /** The controller has completed handling this request UI has been removed */ + void onFinish(); + } + @Inject public TakeScreenshotService(ScreenshotController screenshotController, UserManager userManager, UiEventLogger uiEventLogger, @@ -116,7 +127,8 @@ public class TakeScreenshotService extends Service { Log.d(TAG, "onUnbind"); } if (mScreenshot != null) { - mScreenshot.dismissScreenshot(true); + mScreenshot.removeWindow(); + mScreenshot = null; } unregisterReceiver(mCloseSystemDialogs); return false; @@ -126,18 +138,39 @@ public class TakeScreenshotService extends Service { public void onDestroy() { super.onDestroy(); if (mScreenshot != null) { + mScreenshot.removeWindow(); mScreenshot.releaseContext(); + mScreenshot = null; } if (DEBUG_SERVICE) { Log.d(TAG, "onDestroy"); } } + static class RequestCallbackImpl implements RequestCallback { + private final Messenger mReplyTo; + + RequestCallbackImpl(Messenger replyTo) { + mReplyTo = replyTo; + } + + public void reportError() { + reportUri(mReplyTo, null); + sendComplete(mReplyTo); + } + + @Override + public void onFinish() { + sendComplete(mReplyTo); + } + } + /** Respond to incoming Message via Binder (Messenger) */ + @MainThread private boolean handleMessage(Message msg) { final Messenger replyTo = msg.replyTo; - final Runnable onComplete = () -> sendComplete(replyTo); final Consumer<Uri> uriConsumer = (uri) -> reportUri(replyTo, uri); + RequestCallback requestCallback = new RequestCallbackImpl(replyTo); // If the storage for this user is locked, we have no place to store // the screenshot, so skip taking it instead of showing a misleading @@ -146,14 +179,7 @@ public class TakeScreenshotService extends Service { Log.w(TAG, "Skipping screenshot because storage is locked!"); mNotificationsController.notifyScreenshotError( R.string.screenshot_failed_to_save_user_locked_text); - if (DEBUG_CALLBACK) { - Log.d(TAG, "handleMessage: calling uriConsumer.accept(null)"); - } - uriConsumer.accept(null); - if (DEBUG_CALLBACK) { - Log.d(TAG, "handleMessage: calling onComplete.run()"); - } - onComplete.run(); + requestCallback.reportError(); return true; } @@ -167,13 +193,13 @@ public class TakeScreenshotService extends Service { if (DEBUG_SERVICE) { Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_FULLSCREEN"); } - mScreenshot.takeScreenshotFullscreen(uriConsumer, onComplete); + mScreenshot.takeScreenshotFullscreen(uriConsumer, requestCallback); break; case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION: if (DEBUG_SERVICE) { Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_SELECTED_REGION"); } - mScreenshot.takeScreenshotPartial(uriConsumer, onComplete); + mScreenshot.takeScreenshotPartial(uriConsumer, requestCallback); break; case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE: if (DEBUG_SERVICE) { @@ -186,8 +212,16 @@ public class TakeScreenshotService extends Service { int taskId = screenshotRequest.getTaskId(); int userId = screenshotRequest.getUserId(); ComponentName topComponent = screenshotRequest.getTopComponent(); - mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets, - taskId, userId, topComponent, uriConsumer, onComplete); + + if (screenshot == null) { + Log.e(TAG, "Got null bitmap from screenshot message"); + mNotificationsController.notifyScreenshotError( + R.string.screenshot_failed_to_capture_text); + requestCallback.reportError(); + } else { + mScreenshot.handleImageAsScreenshot(screenshot, screenBounds, insets, + taskId, userId, topComponent, uriConsumer, requestCallback); + } break; default: Log.w(TAG, "Invalid screenshot option: " + msg.what); @@ -196,7 +230,7 @@ public class TakeScreenshotService extends Service { return true; }; - private void sendComplete(Messenger target) { + private static void sendComplete(Messenger target) { try { if (DEBUG_CALLBACK) { Log.d(TAG, "sendComplete: " + target); @@ -207,7 +241,7 @@ public class TakeScreenshotService extends Service { } } - private void reportUri(Messenger target, Uri uri) { + private static void reportUri(Messenger target, Uri uri) { try { if (DEBUG_CALLBACK) { Log.d(TAG, "reportUri: " + target + " -> " + uri); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java index 596aea0f1493..2b12119ca0d6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java @@ -302,8 +302,12 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl } else { mMenuContainer = new FrameLayout(mContext); } - final boolean newFlowHideShelf = Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.SHOW_NEW_NOTIF_DISMISS, 1 /* on by default */) == 1; + // The setting can win (which is needed for tests) but if not set, then use the flag + final int showDismissSetting = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.SHOW_NEW_NOTIF_DISMISS, -1); + final boolean newFlowHideShelf = showDismissSetting == -1 + ? mContext.getResources().getBoolean(R.bool.flag_notif_updates) + : showDismissSetting == 1; if (newFlowHideShelf) { return; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java index 079cf77767c0..45f5b3136b9b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java @@ -16,8 +16,10 @@ package com.android.systemui.statusbar.notification.stack; +import android.content.res.Resources; import android.util.MathUtils; +import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -45,11 +47,6 @@ public class NotificationRoundnessManager { private ExpandableNotificationRow mTrackedHeadsUp; private float mAppearFraction; - // Radius for notification corners WITH adjacent notifications - // as percent of radius WITHOUT adjacent notifications. - // TODO(b/175710408) pull from dimens and hide from beta builds. - static final float SMALL_CORNER_RADIUS = 4f/28; - @Inject NotificationRoundnessManager( KeyguardBypassController keyguardBypassController, @@ -128,7 +125,9 @@ public class NotificationRoundnessManager { if (view.showingPulsing() && !mBypassController.getBypassEnabled()) { return 1.0f; } - return SMALL_CORNER_RADIUS; + final Resources resources = view.getResources(); + return resources.getDimension(R.dimen.notification_corner_radius_small) + / resources.getDimension(R.dimen.notification_corner_radius); } public void setExpanded(float expandedHeight, float appearFraction) { 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 f07d8740c3e9..3f3be4465bb9 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 @@ -41,8 +41,6 @@ import android.graphics.Color; import android.graphics.Outline; import android.graphics.Paint; import android.graphics.PointF; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.os.Bundle; import android.os.UserHandle; @@ -503,7 +501,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mSections = mSectionsManager.createSectionsForBuckets(); mAmbientState = new AmbientState(context, mSectionsManager); - mBgColor = Utils.getColorAttr(mContext, android.R.attr.colorBackground).getDefaultColor(); + mBgColor = Utils.getColorAttr(mContext, android.R.attr.colorBackgroundFloating) + .getDefaultColor(); int minHeight = res.getDimensionPixelSize(R.dimen.notification_min_height); int maxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height); mExpandHelper = new ExpandHelper(getContext(), mExpandHelperCallback, @@ -623,7 +622,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } void updateBgColor() { - mBgColor = Utils.getColorAttr(mContext, android.R.attr.colorBackground).getDefaultColor(); + mBgColor = Utils.getColorAttr(mContext, android.R.attr.colorBackgroundFloating) + .getDefaultColor(); updateBackgroundDimming(); mShelf.onUiModeChanged(); } @@ -2282,7 +2282,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (child.getVisibility() != View.GONE && !(child instanceof StackScrollerDecorView) && child != mShelf - && mSwipeHelper.getSwipedView() != child) { + && (mSwipeHelper.getSwipedView() != child + || !child.getResources().getBoolean(R.bool.flag_notif_updates))) { children.add(child); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 03d17e5050da..19c0b6dc193a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -34,6 +34,7 @@ import android.view.ViewTreeObserver; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; @@ -62,6 +63,8 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; import java.util.function.Consumer; import javax.inject.Inject; @@ -195,6 +198,16 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump private boolean mWakeLockHeld; private boolean mKeyguardOccluded; + /** + * Notifies listeners of animation-related changes (currently just opacity changes). + */ + public interface ScrimChangedListener { + void onAlphaChanged(float alpha); + } + + @NonNull + private final List<ScrimChangedListener> mScrimChangedListeners; + @Inject public ScrimController(LightBarController lightBarController, DozeParameters dozeParameters, AlarmManager alarmManager, KeyguardStateController keyguardStateController, @@ -208,6 +221,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump ScrimState.BUBBLE_EXPANDED.setBubbleAlpha(featureFlags.isShadeOpaque() ? BUSY_SCRIM_ALPHA : GAR_SCRIM_ALPHA); mBlurUtils = blurUtils; + mScrimChangedListeners = new ArrayList<>(); mKeyguardStateController = keyguardStateController; mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen(); @@ -284,6 +298,10 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump mScrimVisibleListener = listener; } + public void addScrimChangedListener(@NonNull ScrimChangedListener listener) { + mScrimChangedListeners.add(listener); + } + public void transitionTo(ScrimState state) { transitionTo(state, null); } @@ -559,6 +577,10 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump throw new IllegalStateException("Scrim opacity is NaN for state: " + mState + ", front: " + mInFrontAlpha + ", back: " + mBehindAlpha); } + + for (ScrimChangedListener listener : mScrimChangedListeners) { + listener.onAlphaChanged(mBehindAlpha); + } } private void applyAndDispatchExpansion() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java index d097cfadd265..499d1e420272 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java @@ -316,6 +316,7 @@ public class Clock extends TextView implements @Override public void onDarkChanged(Rect area, float darkIntensity, int tint) { mNonAdaptedColor = DarkIconDispatcher.getTint(area, this, tint); + setTextColor(mNonAdaptedColor); } // Update text color based when shade scrim changes color. diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java index 8a79acef756c..416de0465f1a 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java @@ -17,13 +17,16 @@ package com.android.systemui.wmshell; import android.content.Context; +import android.os.Handler; import com.android.systemui.dagger.WMSingleton; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.TaskStackListenerImpl; +import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipBoundsAlgorithm; @@ -57,9 +60,10 @@ public abstract class TvPipModule { PipMediaController pipMediaController, TvPipNotificationController tvPipNotificationController, TaskStackListenerImpl taskStackListener, - WindowManagerShellWrapper windowManagerShellWrapper) { + WindowManagerShellWrapper windowManagerShellWrapper, + @ShellMainThread ShellExecutor mainExecutor) { return Optional.of( - new TvPipController( + TvPipController.create( context, pipBoundsState, pipBoundsAlgorithm, @@ -68,7 +72,8 @@ public abstract class TvPipModule { pipMediaController, tvPipNotificationController, taskStackListener, - windowManagerShellWrapper)); + windowManagerShellWrapper, + mainExecutor)); } @WMSingleton @@ -84,21 +89,26 @@ public abstract class TvPipModule { return new PipBoundsState(context); } + // Handler needed for loadDrawableAsync() in PipControlsViewController @WMSingleton @Provides static TvPipMenuController providesTvPipMenuController( Context context, PipBoundsState pipBoundsState, SystemWindows systemWindows, - PipMediaController pipMediaController) { - return new TvPipMenuController(context, pipBoundsState, systemWindows, pipMediaController); + PipMediaController pipMediaController, + @ShellMainThread Handler mainHandler) { + return new TvPipMenuController(context, pipBoundsState, systemWindows, pipMediaController, + mainHandler); } + // Handler needed for registerReceiverForAllUsers() @WMSingleton @Provides static TvPipNotificationController provideTvPipNotificationController(Context context, - PipMediaController pipMediaController) { - return new TvPipNotificationController(context, pipMediaController); + PipMediaController pipMediaController, + @ShellMainThread Handler mainHandler) { + return new TvPipNotificationController(context, pipMediaController, mainHandler); } @WMSingleton @@ -109,9 +119,10 @@ public abstract class TvPipModule { PipBoundsAlgorithm pipBoundsAlgorithm, PipSurfaceTransactionHelper pipSurfaceTransactionHelper, Optional<LegacySplitScreen> splitScreenOptional, DisplayController displayController, - PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer) { + PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer, + @ShellMainThread ShellExecutor mainExecutor) { return new PipTaskOrganizer(context, pipBoundsState, pipBoundsAlgorithm, tvPipMenuController, pipSurfaceTransactionHelper, splitScreenOptional, - displayController, pipUiEventLogger, shellTaskOrganizer); + displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor); } } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java index bbc238a35cbd..103e6bb08ed4 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java @@ -250,14 +250,17 @@ public abstract class WMShellBaseModule { @Provides static PipAppOpsListener providePipAppOpsListener(Context context, IActivityManager activityManager, - PipTouchHandler pipTouchHandler) { - return new PipAppOpsListener(context, activityManager, pipTouchHandler.getMotionHelper()); + PipTouchHandler pipTouchHandler, + @ShellMainThread ShellExecutor mainExecutor) { + return new PipAppOpsListener(context, pipTouchHandler.getMotionHelper(), mainExecutor); } + // Needs handler for registering broadcast receivers @WMSingleton @Provides - static PipMediaController providePipMediaController(Context context) { - return new PipMediaController(context); + static PipMediaController providePipMediaController(Context context, + @ShellMainThread Handler mainHandler) { + return new PipMediaController(context, mainHandler); } @WMSingleton diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java index 8105250f0ca0..12a3b5d66d55 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java @@ -19,6 +19,7 @@ package com.android.systemui.wmshell; import android.animation.AnimationHandler; import android.app.ActivityTaskManager; import android.content.Context; +import android.os.Handler; import android.view.IWindowManager; import com.android.systemui.dagger.WMSingleton; @@ -125,11 +126,15 @@ public class WMShellModule { return new PipBoundsAlgorithm(context, pipBoundsState); } + // Handler is used by Icon.loadDrawableAsync @WMSingleton @Provides static PhonePipMenuController providesPipPhoneMenuController(Context context, - PipMediaController pipMediaController, SystemWindows systemWindows) { - return new PhonePipMenuController(context, pipMediaController, systemWindows); + PipMediaController pipMediaController, SystemWindows systemWindows, + @ShellMainThread ShellExecutor mainExecutor, + @ShellMainThread Handler mainHandler) { + return new PhonePipMenuController(context, pipMediaController, systemWindows, + mainExecutor, mainHandler); } @WMSingleton @@ -154,9 +159,10 @@ public class WMShellModule { PhonePipMenuController menuPhoneController, PipSurfaceTransactionHelper pipSurfaceTransactionHelper, Optional<LegacySplitScreen> splitScreenOptional, DisplayController displayController, - PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer) { + PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer, + @ShellMainThread ShellExecutor mainExecutor) { return new PipTaskOrganizer(context, pipBoundsState, pipBoundsAlgorithm, menuPhoneController, pipSurfaceTransactionHelper, splitScreenOptional, - displayController, pipUiEventLogger, shellTaskOrganizer); + displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index ed3cf9a49a34..dd145e419321 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -45,6 +45,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.settings.FakeSettings; import com.android.systemui.util.time.FakeSystemClock; @@ -90,6 +91,8 @@ public class UdfpsControllerTest extends SysuiTestCase { private WindowManager mWindowManager; @Mock private StatusBarStateController mStatusBarStateController; + @Mock + private ScrimController mScrimController; private FakeSettings mSystemSettings; private FakeExecutor mFgExecutor; @@ -130,7 +133,8 @@ public class UdfpsControllerTest extends SysuiTestCase { mWindowManager, mSystemSettings, mStatusBarStateController, - mFgExecutor); + mFgExecutor, + mScrimController); verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture()); mOverlayController = mOverlayCaptor.getValue(); @@ -245,4 +249,10 @@ public class UdfpsControllerTest extends SysuiTestCase { // THEN the scrim and dot is hidden verify(mUdfpsView).hideScrimAndDot(); } + + @Test + public void registersViewForCallbacks() throws RemoteException { + verify(mStatusBarStateController).addCallback(mUdfpsView); + verify(mScrimController).addScrimChangedListener(mUdfpsView); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java index 63bfd6ab25cc..919ddcb488c2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar.notification.stack; -import static com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager.SMALL_CORNER_RADIUS; - import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -25,12 +23,14 @@ import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.res.Resources; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import androidx.test.filters.SmallTest; +import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -61,10 +61,14 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { private ExpandableNotificationRow mSecond; @Mock private KeyguardBypassController mBypassController; + private float mSmallRadiusRatio; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + final Resources resources = mContext.getResources(); + mSmallRadiusRatio = resources.getDimension(R.dimen.notification_corner_radius_small) + / resources.getDimension(R.dimen.notification_corner_radius); mRoundnessManager = new NotificationRoundnessManager( mBypassController, new NotificationSectionsFeatureManager(new DeviceConfigProxy(), mContext)); @@ -141,7 +145,7 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { createSection(null, null) }); Assert.assertEquals(1.0f, mSecond.getCurrentBottomRoundness(), 0.0f); - Assert.assertEquals(SMALL_CORNER_RADIUS, mSecond.getCurrentTopRoundness(), 0.0f); + Assert.assertEquals(mSmallRadiusRatio, mSecond.getCurrentTopRoundness(), 0.0f); } @Test @@ -168,8 +172,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { row.setHeadsUp(false); mRoundnessManager.updateView(entry.getRow(), false); - Assert.assertEquals(SMALL_CORNER_RADIUS, row.getCurrentBottomRoundness(), 0.0f); - Assert.assertEquals(SMALL_CORNER_RADIUS, row.getCurrentTopRoundness(), 0.0f); + Assert.assertEquals(mSmallRadiusRatio, row.getCurrentBottomRoundness(), 0.0f); + Assert.assertEquals(mSmallRadiusRatio, row.getCurrentTopRoundness(), 0.0f); } @Test @@ -179,7 +183,7 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { createSection(null, mSecond) }); Assert.assertEquals(1.0f, mSecond.getCurrentBottomRoundness(), 0.0f); - Assert.assertEquals(SMALL_CORNER_RADIUS, mSecond.getCurrentTopRoundness(), 0.0f); + Assert.assertEquals(mSmallRadiusRatio, mSecond.getCurrentTopRoundness(), 0.0f); } @Test @@ -188,7 +192,7 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { createSection(mFirst, mFirst), createSection(mSecond, null) }); - Assert.assertEquals(SMALL_CORNER_RADIUS, mSecond.getCurrentBottomRoundness(), 0.0f); + Assert.assertEquals(mSmallRadiusRatio, mSecond.getCurrentBottomRoundness(), 0.0f); Assert.assertEquals(1.0f, mSecond.getCurrentTopRoundness(), 0.0f); } @@ -198,7 +202,7 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { createSection(mFirst, null), createSection(null, null) }); - Assert.assertEquals(SMALL_CORNER_RADIUS, mFirst.getCurrentBottomRoundness(), 0.0f); + Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentBottomRoundness(), 0.0f); Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f); } @@ -208,8 +212,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { createSection(mSecond, mSecond), createSection(null, null) }); - Assert.assertEquals(SMALL_CORNER_RADIUS, mFirst.getCurrentBottomRoundness(), 0.0f); - Assert.assertEquals(SMALL_CORNER_RADIUS, mFirst.getCurrentTopRoundness(), 0.0f); + Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentBottomRoundness(), 0.0f); + Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentTopRoundness(), 0.0f); } @Test @@ -255,8 +259,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { createSection(mSecond, mSecond), createSection(null, null) }); - Assert.assertEquals(SMALL_CORNER_RADIUS, mFirst.getCurrentBottomRoundness(), 0.0f); - Assert.assertEquals(SMALL_CORNER_RADIUS, mFirst.getCurrentTopRoundness(), 0.0f); + Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentBottomRoundness(), 0.0f); + Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentTopRoundness(), 0.0f); } @Test @@ -305,8 +309,8 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { }); mFirst.setHeadsUpAnimatingAway(true); mFirst.setHeadsUpAnimatingAway(false); - Assert.assertEquals(SMALL_CORNER_RADIUS, mFirst.getCurrentBottomRoundness(), 0.0f); - Assert.assertEquals(SMALL_CORNER_RADIUS, mFirst.getCurrentTopRoundness(), 0.0f); + Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentBottomRoundness(), 0.0f); + Assert.assertEquals(mSmallRadiusRatio, mFirst.getCurrentTopRoundness(), 0.0f); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java index 8b86403b554e..4078d4dad984 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java @@ -63,7 +63,6 @@ public class WMShellTest extends SysuiTestCase { @Mock ScreenLifecycle mScreenLifecycle; @Mock SysUiState mSysUiState; @Mock Pip mPip; - @Mock PipTouchHandler mPipTouchHandler; @Mock LegacySplitScreen mLegacySplitScreen; @Mock OneHanded mOneHanded; @Mock HideDisplayCutout mHideDisplayCutout; @@ -80,8 +79,6 @@ public class WMShellTest extends SysuiTestCase { Optional.of(mShellCommandHandler), mCommandQueue, mConfigurationController, mKeyguardUpdateMonitor, mNavigationModeController, mScreenLifecycle, mSysUiState, mProtoTracer, mSysUiMainExecutor); - - when(mPip.getPipTouchHandler()).thenReturn(mPipTouchHandler); } @Test diff --git a/services/core/Android.bp b/services/core/Android.bp index 2f3ad196dfd6..6de227e7ee7a 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -87,6 +87,7 @@ java_library_static { ":storaged_aidl", ":vold_aidl", ":platform-compat-config", + ":platform-compat-overrides", ":display-device-config", ":cec-config", ":device-state-config", diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 74a6e07c27f2..d129b9c074a9 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -5658,7 +5658,9 @@ public class ConnectivityService extends IConnectivityManager.Stub if (ns == null) { return; } - MatchAllNetworkSpecifier.checkNotMatchAllNetworkSpecifier(ns); + if (ns instanceof MatchAllNetworkSpecifier) { + throw new IllegalArgumentException("A MatchAllNetworkSpecifier is not permitted"); + } } private void ensureValid(NetworkCapabilities nc) { @@ -6194,7 +6196,7 @@ public class ConnectivityService extends IConnectivityManager.Stub nai.networkAgentPortalData = lp.getCaptivePortalData(); } - private void updateLinkProperties(NetworkAgentInfo networkAgent, LinkProperties newLp, + private void updateLinkProperties(NetworkAgentInfo networkAgent, @NonNull LinkProperties newLp, @NonNull LinkProperties oldLp) { int netId = networkAgent.network.getNetId(); @@ -6203,8 +6205,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // the LinkProperties for the network are accurate. networkAgent.clatd.fixupLinkProperties(oldLp, newLp); - updateInterfaces(newLp, oldLp, netId, networkAgent.networkCapabilities, - networkAgent.networkInfo.getType()); + updateInterfaces(newLp, oldLp, netId, networkAgent.networkCapabilities); // update filtering rules, need to happen after the interface update so netd knows about the // new interface (the interface name -> index map becomes initialized) @@ -6343,7 +6344,7 @@ public class ConnectivityService extends IConnectivityManager.Stub private void updateInterfaces(final @Nullable LinkProperties newLp, final @Nullable LinkProperties oldLp, final int netId, - final @Nullable NetworkCapabilities caps, final int legacyType) { + final @NonNull NetworkCapabilities caps) { final CompareResult<String> interfaceDiff = new CompareResult<>( oldLp != null ? oldLp.getAllInterfaceNames() : null, newLp != null ? newLp.getAllInterfaceNames() : null); @@ -6354,7 +6355,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (DBG) log("Adding iface " + iface + " to network " + netId); mNetd.networkAddInterface(netId, iface); wakeupModifyInterface(iface, caps, true); - bs.noteNetworkInterfaceType(iface, legacyType); + bs.noteNetworkInterfaceForTransports(iface, caps.getTransportTypes()); } catch (Exception e) { loge("Exception adding interface: " + e); } @@ -6626,6 +6627,7 @@ public class ConnectivityService extends IConnectivityManager.Stub * maintained here that the NetworkAgent is not aware of (e.g., validated, captive portal, * and foreground status). */ + @NonNull private NetworkCapabilities mixInCapabilities(NetworkAgentInfo nai, NetworkCapabilities nc) { // Once a NetworkAgent is connected, complain if some immutable capabilities are removed. // Don't complain for VPNs since they're not driven by requests and there is no risk of @@ -6682,6 +6684,25 @@ public class ConnectivityService extends IConnectivityManager.Stub return newNc; } + private void updateNetworkInfoForRoamingAndSuspended(NetworkAgentInfo nai, + NetworkCapabilities prevNc, NetworkCapabilities newNc) { + final boolean prevSuspended = !prevNc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED); + final boolean suspended = !newNc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED); + final boolean prevRoaming = !prevNc.hasCapability(NET_CAPABILITY_NOT_ROAMING); + final boolean roaming = !newNc.hasCapability(NET_CAPABILITY_NOT_ROAMING); + if (prevSuspended != suspended) { + // TODO (b/73132094) : remove this call once the few users of onSuspended and + // onResumed have been removed. + notifyNetworkCallbacks(nai, suspended ? ConnectivityManager.CALLBACK_SUSPENDED + : ConnectivityManager.CALLBACK_RESUMED); + } + if (prevSuspended != suspended || prevRoaming != roaming) { + // updateNetworkInfo will mix in the suspended info from the capabilities and + // take appropriate action for the network having possibly changed state. + updateNetworkInfo(nai, nai.networkInfo); + } + } + /** * Update the NetworkCapabilities for {@code nai} to {@code nc}. Specifically: * @@ -6713,25 +6734,13 @@ public class ConnectivityService extends IConnectivityManager.Stub // on this network. We might have been called by rematchNetworkAndRequests when a // network changed foreground state. processListenRequests(nai); - final boolean prevSuspended = !prevNc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED); - final boolean suspended = !newNc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED); - final boolean prevRoaming = !prevNc.hasCapability(NET_CAPABILITY_NOT_ROAMING); - final boolean roaming = !newNc.hasCapability(NET_CAPABILITY_NOT_ROAMING); - if (prevSuspended != suspended || prevRoaming != roaming) { - // TODO (b/73132094) : remove this call once the few users of onSuspended and - // onResumed have been removed. - notifyNetworkCallbacks(nai, suspended ? ConnectivityManager.CALLBACK_SUSPENDED - : ConnectivityManager.CALLBACK_RESUMED); - // updateNetworkInfo will mix in the suspended info from the capabilities and - // take appropriate action for the network having possibly changed state. - updateNetworkInfo(nai, nai.networkInfo); - } } else { // If the requestable capabilities have changed or the score changed, we can't have been // called by rematchNetworkAndRequests, so it's safe to start a rematch. rematchAllNetworksAndRequests(); notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED); } + updateNetworkInfoForRoamingAndSuspended(nai, prevNc, newNc); final boolean oldMetered = prevNc.isMetered(); final boolean newMetered = newNc.isMetered(); diff --git a/services/core/java/com/android/server/TestNetworkService.java b/services/core/java/com/android/server/TestNetworkService.java index e8687e57a07b..a08d066513c7 100644 --- a/services/core/java/com/android/server/TestNetworkService.java +++ b/services/core/java/com/android/server/TestNetworkService.java @@ -242,6 +242,7 @@ class TestNetworkService extends ITestNetworkManager.Stub { nc.addTransportType(NetworkCapabilities.TRANSPORT_TEST); nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED); nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED); + nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED); nc.setNetworkSpecifier(new StringNetworkSpecifier(iface)); nc.setAdministratorUids(administratorUids); if (!isMetered) { diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java index 3e80709d03d6..97e313e13f6d 100644 --- a/services/core/java/com/android/server/VibratorService.java +++ b/services/core/java/com/android/server/VibratorService.java @@ -102,7 +102,10 @@ public class VibratorService extends IVibratorService.Stub { private VibrationScaler mVibrationScaler; private InputDeviceDelegate mInputDeviceDelegate; - private volatile VibrationThread mThread; + @GuardedBy("mLock") + private VibrationThread mThread; + @GuardedBy("mLock") + private VibrationThread mNextVibrationThread; @GuardedBy("mLock") private Vibration mCurrentVibration; @@ -132,6 +135,10 @@ public class VibratorService extends IVibratorService.Stub { if (mCurrentVibration != null && mCurrentVibration.id == vibrationId) { mThread = null; reportFinishVibrationLocked(status); + if (mNextVibrationThread != null) { + startVibrationThreadLocked(mNextVibrationThread); + mNextVibrationThread = null; + } } } } @@ -258,18 +265,14 @@ public class VibratorService extends IVibratorService.Stub { @VisibleForTesting public void onVibrationComplete(int vibratorId, long vibrationId) { synchronized (mLock) { - if (mCurrentVibration != null && mCurrentVibration.id == vibrationId) { + if (mCurrentVibration != null && mCurrentVibration.id == vibrationId + && mThread != null) { if (DEBUG) { Slog.d(TAG, "Vibration onComplete callback, notifying VibrationThread"); } - if (mThread != null) { - // Let the thread playing the vibration handle the callback, since it might be - // expecting the vibrator to turn off multiple times during a single vibration. - mThread.vibratorComplete(vibratorId); - } else { - // No vibration is playing in the thread, but clean up service just in case. - doCancelVibrateLocked(Vibration.Status.FINISHED); - } + // Let the thread playing the vibration handle the callback, since it might be + // expecting the vibrator to turn off multiple times during a single vibration. + mThread.vibratorComplete(vibratorId); } } } @@ -462,8 +465,10 @@ public class VibratorService extends IVibratorService.Stub { try { doCancelVibrateLocked(Vibration.Status.CANCELLED); startVibrationLocked(vib); + boolean isNextVibration = mNextVibrationThread != null + && vib.equals(mNextVibrationThread.getVibration()); - if (!vib.hasEnded() && mCurrentVibration.id != vib.id) { + if (!vib.hasEnded() && !vib.equals(mCurrentVibration) && !isNextVibration) { // Vibration was unexpectedly ignored: add to list for debugging endVibrationLocked(vib, Vibration.Status.IGNORED); } @@ -532,6 +537,7 @@ public class VibratorService extends IVibratorService.Stub { } final long ident = Binder.clearCallingIdentity(); try { + mNextVibrationThread = null; doCancelVibrateLocked(Vibration.Status.CANCELLED); } finally { Binder.restoreCallingIdentity(ident); @@ -546,16 +552,14 @@ public class VibratorService extends IVibratorService.Stub { try { if (mThread != null) { mThread.cancel(); - mThread = null; } + mInputDeviceDelegate.cancelVibrateIfAvailable(); if (mCurrentExternalVibration != null) { endVibrationLocked(mCurrentExternalVibration, status); mCurrentExternalVibration.externalVibration.mute(); mCurrentExternalVibration = null; mVibratorController.setExternalControl(false); } - doVibratorOff(); - reportFinishVibrationLocked(status); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); } @@ -579,28 +583,30 @@ public class VibratorService extends IVibratorService.Stub { private void startVibrationInnerLocked(Vibration vib) { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationInnerLocked"); try { - // Set current vibration before starting it, so callback will work. - mCurrentVibration = vib; - VibrationEffect effect = getEffect(vib); - Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0); boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable( vib.uid, vib.opPkg, vib.getEffect(), vib.reason, vib.attrs); if (inputDevicesAvailable) { - // The set current vibration is no longer being played by this service, so drop it. - mCurrentVibration = null; endVibrationLocked(vib, Vibration.Status.FORWARDED_TO_INPUT_DEVICES); + } else if (mThread == null) { + startVibrationThreadLocked(new VibrationThread(vib, mVibratorController, mWakeLock, + mBatteryStatsService, mVibrationCallbacks)); } else { - // mThread better be null here. doCancelVibrate should always be - // called before startVibrationInnerLocked - mThread = new VibrationThread(vib, mVibratorController, mWakeLock, + mNextVibrationThread = new VibrationThread(vib, mVibratorController, mWakeLock, mBatteryStatsService, mVibrationCallbacks); - mThread.start(); } } finally { Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); } } + @GuardedBy("mLock") + private void startVibrationThreadLocked(VibrationThread thread) { + Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0); + mCurrentVibration = thread.getVibration(); + mThread = thread; + mThread.start(); + } + /** Scale the vibration effect by the intensity as appropriate based its intent. */ private void applyVibrationIntensityScalingLocked(Vibration vib) { vib.updateEffect(mVibrationScaler.scale(vib.getEffect(), vib.attrs.getUsage())); @@ -665,13 +671,14 @@ public class VibratorService extends IVibratorService.Stub { @GuardedBy("mLock") private void reportFinishVibrationLocked(Vibration.Status status) { - Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0); Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked"); try { if (mCurrentVibration != null) { endVibrationLocked(mCurrentVibration, status); mAppOps.finishOp(AppOpsManager.OP_VIBRATE, mCurrentVibration.uid, mCurrentVibration.opPkg); + + Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0); mCurrentVibration = null; } } finally { @@ -697,21 +704,6 @@ public class VibratorService extends IVibratorService.Stub { } } - private void doVibratorOff() { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorOff"); - try { - if (DEBUG) { - Slog.d(TAG, "Turning vibrator off."); - } - boolean inputDevicesAvailable = mInputDeviceDelegate.cancelVibrateIfAvailable(); - if (!inputDevicesAvailable) { - mVibratorController.off(); - } - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } - private boolean isSystemHapticFeedback(Vibration vib) { if (vib.attrs.getUsage() != VibrationAttributes.USAGE_TOUCH) { return false; @@ -844,6 +836,7 @@ public class VibratorService extends IVibratorService.Stub { // haptic feedback as part of the transition. So we don't cancel // system vibrations. if (mCurrentVibration != null && !isSystemHapticFeedback(mCurrentVibration)) { + mNextVibrationThread = null; doCancelVibrateLocked(Vibration.Status.CANCELLED); } } @@ -910,6 +903,8 @@ public class VibratorService extends IVibratorService.Stub { return IExternalVibratorService.SCALE_MUTE; } + VibrationThread cancelingVibration = null; + int scale; synchronized (mLock) { if (mCurrentExternalVibration != null && mCurrentExternalVibration.externalVibration.equals(vib)) { @@ -920,11 +915,9 @@ public class VibratorService extends IVibratorService.Stub { if (mCurrentExternalVibration == null) { // If we're not under external control right now, then cancel any normal // vibration that may be playing and ready the vibrator for external control. - if (DEBUG) { - Slog.d(TAG, "Vibrator going under external control."); - } + mNextVibrationThread = null; doCancelVibrateLocked(Vibration.Status.CANCELLED); - mVibratorController.setExternalControl(true); + cancelingVibration = mThread; } else { endVibrationLocked(mCurrentExternalVibration, Vibration.Status.CANCELLED); } @@ -941,11 +934,24 @@ public class VibratorService extends IVibratorService.Stub { vib.linkToDeath(mCurrentExternalDeathRecipient); mCurrentExternalVibration.scale = mVibrationScaler.getExternalVibrationScale( vib.getVibrationAttributes().getUsage()); - if (DEBUG) { - Slog.e(TAG, "Playing external vibration: " + vib); + scale = mCurrentExternalVibration.scale; + } + if (cancelingVibration != null) { + try { + cancelingVibration.join(); + } catch (InterruptedException e) { + Slog.w("Interrupted while waiting current vibration to be cancelled before " + + "starting external vibration", e); } - return mCurrentExternalVibration.scale; } + if (DEBUG) { + Slog.d(TAG, "Vibrator going under external control."); + } + mVibratorController.setExternalControl(true); + if (DEBUG) { + Slog.e(TAG, "Playing external vibration: " + vib); + } + return scale; } @Override diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index b1cbb4acdb13..50e55579dd74 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -1674,11 +1674,11 @@ public final class BatteryStatsService extends IBatteryStats.Stub } @Override - public void noteNetworkInterfaceType(final String iface, final int networkType) { + public void noteNetworkInterfaceForTransports(final String iface, int[] transportTypes) { enforceCallingPermission(); synchronized (mLock) { mHandler.post(() -> { - mStats.noteNetworkInterfaceType(iface, networkType); + mStats.noteNetworkInterfaceForTransports(iface, transportTypes); }); } } diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index dd09a1c85682..27a238dd33ea 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -80,20 +80,22 @@ public final class CachedAppOptimizer { // Phenotype sends int configurations and we map them to the strings we'll use on device, // preventing a weird string value entering the kernel. + private static final int COMPACT_ACTION_NONE = 0; + private static final int COMPACT_ACTION_FILE = 1; + private static final int COMPACT_ACTION_ANON = 2; + private static final int COMPACT_ACTION_FULL = 3; + + private static final String COMPACT_ACTION_STRING[] = {"", "file", "anon", "all"}; + + // Keeps these flags in sync with services/core/jni/com_android_server_am_CachedAppOptimizer.cpp private static final int COMPACT_ACTION_FILE_FLAG = 1; private static final int COMPACT_ACTION_ANON_FLAG = 2; - private static final int COMPACT_ACTION_FULL_FLAG = 3; - private static final int COMPACT_ACTION_NONE_FLAG = 4; - private static final String COMPACT_ACTION_NONE = ""; - private static final String COMPACT_ACTION_FILE = "file"; - private static final String COMPACT_ACTION_ANON = "anon"; - private static final String COMPACT_ACTION_FULL = "all"; // Defaults for phenotype flags. @VisibleForTesting static final Boolean DEFAULT_USE_COMPACTION = false; @VisibleForTesting static final Boolean DEFAULT_USE_FREEZER = false; - @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_1 = COMPACT_ACTION_FILE_FLAG; - @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_2 = COMPACT_ACTION_FULL_FLAG; + @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_1 = COMPACT_ACTION_FILE; + @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_2 = COMPACT_ACTION_FULL; @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_1 = 5_000; @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_2 = 10_000; @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_3 = 500; @@ -232,6 +234,8 @@ public final class CachedAppOptimizer { @VisibleForTesting Handler mCompactionHandler; private Handler mFreezeHandler; + @GuardedBy("mAm") + private boolean mFreezerOverride = false; // Maps process ID to last compaction statistics for processes that we've fully compacted. Used // when evaluating throttles that we only consider for "full" compaction, so we don't store @@ -406,6 +410,14 @@ public final class CachedAppOptimizer { private native void compactSystem(); /** + * Compacts a process or app + * @param pid pid of process to compact + * @param compactionFlags selects the compaction type as defined by COMPACT_ACTION_{TYPE}_FLAG + * constants + */ + static private native void compactProcess(int pid, int compactionFlags); + + /** * Reads the flag value from DeviceConfig to determine whether app compaction * should be enabled, and starts the freeze/compaction thread if needed. */ @@ -454,21 +466,35 @@ public final class CachedAppOptimizer { } } - try { - enableFreezerInternal(enable); - return true; - } catch (java.lang.RuntimeException e) { - if (enable) { - mFreezerDisableCount = 0; - } else { - mFreezerDisableCount = 1; - } + // Override is applied immediately, restore is delayed + synchronized (mAm) { + int processCount = mAm.mProcessList.mLruProcesses.size(); + + mFreezerOverride = !enable; + Slog.d(TAG_AM, "freezer override set to " + mFreezerOverride); + + for (int i = 0; i < processCount; i++) { + ProcessRecord process = mAm.mProcessList.mLruProcesses.get(i); + + if (process == null) { + continue; + } + + if (enable && process.freezerOverride) { + freezeAppAsync(process); + process.freezerOverride = false; + } - Slog.e(TAG_AM, "Exception handling freezer state (enable: " + enable + "): " - + e.toString()); + if (!enable && process.frozen) { + unfreezeAppLocked(process); + + // Set freezerOverride *after* calling unfreezeAppLocked (it resets the flag) + process.freezerOverride = true; + } + } } - return false; + return true; } /** @@ -515,7 +541,7 @@ public final class CachedAppOptimizer { FileReader fr = null; try { - fr = new FileReader("/sys/fs/cgroup/freezer/cgroup.freeze"); + fr = new FileReader("/sys/fs/cgroup/uid_0/cgroup.freeze"); char state = (char) fr.read(); if (state == '1' || state == '0') { @@ -706,18 +732,11 @@ public final class CachedAppOptimizer { @VisibleForTesting static String compactActionIntToString(int action) { - switch(action) { - case COMPACT_ACTION_NONE_FLAG: - return COMPACT_ACTION_NONE; - case COMPACT_ACTION_FILE_FLAG: - return COMPACT_ACTION_FILE; - case COMPACT_ACTION_ANON_FLAG: - return COMPACT_ACTION_ANON; - case COMPACT_ACTION_FULL_FLAG: - return COMPACT_ACTION_FULL; - default: - return COMPACT_ACTION_NONE; + if (action < 0 || action >= COMPACT_ACTION_STRING.length) { + return ""; } + + return COMPACT_ACTION_STRING[action]; } // This will ensure app will be out of the freezer for at least FREEZE_TIMEOUT_MS @@ -744,6 +763,8 @@ public final class CachedAppOptimizer { void unfreezeAppLocked(ProcessRecord app) { mFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app); + app.freezerOverride = false; + if (!app.frozen) { if (DEBUG_FREEZER) { Slog.d(TAG_AM, @@ -753,6 +774,8 @@ public final class CachedAppOptimizer { return; } + // Unfreeze the binder interface first, to avoid transactions triggered by timers fired + // right after unfreezing the process to fail boolean processKilled = false; try { @@ -950,11 +973,11 @@ public final class CachedAppOptimizer { action = mCompactActionFull; break; default: - action = COMPACT_ACTION_NONE; + action = COMPACT_ACTION_STRING[COMPACT_ACTION_NONE]; break; } - if (COMPACT_ACTION_NONE.equals(action)) { + if (COMPACT_ACTION_STRING[COMPACT_ACTION_NONE].equals(action)) { return; } @@ -978,7 +1001,8 @@ public final class CachedAppOptimizer { return; } - if (action.equals(COMPACT_ACTION_FULL) || action.equals(COMPACT_ACTION_ANON)) { + if (action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_FULL]) + || action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_ANON])) { if (mFullAnonRssThrottleKb > 0L && anonRssBefore < mFullAnonRssThrottleKb) { if (DEBUG_COMPACTION) { @@ -1054,8 +1078,8 @@ public final class CachedAppOptimizer { proc.lastCompactTime = end; proc.lastCompactAction = pendingAction; } - if (action.equals(COMPACT_ACTION_FULL) - || action.equals(COMPACT_ACTION_ANON)) { + if (action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_FULL]) + || action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_ANON])) { // Remove entry and insert again to update insertion order. mLastCompactionStats.remove(pid); mLastCompactionStats.put(pid, new LastCompactionStats(rssAfter)); @@ -1131,12 +1155,31 @@ public final class CachedAppOptimizer { return; } + if (mFreezerOverride) { + proc.freezerOverride = true; + Slog.d(TAG_AM, "Skipping freeze for process " + pid + + " " + name + " curAdj = " + proc.curAdj + + "(override)"); + return; + } + if (pid == 0 || proc.frozen) { // Already frozen or not a real process, either one being // launched or one being killed return; } + // Freeze binder interface before the process, to flush any + // transactions that might be pending. + try { + freezeBinder(pid, true); + } catch (RuntimeException e) { + Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name); + proc.kill("Unable to freeze binder interface", + ApplicationExitInfo.REASON_OTHER, + ApplicationExitInfo.SUBREASON_INVALID_STATE, true); + } + long unfreezeTime = proc.freezeUnfreezeTime; try { @@ -1163,15 +1206,6 @@ public final class CachedAppOptimizer { EventLog.writeEvent(EventLogTags.AM_FREEZE, pid, name); - try { - freezeBinder(pid, true); - } catch (RuntimeException e) { - Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name); - proc.kill("Unable to freeze binder interface", - ApplicationExitInfo.REASON_OTHER, - ApplicationExitInfo.SUBREASON_INVALID_STATE, true); - } - // See above for why we're not taking mPhenotypeFlagLock here if (mRandom.nextFloat() < mFreezerStatsdSampleRate) { FrameworkStatsLog.write(FrameworkStatsLog.APP_FREEZE_CHANGED, @@ -1229,8 +1263,12 @@ public final class CachedAppOptimizer { // Compact process. @Override public void performCompaction(String action, int pid) throws IOException { - try (FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim")) { - fos.write(action.getBytes()); + if (action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_FULL])) { + compactProcess(pid, COMPACT_ACTION_FILE_FLAG | COMPACT_ACTION_ANON_FLAG); + } else if (action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_FILE])) { + compactProcess(pid, COMPACT_ACTION_FILE_FLAG); + } else if (action.equals(COMPACT_ACTION_STRING[COMPACT_ACTION_ANON])) { + compactProcess(pid, COMPACT_ACTION_ANON_FLAG); } } } diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 520a28bfd889..63195d35b049 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -179,6 +179,7 @@ class ProcessRecord implements WindowProcessListener { int reqCompactAction; // The most recent compaction action requested for this app. int lastCompactAction; // The most recent compaction action performed for this app. boolean frozen; // True when the process is frozen. + boolean freezerOverride; // An override on the freeze state is in progress. long freezeUnfreezeTime; // Last time the app was (un)frozen, 0 for never boolean shouldNotFreeze; // True if a process has a WPRI binding from an unfrozen process private int mCurSchedGroup; // Currently desired scheduling class diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java index c86bfcb4a88e..271537a9876c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java @@ -563,14 +563,26 @@ public class BiometricScheduler { final boolean isAuthenticating = mCurrentOperation.mClientMonitor instanceof AuthenticationConsumer; final boolean tokenMatches = mCurrentOperation.mClientMonitor.getToken() == token; - if (!isAuthenticating || !tokenMatches) { - Slog.w(getTag(), "Not cancelling authentication" - + ", current operation : " + mCurrentOperation - + ", tokenMatches: " + tokenMatches); - return; - } - cancelInternal(mCurrentOperation); + if (isAuthenticating && tokenMatches) { + Slog.d(getTag(), "Cancelling authentication: " + mCurrentOperation); + cancelInternal(mCurrentOperation); + } else if (!isAuthenticating) { + // Look through the current queue for all authentication clients for the specified + // token, and mark them as STATE_WAITING_IN_QUEUE_CANCELING. Note that we're marking + // all of them, instead of just the first one, since the API surface currently doesn't + // allow us to distinguish between multiple authentication requests from the same + // process. However, this generally does not happen anyway, and would be a class of + // bugs on its own. + for (Operation operation : mPendingOperations) { + if (operation.mClientMonitor instanceof AuthenticationConsumer + && operation.mClientMonitor.getToken() == token) { + Slog.d(getTag(), "Marking " + operation + + " as STATE_WAITING_IN_QUEUE_CANCELING"); + operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING; + } + } + } } /** diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java index 9ba957ef27ae..e3757dfc6a59 100644 --- a/services/core/java/com/android/server/compat/CompatChange.java +++ b/services/core/java/com/android/server/compat/CompatChange.java @@ -23,8 +23,11 @@ import android.content.pm.ApplicationInfo; import com.android.internal.compat.CompatibilityChangeInfo; import com.android.server.compat.config.Change; +import com.android.server.compat.overrides.ChangeOverrides; +import com.android.server.compat.overrides.OverrideValue; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -253,6 +256,71 @@ public final class CompatChange extends CompatibilityChangeInfo { return mDeferredOverrides != null && mDeferredOverrides.containsKey(packageName); } + /** + * Checks whether a change has any package overrides. + * @return true if the change has at least one deferred override + */ + boolean hasAnyPackageOverride() { + return mDeferredOverrides != null && !mDeferredOverrides.isEmpty(); + } + + /** + * Checks whether a change has any deferred overrides. + * @return true if the change has at least one deferred override + */ + boolean hasAnyDeferredOverride() { + return mPackageOverrides != null && !mPackageOverrides.isEmpty(); + } + + void loadOverrides(ChangeOverrides changeOverrides) { + if (mDeferredOverrides == null) { + mDeferredOverrides = new HashMap<>(); + } + mDeferredOverrides.clear(); + for (OverrideValue override : changeOverrides.getDeferred().getOverrideValue()) { + mDeferredOverrides.put(override.getPackageName(), override.getEnabled()); + } + + if (mPackageOverrides == null) { + mPackageOverrides = new HashMap<>(); + } + mPackageOverrides.clear(); + for (OverrideValue override : changeOverrides.getValidated().getOverrideValue()) { + mPackageOverrides.put(override.getPackageName(), override.getEnabled()); + } + } + + ChangeOverrides saveOverrides() { + if (!hasAnyDeferredOverride() && !hasAnyPackageOverride()) { + return null; + } + ChangeOverrides changeOverrides = new ChangeOverrides(); + changeOverrides.setChangeId(getId()); + ChangeOverrides.Deferred deferredOverrides = new ChangeOverrides.Deferred(); + List<OverrideValue> deferredList = deferredOverrides.getOverrideValue(); + if (mDeferredOverrides != null) { + for (Map.Entry<String, Boolean> entry : mDeferredOverrides.entrySet()) { + OverrideValue override = new OverrideValue(); + override.setPackageName(entry.getKey()); + override.setEnabled(entry.getValue()); + deferredList.add(override); + } + } + changeOverrides.setDeferred(deferredOverrides); + ChangeOverrides.Validated validatedOverrides = new ChangeOverrides.Validated(); + List<OverrideValue> validatedList = validatedOverrides.getOverrideValue(); + if (mPackageOverrides != null) { + for (Map.Entry<String, Boolean> entry : mPackageOverrides.entrySet()) { + OverrideValue override = new OverrideValue(); + override.setPackageName(entry.getKey()); + override.setEnabled(entry.getValue()); + validatedList.add(override); + } + } + changeOverrides.setValidated(validatedOverrides); + return changeOverrides; + } + @Override public String toString() { StringBuilder sb = new StringBuilder("ChangeId(") diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java index 69686a2e4678..6b77b9d4ce39 100644 --- a/services/core/java/com/android/server/compat/CompatConfig.java +++ b/services/core/java/com/android/server/compat/CompatConfig.java @@ -34,7 +34,10 @@ import com.android.internal.compat.CompatibilityChangeInfo; import com.android.internal.compat.IOverrideValidator; import com.android.internal.compat.OverrideAllowedState; import com.android.server.compat.config.Change; -import com.android.server.compat.config.XmlParser; +import com.android.server.compat.config.Config; +import com.android.server.compat.overrides.ChangeOverrides; +import com.android.server.compat.overrides.Overrides; +import com.android.server.compat.overrides.XmlWriter; import com.android.server.pm.ApexManager; import org.xmlpull.v1.XmlPullParserException; @@ -60,11 +63,14 @@ import javax.xml.datatype.DatatypeConfigurationException; final class CompatConfig { private static final String TAG = "CompatConfig"; + private static final String APP_COMPAT_DATA_DIR = "/data/misc/appcompat"; + private static final String OVERRIDES_FILE = "compat_framework_overrides.xml"; @GuardedBy("mChanges") private final LongSparseArray<CompatChange> mChanges = new LongSparseArray<>(); private final OverrideValidatorImpl mOverrideValidator; + private File mOverridesFile; @VisibleForTesting CompatConfig(AndroidBuildClassifier androidBuildClassifier, Context context) { @@ -83,6 +89,8 @@ final class CompatConfig { config.initConfigFromLib(Environment.buildPath( apex.apexDirectory, "etc", "compatconfig")); } + File overridesFile = new File(APP_COMPAT_DATA_DIR, OVERRIDES_FILE); + config.initOverrides(overridesFile); config.invalidateCache(); return config; } @@ -202,6 +210,17 @@ final class CompatConfig { * @throws IllegalStateException if overriding is not allowed */ boolean addOverride(long changeId, String packageName, boolean enabled) { + boolean alreadyKnown = addOverrideUnsafe(changeId, packageName, enabled); + saveOverrides(); + invalidateCache(); + return alreadyKnown; + } + + /** + * Unsafe version of {@link #addOverride(long, String, boolean)}. + * It does not invalidate the cache nor save the overrides. + */ + private boolean addOverrideUnsafe(long changeId, String packageName, boolean enabled) { boolean alreadyKnown = true; OverrideAllowedState allowedState = mOverrideValidator.getOverrideAllowedState(changeId, packageName); @@ -224,7 +243,6 @@ final class CompatConfig { throw new IllegalStateException("Should only be able to override changes that " + "are allowed or can be deferred."); } - invalidateCache(); } return alreadyKnown; } @@ -282,6 +300,17 @@ final class CompatConfig { * @return {@code true} if an override existed; */ boolean removeOverride(long changeId, String packageName) { + boolean overrideExists = removeOverrideUnsafe(changeId, packageName); + saveOverrides(); + invalidateCache(); + return overrideExists; + } + + /** + * Unsafe version of {@link #removeOverride(long, String)}. + * It does not invalidate the cache nor save the overrides. + */ + private boolean removeOverrideUnsafe(long changeId, String packageName) { boolean overrideExists = false; synchronized (mChanges) { CompatChange c = mChanges.get(changeId); @@ -300,7 +329,6 @@ final class CompatConfig { } } } - invalidateCache(); return overrideExists; } @@ -315,12 +343,13 @@ final class CompatConfig { void addOverrides(CompatibilityChangeConfig overrides, String packageName) { synchronized (mChanges) { for (Long changeId : overrides.enabledChanges()) { - addOverride(changeId, packageName, true); + addOverrideUnsafe(changeId, packageName, true); } for (Long changeId : overrides.disabledChanges()) { - addOverride(changeId, packageName, false); + addOverrideUnsafe(changeId, packageName, false); } + saveOverrides(); invalidateCache(); } } @@ -337,8 +366,9 @@ final class CompatConfig { synchronized (mChanges) { for (int i = 0; i < mChanges.size(); ++i) { CompatChange change = mChanges.valueAt(i); - removeOverride(change.getId(), packageName); + removeOverrideUnsafe(change.getId(), packageName); } + saveOverrides(); invalidateCache(); } } @@ -372,8 +402,10 @@ final class CompatConfig { int enableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) { long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion); for (long changeId : changes) { - addOverride(changeId, packageName, true); + addOverrideUnsafe(changeId, packageName, true); } + saveOverrides(); + invalidateCache(); return changes.length; } @@ -386,8 +418,10 @@ final class CompatConfig { int disableTargetSdkChangesForPackage(String packageName, int targetSdkVersion) { long[] changes = getAllowedChangesSinceTargetSdkForPackage(packageName, targetSdkVersion); for (long changeId : changes) { - addOverride(changeId, packageName, false); + addOverrideUnsafe(changeId, packageName, false); } + saveOverrides(); + invalidateCache(); return changes.length; } @@ -494,7 +528,8 @@ final class CompatConfig { private void readConfig(File configFile) { try (InputStream in = new BufferedInputStream(new FileInputStream(configFile))) { - for (Change change : XmlParser.read(in).getCompatChange()) { + Config config = com.android.server.compat.config.XmlParser.read(in); + for (Change change : config.getCompatChange()) { Slog.d(TAG, "Adding: " + change.toString()); addChange(new CompatChange(change)); } @@ -503,6 +538,65 @@ final class CompatConfig { } } + void initOverrides(File overridesFile) { + if (!overridesFile.exists()) { + mOverridesFile = overridesFile; + // There have not been any overrides added yet. + return; + } + + try (InputStream in = new BufferedInputStream(new FileInputStream(overridesFile))) { + Overrides overrides = com.android.server.compat.overrides.XmlParser.read(in); + for (ChangeOverrides changeOverrides : overrides.getChangeOverrides()) { + long changeId = changeOverrides.getChangeId(); + CompatChange compatChange = mChanges.get(changeId); + if (compatChange == null) { + Slog.w(TAG, "Change ID " + changeId + " not found. " + + "Skipping overrides for it."); + continue; + } + compatChange.loadOverrides(changeOverrides); + } + } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) { + Slog.w(TAG, "Error processing " + overridesFile + " " + e.toString()); + return; + } + mOverridesFile = overridesFile; + } + + /** + * Persist compat framework overrides to /data/misc/appcompat/compat_framework_overrides.xml + */ + void saveOverrides() { + if (mOverridesFile == null) { + return; + } + synchronized (mChanges) { + // Create the file if it doesn't already exist + try { + mOverridesFile.createNewFile(); + } catch (IOException e) { + Slog.e(TAG, "Could not create override config file: " + e.toString()); + return; + } + try (PrintWriter out = new PrintWriter(mOverridesFile)) { + XmlWriter writer = new XmlWriter(out); + Overrides overrides = new Overrides(); + List<ChangeOverrides> changeOverridesList = overrides.getChangeOverrides(); + for (int idx = 0; idx < mChanges.size(); ++idx) { + CompatChange c = mChanges.valueAt(idx); + ChangeOverrides changeOverrides = c.saveOverrides(); + if (changeOverrides != null) { + changeOverridesList.add(changeOverrides); + } + } + XmlWriter.write(writer, overrides); + } catch (IOException e) { + Slog.e(TAG, e.toString()); + } + } + } + IOverrideValidator getOverrideValidator() { return mOverrideValidator; } diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index ab0360b0395a..b2824846008c 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -329,7 +329,7 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { private final QosCallbackTracker mQosCallbackTracker; public NetworkAgentInfo(INetworkAgent na, Network net, NetworkInfo info, - LinkProperties lp, NetworkCapabilities nc, int score, Context context, + @NonNull LinkProperties lp, @NonNull NetworkCapabilities nc, int score, Context context, Handler handler, NetworkAgentConfig config, ConnectivityService connService, INetd netd, IDnsResolver dnsResolver, INetworkManagementService nms, int factorySerialNumber, int creatorUid, QosCallbackTracker qosCallbackTracker) { diff --git a/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java index a7be657ae7a3..5e6b9f39b40a 100644 --- a/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java +++ b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java @@ -686,7 +686,7 @@ public class NetworkDiagnostics { mHostname = hostname; mMeasurement.description = "DNS TLS dst{" + mTarget.getHostAddress() + "} hostname{" - + TextUtils.emptyIfNull(mHostname) + "}"; + + (mHostname == null ? "" : mHostname) + "}"; } private SSLSocket setupSSLSocket() throws IOException { diff --git a/services/core/java/com/android/server/display/DisplayGroup.java b/services/core/java/com/android/server/display/DisplayGroup.java index 2ba875813734..a11a745d0b7e 100644 --- a/services/core/java/com/android/server/display/DisplayGroup.java +++ b/services/core/java/com/android/server/display/DisplayGroup.java @@ -26,8 +26,6 @@ import java.util.List; */ public class DisplayGroup { - public static final int DEFAULT = 0; - private final List<LogicalDisplay> mDisplays = new ArrayList<>(); private final int mGroupId; diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 55103ca6cd1c..c3f8d8cf219a 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1357,7 +1357,7 @@ public final class DisplayManagerService extends SystemService { // Scan supported modes returned by display.getInfo() to find a mode with the same // size as the default display mode but with the specified refresh rate instead. requestedModeId = display.getDisplayInfoLocked().findDefaultModeByRefreshRate( - requestedRefreshRate); + requestedRefreshRate).getModeId(); } mDisplayModeDirector.getAppRequestObserver().setAppRequestedMode( displayId, requestedModeId); @@ -1538,6 +1538,14 @@ public final class DisplayManagerService extends SystemService { } } + void setDisplayModeDirectorLoggingEnabled(boolean enabled) { + synchronized (mSyncRoot) { + if (mDisplayModeDirector != null) { + mDisplayModeDirector.setLoggingEnabled(enabled); + } + } + } + void setAmbientColorTemperatureOverride(float cct) { synchronized (mSyncRoot) { final DisplayPowerController displayPowerController = mDisplayPowerControllers.get( diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java index 111664a078df..aaea15a27e01 100644 --- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java +++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java @@ -54,6 +54,10 @@ class DisplayManagerShellCommand extends ShellCommand { return setDisplayWhiteBalanceLoggingEnabled(true); case "dwb-logging-disable": return setDisplayWhiteBalanceLoggingEnabled(false); + case "dmd-logging-enable": + return setDisplayModeDirectorLoggingEnabled(true); + case "dmd-logging-disable": + return setDisplayModeDirectorLoggingEnabled(false); case "dwb-set-cct": return setAmbientColorTemperatureOverride(); case "set-fold": @@ -82,6 +86,10 @@ class DisplayManagerShellCommand extends ShellCommand { pw.println(" Enable display white-balance logging."); pw.println(" dwb-logging-disable"); pw.println(" Disable display white-balance logging."); + pw.println(" dmd-logging-enable"); + pw.println(" Enable display mode director logging."); + pw.println(" dmd-logging-disable"); + pw.println(" Disable display mode director logging."); pw.println(" dwb-set-cct CCT"); pw.println(" Sets the ambient color temperature override to CCT (use -1 to disable)."); pw.println(" set-fold [fold|unfold|reset]"); @@ -136,6 +144,11 @@ class DisplayManagerShellCommand extends ShellCommand { return 0; } + private int setDisplayModeDirectorLoggingEnabled(boolean enabled) { + mService.setDisplayModeDirectorLoggingEnabled(enabled); + return 0; + } + private int setAmbientColorTemperatureOverride() { String cctText = getNextArg(); if (cctText == null) { diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java index 006f87571f82..dce6bd849953 100644 --- a/services/core/java/com/android/server/display/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/DisplayModeDirector.java @@ -67,7 +67,7 @@ import java.util.Objects; */ public class DisplayModeDirector { private static final String TAG = "DisplayModeDirector"; - private static final boolean DEBUG = false; + private boolean mLoggingEnabled; private static final int MSG_REFRESH_RATE_RANGE_CHANGED = 1; private static final int MSG_LOW_BRIGHTNESS_THRESHOLDS_CHANGED = 2; @@ -155,6 +155,14 @@ public class DisplayModeDirector { } } + public void setLoggingEnabled(boolean loggingEnabled) { + if (mLoggingEnabled == loggingEnabled) { + return; + } + mLoggingEnabled = loggingEnabled; + mBrightnessObserver.setLoggingEnabled(loggingEnabled); + } + @NonNull private SparseArray<Vote> getVotesLocked(int displayId) { SparseArray<Vote> displayVotes = mVotesByDisplay.get(displayId); @@ -269,7 +277,7 @@ public class DisplayModeDirector { availableModes = filterModes(modes, primarySummary); if (availableModes.length > 0) { - if (DEBUG) { + if (mLoggingEnabled) { Slog.w(TAG, "Found available modes=" + Arrays.toString(availableModes) + " with lowest priority considered " + Vote.priorityToString(lowestConsideredPriority) @@ -282,7 +290,7 @@ public class DisplayModeDirector { break; } - if (DEBUG) { + if (mLoggingEnabled) { Slog.w(TAG, "Couldn't find available modes with lowest priority set to " + Vote.priorityToString(lowestConsideredPriority) + " and with the following constraints: " @@ -307,7 +315,7 @@ public class DisplayModeDirector { Math.min(appRequestSummary.minRefreshRate, primarySummary.minRefreshRate); appRequestSummary.maxRefreshRate = Math.max(appRequestSummary.maxRefreshRate, primarySummary.maxRefreshRate); - if (DEBUG) { + if (mLoggingEnabled) { Slog.i(TAG, String.format("App request range: [%.0f %.0f]", appRequestSummary.minRefreshRate, @@ -357,7 +365,7 @@ public class DisplayModeDirector { for (Display.Mode mode : supportedModes) { if (mode.getPhysicalWidth() != summary.width || mode.getPhysicalHeight() != summary.height) { - if (DEBUG) { + if (mLoggingEnabled) { Slog.w(TAG, "Discarding mode " + mode.getModeId() + ", wrong size" + ": desiredWidth=" + summary.width + ": desiredHeight=" + summary.height @@ -372,7 +380,7 @@ public class DisplayModeDirector { // comparison. if (refreshRate < (summary.minRefreshRate - FLOAT_TOLERANCE) || refreshRate > (summary.maxRefreshRate + FLOAT_TOLERANCE)) { - if (DEBUG) { + if (mLoggingEnabled) { Slog.w(TAG, "Discarding mode " + mode.getModeId() + ", outside refresh rate bounds" + ": minRefreshRate=" + summary.minRefreshRate @@ -516,7 +524,7 @@ public class DisplayModeDirector { } private void updateVoteLocked(int displayId, int priority, Vote vote) { - if (DEBUG) { + if (mLoggingEnabled) { Slog.i(TAG, "updateVoteLocked(displayId=" + displayId + ", priority=" + Vote.priorityToString(priority) + ", vote=" + vote + ")"); @@ -537,7 +545,7 @@ public class DisplayModeDirector { } if (votes.size() == 0) { - if (DEBUG) { + if (mLoggingEnabled) { Slog.i(TAG, "No votes left for display " + displayId + ", removing."); } mVotesByDisplay.remove(displayId); @@ -1287,6 +1295,7 @@ public class DisplayModeDirector { private boolean mShouldObserveAmbientLowChange; private boolean mShouldObserveDisplayHighChange; private boolean mShouldObserveAmbientHighChange; + private boolean mLoggingEnabled; private SensorManager mSensorManager; private Sensor mLightSensor; @@ -1303,7 +1312,6 @@ public class DisplayModeDirector { // changeable and low power mode off. After initialization, these states will // be updated from the same handler thread. private int mDefaultDisplayState = Display.STATE_UNKNOWN; - private boolean mIsDeviceActive = false; private boolean mRefreshRateChangeable = false; private boolean mLowPowerModeEnabled = false; @@ -1415,6 +1423,14 @@ public class DisplayModeDirector { mDeviceConfigDisplaySettings.startListening(); } + public void setLoggingEnabled(boolean loggingEnabled) { + if (mLoggingEnabled == loggingEnabled) { + return; + } + mLoggingEnabled = loggingEnabled; + mLightSensorListener.setLoggingEnabled(loggingEnabled); + } + public void onRefreshRateSettingChangedLocked(float min, float max) { boolean changeable = (max - min > 1f && max > 60f); if (mRefreshRateChangeable != changeable) { @@ -1485,7 +1501,6 @@ public class DisplayModeDirector { pw.println(" mAmbientLux: " + mAmbientLux); pw.println(" mBrightness: " + mBrightness); pw.println(" mDefaultDisplayState: " + mDefaultDisplayState); - pw.println(" mIsDeviceActive: " + mIsDeviceActive); pw.println(" mLowPowerModeEnabled: " + mLowPowerModeEnabled); pw.println(" mRefreshRateChangeable: " + mRefreshRateChangeable); pw.println(" mShouldObserveDisplayLowChange: " + mShouldObserveDisplayLowChange); @@ -1691,7 +1706,7 @@ public class DisplayModeDirector { vote = Vote.forRefreshRates(mRefreshRateInHighZone, mRefreshRateInHighZone); } - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "Display brightness " + mBrightness + ", ambient lux " + mAmbientLux + ", Vote " + vote); } @@ -1720,6 +1735,11 @@ public class DisplayModeDirector { @VisibleForTesting public void setDefaultDisplayState(int state) { + if (mLoggingEnabled) { + Slog.d(TAG, "setDefaultDisplayState: mDefaultDisplayState = " + + mDefaultDisplayState + ", state = " + state); + } + if (mDefaultDisplayState != state) { mDefaultDisplayState = state; updateSensorStatus(); @@ -1731,36 +1751,58 @@ public class DisplayModeDirector { return; } + if (mLoggingEnabled) { + Slog.d(TAG, "updateSensorStatus: mShouldObserveAmbientLowChange = " + + mShouldObserveAmbientLowChange + ", mShouldObserveAmbientHighChange = " + + mShouldObserveAmbientHighChange); + Slog.d(TAG, "updateSensorStatus: mLowPowerModeEnabled = " + + mLowPowerModeEnabled + ", mRefreshRateChangeable = " + + mRefreshRateChangeable); + } + if ((mShouldObserveAmbientLowChange || mShouldObserveAmbientHighChange) && isDeviceActive() && !mLowPowerModeEnabled && mRefreshRateChangeable) { mSensorManager.registerListener(mLightSensorListener, mLightSensor, LIGHT_SENSOR_RATE_MS * 1000, mHandler); + if (mLoggingEnabled) { + Slog.d(TAG, "updateSensorStatus: registerListener"); + } } else { mLightSensorListener.removeCallbacks(); mSensorManager.unregisterListener(mLightSensorListener); + if (mLoggingEnabled) { + Slog.d(TAG, "updateSensorStatus: unregisterListener"); + } } } private boolean isDeviceActive() { - mIsDeviceActive = mInjector.isDeviceInteractive(mContext); - return (mDefaultDisplayState == Display.STATE_ON) - && mIsDeviceActive; + return mDefaultDisplayState == Display.STATE_ON; } private final class LightSensorEventListener implements SensorEventListener { final private static int INJECT_EVENTS_INTERVAL_MS = LIGHT_SENSOR_RATE_MS; private float mLastSensorData; private long mTimestamp; + private boolean mLoggingEnabled; public void dumpLocked(PrintWriter pw) { pw.println(" mLastSensorData: " + mLastSensorData); pw.println(" mTimestamp: " + formatTimestamp(mTimestamp)); } + + public void setLoggingEnabled(boolean loggingEnabled) { + if (mLoggingEnabled == loggingEnabled) { + return; + } + mLoggingEnabled = loggingEnabled; + } + @Override public void onSensorChanged(SensorEvent event) { mLastSensorData = event.values[0]; - if (DEBUG) { + if (mLoggingEnabled) { Slog.d(TAG, "On sensor changed: " + mLastSensorData); } @@ -2009,8 +2051,6 @@ public class DisplayModeDirector { void registerPeakRefreshRateObserver(@NonNull ContentResolver cr, @NonNull ContentObserver observer); - - boolean isDeviceInteractive(@NonNull Context context); } @VisibleForTesting @@ -2041,11 +2081,6 @@ public class DisplayModeDirector { cr.registerContentObserver(PEAK_REFRESH_RATE_URI, false /*notifyDescendants*/, observer, UserHandle.USER_SYSTEM); } - - @Override - public boolean isDeviceInteractive(@NonNull Context ctx) { - return ctx.getSystemService(PowerManager.class).isInteractive(); - } } } diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index 5bf83db4ee34..86de159b5824 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -71,6 +71,9 @@ final class LogicalDisplay { private final int mDisplayId; private final int mLayerStack; + + private int mDisplayGroupId = Display.INVALID_DISPLAY_GROUP; + /** * Override information set by the window manager. Will be reported instead of {@link #mInfo} * if not null. @@ -265,6 +268,19 @@ final class LogicalDisplay { } /** + * Updates the {@link DisplayGroup} to which the logical display belongs. + * + * @param groupId Identifier for the {@link DisplayGroup}. + */ + public void updateDisplayGroupIdLocked(int groupId) { + if (groupId != mDisplayGroupId) { + mDisplayGroupId = groupId; + mBaseDisplayInfo.displayGroupId = groupId; + mInfo.set(null); + } + } + + /** * Updates the state of the logical display based on the available display devices. * The logical display might become invalid if it is attached to a display device * that no longer exists. @@ -365,6 +381,7 @@ final class LogicalDisplay { (deviceInfo.flags & DisplayDeviceInfo.FLAG_MASK_DISPLAY_CUTOUT) != 0; mBaseDisplayInfo.displayCutout = maskCutout ? null : deviceInfo.displayCutout; mBaseDisplayInfo.displayId = mDisplayId; + mBaseDisplayInfo.displayGroupId = mDisplayGroupId; updateFrameRateOverrides(deviceInfo); mBaseDisplayInfo.brightnessMinimum = deviceInfo.brightnessMinimum; mBaseDisplayInfo.brightnessMaximum = deviceInfo.brightnessMaximum; diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index bb2fbed354aa..e7388787ecf9 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -96,7 +96,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { private final SparseArray<LogicalDisplay> mLogicalDisplays = new SparseArray<LogicalDisplay>(); private int mNextNonDefaultDisplayId = Display.DEFAULT_DISPLAY + 1; - private int mNextNonDefaultGroupId = DisplayGroup.DEFAULT + 1; + private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1; /** A mapping from logical display id to display group. */ private final SparseArray<DisplayGroup> mDisplayGroups = new SparseArray<>(); @@ -313,7 +313,18 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { final int displayId = assignDisplayIdLocked(isDefault); final int layerStack = assignLayerStackLocked(displayId); + final DisplayGroup displayGroup; + final boolean addNewDisplayGroup = + isDefault || (deviceInfo.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP) != 0; + if (addNewDisplayGroup) { + final int groupId = assignDisplayGroupIdLocked(isDefault); + displayGroup = new DisplayGroup(groupId); + } else { + displayGroup = mDisplayGroups.get(Display.DEFAULT_DISPLAY); + } + LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device); + display.updateDisplayGroupIdLocked(displayGroup.getGroupId()); display.updateLocked(mDisplayDeviceRepo); if (!display.isValidLocked()) { // This should never happen currently. @@ -324,13 +335,6 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { mLogicalDisplays.put(displayId, display); - final DisplayGroup displayGroup; - if (isDefault || (deviceInfo.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP) != 0) { - final int groupId = assignDisplayGroupIdLocked(isDefault); - displayGroup = new DisplayGroup(groupId); - } else { - displayGroup = mDisplayGroups.get(Display.DEFAULT_DISPLAY); - } displayGroup.addDisplay(display); mDisplayGroups.append(displayId, displayGroup); @@ -369,6 +373,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { final DisplayGroup displayGroup = new DisplayGroup(groupId); displayGroup.addDisplay(display); mDisplayGroups.append(display.getDisplayIdLocked(), displayGroup); + display.updateDisplayGroupIdLocked(groupId); } } else { // The display should be a part of the default DisplayGroup. @@ -377,6 +382,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { displayGroup.removeDisplay(display); defaultDisplayGroup.addDisplay(display); mDisplayGroups.put(displayId, defaultDisplayGroup); + display.updateDisplayGroupIdLocked(defaultDisplayGroup.getGroupId()); } } @@ -406,7 +412,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { } private int assignDisplayGroupIdLocked(boolean isDefault) { - return isDefault ? DisplayGroup.DEFAULT : mNextNonDefaultGroupId++; + return isDefault ? Display.DEFAULT_DISPLAY_GROUP : mNextNonDefaultGroupId++; } private int assignLayerStackLocked(int displayId) { diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java index bda4240a5858..5b3db011b427 100644 --- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java +++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java @@ -22,12 +22,15 @@ import android.content.Context; import android.graphics.Typeface; import android.graphics.fonts.FontFamily; import android.graphics.fonts.FontFileUtil; +import android.graphics.fonts.FontManager; import android.graphics.fonts.SystemFonts; -import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.os.ResultReceiver; import android.os.SharedMemory; +import android.os.ShellCallback; import android.system.ErrnoException; import android.text.FontConfig; +import android.util.AndroidException; import android.util.IndentingPrintWriter; import android.util.Slog; @@ -48,6 +51,7 @@ import java.nio.ByteBuffer; import java.nio.NioUtils; import java.nio.channels.FileChannel; import java.util.Arrays; +import java.util.Collections; import java.util.Map; /** A service for managing system fonts. */ @@ -55,8 +59,6 @@ import java.util.Map; public final class FontManagerService extends IFontManager.Stub { private static final String TAG = "FontManagerService"; - // TODO: make this a DeviceConfig flag. - private static final boolean ENABLE_FONT_UPDATES = false; private static final String FONT_FILES_DIR = "/data/fonts/files"; @Override @@ -64,6 +66,24 @@ public final class FontManagerService extends IFontManager.Stub { return getCurrentFontSettings().getSystemFontConfig(); } + /* package */ static class SystemFontException extends AndroidException { + private final int mErrorCode; + + SystemFontException(@FontManager.ErrorCode int errorCode, String msg, Throwable cause) { + super(msg, cause); + mErrorCode = errorCode; + } + + SystemFontException(int errorCode, String msg) { + super(msg); + mErrorCode = errorCode; + } + + @FontManager.ErrorCode int getErrorCode() { + return mErrorCode; + } + } + /** Class to manage FontManagerService's lifecycle. */ public static final class Lifecycle extends SystemService { private final FontManagerService mService; @@ -151,7 +171,6 @@ public final class FontManagerService extends IFontManager.Stub { @Nullable private static UpdatableFontDir createUpdatableFontDir() { - if (!ENABLE_FONT_UPDATES) return null; // If apk verity is supported, fs-verity should be available. if (!FileIntegrityService.isApkVeritySupported()) return null; return new UpdatableFontDir(new File(FONT_FILES_DIR), @@ -178,19 +197,34 @@ public final class FontManagerService extends IFontManager.Stub { } } - // TODO(b/173619554): Expose as API. - private boolean installFontFile(FileDescriptor fd, byte[] pkcs7Signature) { - if (mUpdatableFontDir == null) return false; + /* package */ void installFontFile(FileDescriptor fd, byte[] pkcs7Signature) + throws SystemFontException { + if (mUpdatableFontDir == null) { + throw new SystemFontException( + FontManager.ERROR_CODE_FONT_UPDATER_DISABLED, + "The font updater is disabled."); + } synchronized (FontManagerService.this) { - try { - mUpdatableFontDir.installFontFile(fd, pkcs7Signature); - } catch (IOException e) { - Slog.w(TAG, "Failed to install font file"); - return false; - } + mUpdatableFontDir.installFontFile(fd, pkcs7Signature); // Create updated font map in the next getSerializedSystemFontMap() call. mCurrentFontSettings = null; - return true; + } + } + + /* package */ void clearUpdates() throws SystemFontException { + if (mUpdatableFontDir == null) { + throw new SystemFontException( + FontManager.ERROR_CODE_FONT_UPDATER_DISABLED, + "The font updater is disabled."); + } + mUpdatableFontDir.clearUpdates(); + } + + /* package */ Map<String, File> getFontFileMap() { + if (mUpdatableFontDir == null) { + return Collections.emptyMap(); + } else { + return mUpdatableFontDir.getFontFileMap(); } } @@ -202,11 +236,13 @@ public final class FontManagerService extends IFontManager.Stub { } @Override - public int handleShellCommand(@NonNull ParcelFileDescriptor in, - @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err, - @NonNull String[] args) { - return new FontManagerShellCommand(this).exec(this, - in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), args); + public void onShellCommand(@Nullable FileDescriptor in, + @Nullable FileDescriptor out, + @Nullable FileDescriptor err, + @NonNull String[] args, + @Nullable ShellCallback callback, + @NonNull ResultReceiver result) throws RemoteException { + new FontManagerShellCommand(this).exec(this, in, out, err, args, callback, result); } /* package */ static class SystemFontSettings { @@ -245,8 +281,7 @@ public final class FontManagerService extends IFontManager.Stub { public static @Nullable SystemFontSettings create( @Nullable UpdatableFontDir updatableFontDir) { if (updatableFontDir != null) { - final FontConfig fontConfig = SystemFonts.getSystemFontConfig( - updatableFontDir.getFontFileMap()); + final FontConfig fontConfig = updatableFontDir.getSystemFontConfig(); final Map<String, FontFamily[]> fallback = SystemFonts.buildSystemFallback(fontConfig); final Map<String, Typeface> typefaceMap = @@ -274,4 +309,5 @@ public final class FontManagerService extends IFontManager.Stub { return null; } } + } diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java index acb582637e55..fd5c020b1a15 100644 --- a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java +++ b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java @@ -16,19 +16,33 @@ package com.android.server.graphics.fonts; +import static com.android.server.graphics.fonts.FontManagerService.SystemFontException; + import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.graphics.fonts.Font; import android.graphics.fonts.FontFamily; +import android.graphics.fonts.FontManager; import android.graphics.fonts.FontVariationAxis; +import android.os.Binder; +import android.os.ParcelFileDescriptor; +import android.os.Process; import android.os.ShellCommand; import android.text.FontConfig; import android.util.IndentingPrintWriter; +import android.util.Slog; import com.android.internal.util.DumpUtils; import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; import java.io.PrintWriter; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; import java.util.List; import java.util.Map; @@ -38,6 +52,13 @@ import java.util.Map; public class FontManagerShellCommand extends ShellCommand { private static final String TAG = "FontManagerShellCommand"; + /** + * The maximum size of signature file. This is just to avoid potential abuse. + * + * This is copied from VerityUtils.java. + */ + private static final int MAX_SIGNATURE_FILE_SIZE_BYTES = 8192; + @NonNull private final FontManagerService mService; FontManagerShellCommand(@NonNull FontManagerService service) { @@ -46,12 +67,31 @@ public class FontManagerShellCommand extends ShellCommand { @Override public int onCommand(String cmd) { + final int callingUid = Binder.getCallingUid(); + if (callingUid != Process.ROOT_UID && callingUid != Process.SHELL_UID) { + // Do not change this string since this string is expected in the CTS. + getErrPrintWriter().println("Only shell or root user can execute font command."); + return 1; + } return execCommand(this, cmd); } @Override public void onHelp() { - dumpHelp(getOutPrintWriter()); + PrintWriter w = getOutPrintWriter(); + w.println("Font service (font) commands"); + w.println("help"); + w.println(" Print this help text."); + w.println(); + w.println("dump [family name]"); + w.println(" Dump all font files in the specified family name."); + w.println(" Dump current system font configuration if no family name was specified."); + w.println(); + w.println("update [font file path] [signature file path]"); + w.println(" Update installed font files with new font file."); + w.println(); + w.println("clear"); + w.println(" Remove all installed font files and reset to the initial state."); } /* package */ void dumpAll(@NonNull IndentingPrintWriter w) { @@ -165,16 +205,6 @@ public class FontManagerShellCommand extends ShellCommand { w.decreaseIndent(); } - private static void dumpHelp(@NonNull PrintWriter w) { - w.println("Font service (font) commands"); - w.println("help"); - w.println(" Print this help text."); - w.println(); - w.println("dump [family name]"); - w.println(" Dump all font files in the specified family name."); - w.println(" Dump current system font configuration if no family name was specified."); - } - private void dumpFallback(@NonNull IndentingPrintWriter writer, @NonNull FontFamily[] families) { for (FontFamily family : families) { @@ -233,37 +263,143 @@ public class FontManagerShellCommand extends ShellCommand { writer.println(sb.toString()); } - private int execCommand(@NonNull ShellCommand shell, @NonNull String cmd) { + private void writeCommandResult(ShellCommand shell, SystemFontException e) { + // Print short summary to the stderr. + PrintWriter pw = shell.getErrPrintWriter(); + pw.println(e.getErrorCode()); + pw.println(e.getMessage()); + + // Dump full stack trace to logcat. + + Slog.e(TAG, "Command failed: " + Arrays.toString(shell.getAllArgs()), e); + } + + private int dump(ShellCommand shell) { final Context ctx = mService.getContext(); - if (cmd == null) { - return shell.handleDefaultCommands(null); + final FontManagerService.SystemFontSettings settings = + mService.getCurrentFontSettings(); + if (!DumpUtils.checkDumpPermission(ctx, TAG, shell.getErrPrintWriter())) { + return 1; + } + final IndentingPrintWriter writer = + new IndentingPrintWriter(shell.getOutPrintWriter(), " "); + String nextArg = shell.getNextArg(); + if (nextArg == null) { + dumpFontConfig(writer, settings.getSystemFontConfig()); + } else { + final Map<String, FontFamily[]> fallbackMap = + settings.getSystemFallbackMap(); + FontFamily[] families = fallbackMap.get(nextArg); + if (families == null) { + writer.println("Font Family \"" + nextArg + "\" not found"); + } else { + dumpFallback(writer, families); + } } + return 0; + } - final FontManagerService.SystemFontSettings settings = mService.getCurrentFontSettings(); + private int update(ShellCommand shell) throws SystemFontException { + String fontPath = shell.getNextArg(); + if (fontPath == null) { + throw new SystemFontException( + FontManager.ERROR_CODE_INVALID_SHELL_ARGUMENT, + "Font file path argument is required."); + } + String signaturePath = shell.getNextArg(); + if (signaturePath == null) { + throw new SystemFontException( + FontManager.ERROR_CODE_INVALID_SHELL_ARGUMENT, + "Signature file argument is required."); + } + + ParcelFileDescriptor fontFd = shell.openFileForSystem(fontPath, "r"); + if (fontFd == null) { + throw new SystemFontException( + FontManager.ERROR_CODE_FAILED_TO_OPEN_FONT_FILE, + "Failed to open font file"); + } - switch (cmd) { - case "dump": - if (!DumpUtils.checkDumpPermission(ctx, TAG, shell.getErrPrintWriter())) { - return 1; + ParcelFileDescriptor sigFd = shell.openFileForSystem(signaturePath, "r"); + if (sigFd == null) { + throw new SystemFontException( + FontManager.ERROR_CODE_FAILED_TO_OPEN_SIGNATURE_FILE, + "Failed to open signature file"); + } + + try (FileInputStream sigFis = new FileInputStream(sigFd.getFileDescriptor())) { + try (FileInputStream fontFis = new FileInputStream(fontFd.getFileDescriptor())) { + int len = sigFis.available(); + if (len > MAX_SIGNATURE_FILE_SIZE_BYTES) { + throw new SystemFontException( + FontManager.ERROR_CODE_SIGNATURE_TOO_LARGE, + "Signature file is too large"); } - final IndentingPrintWriter writer = - new IndentingPrintWriter(shell.getOutPrintWriter(), " "); - String nextArg = shell.getNextArg(); - if (nextArg == null) { - dumpFontConfig(writer, settings.getSystemFontConfig()); - } else { - final Map<String, FontFamily[]> fallbackMap = settings.getSystemFallbackMap(); - FontFamily[] families = fallbackMap.get(nextArg); - if (families == null) { - writer.println("Font Family \"" + nextArg + "\" not found"); - } else { - dumpFallback(writer, families); - } + byte[] signature = new byte[len]; + if (sigFis.read(signature, 0, len) != len) { + throw new SystemFontException( + FontManager.ERROR_CODE_INVALID_SIGNATURE_FILE, + "Invalid read length"); } - return 0; - default: - shell.handleDefaultCommands(cmd); + mService.installFontFile(fontFis.getFD(), signature); + } catch (IOException e) { + throw new SystemFontException( + FontManager.ERROR_CODE_INVALID_SIGNATURE_FILE, + "Failed to read signature file.", e); + } + } catch (IOException e) { + throw new SystemFontException( + FontManager.ERROR_CODE_INVALID_FONT_FILE, + "Failed to read font files.", e); } + + shell.getOutPrintWriter().println("Success"); // TODO: Output more details. return 0; } + + private int clear(ShellCommand shell) throws SystemFontException { + mService.clearUpdates(); + shell.getOutPrintWriter().println("Success"); + return 0; + } + + private int status(ShellCommand shell) throws SystemFontException { + final FontManagerService.SystemFontSettings settings = mService.getCurrentFontSettings(); + final IndentingPrintWriter writer = + new IndentingPrintWriter(shell.getOutPrintWriter(), " "); + FontConfig config = settings.getSystemFontConfig(); + + writer.println("Current Version: " + config.getConfigVersion()); + LocalDateTime dt = LocalDateTime.ofEpochSecond(config.getLastModifiedDate(), 0, + ZoneOffset.UTC); + writer.println("Last Modified Date: " + dt.format(DateTimeFormatter.ISO_DATE_TIME)); + + Map<String, File> fontFileMap = mService.getFontFileMap(); + writer.println("Number of updated font files: " + fontFileMap.size()); + return 0; + } + + private int execCommand(@NonNull ShellCommand shell, @Nullable String cmd) { + if (cmd == null) { + return shell.handleDefaultCommands(null); + } + + try { + switch (cmd) { + case "dump": + return dump(shell); + case "update": + return update(shell); + case "clear": + return clear(shell); + case "status": + return status(shell); + default: + return shell.handleDefaultCommands(cmd); + } + } catch (SystemFontException e) { + writeCommandResult(shell, e); + return 1; + } + } } diff --git a/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java b/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java new file mode 100644 index 000000000000..f0d14ba80383 --- /dev/null +++ b/services/core/java/com/android/server/graphics/fonts/PersistentSystemFontConfig.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.graphics.fonts; + +import android.annotation.NonNull; +import android.text.TextUtils; +import android.util.Slog; +import android.util.TypedXmlPullParser; +import android.util.TypedXmlSerializer; +import android.util.Xml; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/* package */ class PersistentSystemFontConfig { + private static final String TAG = "PersistentSystemFontConfig"; + + private static final String TAG_ROOT = "fontConfig"; + private static final String TAG_LAST_MODIFIED_DATE = "lastModifiedDate"; + private static final String TAG_VALUE = "value"; + + /* package */ static class Config { + public long lastModifiedDate; + + public void reset() { + lastModifiedDate = 0; + } + + public void copyTo(@NonNull Config out) { + out.lastModifiedDate = lastModifiedDate; + } + } + + /** + * Read config XML and write to out argument. + */ + public static void loadFromXml(@NonNull InputStream is, @NonNull Config out) + throws XmlPullParserException, IOException { + out.reset(); + TypedXmlPullParser parser = Xml.resolvePullParser(is); + + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (type != XmlPullParser.START_TAG) { + continue; + } + final int depth = parser.getDepth(); + final String tag = parser.getName(); + if (depth == 1) { + if (!TAG_ROOT.equals(tag)) { + Slog.e(TAG, "Invalid root tag: " + tag); + return; + } + } else if (depth == 2) { + switch (tag) { + case TAG_LAST_MODIFIED_DATE: + out.lastModifiedDate = parseLongAttribute(parser, TAG_VALUE, 0); + break; + default: + Slog.w(TAG, "Skipping unknown tag: " + tag); + } + } + } + + } + + /** + * Write config to OutputStream as XML file. + */ + public static void writeToXml(@NonNull OutputStream os, @NonNull Config config) + throws IOException { + TypedXmlSerializer out = Xml.resolveSerializer(os); + out.startDocument(null /* encoding */, true /* standalone */); + + out.startTag(null, TAG_ROOT); + out.startTag(null, TAG_LAST_MODIFIED_DATE); + out.attribute(null, TAG_VALUE, Long.toString(config.lastModifiedDate)); + out.endTag(null, TAG_LAST_MODIFIED_DATE); + out.endTag(null, TAG_ROOT); + + out.endDocument(); + } + + private static long parseLongAttribute(TypedXmlPullParser parser, String attr, long defValue) { + final String value = parser.getAttributeValue(null /* namespace */, attr); + if (TextUtils.isEmpty(value)) { + return defValue; + } + try { + return Long.parseLong(value); + } catch (NumberFormatException e) { + return defValue; + } + } + +} diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java index 8da579fd4fa3..720105d29836 100644 --- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java +++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java @@ -16,18 +16,30 @@ package com.android.server.graphics.fonts; +import static com.android.server.graphics.fonts.FontManagerService.SystemFontException; + +import android.annotation.NonNull; import android.annotation.Nullable; +import android.graphics.fonts.FontManager; +import android.graphics.fonts.SystemFonts; import android.os.FileUtils; +import android.system.ErrnoException; +import android.system.Os; +import android.text.FontConfig; import android.util.Base64; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import org.xmlpull.v1.XmlPullParserException; + import java.io.File; import java.io.FileDescriptor; +import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.security.SecureRandom; +import java.time.Instant; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -39,6 +51,8 @@ final class UpdatableFontDir { // TODO: Support .otf private static final String ALLOWED_EXTENSION = ".ttf"; + private static final String CONFIG_XML_FILE = "/data/fonts/config/config.xml"; + /** Interface to mock font file access in tests. */ interface FontFileParser { String getPostScriptName(File file) throws IOException; @@ -55,6 +69,15 @@ final class UpdatableFontDir { boolean rename(File src, File dest); } + /** Interface to mock persistent configuration */ + interface PersistentConfig { + void loadFromXml(PersistentSystemFontConfig.Config out) + throws XmlPullParserException, IOException; + void writeToXml(PersistentSystemFontConfig.Config config) + throws IOException; + boolean rename(File src, File dest); + } + /** Data class to hold font file path and revision. */ private static final class FontFileInfo { private final File mFile; @@ -87,6 +110,16 @@ final class UpdatableFontDir { private final List<File> mPreinstalledFontDirs; private final FontFileParser mParser; private final FsverityUtil mFsverityUtil; + private final File mConfigFile; + private final File mTmpConfigFile; + + @GuardedBy("UpdatableFontDir.this") + private final PersistentSystemFontConfig.Config mConfig = + new PersistentSystemFontConfig.Config(); + + @GuardedBy("UpdatableFontDir.this") + private int mConfigVersion = 1; + /** * A mutable map containing mapping from font file name (e.g. "NotoColorEmoji.ttf") to {@link * FontFileInfo}. All files in this map are validated, and have higher revision numbers than @@ -101,6 +134,20 @@ final class UpdatableFontDir { mPreinstalledFontDirs = preinstalledFontDirs; mParser = parser; mFsverityUtil = fsverityUtil; + mConfigFile = new File(CONFIG_XML_FILE); + mTmpConfigFile = new File(CONFIG_XML_FILE + ".tmp"); + loadFontFileMap(); + } + + // For unit testing + UpdatableFontDir(File filesDir, List<File> preinstalledFontDirs, FontFileParser parser, + FsverityUtil fsverityUtil, File configFile) { + mFilesDir = filesDir; + mPreinstalledFontDirs = preinstalledFontDirs; + mParser = parser; + mFsverityUtil = fsverityUtil; + mConfigFile = configFile; + mTmpConfigFile = new File(configFile.getAbsoluteFile() + ".tmp"); loadFontFileMap(); } @@ -108,6 +155,13 @@ final class UpdatableFontDir { // TODO: SIGBUS crash protection synchronized (UpdatableFontDir.this) { boolean success = false; + + try (FileInputStream fis = new FileInputStream(mConfigFile)) { + PersistentSystemFontConfig.loadFromXml(fis, mConfig); + } catch (IOException | XmlPullParserException e) { + mConfig.reset(); + } + mFontFileInfoMap.clear(); try { File[] dirs = mFilesDir.listFiles(); @@ -117,13 +171,13 @@ final class UpdatableFontDir { File[] files = dir.listFiles(); if (files == null || files.length != 1) return; FontFileInfo fontFileInfo = validateFontFile(files[0]); - if (fontFileInfo == null) { - Slog.w(TAG, "Broken file is found. Clearing files."); - return; - } - addFileToMapLocked(fontFileInfo, true /* deleteOldFile */); + addFileToMapIfNewerLocked(fontFileInfo, true /* deleteOldFile */); } success = true; + } catch (Throwable t) { + // If something happened during loading system fonts, clear all contents in finally + // block. Here, just dumping errors. + Slog.e(TAG, "Failed to load font mappings.", t); } finally { // Delete all files just in case if we find a problematic file. if (!success) { @@ -134,6 +188,24 @@ final class UpdatableFontDir { } } + /* package */ void clearUpdates() throws SystemFontException { + synchronized (UpdatableFontDir.this) { + mFontFileInfoMap.clear(); + FileUtils.deleteContents(mFilesDir); + + mConfig.reset(); + mConfig.lastModifiedDate = Instant.now().getEpochSecond(); + try (FileOutputStream fos = new FileOutputStream(mConfigFile)) { + PersistentSystemFontConfig.writeToXml(fos, mConfig); + } catch (Exception e) { + throw new SystemFontException( + FontManager.ERROR_CODE_FAILED_TO_CREATE_CONFIG_FILE, + "Failed to write config XML.", e); + } + mConfigVersion++; + } + } + /** * Installs a new font file, or updates an existing font file. * @@ -143,38 +215,108 @@ final class UpdatableFontDir { * * @param fd A file descriptor to the font file. * @param pkcs7Signature A PKCS#7 detached signature to enable fs-verity for the font file. + * @throws SystemFontException if error occurs. */ - void installFontFile(FileDescriptor fd, byte[] pkcs7Signature) throws IOException { + void installFontFile(FileDescriptor fd, byte[] pkcs7Signature) throws SystemFontException { synchronized (UpdatableFontDir.this) { File newDir = getRandomDir(mFilesDir); if (!newDir.mkdir()) { - // TODO: Define and return an error code for API - throw new IOException("Failed to create a new dir"); + throw new SystemFontException( + FontManager.ERROR_CODE_FAILED_TO_WRITE_FONT_FILE, + "Failed to create font directory."); + } + try { + // Make newDir executable so that apps can access font file inside newDir. + Os.chmod(newDir.getAbsolutePath(), 0711); + } catch (ErrnoException e) { + throw new SystemFontException( + FontManager.ERROR_CODE_FAILED_TO_WRITE_FONT_FILE, + "Failed to change mode to 711", e); } boolean success = false; try { File tempNewFontFile = new File(newDir, "font.ttf"); try (FileOutputStream out = new FileOutputStream(tempNewFontFile)) { FileUtils.copy(fd, out.getFD()); + } catch (IOException e) { + throw new SystemFontException( + FontManager.ERROR_CODE_FAILED_TO_WRITE_FONT_FILE, + "Failed to write font file to storage.", e); + } + try { + // Do not parse font file before setting up fs-verity. + // setUpFsverity throws IOException if failed. + mFsverityUtil.setUpFsverity(tempNewFontFile.getAbsolutePath(), + pkcs7Signature); + } catch (IOException e) { + throw new SystemFontException( + FontManager.ERROR_CODE_VERIFICATION_FAILURE, + "Failed to setup fs-verity.", e); + } + String postScriptName; + try { + postScriptName = mParser.getPostScriptName(tempNewFontFile); + } catch (IOException e) { + throw new SystemFontException( + FontManager.ERROR_CODE_INVALID_FONT_FILE, + "Failed to read PostScript name from font file", e); + } + if (postScriptName == null) { + throw new SystemFontException( + FontManager.ERROR_CODE_MISSING_POST_SCRIPT_NAME, + "Failed to read PostScript name from font file"); } - // Do not parse font file before setting up fs-verity. - // setUpFsverity throws IOException if failed. - mFsverityUtil.setUpFsverity(tempNewFontFile.getAbsolutePath(), pkcs7Signature); - String postScriptName = mParser.getPostScriptName(tempNewFontFile); File newFontFile = new File(newDir, postScriptName + ALLOWED_EXTENSION); if (!mFsverityUtil.rename(tempNewFontFile, newFontFile)) { - // TODO: Define and return an error code for API - throw new IOException("Failed to rename"); + throw new SystemFontException( + FontManager.ERROR_CODE_FAILED_TO_WRITE_FONT_FILE, + "Failed to move verified font file."); + } + try { + // Make the font file readable by apps. + Os.chmod(newFontFile.getAbsolutePath(), 0644); + } catch (ErrnoException e) { + throw new SystemFontException( + FontManager.ERROR_CODE_FAILED_TO_WRITE_FONT_FILE, + "Failed to change mode to 711", e); } FontFileInfo fontFileInfo = validateFontFile(newFontFile); - if (fontFileInfo == null) { - // TODO: Define and return an error code for API - throw new IllegalArgumentException("Invalid file"); + + // Write config file. + PersistentSystemFontConfig.Config copied = new PersistentSystemFontConfig.Config(); + mConfig.copyTo(copied); + + copied.lastModifiedDate = Instant.now().getEpochSecond(); + try (FileOutputStream fos = new FileOutputStream(mTmpConfigFile)) { + PersistentSystemFontConfig.writeToXml(fos, copied); + } catch (Exception e) { + throw new SystemFontException( + FontManager.ERROR_CODE_FAILED_TO_CREATE_CONFIG_FILE, + "Failed to write config XML.", e); + } + + // Backup the mapping for rollback. + HashMap<String, FontFileInfo> backup = new HashMap<>(mFontFileInfoMap); + if (!addFileToMapIfNewerLocked(fontFileInfo, false)) { + throw new SystemFontException( + FontManager.ERROR_CODE_DOWNGRADING, + "Downgrading font file is forbidden."); } - if (!addFileToMapLocked(fontFileInfo, false)) { - // TODO: Define and return an error code for API - throw new IllegalArgumentException("Version downgrade"); + + if (!mFsverityUtil.rename(mTmpConfigFile, mConfigFile)) { + // If we fail to stage the config file, need to rollback the config. + mFontFileInfoMap.clear(); + mFontFileInfoMap.putAll(backup); + throw new SystemFontException( + FontManager.ERROR_CODE_FAILED_TO_CREATE_CONFIG_FILE, + "Failed to stage the config file."); } + + + // Now font update is succeeded. Update config version. + copied.copyTo(mConfig); + mConfigVersion++; + success = true; } finally { if (!success) { @@ -207,7 +349,7 @@ final class UpdatableFontDir { * higher than the currently used font file (either in {@link #mFontFileInfoMap} or {@link * #mPreinstalledFontDirs}). */ - private boolean addFileToMapLocked(FontFileInfo fontFileInfo, boolean deleteOldFile) { + private boolean addFileToMapIfNewerLocked(FontFileInfo fontFileInfo, boolean deleteOldFile) { String name = fontFileInfo.getFile().getName(); FontFileInfo existingInfo = mFontFileInfoMap.get(name); final boolean shouldAddToMap; @@ -224,13 +366,12 @@ final class UpdatableFontDir { FileUtils.deleteContentsAndDir(existingInfo.getRandomizedFontDir()); } mFontFileInfoMap.put(name, fontFileInfo); - return true; } else { if (deleteOldFile) { FileUtils.deleteContentsAndDir(fontFileInfo.getRandomizedFontDir()); } - return false; } + return shouldAddToMap; } private long getPreinstalledFontRevision(String name) { @@ -255,20 +396,23 @@ final class UpdatableFontDir { * returns a {@link FontFileInfo} on success. This method does not check if the font revision * is higher than the currently used font. */ - @Nullable - private FontFileInfo validateFontFile(File file) { + @NonNull + private FontFileInfo validateFontFile(File file) throws SystemFontException { if (!mFsverityUtil.hasFsverity(file.getAbsolutePath())) { - Slog.w(TAG, "Font validation failed. Fs-verity is not enabled: " + file); - return null; + throw new SystemFontException( + FontManager.ERROR_CODE_VERIFICATION_FAILURE, + "Font validation failed. Fs-verity is not enabled: " + file); } if (!validateFontFileName(file)) { - Slog.w(TAG, "Font validation failed. Could not validate font file name: " + file); - return null; + throw new SystemFontException( + FontManager.ERROR_CODE_FONT_NAME_MISMATCH, + "Font validation failed. Could not validate font file name: " + file); } long revision = getFontRevision(file); if (revision == -1) { - Slog.w(TAG, "Font validation failed. Could not read font revision: " + file); - return null; + throw new SystemFontException( + FontManager.ERROR_CODE_INVALID_FONT_FILE, + "Font validation failed. Could not read font revision: " + file); } return new FontFileInfo(file, revision); } @@ -318,4 +462,14 @@ final class UpdatableFontDir { } return map; } + + /* package */ FontConfig getSystemFontConfig() { + synchronized (UpdatableFontDir.this) { + return SystemFonts.getSystemFontConfig( + getFontFileMap(), + mConfig.lastModifiedDate, + mConfigVersion + ); + } + } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 143ec157119e..6308ace26011 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -3947,58 +3947,61 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } @Override - public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) { - // By this IPC call, only a process which shares the same uid with the IME can add - // additional input method subtypes to the IME. - if (TextUtils.isEmpty(imiId) || subtypes == null) return; - final ArrayList<InputMethodSubtype> toBeAdded = new ArrayList<>(); - for (InputMethodSubtype subtype : subtypes) { - if (!toBeAdded.contains(subtype)) { - toBeAdded.add(subtype); - } else { - Slog.w(TAG, "Duplicated subtype definition found: " - + subtype.getLocale() + ", " + subtype.getMode()); - } - } - synchronized (mMethodMap) { - if (!calledFromValidUserLocked()) { - return; - } - if (!mSystemReady) { - return; - } - final InputMethodInfo imi = mMethodMap.get(imiId); - if (imi == null) return; - final String[] packageInfos; - try { - packageInfos = mIPackageManager.getPackagesForUid(Binder.getCallingUid()); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to get package infos"); - return; + public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes, + IVoidResultCallback resultCallback) { + CallbackUtils.onResult(resultCallback, () -> { + // By this IPC call, only a process which shares the same uid with the IME can add + // additional input method subtypes to the IME. + if (TextUtils.isEmpty(imiId) || subtypes == null) return; + final ArrayList<InputMethodSubtype> toBeAdded = new ArrayList<>(); + for (InputMethodSubtype subtype : subtypes) { + if (!toBeAdded.contains(subtype)) { + toBeAdded.add(subtype); + } else { + Slog.w(TAG, "Duplicated subtype definition found: " + + subtype.getLocale() + ", " + subtype.getMode()); + } } - if (packageInfos != null) { - final int packageNum = packageInfos.length; - for (int i = 0; i < packageNum; ++i) { - if (packageInfos[i].equals(imi.getPackageName())) { - if (subtypes.length > 0) { - mAdditionalSubtypeMap.put(imi.getId(), toBeAdded); - } else { - mAdditionalSubtypeMap.remove(imi.getId()); - } - AdditionalSubtypeUtils.save(mAdditionalSubtypeMap, mMethodMap, - mSettings.getCurrentUserId()); - final long ident = Binder.clearCallingIdentity(); - try { - buildInputMethodListLocked(false /* resetDefaultEnabledIme */); - } finally { - Binder.restoreCallingIdentity(ident); + synchronized (mMethodMap) { + if (!calledFromValidUserLocked()) { + return; + } + if (!mSystemReady) { + return; + } + final InputMethodInfo imi = mMethodMap.get(imiId); + if (imi == null) return; + final String[] packageInfos; + try { + packageInfos = mIPackageManager.getPackagesForUid(Binder.getCallingUid()); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to get package infos"); + return; + } + if (packageInfos != null) { + final int packageNum = packageInfos.length; + for (int i = 0; i < packageNum; ++i) { + if (packageInfos[i].equals(imi.getPackageName())) { + if (subtypes.length > 0) { + mAdditionalSubtypeMap.put(imi.getId(), toBeAdded); + } else { + mAdditionalSubtypeMap.remove(imi.getId()); + } + AdditionalSubtypeUtils.save(mAdditionalSubtypeMap, mMethodMap, + mSettings.getCurrentUserId()); + final long ident = Binder.clearCallingIdentity(); + try { + buildInputMethodListLocked(false /* resetDefaultEnabledIme */); + } finally { + Binder.restoreCallingIdentity(ident); + } + return; } - return; } } } - } - return; + return; + }); } /** @@ -4103,16 +4106,21 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } @Override - public void removeImeSurface() { - mContext.enforceCallingPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW, null); - mHandler.sendMessage(mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE)); + public void removeImeSurface(IVoidResultCallback resultCallback) { + CallbackUtils.onResult(resultCallback, () -> { + mContext.enforceCallingPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW, null); + mHandler.sendMessage(mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE)); + }); } @Override - public void removeImeSurfaceFromWindow(IBinder windowToken) { - // No permission check, because we'll only execute the request if the calling window is - // also the current IME client. - mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE_FROM_WINDOW, windowToken).sendToTarget(); + public void removeImeSurfaceFromWindow(IBinder windowToken, + IVoidResultCallback resultCallback) { + CallbackUtils.onResult(resultCallback, () -> { + // No permission check, because we'll only execute the request if the calling window is + // also the current IME client. + mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE_FROM_WINDOW, windowToken).sendToTarget(); + }); } /** diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java index 2dd7096cf763..7f9c76634422 100644 --- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java @@ -1502,14 +1502,17 @@ public final class MultiClientInputMethodManagerService { @BinderThread @Override - public void removeImeSurface() { + public void removeImeSurface(IVoidResultCallback resultCallback) { reportNotSupported(); + CallbackUtils.onResult(resultCallback, () -> { }); } @BinderThread @Override - public void removeImeSurfaceFromWindow(IBinder windowToken) { + public void removeImeSurfaceFromWindow(IBinder windowToken, + IVoidResultCallback resultCallback) { reportNotSupported(); + CallbackUtils.onResult(resultCallback, () -> { }); } @BinderThread @@ -1815,8 +1818,10 @@ public final class MultiClientInputMethodManagerService { @BinderThread @Override - public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) { + public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes, + IVoidResultCallback resultCallback) { reportNotSupported(); + CallbackUtils.onResult(resultCallback, () -> { }); } @BinderThread diff --git a/services/core/java/com/android/server/location/timezone/OWNERS b/services/core/java/com/android/server/location/timezone/OWNERS deleted file mode 100644 index 28aff188dbd8..000000000000 --- a/services/core/java/com/android/server/location/timezone/OWNERS +++ /dev/null @@ -1,3 +0,0 @@ -# Bug component: 847766 -nfuller@google.com -include /core/java/android/app/timedetector/OWNERS diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java index 7dd961a6e4e4..b92a83f34e8c 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java @@ -89,6 +89,7 @@ class LockSettingsStorage { private static final String CHILD_PROFILE_LOCK_FILE = "gatekeeper.profile.key"; private static final String REBOOT_ESCROW_FILE = "reboot.escrow.key"; + private static final String REBOOT_ESCROW_SERVER_BLOB = "reboot.escrow.server.blob.key"; private static final String SYNTHETIC_PASSWORD_DIRECTORY = "spblob/"; @@ -318,6 +319,22 @@ class LockSettingsStorage { deleteFile(getRebootEscrowFile(userId)); } + public void writeRebootEscrowServerBlob(byte[] serverBlob) { + writeFile(getRebootEscrowServerBlob(), serverBlob); + } + + public byte[] readRebootEscrowServerBlob() { + return readFile(getRebootEscrowServerBlob()); + } + + public boolean hasRebootEscrowServerBlob() { + return hasFile(getRebootEscrowServerBlob()); + } + + public void removeRebootEscrowServerBlob() { + deleteFile(getRebootEscrowServerBlob()); + } + public boolean hasPassword(int userId) { return hasFile(getLockPasswordFilename(userId)); } @@ -446,6 +463,12 @@ class LockSettingsStorage { return getLockCredentialFilePathForUser(userId, REBOOT_ESCROW_FILE); } + @VisibleForTesting + String getRebootEscrowServerBlob() { + // There is a single copy of server blob for all users. + return getLockCredentialFilePathForUser(UserHandle.USER_SYSTEM, REBOOT_ESCROW_SERVER_BLOB); + } + private String getLockCredentialFilePathForUser(int userId, String basename) { String dataSystemDirectory = Environment.getDataDirectory().getAbsolutePath() + SYSTEM_DIRECTORY; diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java index fbec91576ca1..06962d414009 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java @@ -124,26 +124,28 @@ class RebootEscrowManager { static class Injector { protected Context mContext; private final RebootEscrowKeyStoreManager mKeyStoreManager; - private final RebootEscrowProviderInterface mRebootEscrowProvider; + private final LockSettingsStorage mStorage; + private RebootEscrowProviderInterface mRebootEscrowProvider; - Injector(Context context) { + Injector(Context context, LockSettingsStorage storage) { mContext = context; + mStorage = storage; mKeyStoreManager = new RebootEscrowKeyStoreManager(); + } - RebootEscrowProviderInterface rebootEscrowProvider = null; - // TODO(xunchang) add implementation for server based ror. + private RebootEscrowProviderInterface createRebootEscrowProvider() { + RebootEscrowProviderInterface rebootEscrowProvider; if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_OTA, "server_based_ror_enabled", false)) { - Slog.e(TAG, "Server based ror isn't implemented yet."); + rebootEscrowProvider = new RebootEscrowProviderServerBasedImpl(mContext, mStorage); } else { rebootEscrowProvider = new RebootEscrowProviderHalImpl(); } - if (rebootEscrowProvider != null && rebootEscrowProvider.hasRebootEscrowSupport()) { - mRebootEscrowProvider = rebootEscrowProvider; - } else { - mRebootEscrowProvider = null; + if (rebootEscrowProvider.hasRebootEscrowSupport()) { + return rebootEscrowProvider; } + return null; } public Context getContext() { @@ -159,6 +161,12 @@ class RebootEscrowManager { } public RebootEscrowProviderInterface getRebootEscrowProvider() { + // Initialize for the provider lazily. Because the device_config and service + // implementation apps may change when system server is running. + if (mRebootEscrowProvider == null) { + mRebootEscrowProvider = createRebootEscrowProvider(); + } + return mRebootEscrowProvider; } @@ -177,7 +185,7 @@ class RebootEscrowManager { } RebootEscrowManager(Context context, Callbacks callbacks, LockSettingsStorage storage) { - this(new Injector(context), callbacks, storage); + this(new Injector(context, storage), callbacks, storage); } @VisibleForTesting diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java new file mode 100644 index 000000000000..ba1a680ba7fb --- /dev/null +++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.locksettings; + +import android.annotation.Nullable; +import android.content.Context; +import android.os.RemoteException; +import android.provider.DeviceConfig; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.locksettings.ResumeOnRebootServiceProvider.ResumeOnRebootServiceConnection; + +import java.io.IOException; +import java.util.concurrent.TimeoutException; + +import javax.crypto.SecretKey; + +/** + * An implementation of the {@link RebootEscrowProviderInterface} by communicating with server to + * encrypt & decrypt the blob. + */ +class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterface { + private static final String TAG = "RebootEscrowProvider"; + + // Timeout for service binding + private static final long DEFAULT_SERVICE_TIMEOUT_IN_SECONDS = 10; + + /** + * Use the default lifetime of 10 minutes. The lifetime covers the following activities: + * Server wrap secret -> device reboot -> server unwrap blob. + */ + private static final long DEFAULT_SERVER_BLOB_LIFETIME_IN_MILLIS = 600_1000; + + private final LockSettingsStorage mStorage; + + private final Injector mInjector; + + static class Injector { + private ResumeOnRebootServiceConnection mServiceConnection = null; + + Injector(Context context) { + mServiceConnection = new ResumeOnRebootServiceProvider(context).getServiceConnection(); + if (mServiceConnection == null) { + Slog.e(TAG, "Failed to resolve resume on reboot server service."); + } + } + + Injector(ResumeOnRebootServiceConnection serviceConnection) { + mServiceConnection = serviceConnection; + } + + @Nullable + private ResumeOnRebootServiceConnection getServiceConnection() { + return mServiceConnection; + } + + long getServiceTimeoutInSeconds() { + return DeviceConfig.getLong(DeviceConfig.NAMESPACE_OTA, + "server_based_service_timeout_in_seconds", + DEFAULT_SERVICE_TIMEOUT_IN_SECONDS); + } + + long getServerBlobLifetimeInMillis() { + return DeviceConfig.getLong(DeviceConfig.NAMESPACE_OTA, + "server_based_server_blob_lifetime_in_millis", + DEFAULT_SERVER_BLOB_LIFETIME_IN_MILLIS); + } + } + + RebootEscrowProviderServerBasedImpl(Context context, LockSettingsStorage storage) { + this(storage, new Injector(context)); + } + + @VisibleForTesting + RebootEscrowProviderServerBasedImpl(LockSettingsStorage storage, Injector injector) { + mStorage = storage; + mInjector = injector; + } + + @Override + public boolean hasRebootEscrowSupport() { + return mInjector.getServiceConnection() != null; + } + + private byte[] unwrapServerBlob(byte[] serverBlob, SecretKey decryptionKey) throws + TimeoutException, RemoteException, IOException { + ResumeOnRebootServiceConnection serviceConnection = mInjector.getServiceConnection(); + if (serviceConnection == null) { + Slog.w(TAG, "Had reboot escrow data for users, but resume on reboot server" + + " service is unavailable"); + return null; + } + + // Decrypt with k_k from the key store first. + byte[] decryptedBlob = AesEncryptionUtil.decrypt(decryptionKey, serverBlob); + if (decryptedBlob == null) { + Slog.w(TAG, "Decrypted server blob should not be null"); + return null; + } + + // Ask the server connection service to decrypt the inner layer, to get the reboot + // escrow key (k_s). + serviceConnection.bindToService(mInjector.getServiceTimeoutInSeconds()); + byte[] escrowKeyBytes = serviceConnection.unwrap(decryptedBlob, + mInjector.getServiceTimeoutInSeconds()); + serviceConnection.unbindService(); + + return escrowKeyBytes; + } + + @Override + public RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey) { + byte[] serverBlob = mStorage.readRebootEscrowServerBlob(); + // Delete the server blob in storage. + mStorage.removeRebootEscrowServerBlob(); + if (serverBlob == null) { + Slog.w(TAG, "Failed to read reboot escrow server blob from storage"); + return null; + } + + try { + byte[] escrowKeyBytes = unwrapServerBlob(serverBlob, decryptionKey); + if (escrowKeyBytes == null) { + Slog.w(TAG, "Decrypted reboot escrow key bytes should not be null"); + return null; + } else if (escrowKeyBytes.length != 32) { + Slog.e(TAG, "Decrypted reboot escrow key has incorrect size " + + escrowKeyBytes.length); + return null; + } + + return RebootEscrowKey.fromKeyBytes(escrowKeyBytes); + } catch (TimeoutException | RemoteException | IOException e) { + Slog.w(TAG, "Failed to decrypt the server blob ", e); + return null; + } + } + + @Override + public void clearRebootEscrowKey() { + mStorage.removeRebootEscrowServerBlob(); + } + + private byte[] wrapEscrowKey(byte[] escrowKeyBytes, SecretKey encryptionKey) throws + TimeoutException, RemoteException, IOException { + ResumeOnRebootServiceConnection serviceConnection = mInjector.getServiceConnection(); + if (serviceConnection == null) { + Slog.w(TAG, "Failed to encrypt the reboot escrow key: resume on reboot server" + + " service is unavailable"); + return null; + } + + serviceConnection.bindToService(mInjector.getServiceTimeoutInSeconds()); + // Ask the server connection service to encrypt the reboot escrow key. + byte[] serverEncryptedBlob = serviceConnection.wrapBlob(escrowKeyBytes, + mInjector.getServerBlobLifetimeInMillis(), mInjector.getServiceTimeoutInSeconds()); + serviceConnection.unbindService(); + + if (serverEncryptedBlob == null) { + Slog.w(TAG, "Server encrypted reboot escrow key cannot be null"); + return null; + } + + // Additionally wrap the server blob with a local key. + return AesEncryptionUtil.encrypt(encryptionKey, serverEncryptedBlob); + } + + @Override + public boolean storeRebootEscrowKey(RebootEscrowKey escrowKey, SecretKey encryptionKey) { + mStorage.removeRebootEscrowServerBlob(); + try { + byte[] wrappedBlob = wrapEscrowKey(escrowKey.getKeyBytes(), encryptionKey); + if (wrappedBlob == null) { + Slog.w(TAG, "Failed to encrypt the reboot escrow key"); + return false; + } + mStorage.writeRebootEscrowServerBlob(wrappedBlob); + + Slog.i(TAG, "Reboot escrow key encrypted and stored."); + return true; + } catch (TimeoutException | RemoteException | IOException e) { + Slog.w(TAG, "Failed to encrypt the reboot escrow key ", e); + } + + return false; + } +} diff --git a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java index 0a4d17f20aec..e2e5046d98bf 100644 --- a/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java +++ b/services/core/java/com/android/server/media/MediaButtonReceiverHolder.java @@ -141,6 +141,11 @@ final class MediaButtonReceiverHolder { packageName != null ? packageName : ""); } + public static MediaButtonReceiverHolder create(int userId, ComponentName broadcastReceiver) { + return new MediaButtonReceiverHolder(userId, null, broadcastReceiver, + COMPONENT_TYPE_BROADCAST); + } + private MediaButtonReceiverHolder(int userId, PendingIntent pendingIntent, ComponentName componentName, @ComponentType int componentType) { mUserId = userId; diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index ea6e7d7d0bf6..ae58d4c40622 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -18,6 +18,7 @@ package com.android.server.media; import android.annotation.Nullable; import android.app.PendingIntent; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ParceledListSlice; @@ -858,6 +859,21 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR } @Override + public void setMediaButtonBroadcastReceiver(ComponentName receiver) throws RemoteException { + final long token = Binder.clearCallingIdentity(); + try { + if ((mPolicies & SessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER) + != 0) { + return; + } + mMediaButtonReceiverHolder = MediaButtonReceiverHolder.create(mUserId, receiver); + mService.onMediaButtonReceiverChanged(MediaSessionRecord.this); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override public void setLaunchPendingIntent(PendingIntent pi) throws RemoteException { mLaunchIntent = pi; } diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index a12932a88487..de85d9e25642 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -30,9 +30,9 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; -import android.content.pm.PackageParser; import android.content.pm.PackageParser.PackageParserException; import android.content.pm.parsing.PackageInfoWithoutStateUtils; +import android.content.pm.parsing.ParsingPackageUtils; import android.os.Binder; import android.os.Environment; import android.os.RemoteException; @@ -508,7 +508,8 @@ public abstract class ApexManager { for (ApexInfo ai : allPkgs) { File apexFile = new File(ai.modulePath); - parallelPackageParser.submit(apexFile, PackageParser.PARSE_COLLECT_CERTIFICATES); + parallelPackageParser.submit(apexFile, + ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES); parsingApexInfo.put(apexFile, ai); } diff --git a/services/core/java/com/android/server/pm/ApkChecksums.java b/services/core/java/com/android/server/pm/ApkChecksums.java index ff3a12ae7355..5373f996d7f8 100644 --- a/services/core/java/com/android/server/pm/ApkChecksums.java +++ b/services/core/java/com/android/server/pm/ApkChecksums.java @@ -24,7 +24,7 @@ import static android.content.pm.Checksum.TYPE_WHOLE_SHA1; import static android.content.pm.Checksum.TYPE_WHOLE_SHA256; import static android.content.pm.Checksum.TYPE_WHOLE_SHA512; import static android.content.pm.PackageManager.EXTRA_CHECKSUMS; -import static android.content.pm.PackageParser.APK_FILE_EXTENSION; +import static android.content.pm.parsing.ApkLiteParseUtils.APK_FILE_EXTENSION; import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256; import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512; import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256; @@ -39,6 +39,7 @@ import android.content.pm.Checksum; import android.content.pm.PackageManagerInternal; import android.content.pm.PackageParser; import android.content.pm.Signature; +import android.content.pm.parsing.ApkLiteParseUtils; import android.os.Handler; import android.os.SystemClock; import android.os.incremental.IncrementalManager; @@ -171,7 +172,7 @@ public class ApkChecksums { * @throws IllegalArgumentException if the code path is not an .apk. */ public static String buildDigestsPathForApk(String codePath) { - if (!PackageParser.isApkPath(codePath)) { + if (!ApkLiteParseUtils.isApkPath(codePath)) { throw new IllegalStateException("Code path is not an apk " + codePath); } return codePath.substring(0, codePath.length() - APK_FILE_EXTENSION.length()) diff --git a/services/core/java/com/android/server/pm/IncrementalStates.java b/services/core/java/com/android/server/pm/IncrementalStates.java index 780c522d2ae7..f5ec595cc45a 100644 --- a/services/core/java/com/android/server/pm/IncrementalStates.java +++ b/services/core/java/com/android/server/pm/IncrementalStates.java @@ -61,12 +61,12 @@ public final class IncrementalStates { public IncrementalStates() { // By default the package is not startable and not fully loaded (i.e., is loading) - this(false, true); + this(false, true, 0); } - public IncrementalStates(boolean isStartable, boolean isLoading) { + public IncrementalStates(boolean isStartable, boolean isLoading, float loadingProgress) { mStartableState = new StartableState(isStartable); - mLoadingState = new LoadingState(isLoading); + mLoadingState = new LoadingState(isLoading, loadingProgress); mStatusConsumer = new StatusConsumer(); } @@ -405,9 +405,10 @@ public final class IncrementalStates { private boolean mIsLoading; private float mProgress; - LoadingState(boolean isLoading) { + LoadingState(boolean isLoading, float loadingProgress) { mIsLoading = isLoading; - mProgress = isLoading ? 0 : 1; + // loading progress is reset to 1 if loading has finished + mProgress = isLoading ? loadingProgress : 1; } public boolean isLoading() { diff --git a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java index 71b99bd4ced2..13fe8a09ce8f 100644 --- a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java +++ b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java @@ -17,7 +17,7 @@ package com.android.server.pm; import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR; -import static android.content.pm.PackageParser.isApkFile; +import static android.content.pm.parsing.ApkLiteParseUtils.isApkFile; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import static android.os.incremental.IncrementalManager.isIncrementalPath; diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index e143bd0cb624..e218dc174ae8 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -81,11 +81,12 @@ import android.content.pm.PackageInstaller.SessionParams; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.PackageParser; -import android.content.pm.PackageParser.ApkLite; -import android.content.pm.PackageParser.PackageLite; import android.content.pm.PackageParser.PackageParserException; import android.content.pm.dex.DexMetadataHelper; +import android.content.pm.parsing.ApkLite; import android.content.pm.parsing.ApkLiteParseUtils; +import android.content.pm.parsing.PackageLite; +import android.content.pm.parsing.ParsingPackageUtils; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.graphics.Bitmap; @@ -2671,16 +2672,17 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // Populate package name of the apex session mPackageName = null; - final ApkLite apk; - try { - apk = PackageParser.parseApkLite( - mResolvedBaseFile, PackageParser.PARSE_COLLECT_CERTIFICATES); - } catch (PackageParserException e) { - throw PackageManagerException.from(e); + final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing(); + final ParseResult<ApkLite> ret = ApkLiteParseUtils.parseApkLite(input.reset(), + mResolvedBaseFile, ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES); + if (ret.isError()) { + throw new PackageManagerException(ret.getErrorCode(), ret.getErrorMessage(), + ret.getException()); } + final ApkLite apk = ret.getResult(); if (mPackageName == null) { - mPackageName = apk.packageName; + mPackageName = apk.getPackageName(); mVersionCode = apk.getLongVersionCode(); } } @@ -2745,29 +2747,29 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // Verify that all staged packages are internally consistent final ArraySet<String> stagedSplits = new ArraySet<>(); - final ArrayMap<String, PackageParser.ApkLite> splitApks = new ArrayMap<>(); - ParseTypeImpl input = ParseTypeImpl.forDefaultParsing(); + final ArrayMap<String, ApkLite> splitApks = new ArrayMap<>(); + final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing(); for (File addedFile : addedFiles) { - ParseResult<ApkLite> result = ApkLiteParseUtils.parseApkLite(input.reset(), - addedFile, PackageParser.PARSE_COLLECT_CERTIFICATES); + final ParseResult<ApkLite> result = ApkLiteParseUtils.parseApkLite(input.reset(), + addedFile, ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES); if (result.isError()) { throw new PackageManagerException(result.getErrorCode(), result.getErrorMessage(), result.getException()); } final ApkLite apk = result.getResult(); - if (!stagedSplits.add(apk.splitName)) { + if (!stagedSplits.add(apk.getSplitName())) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, - "Split " + apk.splitName + " was defined multiple times"); + "Split " + apk.getSplitName() + " was defined multiple times"); } // Use first package to define unknown values if (mPackageName == null) { - mPackageName = apk.packageName; + mPackageName = apk.getPackageName(); mVersionCode = apk.getLongVersionCode(); } if (mSigningDetails == PackageParser.SigningDetails.UNKNOWN) { - mSigningDetails = apk.signingDetails; + mSigningDetails = apk.getSigningDetails(); } assertApkConsistentLocked(String.valueOf(addedFile), apk); @@ -2780,10 +2782,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } // Yell loudly if installers drop attribute installLocation when apps explicitly set. - if (apk.installLocation != PackageInfo.INSTALL_LOCATION_UNSPECIFIED) { + if (apk.getInstallLocation() != PackageInfo.INSTALL_LOCATION_UNSPECIFIED) { final String installerPackageName = getInstallerPackageName(); if (installerPackageName != null - && (params.installLocation != apk.installLocation)) { + && (params.installLocation != apk.getInstallLocation())) { Slog.wtf(TAG, installerPackageName + " drops manifest attribute android:installLocation in " + targetName + " for " + mPackageName); @@ -2791,14 +2793,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } final File targetFile = new File(stageDir, targetName); - resolveAndStageFileLocked(addedFile, targetFile, apk.splitName); + resolveAndStageFileLocked(addedFile, targetFile, apk.getSplitName()); // Base is coming from session - if (apk.splitName == null) { + if (apk.getSplitName() == null) { mResolvedBaseFile = targetFile; baseApk = apk; } else { - splitApks.put(apk.splitName, apk); + splitApks.put(apk.getSplitName(), apk); } } @@ -2854,7 +2856,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "Full install must include a base package"); } - if (baseApk.isSplitRequired && stagedSplits.size() <= 1) { + if (baseApk.isSplitRequired() && stagedSplits.size() <= 1) { throw new PackageManagerException(INSTALL_FAILED_MISSING_SPLIT, "Missing split for " + mPackageName); } @@ -2879,10 +2881,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } final PackageLite existing = pkgLiteResult.getResult(); packageLite = existing; - assertPackageConsistentLocked("Existing", existing.packageName, + assertPackageConsistentLocked("Existing", existing.getPackageName(), existing.getLongVersionCode()); final PackageParser.SigningDetails signingDetails = - unsafeGetCertsWithoutVerification(existing.baseCodePath); + unsafeGetCertsWithoutVerification(existing.getBaseApkPath()); if (!mSigningDetails.signaturesMatchExactly(signingDetails)) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "Existing signatures are inconsistent"); @@ -2895,10 +2897,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } // Inherit splits if not overridden. - if (!ArrayUtils.isEmpty(existing.splitNames)) { - for (int i = 0; i < existing.splitNames.length; i++) { - final String splitName = existing.splitNames[i]; - final File splitFile = new File(existing.splitCodePaths[i]); + if (!ArrayUtils.isEmpty(existing.getSplitNames())) { + for (int i = 0; i < existing.getSplitNames().length; i++) { + final String splitName = existing.getSplitNames()[i]; + final File splitFile = new File(existing.getSplitApkPaths()[i]); final boolean splitRemoved = removeSplitList.contains(splitName); if (!stagedSplits.contains(splitName) && !splitRemoved) { inheritFileLocked(splitFile); @@ -2978,8 +2980,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } // For the case of split required, failed if no splits existed - if (packageLite.isSplitRequired) { - final int existingSplits = ArrayUtils.size(existing.splitNames); + if (packageLite.isSplitRequired()) { + final int existingSplits = ArrayUtils.size(existing.getSplitNames()); final boolean allSplitsRemoved = (existingSplits == removeSplitList.size()); final boolean onlyBaseFileStaged = (stagedSplits.size() == 1 && stagedSplits.contains(null)); @@ -2989,7 +2991,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } } - if (packageLite.useEmbeddedDex) { + if (packageLite.isUseEmbeddedDex()) { for (File file : mResolvedStagedFiles) { if (file.getName().endsWith(".apk") && !DexManager.auditUncompressedDexInApk(file.getPath())) { @@ -3002,7 +3004,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final boolean isInstallerShell = (mInstallerUid == Process.SHELL_UID); if (isInstallerShell && isIncrementalInstallation() && mIncrementalFileStorages != null) { - if (!packageLite.debuggable && !packageLite.profilableByShell) { + if (!packageLite.isDebuggable() && !packageLite.isProfileableByShell()) { mIncrementalFileStorages.disallowReadLogs(); } } @@ -3174,8 +3176,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @GuardedBy("mLock") private void assertApkConsistentLocked(String tag, ApkLite apk) throws PackageManagerException { - assertPackageConsistentLocked(tag, apk.packageName, apk.getLongVersionCode()); - if (!mSigningDetails.signaturesMatchExactly(apk.signingDetails)) { + assertPackageConsistentLocked(tag, apk.getPackageName(), apk.getLongVersionCode()); + if (!mSigningDetails.signaturesMatchExactly(apk.getSigningDetails())) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " signatures are inconsistent"); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index bba8406501a9..c27e670c4c99 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -101,7 +101,7 @@ import static android.content.pm.PackageManager.TYPE_UNKNOWN; import static android.content.pm.PackageManager.UNINSTALL_REASON_UNKNOWN; import static android.content.pm.PackageManagerInternal.LAST_KNOWN_PACKAGE; import static android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4; -import static android.content.pm.PackageParser.isApkFile; +import static android.content.pm.parsing.ApkLiteParseUtils.isApkFile; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import static android.os.incremental.IncrementalManager.isIncrementalPath; import static android.os.storage.StorageManager.FLAG_STORAGE_CE; @@ -211,9 +211,7 @@ import android.content.pm.PackageManagerInternal; import android.content.pm.PackageManagerInternal.PackageListObserver; import android.content.pm.PackageManagerInternal.PrivateResolveFlags; import android.content.pm.PackageParser; -import android.content.pm.PackageParser.PackageLite; import android.content.pm.PackageParser.PackageParserException; -import android.content.pm.PackageParser.ParseFlags; import android.content.pm.PackageParser.SigningDetails; import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion; import android.content.pm.PackagePartitions; @@ -241,7 +239,9 @@ import android.content.pm.dex.ArtManager; import android.content.pm.dex.DexMetadataHelper; import android.content.pm.dex.IArtManager; import android.content.pm.parsing.ApkLiteParseUtils; +import android.content.pm.parsing.PackageLite; import android.content.pm.parsing.ParsingPackageUtils; +import android.content.pm.parsing.ParsingPackageUtils.ParseFlags; import android.content.pm.parsing.component.ParsedActivity; import android.content.pm.parsing.component.ParsedInstrumentation; import android.content.pm.parsing.component.ParsedIntentInfo; @@ -6352,7 +6352,7 @@ public class PackageManagerService extends IPackageManager.Stub if (separateProcesses != null && separateProcesses.length() > 0) { if ("*".equals(separateProcesses)) { - mDefParseFlags = PackageParser.PARSE_IGNORE_PROCESSES; + mDefParseFlags = ParsingPackageUtils.PARSE_IGNORE_PROCESSES; mSeparateProcesses = null; Slog.w(TAG, "Running with debug.separate_processes: * (ALL)"); } else { @@ -6552,7 +6552,7 @@ public class PackageManagerService extends IPackageManager.Stub scanFlags = scanFlags | SCAN_FIRST_BOOT_OR_UPGRADE; } - final int systemParseFlags = mDefParseFlags | PackageParser.PARSE_IS_SYSTEM_DIR; + final int systemParseFlags = mDefParseFlags | ParsingPackageUtils.PARSE_IS_SYSTEM_DIR; final int systemScanFlags = scanFlags | SCAN_AS_SYSTEM; PackageParser2 packageParser = injector.getScanningCachingPackageParser(); @@ -7192,7 +7192,7 @@ public class PackageManagerService extends IPackageManager.Stub */ private boolean enableCompressedPackage(AndroidPackage stubPkg, @NonNull PackageSetting stubPkgSetting) { - final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY + final int parseFlags = mDefParseFlags | ParsingPackageUtils.PARSE_CHATTY | PackageParser.PARSE_ENFORCE_CODE; synchronized (mInstallLock) { final AndroidPackage pkg; @@ -11418,7 +11418,8 @@ public class PackageManagerService extends IPackageManager.Stub @ParseFlags int parseFlags, @ScanFlags int scanFlags, long currentTime, @Nullable UserHandle user) throws PackageManagerException { - final boolean scanSystemPartition = (parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0; + final boolean scanSystemPartition = + (parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0; final String renamedPkgName; final PackageSetting disabledPkgSetting; final boolean isSystemPkgUpdated; @@ -11461,7 +11462,7 @@ public class PackageManagerService extends IPackageManager.Stub 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/, true) : null; if (DEBUG_PACKAGE_SCANNING - && (parseFlags & PackageParser.PARSE_CHATTY) != 0 + && (parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0 && sharedUserSetting != null) { Log.d(TAG, "Shared UserID " + parsedPackage.getSharedUserId() + " (uid=" + sharedUserSetting.userId + "):" @@ -13280,10 +13281,11 @@ public class PackageManagerService extends IPackageManager.Stub sharedUserSetting = mSettings.getSharedUserLPw(parsedPackage.getSharedUserId(), 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/, true /*create*/); if (DEBUG_PACKAGE_SCANNING) { - if ((parseFlags & PackageParser.PARSE_CHATTY) != 0) + if ((parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0) { Log.d(TAG, "Shared UserID " + parsedPackage.getSharedUserId() + " (uid=" + sharedUserSetting.userId + "):" + " packages=" + sharedUserSetting.packages); + } } } String platformPackageName = mPlatformPackage == null @@ -13440,7 +13442,7 @@ public class PackageManagerService extends IPackageManager.Stub final int userId = user == null ? 0 : user.getIdentifier(); // Modify state for the given package setting commitPackageSettings(pkg, oldPkg, pkgSetting, scanFlags, - (parseFlags & PackageParser.PARSE_CHATTY) != 0 /*chatty*/, reconciledPkg); + (parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0 /*chatty*/, reconciledPkg); if (pkgSetting.getInstantApp(userId)) { mInstantAppRegistry.addInstantAppLPw(userId, pkgSetting.appId); } @@ -13649,8 +13651,9 @@ public class PackageManagerService extends IPackageManager.Stub List<String> changedAbiCodePath = null; if (DEBUG_PACKAGE_SCANNING) { - if ((parseFlags & PackageParser.PARSE_CHATTY) != 0) + if ((parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0) { Log.d(TAG, "Scanning package " + parsedPackage.getPackageName()); + } } // Initialize package source and resource directories @@ -13917,7 +13920,7 @@ public class PackageManagerService extends IPackageManager.Stub } else if (pkgSetting.firstInstallTime == 0) { // We need *something*. Take time time stamp of the file. pkgSetting.firstInstallTime = pkgSetting.lastUpdateTime = scanFileTime; - } else if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0) { + } else if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0) { if (scanFileTime != pkgSetting.timeStamp) { // A package on the system image has changed; consider this // to be an update. @@ -14114,7 +14117,7 @@ public class PackageManagerService extends IPackageManager.Stub private void assertPackageIsValid(AndroidPackage pkg, final @ParseFlags int parseFlags, final @ScanFlags int scanFlags) throws PackageManagerException { - if ((parseFlags & PackageParser.PARSE_ENFORCE_CODE) != 0) { + if ((parseFlags & ParsingPackageUtils.PARSE_ENFORCE_CODE) != 0) { assertCodePolicy(pkg); } @@ -14371,7 +14374,7 @@ public class PackageManagerService extends IPackageManager.Stub if ((scanFlags & SCAN_AS_SYSTEM) != 0) { // We are scanning a system overlay. This can be the first scan of the // system/vendor/oem partition, or an update to the system overlay. - if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) { + if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) { // This must be an update to a system overlay. Immutable overlays cannot be // upgraded. Objects.requireNonNull(mOverlayConfig, @@ -14451,7 +14454,7 @@ public class PackageManagerService extends IPackageManager.Stub // If the package is not on a system partition ensure it is signed with at least the // minimum signature scheme version required for its target SDK. - if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) { + if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) { int minSignatureSchemeVersion = ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk( pkg.getTargetSdkVersion()); @@ -18116,11 +18119,12 @@ public class PackageManagerService extends IPackageManager.Stub // Try enumerating all code paths before deleting List<String> allCodePaths = Collections.EMPTY_LIST; if (codeFile != null && codeFile.exists()) { - try { - final PackageLite pkg = PackageParser.parsePackageLite(codeFile, 0); - allCodePaths = pkg.getAllCodePaths(); - } catch (PackageParserException e) { - // Ignored; we tried our best + final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing(); + final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite( + input.reset(), codeFile, /* flags */ 0); + if (result.isSuccess()) { + // Ignore error; we tried our best + allCodePaths = result.getResult().getAllApkPaths(); } } @@ -18738,7 +18742,7 @@ public class PackageManagerService extends IPackageManager.Stub // We just determined the app is signed correctly, so bring // over the latest parsed certs. } else { - if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) { + if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) { throw new ReconcileFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package " + parsedPackage.getPackageName() + " upgrade keys do not match the previously installed" @@ -18788,7 +18792,7 @@ public class PackageManagerService extends IPackageManager.Stub } } } catch (PackageManagerException e) { - if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) { + if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) { throw new ReconcileFailure(e); } signingDetails = parsedPackage.getSigningDetails(); @@ -18867,7 +18871,8 @@ public class PackageManagerService extends IPackageManager.Stub // apps are scanned to avoid dependency based scanning. final ScanResult scanResult = scannedPackages.get(installPackageName); if ((scanResult.request.scanFlags & SCAN_BOOTING) != 0 - || (scanResult.request.parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0) { + || (scanResult.request.parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) + != 0) { continue; } try { @@ -19753,9 +19758,9 @@ public class PackageManagerService extends IPackageManager.Stub } // Retrieve PackageSettings and parse package - @ParseFlags final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY - | PackageParser.PARSE_ENFORCE_CODE - | (onExternal ? PackageParser.PARSE_EXTERNAL_STORAGE : 0); + @ParseFlags final int parseFlags = mDefParseFlags | ParsingPackageUtils.PARSE_CHATTY + | ParsingPackageUtils.PARSE_ENFORCE_CODE + | (onExternal ? ParsingPackageUtils.PARSE_EXTERNAL_STORAGE : 0); Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage"); final ParsedPackage parsedPackage; @@ -21485,8 +21490,8 @@ public class PackageManagerService extends IPackageManager.Stub final File codePath = new File(codePathString); @ParseFlags int parseFlags = mDefParseFlags - | PackageParser.PARSE_MUST_BE_APK - | PackageParser.PARSE_IS_SYSTEM_DIR; + | ParsingPackageUtils.PARSE_MUST_BE_APK + | ParsingPackageUtils.PARSE_IS_SYSTEM_DIR; @ScanFlags int scanFlags = SCAN_AS_SYSTEM; for (int i = mDirsToScanAsSystem.size() - 1; i >= 0; i--) { ScanPartition partition = mDirsToScanAsSystem.get(i); @@ -24938,7 +24943,7 @@ public class PackageManagerService extends IPackageManager.Stub final ArrayList<PackageFreezer> freezers = new ArrayList<>(); final ArrayList<AndroidPackage> loaded = new ArrayList<>(); - final int parseFlags = mDefParseFlags | PackageParser.PARSE_EXTERNAL_STORAGE; + final int parseFlags = mDefParseFlags | ParsingPackageUtils.PARSE_EXTERNAL_STORAGE; final VersionInfo ver; final List<PackageSetting> packages; diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index d3d7c6055fce..ee94b8599625 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -35,9 +35,12 @@ import android.content.Intent; import android.content.pm.PackageInfoLite; import android.content.pm.PackageManager; import android.content.pm.PackageParser; -import android.content.pm.PackageParser.PackageParserException; import android.content.pm.ResolveInfo; import android.content.pm.Signature; +import android.content.pm.parsing.ApkLiteParseUtils; +import android.content.pm.parsing.PackageLite; +import android.content.pm.parsing.result.ParseResult; +import android.content.pm.parsing.result.ParseTypeImpl; import android.os.Build; import android.os.Debug; import android.os.Environment; @@ -819,8 +822,8 @@ public class PackageManagerServiceUtils { /** * Parse given package and return minimal details. */ - public static PackageInfoLite getMinimalPackageInfo(Context context, - PackageParser.PackageLite pkg, String packagePath, int flags, String abiOverride) { + public static PackageInfoLite getMinimalPackageInfo(Context context, PackageLite pkg, + String packagePath, int flags, String abiOverride) { final PackageInfoLite ret = new PackageInfoLite(); if (packagePath == null || pkg == null) { Slog.i(TAG, "Invalid package file " + packagePath); @@ -843,19 +846,19 @@ public class PackageManagerServiceUtils { } final int recommendedInstallLocation = PackageHelper.resolveInstallLocation(context, - pkg.packageName, pkg.installLocation, sizeBytes, flags); - - ret.packageName = pkg.packageName; - ret.splitNames = pkg.splitNames; - ret.versionCode = pkg.versionCode; - ret.versionCodeMajor = pkg.versionCodeMajor; - ret.baseRevisionCode = pkg.baseRevisionCode; - ret.splitRevisionCodes = pkg.splitRevisionCodes; - ret.installLocation = pkg.installLocation; - ret.verifiers = pkg.verifiers; + pkg.getPackageName(), pkg.getInstallLocation(), sizeBytes, flags); + + ret.packageName = pkg.getPackageName(); + ret.splitNames = pkg.getSplitNames(); + ret.versionCode = pkg.getVersionCode(); + ret.versionCodeMajor = pkg.getVersionCodeMajor(); + ret.baseRevisionCode = pkg.getBaseRevisionCode(); + ret.splitRevisionCodes = pkg.getSplitRevisionCodes(); + ret.installLocation = pkg.getInstallLocation(); + ret.verifiers = pkg.getVerifiers(); ret.recommendedInstallLocation = recommendedInstallLocation; - ret.multiArch = pkg.multiArch; - ret.debuggable = pkg.debuggable; + ret.multiArch = pkg.isMultiArch(); + ret.debuggable = pkg.isDebuggable(); return ret; } @@ -868,11 +871,16 @@ public class PackageManagerServiceUtils { */ public static long calculateInstalledSize(String packagePath, String abiOverride) { final File packageFile = new File(packagePath); - final PackageParser.PackageLite pkg; try { - pkg = PackageParser.parsePackageLite(packageFile, 0); - return PackageHelper.calculateInstalledSize(pkg, abiOverride); - } catch (PackageParserException | IOException e) { + final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing(); + final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite( + input.reset(), packageFile, /* flags */ 0); + if (result.isError()) { + throw new PackageManagerException(result.getErrorCode(), + result.getErrorMessage(), result.getException()); + } + return PackageHelper.calculateInstalledSize(result.getResult(), abiOverride); + } catch (PackageManagerException | IOException e) { Slog.w(TAG, "Failed to calculate installed size: " + e); return -1; } @@ -931,16 +939,23 @@ public class PackageManagerServiceUtils { try { final File packageFile = new File(packagePath); - final PackageParser.PackageLite pkg = PackageParser.parsePackageLite(packageFile, 0); - copyFile(pkg.baseCodePath, targetDir, "base.apk"); - if (!ArrayUtils.isEmpty(pkg.splitNames)) { - for (int i = 0; i < pkg.splitNames.length; i++) { - copyFile(pkg.splitCodePaths[i], targetDir, - "split_" + pkg.splitNames[i] + ".apk"); + final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing(); + final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite( + input.reset(), packageFile, /* flags */ 0); + if (result.isError()) { + Slog.w(TAG, "Failed to parse package at " + packagePath); + return result.getErrorCode(); + } + final PackageLite pkg = result.getResult(); + copyFile(pkg.getBaseApkPath(), targetDir, "base.apk"); + if (!ArrayUtils.isEmpty(pkg.getSplitNames())) { + for (int i = 0; i < pkg.getSplitNames().length; i++) { + copyFile(pkg.getSplitApkPaths()[i], targetDir, + "split_" + pkg.getSplitNames()[i] + ".apk"); } } return PackageManager.INSTALL_SUCCEEDED; - } catch (PackageParserException | IOException | ErrnoException e) { + } catch (IOException | ErrnoException e) { Slog.w(TAG, "Failed to copy package at " + packagePath + ": " + e); return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; } diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 446342a8f512..3207d56a820e 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -49,8 +49,6 @@ import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManagerInternal; -import android.content.pm.PackageParser.ApkLite; -import android.content.pm.PackageParser.PackageLite; import android.content.pm.ParceledListSlice; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; @@ -61,7 +59,9 @@ import android.content.pm.VersionedPackage; import android.content.pm.dex.ArtManager; import android.content.pm.dex.DexMetadataHelper; import android.content.pm.dex.ISnapshotRuntimeProfileCallback; +import android.content.pm.parsing.ApkLite; import android.content.pm.parsing.ApkLiteParseUtils; +import android.content.pm.parsing.PackageLite; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.content.res.AssetManager; @@ -555,8 +555,8 @@ class PackageManagerShellCommand extends ShellCommand { apkLiteResult.getException()); } final ApkLite apkLite = apkLiteResult.getResult(); - PackageLite pkgLite = new PackageLite(null, apkLite.codePath, apkLite, null, null, - null, null, null, null); + final PackageLite pkgLite = new PackageLite(null, apkLite.getPath(), apkLite, null, + null, null, null, null, null); sessionSize += PackageHelper.calculateInstalledSize(pkgLite, params.sessionParams.abiOverride, fd.getFileDescriptor()); } catch (IOException e) { diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 3369a4fcd7f6..2929568ab1d7 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -73,6 +73,7 @@ import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; +import android.os.incremental.IncrementalManager; import android.os.storage.StorageManager; import android.os.storage.VolumeInfo; import android.service.pm.PackageServiceDumpProto; @@ -2957,6 +2958,8 @@ public final class Settings implements Watchable, Snappable { if (pkg.isPackageLoading()) { serializer.attributeBoolean(null, "isLoading", true); } + serializer.attributeFloat(null, "loadingProgress", + pkg.getIncrementalStates().getProgress()); writeUsesStaticLibLPw(serializer, pkg.usesStaticLibraries, pkg.usesStaticLibrariesVersions); @@ -3699,6 +3702,7 @@ public final class Settings implements Watchable, Snappable { boolean installedForceQueryable = false; boolean isStartable = false; boolean isLoading = false; + float loadingProgress = 0; try { name = parser.getAttributeValue(null, ATTR_NAME); realName = parser.getAttributeValue(null, "realName"); @@ -3717,6 +3721,7 @@ public final class Settings implements Watchable, Snappable { installedForceQueryable = parser.getAttributeBoolean(null, "forceQueryable", false); isStartable = parser.getAttributeBoolean(null, "isStartable", false); isLoading = parser.getAttributeBoolean(null, "isLoading", false); + loadingProgress = parser.getAttributeFloat(null, "loadingProgress", 0); if (primaryCpuAbiString == null && legacyCpuAbiString != null) { primaryCpuAbiString = legacyCpuAbiString; @@ -3864,7 +3869,8 @@ public final class Settings implements Watchable, Snappable { packageSetting.secondaryCpuAbiString = secondaryCpuAbiString; packageSetting.updateAvailable = updateAvailable; packageSetting.forceQueryableOverride = installedForceQueryable; - packageSetting.incrementalStates = new IncrementalStates(isStartable, isLoading); + packageSetting.incrementalStates = new IncrementalStates(isStartable, isLoading, + loadingProgress); // Handle legacy string here for single-user mode final String enabledStr = parser.getAttributeValue(null, ATTR_ENABLED); if (enabledStr != null) { @@ -4814,6 +4820,10 @@ public final class Settings implements Watchable, Snappable { pw.print(prefix); pw.print(" installerAttributionTag="); pw.println(ps.installSource.installerAttributionTag); } + if (IncrementalManager.isIncrementalPath(ps.getPathString())) { + pw.print(prefix); pw.println(" loadingProgress=" + + (int) (ps.getIncrementalStates().getProgress() * 100) + "%"); + } if (ps.volumeUuid != null) { pw.print(prefix); pw.print(" volumeUuid="); pw.println(ps.volumeUuid); diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 314510b89cb9..f43240bd810a 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -47,6 +47,7 @@ import android.content.pm.PackageManagerInternal; import android.content.pm.ShortcutServiceInternal; import android.content.pm.UserInfo; import android.content.pm.UserInfo.UserInfoFlag; +import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Bitmap; import android.os.Binder; @@ -92,6 +93,7 @@ import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.SparseIntArray; import android.util.TimeUtils; +import android.util.TypedValue; import android.util.TypedXmlPullParser; import android.util.TypedXmlSerializer; import android.util.Xml; @@ -139,6 +141,7 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; /** * Service for {@link UserManager}. @@ -461,6 +464,26 @@ public class UserManagerService extends IUserManager.Stub { } }; + /** + * Cache the owner name string, since it could be read repeatedly on a critical code path + * but hit by slow IO. This could be eliminated once we have the cached UserInfo in place. + */ + private final AtomicReference<String> mOwnerName = new AtomicReference<>(); + + private final TypedValue mOwnerNameTypedValue = new TypedValue(); + + private final Configuration mLastConfiguration = new Configuration(); + + private final BroadcastReceiver mConfigurationChangeReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (!Intent.ACTION_CONFIGURATION_CHANGED.equals(intent.getAction())) { + return; + } + invalidateOwnerNameIfNecessary(context.getResources(), false /* forceUpdate */); + } + }; + // TODO(b/161915546): remove once userWithName() is fixed / removed // Use to debug / dump when user 0 is allocated at userWithName() public static final boolean DBG_ALLOCATION = false; // DO NOT SUBMIT WITH TRUE @@ -636,6 +659,7 @@ public class UserManagerService extends IUserManager.Stub { mHandler = new MainHandler(); mUserDataPreparer = userDataPreparer; mUserTypes = UserTypeFactory.getUserTypes(); + invalidateOwnerNameIfNecessary(context.getResources(), true /* forceUpdate */); synchronized (mPackagesLock) { mUsersDir = new File(dataDir, USER_INFO_DIR); mUsersDir.mkdirs(); @@ -669,6 +693,10 @@ public class UserManagerService extends IUserManager.Stub { mContext.registerReceiver(mDisableQuietModeCallback, new IntentFilter(ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK), null, mHandler); + + mContext.registerReceiver(mConfigurationChangeReceiver, + new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED), + null, mHandler); } /** @@ -2851,7 +2879,16 @@ public class UserManagerService extends IUserManager.Stub { } private String getOwnerName() { - return mContext.getResources().getString(com.android.internal.R.string.owner_name); + return mOwnerName.get(); + } + + private void invalidateOwnerNameIfNecessary(@NonNull Resources res, boolean forceUpdate) { + final int configChanges = mLastConfiguration.updateFrom(res.getConfiguration()); + if (forceUpdate || (configChanges & mOwnerNameTypedValue.changingConfigurations) != 0) { + res.getValue(com.android.internal.R.string.owner_name, mOwnerNameTypedValue, true); + final CharSequence ownerName = mOwnerNameTypedValue.coerceToString(); + mOwnerName.set(ownerName != null ? ownerName.toString() : null); + } } private void scheduleWriteUser(UserData userData) { diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackage.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackage.java index a13680ad32af..471a4d3ada46 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackage.java +++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackage.java @@ -24,6 +24,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageParser; import android.content.pm.PermissionGroupInfo; import android.content.pm.parsing.ParsingPackageRead; +import android.content.pm.parsing.ParsingPackageUtils; import android.content.pm.parsing.component.ParsedAttribution; import android.content.pm.parsing.component.ParsedIntentInfo; import android.content.pm.parsing.component.ParsedPermissionGroup; @@ -56,7 +57,7 @@ public interface AndroidPackage extends PkgAppInfo, PkgPackageInfo, ParsingPacka /** * The names of packages to adopt ownership of permissions from, parsed under - * {@link PackageParser#TAG_ADOPT_PERMISSIONS}. + * {@link ParsingPackageUtils#TAG_ADOPT_PERMISSIONS}. * @see R.styleable#AndroidManifestOriginalPackage_name */ @NonNull @@ -84,7 +85,7 @@ public interface AndroidPackage extends PkgAppInfo, PkgPackageInfo, ParsingPacka /** * For use with {@link com.android.server.pm.KeySetManagerService}. Parsed in - * {@link PackageParser#TAG_KEY_SETS}. + * {@link ParsingPackageUtils#TAG_KEY_SETS}. * @see R.styleable#AndroidManifestKeySet * @see R.styleable#AndroidManifestPublicKey */ @@ -230,7 +231,7 @@ public interface AndroidPackage extends PkgAppInfo, PkgPackageInfo, ParsingPacka /** * For use with {@link com.android.server.pm.KeySetManagerService}. Parsed in - * {@link PackageParser#TAG_KEY_SETS}. + * {@link ParsingPackageUtils#TAG_KEY_SETS}. * @see R.styleable#AndroidManifestUpgradeKeySet */ @NonNull 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 ab25a7c772c0..37dfea4ee0f3 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 @@ -21,12 +21,12 @@ import android.annotation.Nullable; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.pm.PackageParser; import android.content.pm.PackageParser.PackageParserException; import android.content.pm.SharedLibraryInfo; import android.content.pm.VersionedPackage; import android.content.pm.dex.DexMetadataHelper; import android.content.pm.parsing.ParsingPackageRead; +import android.content.pm.parsing.ParsingPackageUtils; import android.content.pm.parsing.component.ParsedActivity; import android.content.pm.parsing.component.ParsedInstrumentation; import android.content.pm.parsing.component.ParsedProvider; @@ -233,7 +233,7 @@ public class AndroidPackageUtils { } public static int getIcon(ParsingPackageRead pkg) { - return (PackageParser.sUseRoundIcon && pkg.getRoundIconRes() != 0) + return (ParsingPackageUtils.sUseRoundIcon && pkg.getRoundIconRes() != 0) ? pkg.getRoundIconRes() : pkg.getIconRes(); } diff --git a/services/core/java/com/android/server/power/DisplayPowerRequestMapper.java b/services/core/java/com/android/server/power/DisplayPowerRequestMapper.java index 6477552eb550..2fc3e40acd4d 100644 --- a/services/core/java/com/android/server/power/DisplayPowerRequestMapper.java +++ b/services/core/java/com/android/server/power/DisplayPowerRequestMapper.java @@ -25,7 +25,6 @@ import android.util.SparseIntArray; import android.view.Display; import com.android.internal.annotations.GuardedBy; -import com.android.server.display.DisplayGroup; /** * Responsible for creating {@link DisplayPowerRequest}s and associating them with @@ -110,8 +109,8 @@ class DisplayPowerRequestMapper { DisplayManagerInternal displayManagerInternal, Handler handler) { mDisplayManagerInternal = displayManagerInternal; displayManager.registerDisplayListener(mDisplayListener, handler); - mDisplayPowerRequests.append(DisplayGroup.DEFAULT, new DisplayPowerRequest()); - mDisplayGroupIds.append(Display.DEFAULT_DISPLAY, DisplayGroup.DEFAULT); + mDisplayPowerRequests.append(Display.DEFAULT_DISPLAY_GROUP, new DisplayPowerRequest()); + mDisplayGroupIds.append(Display.DEFAULT_DISPLAY, Display.DEFAULT_DISPLAY_GROUP); } DisplayPowerRequest get(int displayId) { diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index bd66aa39b8d2..a4459d04daa2 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -33,9 +33,9 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; -import android.content.pm.PackageParser; import android.content.pm.ParceledListSlice; import android.content.pm.VersionedPackage; +import android.content.pm.parsing.ApkLite; import android.content.pm.parsing.ApkLiteParseUtils; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; @@ -831,24 +831,24 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba } // Get information about the package to be installed. - ParseTypeImpl input = ParseTypeImpl.forDefaultParsing(); - ParseResult<PackageParser.ApkLite> parseResult = ApkLiteParseUtils.parseApkLite( + final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing(); + final ParseResult<ApkLite> parseResult = ApkLiteParseUtils.parseApkLite( input.reset(), new File(session.resolvedBaseCodePath), 0); if (parseResult.isError()) { Slog.e(TAG, "Unable to parse new package: " + parseResult.getErrorMessage(), parseResult.getException()); return false; } - PackageParser.ApkLite newPackage = parseResult.getResult(); + final ApkLite newPackage = parseResult.getResult(); - String packageName = newPackage.packageName; - int rollbackDataPolicy = computeRollbackDataPolicy( - session.rollbackDataPolicy, newPackage.rollbackDataPolicy); + final String packageName = newPackage.getPackageName(); + final int rollbackDataPolicy = computeRollbackDataPolicy( + session.rollbackDataPolicy, newPackage.getRollbackDataPolicy()); Slog.i(TAG, "Enabling rollback for install of " + packageName + ", session:" + session.sessionId + ", rollbackDataPolicy=" + rollbackDataPolicy); - String installerPackageName = session.getInstallerPackageName(); + final String installerPackageName = session.getInstallerPackageName(); if (!enableRollbackAllowed(installerPackageName, packageName)) { Slog.e(TAG, "Installer " + installerPackageName + " is not allowed to enable rollback on " + packageName); @@ -900,7 +900,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba * a rollback object is inconsistent because it doesn't count apk-in-apex. */ ApplicationInfo appInfo = pkgInfo.applicationInfo; - return rollback.enableForPackage(packageName, newPackage.versionCode, + return rollback.enableForPackage(packageName, newPackage.getVersionCode(), pkgInfo.getLongVersionCode(), isApex, appInfo.sourceDir, appInfo.splitSourceDirs, rollbackDataPolicy); } diff --git a/services/core/java/com/android/server/rotationresolver/OWNERS b/services/core/java/com/android/server/rotationresolver/OWNERS new file mode 100644 index 000000000000..81b6f05a1658 --- /dev/null +++ b/services/core/java/com/android/server/rotationresolver/OWNERS @@ -0,0 +1 @@ +include /core/java/android/rotationresolver/OWNERS diff --git a/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java index 210fb5c0a1ab..c0c9e6d58622 100644 --- a/services/core/java/com/android/server/location/timezone/BinderLocationTimeZoneProvider.java +++ b/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java @@ -14,15 +14,15 @@ * limitations under the License. */ -package com.android.server.location.timezone; - -import static com.android.server.location.timezone.LocationTimeZoneManagerService.debugLog; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED; +package com.android.server.timezonedetector.location; + +import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.debugLog; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/location/timezone/ControllerCallbackImpl.java b/services/core/java/com/android/server/timezonedetector/location/ControllerCallbackImpl.java index cd9aa2fd6a5b..46eaad075b54 100644 --- a/services/core/java/com/android/server/location/timezone/ControllerCallbackImpl.java +++ b/services/core/java/com/android/server/timezonedetector/location/ControllerCallbackImpl.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.location.timezone; +package com.android.server.timezonedetector.location; import android.annotation.NonNull; diff --git a/services/core/java/com/android/server/location/timezone/ControllerEnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java index d896f6e441d8..83b33ee75de9 100644 --- a/services/core/java/com/android/server/location/timezone/ControllerEnvironmentImpl.java +++ b/services/core/java/com/android/server/timezonedetector/location/ControllerEnvironmentImpl.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.location.timezone; +package com.android.server.timezonedetector.location; import android.annotation.NonNull; diff --git a/services/core/java/com/android/server/location/timezone/ControllerImpl.java b/services/core/java/com/android/server/timezonedetector/location/ControllerImpl.java index 0d284fc2de4f..fb2a18493b7d 100644 --- a/services/core/java/com/android/server/location/timezone/ControllerImpl.java +++ b/services/core/java/com/android/server/timezonedetector/location/ControllerImpl.java @@ -14,20 +14,20 @@ * limitations under the License. */ -package com.android.server.location.timezone; - -import static com.android.server.location.timezone.LocationTimeZoneManagerService.debugLog; -import static com.android.server.location.timezone.LocationTimeZoneManagerService.warnLog; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED; -import static com.android.server.location.timezone.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE; -import static com.android.server.location.timezone.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION; -import static com.android.server.location.timezone.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN; +package com.android.server.timezonedetector.location; + +import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.debugLog; +import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.warnLog; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED; +import static com.android.server.timezonedetector.location.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE; +import static com.android.server.timezonedetector.location.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION; +import static com.android.server.timezonedetector.location.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN; import android.annotation.DurationMillisLong; import android.annotation.NonNull; @@ -36,9 +36,9 @@ import android.os.RemoteCallback; import android.util.IndentingPrintWriter; import com.android.internal.annotations.GuardedBy; -import com.android.server.location.timezone.ThreadingDomain.SingleRunnableQueue; import com.android.server.timezonedetector.ConfigurationInternal; import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion; +import com.android.server.timezonedetector.location.ThreadingDomain.SingleRunnableQueue; import java.time.Duration; import java.util.List; diff --git a/services/core/java/com/android/server/location/timezone/HandlerThreadingDomain.java b/services/core/java/com/android/server/timezonedetector/location/HandlerThreadingDomain.java index 3055ff8a2b59..0dd2922bb240 100644 --- a/services/core/java/com/android/server/location/timezone/HandlerThreadingDomain.java +++ b/services/core/java/com/android/server/timezonedetector/location/HandlerThreadingDomain.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.location.timezone; +package com.android.server.timezonedetector.location; import android.annotation.DurationMillisLong; import android.annotation.NonNull; diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java index 54535eb50130..5bee7ee9d4b2 100644 --- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerService.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.location.timezone; +package com.android.server.timezonedetector.location; import static android.app.time.LocationTimeZoneManager.PRIMARY_PROVIDER_NAME; import static android.app.time.LocationTimeZoneManager.PROVIDER_MODE_OVERRIDE_DISABLED; diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerServiceState.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java index b1dd55f3d4fd..113926a265f5 100644 --- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerServiceState.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java @@ -14,13 +14,13 @@ * limitations under the License. */ -package com.android.server.location.timezone; +package com.android.server.timezonedetector.location; import android.annotation.NonNull; import android.annotation.Nullable; -import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState; import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion; +import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState; import java.util.ArrayList; import java.util.Collections; diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerShellCommand.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java index 6f9863c9bd09..b53150c729bc 100644 --- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneManagerShellCommand.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.server.location.timezone; +package com.android.server.timezonedetector.location; import static android.app.time.LocationTimeZoneManager.DUMP_STATE_OPTION_PROTO; import static android.app.time.LocationTimeZoneManager.PRIMARY_PROVIDER_NAME; @@ -28,13 +28,13 @@ import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_SET_PROVIDE import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_START; import static android.app.time.LocationTimeZoneManager.SHELL_COMMAND_STOP; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_UNKNOWN; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_UNKNOWN; import android.annotation.NonNull; import android.app.time.GeolocationTimeZoneSuggestionProto; @@ -47,8 +47,8 @@ import android.util.IndentingPrintWriter; import android.util.proto.ProtoOutputStream; import com.android.internal.util.dump.DualDumpOutputStream; -import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.ProviderStateEnum; import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion; +import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.ProviderStateEnum; import java.io.FileDescriptor; import java.io.PrintWriter; diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java index 9a7b7750659c..ef2f357b8c3e 100644 --- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProvider.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java @@ -14,22 +14,22 @@ * limitations under the License. */ -package com.android.server.location.timezone; +package com.android.server.timezonedetector.location; import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_ERROR_KEY; import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_SUCCESS_KEY; -import static com.android.server.location.timezone.LocationTimeZoneManagerService.debugLog; -import static com.android.server.location.timezone.LocationTimeZoneManagerService.warnLog; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED; -import static com.android.server.location.timezone.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE; -import static com.android.server.location.timezone.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION; -import static com.android.server.location.timezone.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN; +import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.debugLog; +import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.warnLog; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED; +import static com.android.server.timezonedetector.location.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE; +import static com.android.server.timezonedetector.location.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION; +import static com.android.server.timezonedetector.location.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN; import android.annotation.ElapsedRealtimeLong; import android.annotation.IntDef; @@ -42,11 +42,11 @@ import android.os.SystemClock; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.ProviderStateEnum; -import com.android.server.location.timezone.ThreadingDomain.SingleRunnableQueue; import com.android.server.timezonedetector.ConfigurationInternal; import com.android.server.timezonedetector.Dumpable; import com.android.server.timezonedetector.ReferenceWithHistory; +import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.ProviderStateEnum; +import com.android.server.timezonedetector.location.ThreadingDomain.SingleRunnableQueue; import java.time.Duration; import java.util.ArrayList; diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java index ec2bc13b8a16..b4aff3e005ad 100644 --- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderController.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java @@ -14,17 +14,17 @@ * limitations under the License. */ -package com.android.server.location.timezone; +package com.android.server.timezonedetector.location; import android.annotation.DurationMillisLong; import android.annotation.NonNull; import android.os.Handler; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState; import com.android.server.timezonedetector.ConfigurationInternal; import com.android.server.timezonedetector.Dumpable; import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion; +import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState; import java.time.Duration; import java.util.Objects; diff --git a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderProxy.java index 8368b5ed5d75..43b1b5f017b2 100644 --- a/services/core/java/com/android/server/location/timezone/LocationTimeZoneProviderProxy.java +++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderProxy.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.location.timezone; +package com.android.server.timezonedetector.location; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/location/timezone/NullLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/timezonedetector/location/NullLocationTimeZoneProviderProxy.java index c2abbf9a1b8c..1f45e828aad4 100644 --- a/services/core/java/com/android/server/location/timezone/NullLocationTimeZoneProviderProxy.java +++ b/services/core/java/com/android/server/timezonedetector/location/NullLocationTimeZoneProviderProxy.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.location.timezone; +package com.android.server.timezonedetector.location; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/location/timezone/RealLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java index 0904ba419b3d..38211efc1c63 100644 --- a/services/core/java/com/android/server/location/timezone/RealLocationTimeZoneProviderProxy.java +++ b/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java @@ -14,13 +14,13 @@ * limitations under the License. */ -package com.android.server.location.timezone; +package com.android.server.timezonedetector.location; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_ERROR_KEY; import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_SUCCESS_KEY; -import static com.android.server.location.timezone.LocationTimeZoneManagerService.warnLog; +import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.warnLog; import android.Manifest; import android.annotation.NonNull; diff --git a/services/core/java/com/android/server/location/timezone/SimulatedLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/timezonedetector/location/SimulatedLocationTimeZoneProviderProxy.java index 66ccaed25e32..02b0a849c1b1 100644 --- a/services/core/java/com/android/server/location/timezone/SimulatedLocationTimeZoneProviderProxy.java +++ b/services/core/java/com/android/server/timezonedetector/location/SimulatedLocationTimeZoneProviderProxy.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.location.timezone; +package com.android.server.timezonedetector.location; import static android.app.time.LocationTimeZoneManager.SIMULATED_PROVIDER_TEST_COMMAND_ON_BIND; import static android.app.time.LocationTimeZoneManager.SIMULATED_PROVIDER_TEST_COMMAND_ON_UNBIND; diff --git a/services/core/java/com/android/server/location/timezone/TestCommand.java b/services/core/java/com/android/server/timezonedetector/location/TestCommand.java index 0df3ca087fc7..21482ea6ff79 100644 --- a/services/core/java/com/android/server/location/timezone/TestCommand.java +++ b/services/core/java/com/android/server/timezonedetector/location/TestCommand.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.location.timezone; +package com.android.server.timezonedetector.location; import android.annotation.NonNull; import android.net.Uri; diff --git a/services/core/java/com/android/server/location/timezone/ThreadingDomain.java b/services/core/java/com/android/server/timezonedetector/location/ThreadingDomain.java index 4ada6f50b40e..9e3497f92dc0 100644 --- a/services/core/java/com/android/server/location/timezone/ThreadingDomain.java +++ b/services/core/java/com/android/server/timezonedetector/location/ThreadingDomain.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.location.timezone; +package com.android.server.timezonedetector.location; import android.annotation.DurationMillisLong; import android.annotation.NonNull; diff --git a/services/core/java/com/android/server/location/timezone/TimeZoneProviderEvent.java b/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderEvent.java index 2d6f8ad446f0..3e224e03fda0 100644 --- a/services/core/java/com/android/server/location/timezone/TimeZoneProviderEvent.java +++ b/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderEvent.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.location.timezone; +package com.android.server.timezonedetector.location; import android.annotation.IntDef; import android.annotation.NonNull; diff --git a/services/core/java/com/android/server/location/timezone/TimeZoneProviderRequest.java b/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderRequest.java index 649a74bbed49..14820319d9df 100644 --- a/services/core/java/com/android/server/location/timezone/TimeZoneProviderRequest.java +++ b/services/core/java/com/android/server/timezonedetector/location/TimeZoneProviderRequest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.location.timezone; +package com.android.server.timezonedetector.location; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 0fa97a26e67c..8805fa2f4dbb 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -122,7 +122,9 @@ public class VcnGatewayConnection extends StateMachine { private static final int TOKEN_ALL = Integer.MIN_VALUE; private static final int NETWORK_LOSS_DISCONNECT_TIMEOUT_SECONDS = 30; - private static final int TEARDOWN_TIMEOUT_SECONDS = 5; + + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final int TEARDOWN_TIMEOUT_SECONDS = 5; private interface EventInfo {} @@ -413,13 +415,6 @@ public class VcnGatewayConnection extends StateMachine { private int mCurrentToken = -1; /** - * The next usable token. - * - * <p>A new token MUST be used for all new IKE sessions. - */ - private int mNextToken = 0; - - /** * The number of unsuccessful attempts since the last successful connection. * * <p>This number MUST be incremented each time the RetryTimeout state is entered, and cleared @@ -440,7 +435,7 @@ public class VcnGatewayConnection extends StateMachine { * <p>Set in Connecting or Migrating States, always @NonNull in Connecting, Connected, and * Migrating states, null otherwise. */ - private IkeSession mIkeSession; + private VcnIkeSession mIkeSession; /** * The last known child configuration. @@ -774,7 +769,70 @@ public class VcnGatewayConnection extends StateMachine { */ private class DisconnectingState extends ActiveBaseState { @Override - protected void processStateMsg(Message msg) {} + protected void enterState() throws Exception { + if (mIkeSession == null) { + Slog.wtf(TAG, "IKE session was already closed when entering Disconnecting state."); + sendMessage(EVENT_SESSION_CLOSED, mCurrentToken); + return; + } + + // If underlying network has already been lost, save some time and just kill the session + if (mUnderlying == null) { + // Will trigger a EVENT_SESSION_CLOSED as IkeSession shuts down. + mIkeSession.kill(); + return; + } + + sendMessageDelayed( + EVENT_TEARDOWN_TIMEOUT_EXPIRED, + mCurrentToken, + TimeUnit.SECONDS.toMillis(TEARDOWN_TIMEOUT_SECONDS)); + } + + @Override + protected void processStateMsg(Message msg) { + switch (msg.what) { + case EVENT_UNDERLYING_NETWORK_CHANGED: // Fallthrough + mUnderlying = ((EventUnderlyingNetworkChangedInfo) msg.obj).newUnderlying; + + // If we received a new underlying network, continue. + if (mUnderlying != null) { + break; + } + + // Fallthrough; no network exists to send IKE close session requests. + case EVENT_TEARDOWN_TIMEOUT_EXPIRED: + // Grace period ended. Kill session, triggering EVENT_SESSION_CLOSED + mIkeSession.kill(); + + break; + case EVENT_DISCONNECT_REQUESTED: + teardownNetwork(); + + String reason = ((EventDisconnectRequestedInfo) msg.obj).reason; + if (reason.equals(DISCONNECT_REASON_UNDERLYING_NETWORK_LOST)) { + // Will trigger EVENT_SESSION_CLOSED immediately. + mIkeSession.kill(); + break; + } + + // Otherwise we are already in the process of shutting down. + break; + case EVENT_SESSION_CLOSED: + mIkeSession = null; + + if (mIsRunning && mUnderlying != null) { + transitionTo(mRetryTimeoutState); + } else { + teardownNetwork(); + transitionTo(mDisconnectedState); + } + break; + default: + logUnhandledMessage(msg); + break; + } + } } /** @@ -946,6 +1004,38 @@ public class VcnGatewayConnection extends StateMachine { mIsRunning = isRunning; } + @VisibleForTesting(visibility = Visibility.PRIVATE) + VcnIkeSession getIkeSession() { + return mIkeSession; + } + + @VisibleForTesting(visibility = Visibility.PRIVATE) + void setIkeSession(@Nullable VcnIkeSession session) { + mIkeSession = session; + } + + private IkeSessionParams buildIkeParams() { + // TODO: Implement this with ConnectingState + return null; + } + + private ChildSessionParams buildChildParams() { + // TODO: Implement this with ConnectingState + return null; + } + + @VisibleForTesting(visibility = Visibility.PRIVATE) + VcnIkeSession buildIkeSession() { + final int token = ++mCurrentToken; + + return mDeps.newIkeSession( + mVcnContext, + buildIkeParams(), + buildChildParams(), + new IkeSessionCallbackImpl(token), + new ChildSessionCallbackImpl(token)); + } + /** External dependencies used by VcnGatewayConnection, for injection in tests */ @VisibleForTesting(visibility = Visibility.PRIVATE) public static class Dependencies { @@ -958,19 +1048,67 @@ public class VcnGatewayConnection extends StateMachine { } /** Builds a new IkeSession. */ - public IkeSession newIkeSession( + public VcnIkeSession newIkeSession( VcnContext vcnContext, IkeSessionParams ikeSessionParams, ChildSessionParams childSessionParams, IkeSessionCallback ikeSessionCallback, ChildSessionCallback childSessionCallback) { - return new IkeSession( - vcnContext.getContext(), + return new VcnIkeSession( + vcnContext, ikeSessionParams, childSessionParams, - new HandlerExecutor(new Handler(vcnContext.getLooper())), ikeSessionCallback, childSessionCallback); } } + + /** Proxy implementation of IKE session, used for testing. */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + public static class VcnIkeSession { + private final IkeSession mImpl; + + public VcnIkeSession( + VcnContext vcnContext, + IkeSessionParams ikeSessionParams, + ChildSessionParams childSessionParams, + IkeSessionCallback ikeSessionCallback, + ChildSessionCallback childSessionCallback) { + mImpl = + new IkeSession( + vcnContext.getContext(), + ikeSessionParams, + childSessionParams, + new HandlerExecutor(new Handler(vcnContext.getLooper())), + ikeSessionCallback, + childSessionCallback); + } + + /** Creates a new IKE Child session. */ + public void openChildSession( + @NonNull ChildSessionParams childSessionParams, + @NonNull ChildSessionCallback childSessionCallback) { + mImpl.openChildSession(childSessionParams, childSessionCallback); + } + + /** Closes an IKE session as identified by the ChildSessionCallback. */ + public void closeChildSession(@NonNull ChildSessionCallback childSessionCallback) { + mImpl.closeChildSession(childSessionCallback); + } + + /** Gracefully closes this IKE Session, waiting for remote acknowledgement. */ + public void close() { + mImpl.close(); + } + + /** Forcibly kills this IKE Session, without waiting for a closure confirmation. */ + public void kill() { + mImpl.kill(); + } + + /** Sets the underlying network used by the IkeSession. */ + public void setNetwork(@NonNull Network network) { + mImpl.setNetwork(network); + } + } } diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java index c36375ef0af5..53552526c936 100644 --- a/services/core/java/com/android/server/vibrator/VibrationThread.java +++ b/services/core/java/com/android/server/vibrator/VibrationThread.java @@ -74,6 +74,7 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi void onVibrationEnded(long vibrationId, Vibration.Status status); } + private final Object mLock = new Object(); private final WorkSource mWorkSource = new WorkSource(); private final PowerManager.WakeLock mWakeLock; private final IBatteryStats mBatteryStatsService; @@ -81,10 +82,10 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi private final VibrationCallbacks mCallbacks; private final SparseArray<VibratorController> mVibrators; - @GuardedBy("this") + @GuardedBy("mLock") @Nullable private VibrateStep mCurrentVibrateStep; - @GuardedBy("this") + @GuardedBy("mLock") private boolean mForceStop; // TODO(b/159207608): Remove this constructor once VibratorService is removed @@ -113,6 +114,10 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi } } + public Vibration getVibration() { + return mVibration; + } + @Override public void binderDied() { cancel(); @@ -136,15 +141,15 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi /** Cancel current vibration and shuts down the thread gracefully. */ public void cancel() { - synchronized (this) { + synchronized (mLock) { mForceStop = true; - notify(); + mLock.notify(); } } /** Notify current vibration that a step has completed on given vibrator. */ public void vibratorComplete(int vibratorId) { - synchronized (this) { + synchronized (mLock) { if (mCurrentVibrateStep != null) { mCurrentVibrateStep.vibratorComplete(vibratorId); } @@ -168,7 +173,7 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi final int stepCount = steps.size(); for (int i = 0; i < stepCount; i++) { Step step = steps.get(i); - synchronized (this) { + synchronized (mLock) { if (step instanceof VibrateStep) { mCurrentVibrateStep = (VibrateStep) step; } else { @@ -295,21 +300,48 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi * Sleeps until given {@code wakeUpTime}. * * <p>This stops immediately when {@link #cancel()} is called. + * + * @return true if waited until wake-up time, false if it was cancelled. */ - private void waitUntil(long wakeUpTime) { - synchronized (this) { + private boolean waitUntil(long wakeUpTime) { + synchronized (mLock) { long durationRemaining = wakeUpTime - SystemClock.uptimeMillis(); while (durationRemaining > 0) { try { - VibrationThread.this.wait(durationRemaining); + mLock.wait(durationRemaining); } catch (InterruptedException e) { } if (mForceStop) { - break; + return false; + } + durationRemaining = wakeUpTime - SystemClock.uptimeMillis(); + } + } + return true; + } + + /** + * Sleeps until given {@link VibrateStep#isVibrationComplete()}, or until {@code wakeUpTime}. + * + * <p>This stops immediately when {@link #cancel()} is called. + * + * @return true if finished on vibration complete, false if it was cancelled or timed out. + */ + private boolean waitForVibrationComplete(VibrateStep step, long wakeUpTime) { + synchronized (mLock) { + long durationRemaining = wakeUpTime - SystemClock.uptimeMillis(); + while (!step.isVibrationComplete() && durationRemaining > 0) { + try { + mLock.wait(durationRemaining); + } catch (InterruptedException e) { + } + if (mForceStop) { + return false; } durationRemaining = wakeUpTime - SystemClock.uptimeMillis(); } } + return step.isVibrationComplete(); } private void noteVibratorOn(long duration) { @@ -341,6 +373,9 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi private interface VibrateStep extends Step { /** Callback to notify a vibrator has finished playing a effect. */ void vibratorComplete(int vibratorId); + + /** Returns true if the vibration played by this step is complete. */ + boolean isVibrationComplete(); } /** Represent a vibration on a single vibrator. */ @@ -348,11 +383,20 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi private final VibratorController mVibrator; private final VibrationEffect mEffect; + @GuardedBy("mLock") + private boolean mVibrationComplete; + SingleVibrateStep(VibratorController vibrator, VibrationEffect effect) { mVibrator = vibrator; mEffect = effect; } + @GuardedBy("mLock") + @Override + public boolean isVibrationComplete() { + return mVibrationComplete; + } + @Override public void vibratorComplete(int vibratorId) { if (mVibrator.getVibratorInfo().getId() != vibratorId) { @@ -364,8 +408,9 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi return; } mVibrator.off(); - synchronized (VibrationThread.this) { - VibrationThread.this.notify(); + synchronized (mLock) { + mVibrationComplete = true; + mLock.notify(); } } @@ -384,12 +429,13 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi noteVibratorOn(duration); // Vibration is playing with no need to control amplitudes, just wait for native // callback or timeout. - waitUntil(startTime + duration + CALLBACKS_EXTRA_TIMEOUT); - if (mForceStop) { - mVibrator.off(); - return Vibration.Status.CANCELLED; + if (waitForVibrationComplete(this, + startTime + duration + CALLBACKS_EXTRA_TIMEOUT)) { + return Vibration.Status.FINISHED; } - return Vibration.Status.FINISHED; + // Timed out or vibration cancelled. Stop vibrator anyway. + mVibrator.off(); + return mForceStop ? Vibration.Status.CANCELLED : Vibration.Status.FINISHED; } startTime = SystemClock.uptimeMillis(); @@ -407,8 +453,7 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi noteVibratorOn(duration); } while (amplitudeStep != null) { - waitUntil(amplitudeStep.startTime); - if (mForceStop) { + if (!waitUntil(amplitudeStep.startTime)) { mVibrator.off(); return Vibration.Status.CANCELLED; } @@ -482,7 +527,7 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi private final int mRequiredCapabilities; private final int[] mVibratorIds; - @GuardedBy("VibrationThread.this") + @GuardedBy("mLock") private int mActiveVibratorCounter; SyncedVibrateStep(SparseArray<VibrationEffect> effects) { @@ -496,6 +541,12 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi } } + @GuardedBy("mLock") + @Override + public boolean isVibrationComplete() { + return mActiveVibratorCounter <= 0; + } + @Override public void vibratorComplete(int vibratorId) { VibrationEffect effect = mEffects.get(vibratorId); @@ -508,10 +559,9 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi return; } mVibrators.get(vibratorId).off(); - synchronized (VibrationThread.this) { - if (--mActiveVibratorCounter <= 0) { - VibrationThread.this.notify(); - } + synchronized (mLock) { + --mActiveVibratorCounter; + mLock.notify(); } } @@ -532,8 +582,7 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi while (!nextSteps.isEmpty()) { AmplitudeStep step = nextSteps.poll(); - waitUntil(step.startTime); - if (mForceStop) { + if (!waitUntil(step.startTime)) { stopAllVibrators(); return Vibration.Status.CANCELLED; } @@ -541,7 +590,7 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi AmplitudeStep nextStep = step.nextStep(); if (nextStep == null) { // This vibrator has finished playing the effect for this step. - synchronized (VibrationThread.this) { + synchronized (mLock) { mActiveVibratorCounter--; } } else { @@ -549,19 +598,18 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi } } - // All OneShot and Waveform effects have finished. Just wait for the other effects - // to end via native callbacks before finishing this synced step. - synchronized (VibrationThread.this) { - if (mActiveVibratorCounter > 0) { - waitUntil(startTime + timeout + CALLBACKS_EXTRA_TIMEOUT); + synchronized (mLock) { + // All OneShot and Waveform effects have finished. Just wait for the other + // effects to end via native callbacks before finishing this synced step. + final long wakeUpTime = startTime + timeout + CALLBACKS_EXTRA_TIMEOUT; + if (mActiveVibratorCounter <= 0 || waitForVibrationComplete(this, wakeUpTime)) { + return Vibration.Status.FINISHED; } - } - if (mForceStop) { + + // Timed out or vibration cancelled. Stop all vibrators anyway. stopAllVibrators(); - return Vibration.Status.CANCELLED; + return mForceStop ? Vibration.Status.CANCELLED : Vibration.Status.FINISHED; } - - return Vibration.Status.FINISHED; } finally { if (timeout > 0) { noteVibratorOff(); @@ -774,8 +822,10 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi if (DEBUG) { Slog.d(TAG, "DelayStep of " + mDelay + "ms starting..."); } - waitUntil(SystemClock.uptimeMillis() + mDelay); - return mForceStop ? Vibration.Status.CANCELLED : Vibration.Status.FINISHED; + if (waitUntil(SystemClock.uptimeMillis() + mDelay)) { + return Vibration.Status.FINISHED; + } + return Vibration.Status.CANCELLED; } finally { if (DEBUG) { Slog.d(TAG, "DelayStep done."); diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index c25f1b419e66..5fe853a38dd7 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -63,6 +63,7 @@ import android.util.Slog; import android.view.RemoteAnimationDefinition; import com.android.internal.app.AssistUtils; +import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.protolog.common.ProtoLog; import com.android.server.LocalServices; import com.android.server.Watchdog; @@ -1019,6 +1020,50 @@ class ActivityClientController extends IActivityClientController.Stub { } @Override + public void restartActivityProcessIfVisible(IBinder token) { + ActivityTaskManagerService.enforceTaskPermission("restartActivityProcess"); + final long callingId = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token); + if (r != null) { + r.restartProcessIfVisible(); + } + } + } finally { + Binder.restoreCallingIdentity(callingId); + } + } + + @Override + public void invalidateHomeTaskSnapshot(IBinder token) { + synchronized (mGlobalLock) { + final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token); + if (r != null && r.isActivityTypeHome()) { + mService.mWindowManager.mTaskSnapshotController.removeSnapshotCache( + r.getTask().mTaskId); + } + } + } + + @Override + public void dismissKeyguard(IBinder token, IKeyguardDismissCallback callback, + CharSequence message) { + if (message != null) { + mService.mAmInternal.enforceCallingPermission( + android.Manifest.permission.SHOW_KEYGUARD_MESSAGE, "dismissKeyguard"); + } + final long callingId = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + mService.mKeyguardController.dismissKeyguard(token, callback, message); + } + } finally { + Binder.restoreCallingIdentity(callingId); + } + } + + @Override public void registerRemoteAnimations(IBinder token, RemoteAnimationDefinition definition) { mService.mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS, "registerRemoteAnimations"); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 56105737bb82..f0db3f9855df 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -231,7 +231,6 @@ import com.android.internal.app.ProcessMap; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.TransferPipe; -import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.KeyguardDismissCallback; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.ArrayUtils; @@ -1796,23 +1795,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override - public void restartActivityProcessIfVisible(IBinder activityToken) { - enforceTaskPermission("restartActivityProcess()"); - final long callingId = Binder.clearCallingIdentity(); - try { - synchronized (mGlobalLock) { - final ActivityRecord r = ActivityRecord.isInRootTaskLocked(activityToken); - if (r == null) { - return; - } - r.restartProcessIfVisible(); - } - } finally { - Binder.restoreCallingIdentity(callingId); - } - } - - @Override public boolean removeTask(int taskId) { enforceCallerIsRecentsOrHasPermission(REMOVE_TASKS, "removeTask()"); synchronized (mGlobalLock) { @@ -3263,7 +3245,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { // If the keyguard is showing or occluded, then try and dismiss it before // entering picture-in-picture (this will prompt the user to authenticate if the // device is currently locked). - dismissKeyguard(r.appToken, new KeyguardDismissCallback() { + mActivityClientController.dismissKeyguard(r.appToken, new KeyguardDismissCallback() { @Override public void onDismissSucceeded() { mH.post(enterPipRunnable); @@ -3388,23 +3370,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override - public void dismissKeyguard(IBinder token, IKeyguardDismissCallback callback, - CharSequence message) { - if (message != null) { - mAmInternal.enforceCallingPermission( - Manifest.permission.SHOW_KEYGUARD_MESSAGE, "dismissKeyguard()"); - } - final long callingId = Binder.clearCallingIdentity(); - try { - synchronized (mGlobalLock) { - mKeyguardController.dismissKeyguard(token, callback, message); - } - } finally { - Binder.restoreCallingIdentity(callingId); - } - } - - @Override public void cancelTaskWindowTransition(int taskId) { enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_TASKS, "cancelTaskWindowTransition()"); @@ -3450,17 +3415,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return task.getSnapshot(isLowResolution, restoreFromDisk); } - @Override - public void invalidateHomeTaskSnapshot(IBinder token) { - synchronized (mGlobalLock) { - final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token); - if (r == null || !r.isActivityTypeHome()) { - return; - } - mWindowManager.mTaskSnapshotController.removeSnapshotCache(r.getTask().mTaskId); - } - } - /** Return the user id of the last resumed activity. */ @Override public @UserIdInt diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 8841a9b87498..2a40500258c9 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -669,9 +669,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // Used in updating override configurations private final Configuration mTempConfig = new Configuration(); - // Used in performing layout, to record the insets provided by other windows above the current - // window. - private InsetsState mTmpAboveInsetsState = new InsetsState(); + // Used in performing layout + private boolean mTmpWindowsBehindIme; /** * Used to prevent recursions when calling @@ -770,11 +769,17 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp + " parentHidden=" + w.isParentWindowHidden()); } - // Sets mAboveInsets for each window. Windows behind the window providing the insets can - // receive the insets. - if (!w.mAboveInsetsState.equals(mTmpAboveInsetsState)) { - w.mAboveInsetsState.set(mTmpAboveInsetsState); - mWinInsetsChanged.add(w); + // Sets mBehindIme for each window. Windows behind IME can get IME insets. + if (w.mBehindIme != mTmpWindowsBehindIme) { + w.mBehindIme = mTmpWindowsBehindIme; + if (getInsetsStateController().getRawInsetsState().getSourceOrDefaultVisibility( + ITYPE_IME)) { + // If IME is invisible, behind IME or not doesn't make the insets different. + mWinInsetsChanged.add(w); + } + } + if (w == mInputMethodWindow) { + mTmpWindowsBehindIme = true; } // If this view is GONE, then skip it -- keep the current frame, and let the caller know @@ -810,16 +815,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp + " mContainingFrame=" + w.getContainingFrame() + " mDisplayFrame=" + w.getDisplayFrame()); } - provideInsetsByWindow(w); }; - private void provideInsetsByWindow(WindowState w) { - for (int i = 0; i < w.mProvidedInsetsSources.size(); i++) { - final InsetsSource providedSource = w.mProvidedInsetsSources.valueAt(i); - mTmpAboveInsetsState.addSource(providedSource); - } - } - private final Consumer<WindowState> mPerformLayoutAttached = w -> { if (w.mLayoutAttached) { if (DEBUG_LAYOUT) Slog.v(TAG, "2ND PASS " + w + " mHaveFrame=" + w.mHaveFrame @@ -4272,20 +4269,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp + " dh=" + mDisplayInfo.logicalHeight); } - // Used to indicate that we have processed the insets windows. This needs to be after - // beginLayoutLw to ensure the raw insets state display related info is initialized. - final InsetsState rawInsetsState = getInsetsStateController().getRawInsetsState(); - mTmpAboveInsetsState = new InsetsState(); - mTmpAboveInsetsState.setDisplayFrame(rawInsetsState.getDisplayFrame()); - mTmpAboveInsetsState.setDisplayCutout(rawInsetsState.getDisplayCutout()); - mTmpAboveInsetsState.mirrorAlwaysVisibleInsetsSources(rawInsetsState); - int seq = mLayoutSeq + 1; if (seq < 0) seq = 0; mLayoutSeq = seq; mTmpInitial = initial; + // Used to indicate that we have processed the IME window. + mTmpWindowsBehindIme = false; // First perform layout of any root windows (not attached to another window). forAllWindows(mPerformLayout, true /* traverseTopToBottom */); diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index 7d0854d59604..baa672c4805c 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -152,7 +152,6 @@ class InsetsSourceProvider { // animate-out as new one animates-in. mWin.cancelAnimation(); mWin.mPendingPositionChanged = null; - mWin.mProvidedInsetsSources.remove(mSource.getType()); } ProtoLog.d(WM_DEBUG_IME, "InsetsSource setWin %s", win); mWin = win; @@ -162,14 +161,11 @@ class InsetsSourceProvider { setServerVisible(false); mSource.setFrame(new Rect()); mSource.setVisibleFrame(null); - } else { - mWin.mProvidedInsetsSources.put(mSource.getType(), mSource); - if (mControllable) { - mWin.setControllableInsetProvider(this); - if (mPendingControlTarget != null) { - updateControlForTarget(mPendingControlTarget, true /* force */); - mPendingControlTarget = null; - } + } else if (mControllable) { + mWin.setControllableInsetProvider(this); + if (mPendingControlTarget != null) { + updateControlForTarget(mPendingControlTarget, true /* force */); + mPendingControlTarget = null; } } } diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index 7b709ea5d641..398049fb97c3 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -104,8 +104,6 @@ class InsetsStateController { * visible to the target. e.g., the source which represents the target window itself, and the * IME source when the target is above IME. We also need to exclude certain types of insets * source for client within specific windowing modes. - * This is to get the insets for a window layout on the screen. If the window is not there, use - * the {@link #getInsetsForWindowMetrics} to get insets instead. * * @param target The window associate with the perspective. * @return The state stripped of the necessary information. @@ -119,7 +117,7 @@ class InsetsStateController { final @InternalInsetsType int type = provider != null ? provider.getSource().getType() : ITYPE_INVALID; return getInsetsForTarget(type, target.getWindowingMode(), target.isAlwaysOnTop(), - target.mAboveInsetsState); + isAboveIme(target)); } InsetsState getInsetsForWindowMetrics(@NonNull WindowManager.LayoutParams attrs) { @@ -134,7 +132,19 @@ class InsetsStateController { final @WindowingMode int windowingMode = token != null ? token.getWindowingMode() : WINDOWING_MODE_UNDEFINED; final boolean alwaysOnTop = token != null && token.isAlwaysOnTop(); - return getInsetsForTarget(type, windowingMode, alwaysOnTop, mState); + return getInsetsForTarget(type, windowingMode, alwaysOnTop, isAboveIme(token)); + } + + private boolean isAboveIme(WindowContainer target) { + final WindowState imeWindow = mDisplayContent.mInputMethodWindow; + if (target == null || imeWindow == null) { + return false; + } + if (target instanceof WindowState) { + final WindowState win = (WindowState) target; + return win.needsRelativeLayeringToIme() || !win.mBehindIme; + } + return false; } private static @InternalInsetsType @@ -170,12 +180,11 @@ class InsetsStateController { * @see #getInsetsForWindowMetrics */ private InsetsState getInsetsForTarget(@InternalInsetsType int type, - @WindowingMode int windowingMode, boolean isAlwaysOnTop, InsetsState state) { - boolean stateCopied = false; + @WindowingMode int windowingMode, boolean isAlwaysOnTop, boolean aboveIme) { + InsetsState state = mState; if (type != ITYPE_INVALID) { state = new InsetsState(state); - stateCopied = true; state.removeSource(type); // Navigation bar doesn't get influenced by anything else @@ -210,15 +219,23 @@ class InsetsStateController { if (WindowConfiguration.isFloating(windowingMode) || (windowingMode == WINDOWING_MODE_MULTI_WINDOW && isAlwaysOnTop)) { - if (!stateCopied) { - state = new InsetsState(state); - stateCopied = true; - } + state = new InsetsState(state); state.removeSource(ITYPE_STATUS_BAR); state.removeSource(ITYPE_NAVIGATION_BAR); state.removeSource(ITYPE_EXTRA_NAVIGATION_BAR); } + if (aboveIme) { + InsetsSource imeSource = state.peekSource(ITYPE_IME); + if (imeSource != null && imeSource.isVisible()) { + imeSource = new InsetsSource(imeSource); + imeSource.setVisible(false); + imeSource.setFrame(0, 0, 0, 0); + state = new InsetsState(state); + state.addSource(imeSource); + } + } + return state; } diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java index 91014aa69831..26871d130fbf 100644 --- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java +++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java @@ -28,7 +28,7 @@ import android.view.DisplayInfo; */ class RefreshRatePolicy { - private final int mLowRefreshRateId; + private final Mode mLowRefreshRateMode; private final ArraySet<String> mNonHighRefreshRatePackages = new ArraySet<>(); private final HighRefreshRateDenylist mHighRefreshRateDenylist; private final WindowManagerService mWmService; @@ -56,7 +56,7 @@ class RefreshRatePolicy { RefreshRatePolicy(WindowManagerService wmService, DisplayInfo displayInfo, HighRefreshRateDenylist denylist) { - mLowRefreshRateId = findLowRefreshRateModeId(displayInfo); + mLowRefreshRateMode = findLowRefreshRateMode(displayInfo); mHighRefreshRateDenylist = denylist; mWmService = wmService; } @@ -65,7 +65,7 @@ class RefreshRatePolicy { * Finds the mode id with the lowest refresh rate which is >= 60hz and same resolution as the * default mode. */ - private int findLowRefreshRateModeId(DisplayInfo displayInfo) { + private Mode findLowRefreshRateMode(DisplayInfo displayInfo) { Mode mode = displayInfo.getDefaultMode(); float[] refreshRates = displayInfo.getDefaultRefreshRates(); float bestRefreshRate = mode.getRefreshRate(); @@ -104,13 +104,9 @@ class RefreshRatePolicy { // If app is using Camera, force it to default (lower) refresh rate. if (mNonHighRefreshRatePackages.contains(packageName)) { - return mLowRefreshRateId; + return mLowRefreshRateMode.getModeId(); } - // If app is denylisted using higher refresh rate, return default (lower) refresh rate - if (mHighRefreshRateDenylist.isDenylisted(packageName)) { - return mLowRefreshRateId; - } return 0; } @@ -137,4 +133,18 @@ class RefreshRatePolicy { } return LAYER_PRIORITY_UNSET; } + + float getPreferredRefreshRate(WindowState w) { + // If app is animating, it's not able to control refresh rate because we want the animation + // to run in default refresh rate. + if (w.isAnimating(TRANSITION | PARENTS)) { + return 0; + } + + final String packageName = w.getOwningPackage(); + if (mHighRefreshRateDenylist.isDenylisted(packageName)) { + return mLowRefreshRateMode.getRefreshRate(); + } + return 0; + } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 79d112385d67..ec1588d15320 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -124,6 +124,7 @@ import static com.android.server.wm.Task.ActivityState.RESUMED; import static com.android.server.wm.Task.ActivityState.STARTED; import static com.android.server.wm.Task.ActivityState.STOPPING; import static com.android.server.wm.TaskProto.ACTIVITY_TYPE; +import static com.android.server.wm.TaskProto.AFFINITY; import static com.android.server.wm.TaskProto.BOUNDS; import static com.android.server.wm.TaskProto.CREATED_BY_ORGANIZER; import static com.android.server.wm.TaskProto.DISPLAY_ID; @@ -1244,27 +1245,20 @@ class Task extends WindowContainer<WindowContainer> { mCallingFeatureId = r.launchedFromFeatureId; setIntent(intent != null ? intent : r.intent, info != null ? info : r.info); setLockTaskAuth(r); - - final WindowContainer parent = getParent(); - if (parent != null) { - final Task t = parent.asTask(); - if (t != null) { - t.setIntent(r); - } - } } /** Sets the original intent, _without_ updating the calling uid or package. */ private void setIntent(Intent _intent, ActivityInfo info) { - final boolean isLeaf = isLeafTask(); + if (!isLeafTask()) return; + if (intent == null) { mNeverRelinquishIdentity = (info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0; - } else if (mNeverRelinquishIdentity && isLeaf) { + } else if (mNeverRelinquishIdentity) { return; } - affinity = isLeaf ? info.taskAffinity : null; + affinity = info.taskAffinity; if (intent == null) { // If this task already has an intent associated with it, don't set the root // affinity -- we don't want it changing after initially set, but the initially @@ -7809,6 +7803,7 @@ class Task extends WindowContainer<WindowContainer> { } proto.write(CREATED_BY_ORGANIZER, mCreatedByOrganizer); + proto.write(AFFINITY, affinity); proto.end(token); } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 3be4e78a122b..093106f123e5 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -212,7 +212,6 @@ import android.os.Trace; import android.os.WorkSource; import android.provider.Settings; import android.text.TextUtils; -import android.util.ArrayMap; import android.util.ArraySet; import android.util.DisplayMetrics; import android.util.MergedConfiguration; @@ -233,6 +232,7 @@ import android.view.InputWindowHandle; import android.view.InsetsSource; import android.view.InsetsState; import android.view.InsetsState.InternalInsetsType; +import android.view.Surface; import android.view.Surface.Rotation; import android.view.SurfaceControl; import android.view.SurfaceSession; @@ -647,14 +647,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP boolean mSeamlesslyRotated = false; /** - * The insets state of sources provided by windows above the current window. + * Indicates if this window is behind IME. Only windows behind IME can get insets from IME. */ - InsetsState mAboveInsetsState = new InsetsState(); - - /** - * The insets sources provided by this window. - */ - ArrayMap<Integer, InsetsSource> mProvidedInsetsSources = new ArrayMap<>(); + boolean mBehindIme = false; /** * Surface insets from the previous call to relayout(), used to track @@ -730,6 +725,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP */ int mFrameRateSelectionPriority = RefreshRatePolicy.LAYER_PRIORITY_UNSET; + /** + * This is the frame rate which is passed to SurfaceFlinger if the window is part of the + * high refresh rate deny list. The variable is cached, so we do not send too many updates to + * SF. + */ + float mDenyListFrameRate = 0f; + static final int BLAST_TIMEOUT_DURATION = 5000; /* milliseconds */ private final WindowProcessController mWpcForDisplayAreaConfigChanges; @@ -5233,7 +5235,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return (mAttrs.flags & FLAG_BLUR_BEHIND) != 0 && mOwnerCanUseBackgroundBlur; } - /** * Notifies SF about the priority of the window, if it changed. SF then uses this information * to decide which window's desired rendering rate should have a priority when deciding about @@ -5242,13 +5243,21 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP */ @VisibleForTesting void updateFrameRateSelectionPriorityIfNeeded() { - final int priority = getDisplayContent().getDisplayPolicy().getRefreshRatePolicy() - .calculatePriority(this); + RefreshRatePolicy refreshRatePolicy = + getDisplayContent().getDisplayPolicy().getRefreshRatePolicy(); + final int priority = refreshRatePolicy.calculatePriority(this); if (mFrameRateSelectionPriority != priority) { mFrameRateSelectionPriority = priority; getPendingTransaction().setFrameRateSelectionPriority(mSurfaceControl, mFrameRateSelectionPriority); } + + final float refreshRate = refreshRatePolicy.getPreferredRefreshRate(this); + if (mDenyListFrameRate != refreshRate) { + mDenyListFrameRate = refreshRate; + getPendingTransaction().setFrameRate( + mSurfaceControl, mDenyListFrameRate, Surface.FRAME_RATE_COMPATIBILITY_EXACT); + } } private void updateGlobalScaleIfNeeded() { diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 9d013c1aeef6..345b2464d75a 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -70,6 +70,7 @@ cc_library_static { "frameworks/base/libs", "frameworks/native/services", "system/gatekeeper/include", + "system/memory/libmeminfo/include", ], header_libs: [ @@ -97,6 +98,7 @@ cc_defaults { "libhardware_legacy", "libhidlbase", "libkeystore_binder", + "libmeminfo", "libmtp", "libnativehelper", "libnativewindow", diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp index 678308af34ea..156ef795db6d 100644 --- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp +++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp @@ -17,15 +17,25 @@ #define LOG_TAG "CachedAppOptimizer" //#define LOG_NDEBUG 0 +#include <android-base/file.h> +#include <android-base/stringprintf.h> +#include <android_runtime/AndroidRuntime.h> +#include <cutils/compiler.h> #include <dirent.h> +#include <jni.h> +#include <linux/errno.h> +#include <log/log.h> +#include <meminfo/procmeminfo.h> +#include <nativehelper/JNIHelp.h> #include <stddef.h> #include <stdio.h> +#include <sys/mman.h> #include <sys/stat.h> +#include <sys/syscall.h> #include <sys/types.h> #include <unistd.h> -#include <android-base/stringprintf.h> -#include <android-base/file.h> +#include <algorithm> #include <nativehelper/JNIHelp.h> #include <android_runtime/AndroidRuntime.h> @@ -35,12 +45,149 @@ using android::base::StringPrintf; using android::base::WriteStringToFile; +using android::meminfo::ProcMemInfo; +using namespace android::meminfo; + +// This is temporarily hard-coded and should be removed once +// bionic/libc/kernel/uapi/asm-generic/unistd.h are updated with process_madvise syscall header +#ifndef __NR_process_madvise +#define __NR_process_madvise 440 +#define MADV_COLD 20 /* deactivate these pages */ +#define MADV_PAGEOUT 21 +#endif + +#define COMPACT_ACTION_FILE_FLAG 1 +#define COMPACT_ACTION_ANON_FLAG 2 + +using VmaToAdviseFunc = std::function<int(const Vma&)>; #define SYNC_RECEIVED_WHILE_FROZEN (1) #define ASYNC_RECEIVED_WHILE_FROZEN (2) namespace android { +// Legacy method for compacting processes, any new code should +// use compactProcess instead. +static inline void compactProcessProcfs(int pid, const std::string& compactionType) { + std::string reclaim_path = StringPrintf("/proc/%d/reclaim", pid); + WriteStringToFile(compactionType, reclaim_path); +} + +static int compactMemory(const std::vector<Vma>& vmas, int pid, int madviseType) { + // UIO_MAXIOV is currently a small value and we might have more addresses + // we do multiple syscalls if we exceed its maximum + static struct iovec vmasToKernel[UIO_MAXIOV]; + + int err = 0; + + if (vmas.empty()) { + return err; + } + + int pidfd = syscall(__NR_pidfd_open, pid, 0); + err = -errno; + if (err < 0) { + // Skip compaction if failed to open pidfd with any error + return err; + } + + for (int iBase = 0; iBase < vmas.size(); iBase += UIO_MAXIOV) { + int totalVmasToKernel = std::min(UIO_MAXIOV, (int)(vmas.size() - iBase)); + for (int iVec = 0, iVma = iBase; iVec < totalVmasToKernel; ++iVec, ++iVma) { + vmasToKernel[iVec].iov_base = (void*)vmas[iVma].start; + vmasToKernel[iVec].iov_len = vmas[iVma].end - vmas[iVma].start; + } + + process_madvise(pidfd, vmasToKernel, totalVmasToKernel, madviseType, 0); + err = -errno; + if (CC_UNLIKELY(err == -ENOSYS)) { + // Syscall does not exist, skip trying more calls process_madvise + break; + } + } + + close(pidfd); + + return err; +} + +static int getFilePageAdvice(const Vma& vma) { + if (vma.inode > 0 && !vma.is_shared) { + return MADV_COLD; + } + return -1; +} +static int getAnonPageAdvice(const Vma& vma) { + if (vma.inode == 0 && !vma.is_shared) { + return MADV_PAGEOUT; + } + return -1; +} +static bool getAnyPageAdvice(const Vma& vma) { + if (vma.inode == 0 && !vma.is_shared) { + return MADV_PAGEOUT; + } + return MADV_COLD; +} + +// Perform a full process compaction using process_madvise syscall +// reading all filtering VMAs and filtering pages as specified by pageFilter +static int compactProcess(int pid, VmaToAdviseFunc vmaToAdviseFunc) { + ProcMemInfo meminfo(pid); + std::vector<Vma> pageoutVmas, coldVmas; + auto vmaCollectorCb = [&](Vma vma) { + int advice = vmaToAdviseFunc(vma); + switch (advice) { + case MADV_COLD: + coldVmas.push_back(vma); + break; + case MADV_PAGEOUT: + pageoutVmas.push_back(vma); + break; + } + }; + meminfo.ForEachVma(vmaCollectorCb); + + int err = compactMemory(pageoutVmas, pid, MADV_PAGEOUT); + if (!err) { + err = compactMemory(coldVmas, pid, MADV_COLD); + } + return err; +} + +// Compact process using process_madvise syscall or fallback to procfs in +// case syscall does not exist. +static void compactProcessOrFallback(int pid, int compactionFlags) { + if ((compactionFlags & (COMPACT_ACTION_ANON_FLAG | COMPACT_ACTION_FILE_FLAG)) == 0) return; + + bool compactAnon = compactionFlags & COMPACT_ACTION_ANON_FLAG; + bool compactFile = compactionFlags & COMPACT_ACTION_FILE_FLAG; + + // Set when the system does not support process_madvise syscall to avoid + // gathering VMAs in subsequent calls prior to falling back to procfs + static bool shouldForceProcFs = false; + std::string compactionType; + VmaToAdviseFunc vmaToAdviseFunc; + + if (compactAnon) { + if (compactFile) { + compactionType = "all"; + vmaToAdviseFunc = getAnyPageAdvice; + } else { + compactionType = "anon"; + vmaToAdviseFunc = getAnonPageAdvice; + } + } else { + compactionType = "file"; + vmaToAdviseFunc = getFilePageAdvice; + } + + if (shouldForceProcFs || compactProcess(pid, vmaToAdviseFunc) == -ENOSYS) { + shouldForceProcFs = true; + compactProcessProcfs(pid, compactionType); + } +} + // This performs per-process reclaim on all processes belonging to non-app UIDs. // For the most part, these are non-zygote processes like Treble HALs, but it // also includes zygote-derived processes that run in system UIDs, like bluetooth @@ -74,11 +221,17 @@ static void com_android_server_am_CachedAppOptimizer_compactSystem(JNIEnv *, job continue; } - std::string reclaim_path = StringPrintf("/proc/%s/reclaim", current->d_name); - WriteStringToFile(std::string("all"), reclaim_path); + int pid = atoi(current->d_name); + + compactProcessOrFallback(pid, COMPACT_ACTION_ANON_FLAG | COMPACT_ACTION_FILE_FLAG); } } +static void com_android_server_am_CachedAppOptimizer_compactProcess(JNIEnv*, jobject, jint pid, + jint compactionFlags) { + compactProcessOrFallback(pid, compactionFlags); +} + static void com_android_server_am_CachedAppOptimizer_enableFreezerInternal( JNIEnv *env, jobject clazz, jboolean enable) { bool success = true; @@ -126,14 +279,14 @@ static jint com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo(JNIEnv } static const JNINativeMethod sMethods[] = { - /* name, signature, funcPtr */ - {"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem}, - {"enableFreezerInternal", "(Z)V", - (void*)com_android_server_am_CachedAppOptimizer_enableFreezerInternal}, - {"freezeBinder", "(IZ)V", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder}, - {"getBinderFreezeInfo", "(I)I", - (void*)com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo} -}; + /* name, signature, funcPtr */ + {"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem}, + {"compactProcess", "(II)V", (void*)com_android_server_am_CachedAppOptimizer_compactProcess}, + {"enableFreezerInternal", "(Z)V", + (void*)com_android_server_am_CachedAppOptimizer_enableFreezerInternal}, + {"freezeBinder", "(IZ)V", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder}, + {"getBinderFreezeInfo", "(I)I", + (void*)com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo}}; int register_android_server_am_CachedAppOptimizer(JNIEnv* env) { diff --git a/services/core/xsd/Android.bp b/services/core/xsd/Android.bp index d1918d8dbe14..bdccf45627f4 100644 --- a/services/core/xsd/Android.bp +++ b/services/core/xsd/Android.bp @@ -8,11 +8,19 @@ xsd_config { xsd_config { name: "platform-compat-config", - srcs: ["platform-compat-config.xsd"], - api_dir: "platform-compat-schema", + srcs: ["platform-compat/config/platform-compat-config.xsd"], + api_dir: "platform-compat/config/schema", package_name: "com.android.server.compat.config", } +xsd_config { + name: "platform-compat-overrides", + srcs: ["platform-compat/overrides/platform-compat-overrides.xsd"], + api_dir: "platform-compat/overrides/schema", + package_name: "com.android.server.compat.overrides", + gen_writer: true, +} + xsd_config { name: "display-device-config", diff --git a/services/core/xsd/platform-compat-schema/OWNERS b/services/core/xsd/platform-compat/OWNERS index f8c3520e9fa8..f8c3520e9fa8 100644 --- a/services/core/xsd/platform-compat-schema/OWNERS +++ b/services/core/xsd/platform-compat/OWNERS diff --git a/services/core/xsd/platform-compat-config.xsd b/services/core/xsd/platform-compat/config/platform-compat-config.xsd index a62e2c385766..a62e2c385766 100644 --- a/services/core/xsd/platform-compat-config.xsd +++ b/services/core/xsd/platform-compat/config/platform-compat-config.xsd diff --git a/services/core/xsd/platform-compat-schema/current.txt b/services/core/xsd/platform-compat/config/schema/current.txt index fb8bbefd8374..fb8bbefd8374 100644 --- a/services/core/xsd/platform-compat-schema/current.txt +++ b/services/core/xsd/platform-compat/config/schema/current.txt diff --git a/services/core/xsd/platform-compat-schema/last_current.txt b/services/core/xsd/platform-compat/config/schema/last_current.txt index e69de29bb2d1..e69de29bb2d1 100644 --- a/services/core/xsd/platform-compat-schema/last_current.txt +++ b/services/core/xsd/platform-compat/config/schema/last_current.txt diff --git a/services/core/xsd/platform-compat-schema/last_removed.txt b/services/core/xsd/platform-compat/config/schema/last_removed.txt index e69de29bb2d1..e69de29bb2d1 100644 --- a/services/core/xsd/platform-compat-schema/last_removed.txt +++ b/services/core/xsd/platform-compat/config/schema/last_removed.txt diff --git a/services/core/xsd/platform-compat-schema/removed.txt b/services/core/xsd/platform-compat/config/schema/removed.txt index d802177e249b..d802177e249b 100644 --- a/services/core/xsd/platform-compat-schema/removed.txt +++ b/services/core/xsd/platform-compat/config/schema/removed.txt diff --git a/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd b/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd new file mode 100644 index 000000000000..e27e1b8ca89d --- /dev/null +++ b/services/core/xsd/platform-compat/overrides/platform-compat-overrides.xsd @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2019 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<!-- This defines the format of the XML file used to store compat config overrides in + ~ /data/misc/appcompat/compat_framework_overrides.xml +--> +<xs:schema version="2.0" elementFormDefault="qualified" + xmlns:xs="http://www.w3.org/2001/XMLSchema"> + + + <xs:complexType name="override-value"> + <xs:attribute type="xs:string" name="packageName" use="required" /> + <xs:attribute type="xs:boolean" name="enabled" use="required" /> + </xs:complexType> + + <xs:complexType name="change-overrides"> + <xs:attribute type="xs:long" name="changeId" use="required"/> + <xs:element name="validated"> + <xs:complexType> + <xs:sequence> + <xs:element name="override-value" type="override-value" maxOccurs="unbounded" minOccurs="0" /> + </xs:sequence> + </xs:complexType> + </xs:element> + <xs:element name="deferred"> + <xs:complexType> + <xs:sequence> + <xs:element name="override-value" type="override-value" maxOccurs="unbounded" minOccurs="0" /> + </xs:sequence> + </xs:complexType> + </xs:element> + </xs:complexType> + + <xs:element name="overrides"> + <xs:complexType> + <xs:sequence> + <xs:element name="change-overrides" type="change-overrides" maxOccurs="unbounded" minOccurs="0" /> + </xs:sequence> + </xs:complexType> + </xs:element> +</xs:schema> diff --git a/services/core/xsd/platform-compat/overrides/schema/current.txt b/services/core/xsd/platform-compat/overrides/schema/current.txt new file mode 100644 index 000000000000..08b82072747b --- /dev/null +++ b/services/core/xsd/platform-compat/overrides/schema/current.txt @@ -0,0 +1,51 @@ +// Signature format: 2.0 +package com.android.server.compat.overrides { + + public class ChangeOverrides { + ctor public ChangeOverrides(); + method public long getChangeId(); + method public com.android.server.compat.overrides.ChangeOverrides.Deferred getDeferred(); + method public com.android.server.compat.overrides.ChangeOverrides.Validated getValidated(); + method public void setChangeId(long); + method public void setDeferred(com.android.server.compat.overrides.ChangeOverrides.Deferred); + method public void setValidated(com.android.server.compat.overrides.ChangeOverrides.Validated); + } + + public static class ChangeOverrides.Deferred { + ctor public ChangeOverrides.Deferred(); + method public java.util.List<com.android.server.compat.overrides.OverrideValue> getOverrideValue(); + } + + public static class ChangeOverrides.Validated { + ctor public ChangeOverrides.Validated(); + method public java.util.List<com.android.server.compat.overrides.OverrideValue> getOverrideValue(); + } + + public class OverrideValue { + ctor public OverrideValue(); + method public boolean getEnabled(); + method public String getPackageName(); + method public void setEnabled(boolean); + method public void setPackageName(String); + } + + public class Overrides { + ctor public Overrides(); + method public java.util.List<com.android.server.compat.overrides.ChangeOverrides> getChangeOverrides(); + } + + public class XmlParser { + ctor public XmlParser(); + method public static com.android.server.compat.overrides.Overrides read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method public static void skip(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; + } + + public class XmlWriter implements java.io.Closeable { + ctor public XmlWriter(java.io.PrintWriter); + method public void close(); + method public static void write(com.android.server.compat.overrides.XmlWriter, com.android.server.compat.overrides.Overrides) throws java.io.IOException; + } + +} + diff --git a/services/core/xsd/platform-compat/overrides/schema/last_current.txt b/services/core/xsd/platform-compat/overrides/schema/last_current.txt new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/services/core/xsd/platform-compat/overrides/schema/last_current.txt diff --git a/services/core/xsd/platform-compat/overrides/schema/last_removed.txt b/services/core/xsd/platform-compat/overrides/schema/last_removed.txt new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/services/core/xsd/platform-compat/overrides/schema/last_removed.txt diff --git a/services/core/xsd/platform-compat/overrides/schema/removed.txt b/services/core/xsd/platform-compat/overrides/schema/removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/services/core/xsd/platform-compat/overrides/schema/removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 12595af1db7f..0768bb93839d 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -326,7 +326,7 @@ public final class SystemServer implements Dumpable { private static final String TIME_ZONE_DETECTOR_SERVICE_CLASS = "com.android.server.timezonedetector.TimeZoneDetectorService$Lifecycle"; private static final String LOCATION_TIME_ZONE_MANAGER_SERVICE_CLASS = - "com.android.server.location.timezone.LocationTimeZoneManagerService$Lifecycle"; + "com.android.server.timezonedetector.location.LocationTimeZoneManagerService$Lifecycle"; private static final String GNSS_TIME_UPDATE_SERVICE_CLASS = "com.android.server.timedetector.GnssTimeUpdateService$Lifecycle"; private static final String ACCESSIBILITY_MANAGER_SERVICE_CLASS = diff --git a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java index a691a8d44e48..607fb4760236 100644 --- a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java @@ -271,8 +271,7 @@ public class AppStateTrackerTest { verify(mMockIActivityManager).registerUidObserver( uidObserverArgumentCaptor.capture(), eq(ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_IDLE - | ActivityManager.UID_OBSERVER_ACTIVE - | ActivityManager.UID_OBSERVER_PROCSTATE), + | ActivityManager.UID_OBSERVER_ACTIVE), eq(ActivityManager.PROCESS_STATE_UNKNOWN), isNull()); verify(mMockIAppOpsService).startWatchingMode( @@ -650,11 +649,6 @@ public class AppStateTrackerTest { assertFalse(instance.isUidActiveSynced(UID_2)); assertTrue(instance.isUidActiveSynced(Process.SYSTEM_UID)); - assertFalse(instance.isUidInForeground(UID_1)); - assertFalse(instance.isUidInForeground(UID_2)); - assertTrue(instance.isUidInForeground(Process.SYSTEM_UID)); - - mIUidObserver.onUidStateChanged(UID_2, ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE, 0, ActivityManager.PROCESS_CAPABILITY_NONE); @@ -670,11 +664,6 @@ public class AppStateTrackerTest { assertFalse(instance.isUidActiveSynced(UID_2)); assertTrue(instance.isUidActiveSynced(Process.SYSTEM_UID)); - assertFalse(instance.isUidInForeground(UID_1)); - assertTrue(instance.isUidInForeground(UID_2)); - assertTrue(instance.isUidInForeground(Process.SYSTEM_UID)); - - mIUidObserver.onUidStateChanged(UID_1, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, ActivityManager.PROCESS_CAPABILITY_NONE); @@ -686,10 +675,6 @@ public class AppStateTrackerTest { assertFalse(instance.isUidActive(UID_2)); assertTrue(instance.isUidActive(Process.SYSTEM_UID)); - assertTrue(instance.isUidInForeground(UID_1)); - assertTrue(instance.isUidInForeground(UID_2)); - assertTrue(instance.isUidInForeground(Process.SYSTEM_UID)); - mIUidObserver.onUidGone(UID_1, true); waitUntilMainHandlerDrain(); @@ -699,10 +684,6 @@ public class AppStateTrackerTest { assertFalse(instance.isUidActive(UID_2)); assertTrue(instance.isUidActive(Process.SYSTEM_UID)); - assertFalse(instance.isUidInForeground(UID_1)); - assertTrue(instance.isUidInForeground(UID_2)); - assertTrue(instance.isUidInForeground(Process.SYSTEM_UID)); - mIUidObserver.onUidIdle(UID_2, true); waitUntilMainHandlerDrain(); @@ -712,10 +693,6 @@ public class AppStateTrackerTest { assertFalse(instance.isUidActive(UID_2)); assertTrue(instance.isUidActive(Process.SYSTEM_UID)); - assertFalse(instance.isUidInForeground(UID_1)); - assertFalse(instance.isUidInForeground(UID_2)); - assertTrue(instance.isUidInForeground(Process.SYSTEM_UID)); - mIUidObserver.onUidStateChanged(UID_1, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, ActivityManager.PROCESS_CAPABILITY_NONE); @@ -727,10 +704,6 @@ public class AppStateTrackerTest { assertFalse(instance.isUidActive(UID_2)); assertTrue(instance.isUidActive(Process.SYSTEM_UID)); - assertTrue(instance.isUidInForeground(UID_1)); - assertFalse(instance.isUidInForeground(UID_2)); - assertTrue(instance.isUidInForeground(Process.SYSTEM_UID)); - mIUidObserver.onUidStateChanged(UID_1, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, ActivityManager.PROCESS_CAPABILITY_NONE); @@ -746,10 +719,6 @@ public class AppStateTrackerTest { assertFalse(instance.isUidActiveSynced(UID_2)); assertTrue(instance.isUidActiveSynced(Process.SYSTEM_UID)); - assertFalse(instance.isUidInForeground(UID_1)); - assertFalse(instance.isUidInForeground(UID_2)); - assertTrue(instance.isUidInForeground(Process.SYSTEM_UID)); - // The result from AMI.isUidActive() only affects isUidActiveSynced(). when(mMockIActivityManagerInternal.isUidActive(anyInt())).thenReturn(true); @@ -760,11 +729,6 @@ public class AppStateTrackerTest { assertTrue(instance.isUidActiveSynced(UID_1)); assertTrue(instance.isUidActiveSynced(UID_2)); assertTrue(instance.isUidActiveSynced(Process.SYSTEM_UID)); - - assertFalse(instance.isUidInForeground(UID_1)); - assertFalse(instance.isUidInForeground(UID_2)); - assertTrue(instance.isUidInForeground(Process.SYSTEM_UID)); - } @Test @@ -1480,7 +1444,6 @@ public class AppStateTrackerTest { callStart(instance); instance.mActiveUids.put(UID_1, true); - instance.mForegroundUids.put(UID_2, true); instance.mRunAnyRestrictedPackages.add(Pair.create(UID_1, PACKAGE_1)); instance.mExemptedBucketPackages.add(UserHandle.getUserId(UID_2), PACKAGE_2); @@ -1493,7 +1456,6 @@ public class AppStateTrackerTest { mReceiver.onReceive(mMockContext, packageRemoved); assertEquals(1, instance.mActiveUids.size()); - assertEquals(1, instance.mForegroundUids.size()); assertEquals(1, instance.mRunAnyRestrictedPackages.size()); assertEquals(1, instance.mExemptedBucketPackages.size()); @@ -1506,7 +1468,6 @@ public class AppStateTrackerTest { mReceiver.onReceive(mMockContext, packageRemoved); assertEquals(1, instance.mActiveUids.size()); - assertEquals(1, instance.mForegroundUids.size()); assertEquals(1, instance.mRunAnyRestrictedPackages.size()); assertEquals(1, instance.mExemptedBucketPackages.size()); @@ -1518,7 +1479,6 @@ public class AppStateTrackerTest { mReceiver.onReceive(mMockContext, packageRemoved); assertEquals(0, instance.mActiveUids.size()); - assertEquals(1, instance.mForegroundUids.size()); assertEquals(0, instance.mRunAnyRestrictedPackages.size()); assertEquals(1, instance.mExemptedBucketPackages.size()); @@ -1530,7 +1490,6 @@ public class AppStateTrackerTest { mReceiver.onReceive(mMockContext, packageRemoved); assertEquals(0, instance.mActiveUids.size()); - assertEquals(0, instance.mForegroundUids.size()); assertEquals(0, instance.mRunAnyRestrictedPackages.size()); assertEquals(0, instance.mExemptedBucketPackages.size()); } diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index 7a970a1c3d46..1254df95a1c0 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -44,14 +44,15 @@ import static com.android.server.alarm.AlarmManagerService.ACTIVE_INDEX; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.APP_STANDBY_BUCKET_CHANGED; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHARGING_STATUS_CHANGED; import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_FOR_CANCELED; -import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_LONG_TIME; -import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_SHORT_TIME; +import static com.android.server.alarm.AlarmManagerService.Constants.ALLOW_WHILE_IDLE_WINDOW; +import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_QUOTA; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_LAZY_BATCHING; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_LISTENER_TIMEOUT; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MAX_INTERVAL; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_FUTURITY; import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_INTERVAL; +import static com.android.server.alarm.AlarmManagerService.FREQUENT_INDEX; import static com.android.server.alarm.AlarmManagerService.INDEFINITE_DELAY; import static com.android.server.alarm.AlarmManagerService.IS_WAKEUP_MASK; import static com.android.server.alarm.AlarmManagerService.TIME_CHANGED_MASK; @@ -409,6 +410,12 @@ public class AlarmManagerServiceTest { return mockPi; } + private void setDeviceConfigInt(String key, int val) { + mDeviceConfigKeys.add(key); + doReturn(val).when(mDeviceConfigProperties).getInt(eq(key), anyInt()); + mService.mConstants.onPropertiesChanged(mDeviceConfigProperties); + } + private void setDeviceConfigLong(String key, long val) { mDeviceConfigKeys.add(key); doReturn(val).when(mDeviceConfigProperties).getLong(eq(key), anyLong()); @@ -430,10 +437,12 @@ public class AlarmManagerServiceTest { setDeviceConfigLong(KEY_MIN_INTERVAL, 0); mDeviceConfigKeys.add(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[ACTIVE_INDEX]); mDeviceConfigKeys.add(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[WORKING_INDEX]); - doReturn(8).when(mDeviceConfigProperties) + doReturn(50).when(mDeviceConfigProperties) .getInt(eq(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[ACTIVE_INDEX]), anyInt()); - doReturn(5).when(mDeviceConfigProperties) + doReturn(35).when(mDeviceConfigProperties) .getInt(eq(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[WORKING_INDEX]), anyInt()); + doReturn(20).when(mDeviceConfigProperties) + .getInt(eq(mService.mConstants.KEYS_APP_STANDBY_QUOTAS[FREQUENT_INDEX]), anyInt()); mService.mConstants.onPropertiesChanged(mDeviceConfigProperties); } @@ -496,15 +505,13 @@ public class AlarmManagerServiceTest { setDeviceConfigLong(KEY_MIN_FUTURITY, 5); setDeviceConfigLong(KEY_MIN_INTERVAL, 10); setDeviceConfigLong(KEY_MAX_INTERVAL, 15); - setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_SHORT_TIME, 20); - setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_LONG_TIME, 25); + setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_QUOTA, 20); setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION, 30); setDeviceConfigLong(KEY_LISTENER_TIMEOUT, 35); assertEquals(5, mService.mConstants.MIN_FUTURITY); assertEquals(10, mService.mConstants.MIN_INTERVAL); assertEquals(15, mService.mConstants.MAX_INTERVAL); - assertEquals(20, mService.mConstants.ALLOW_WHILE_IDLE_SHORT_TIME); - assertEquals(25, mService.mConstants.ALLOW_WHILE_IDLE_LONG_TIME); + assertEquals(20, mService.mConstants.ALLOW_WHILE_IDLE_QUOTA); assertEquals(30, mService.mConstants.ALLOW_WHILE_IDLE_WHITELIST_DURATION); assertEquals(35, mService.mConstants.LISTENER_TIMEOUT); } @@ -1301,62 +1308,54 @@ public class AlarmManagerServiceTest { public void allowWhileIdleAlarmsWhileDeviceIdle() throws Exception { doReturn(0).when(mService).fuzzForDuration(anyLong()); - final long awiDelayForTest = 23; - setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_LONG_TIME, awiDelayForTest); - - setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 1000, + setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + ALLOW_WHILE_IDLE_WINDOW + 1000, getNewMockPendingIntent()); assertNotNull(mService.mPendingIdleUntil); - final long seedTrigger = mNowElapsedTest + 3; - final int numAlarms = 10; - final PendingIntent[] pis = new PendingIntent[numAlarms]; - for (int i = 0; i < numAlarms; i++) { - pis[i] = getNewMockPendingIntent(); - setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, seedTrigger + i * i, pis[i], false); - } - - long lastAwiDispatch = -1; - int i = 0; - while (i < numAlarms) { - final long nextDispatch = (lastAwiDispatch >= 0) ? (lastAwiDispatch + awiDelayForTest) - : (seedTrigger + i * i); - assertEquals("Wrong allow-while-idle dispatch", nextDispatch, mTestTimer.getElapsed()); - - mNowElapsedTest = nextDispatch; + final int quota = mService.mConstants.ALLOW_WHILE_IDLE_QUOTA; + final long firstTrigger = mNowElapsedTest + 10; + for (int i = 0; i < quota; i++) { + setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, + getNewMockPendingIntent(), false); + mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); - - while (i < numAlarms && (seedTrigger + i * i) <= nextDispatch) { - verify(pis[i]).send(eq(mMockContext), eq(0), any(Intent.class), any(), - any(Handler.class), isNull(), any()); - i++; - } - Log.d(TAG, "Dispatched alarms upto " + i + " at " + nextDispatch); - lastAwiDispatch = nextDispatch; } + // This one should get deferred on set. + setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota, + getNewMockPendingIntent(), false); + final long expectedNextTrigger = firstTrigger + ALLOW_WHILE_IDLE_WINDOW; + assertEquals("Incorrect trigger when no quota left", expectedNextTrigger, + mTestTimer.getElapsed()); + + // Bring the idle until alarm back. + setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, expectedNextTrigger - 50, + getNewMockPendingIntent()); + assertEquals(expectedNextTrigger - 50, mService.mPendingIdleUntil.getWhenElapsed()); + assertEquals(expectedNextTrigger - 50, mTestTimer.getElapsed()); } @Test - public void allowWhileIdleUnrestrictedInIdle() throws Exception { + public void allowWhileIdleUnrestricted() throws Exception { doReturn(0).when(mService).fuzzForDuration(anyLong()); - final long awiDelayForTest = 127; - setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_LONG_TIME, awiDelayForTest); - setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_SHORT_TIME, 0); - + // Both battery saver and doze are on. setIdleUntilAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 1000, getNewMockPendingIntent()); assertNotNull(mService.mPendingIdleUntil); - final long seedTrigger = mNowElapsedTest + 3; - for (int i = 1; i <= 5; i++) { - setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, seedTrigger + i * i, + when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID, + TEST_CALLING_PACKAGE)).thenReturn(true); + + final int numAlarms = mService.mConstants.ALLOW_WHILE_IDLE_QUOTA + 100; + final long firstTrigger = mNowElapsedTest + 10; + for (int i = 0; i < numAlarms; i++) { + setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, getNewMockPendingIntent(), true); } - for (int i = 1; i <= 5; i++) { - final long nextTrigger = mTestTimer.getElapsed(); - assertEquals("Wrong trigger for alarm " + i, seedTrigger + i * i, nextTrigger); - mNowElapsedTest = nextTrigger; + // All of them should fire as expected. + for (int i = 0; i < numAlarms; i++) { + mNowElapsedTest = mTestTimer.getElapsed(); + assertEquals("Incorrect trigger at i=" + i, firstTrigger + i, mNowElapsedTest); mTestTimer.expire(); } } @@ -1427,9 +1426,10 @@ public class AlarmManagerServiceTest { verify(mAppStateTracker).addListener(listenerArgumentCaptor.capture()); final AppStateTrackerImpl.Listener listener = listenerArgumentCaptor.getValue(); - final PendingIntent alarmPi = getNewMockPendingIntent(); when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID, TEST_CALLING_PACKAGE)).thenReturn(true); + + final PendingIntent alarmPi = getNewMockPendingIntent(); setTestAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 7, alarmPi); assertEquals(mNowElapsedTest + INDEFINITE_DELAY, mTestTimer.getElapsed()); @@ -1446,61 +1446,64 @@ public class AlarmManagerServiceTest { @Test public void allowWhileIdleAlarmsInBatterySaver() throws Exception { - final ArgumentCaptor<AppStateTrackerImpl.Listener> listenerArgumentCaptor = - ArgumentCaptor.forClass(AppStateTrackerImpl.Listener.class); - verify(mAppStateTracker).addListener(listenerArgumentCaptor.capture()); - final AppStateTrackerImpl.Listener listener = listenerArgumentCaptor.getValue(); - - final long longDelay = 23; - final long shortDelay = 7; - setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_LONG_TIME, longDelay); - setDeviceConfigLong(KEY_ALLOW_WHILE_IDLE_SHORT_TIME, shortDelay); - when(mAppStateTracker.areAlarmsRestrictedByBatterySaver(TEST_CALLING_UID, TEST_CALLING_PACKAGE)).thenReturn(true); - setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 1, - getNewMockPendingIntent(), false); - setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 2, - getNewMockPendingIntent(), false); + when(mAppStateTracker.isForceAllAppsStandbyEnabled()).thenReturn(true); - assertEquals(mNowElapsedTest + 1, mTestTimer.getElapsed()); - - mNowElapsedTest += 1; - mTestTimer.expire(); + final int quota = mService.mConstants.ALLOW_WHILE_IDLE_QUOTA; + long firstTrigger = mNowElapsedTest + 10; + for (int i = 0; i < quota; i++) { + setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, + getNewMockPendingIntent(), false); + mNowElapsedTest = mTestTimer.getElapsed(); + mTestTimer.expire(); + } + // This one should get deferred on set. + setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota, + getNewMockPendingIntent(), false); + long expectedNextTrigger = firstTrigger + ALLOW_WHILE_IDLE_WINDOW; + assertEquals("Incorrect trigger when no quota available", expectedNextTrigger, + mTestTimer.getElapsed()); - assertEquals(mNowElapsedTest + longDelay, mTestTimer.getElapsed()); - listener.onUidForeground(TEST_CALLING_UID, true); - // The next alarm should be deferred by shortDelay. - assertEquals(mNowElapsedTest + shortDelay, mTestTimer.getElapsed()); + // Refresh the state + mService.removeLocked(TEST_CALLING_UID); + mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER); - mNowElapsedTest = mTestTimer.getElapsed(); - setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 1, + firstTrigger = mNowElapsedTest + 10; + for (int i = 0; i < quota; i++) { + setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, + getNewMockPendingIntent(), false); + } + // This one should get deferred after the latest alarm expires. + setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota, getNewMockPendingIntent(), false); + for (int i = 0; i < quota; i++) { + mNowElapsedTest = mTestTimer.getElapsed(); + mTestTimer.expire(); + } + expectedNextTrigger = firstTrigger + ALLOW_WHILE_IDLE_WINDOW; + assertEquals("Incorrect trigger when no quota available", expectedNextTrigger, + mTestTimer.getElapsed()); - when(mAppStateTracker.isUidInForeground(TEST_CALLING_UID)).thenReturn(true); - mTestTimer.expire(); - // The next alarm should be deferred by shortDelay again. - assertEquals(mNowElapsedTest + shortDelay, mTestTimer.getElapsed()); - - mNowElapsedTest = mTestTimer.getElapsed(); - setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + 1, - getNewMockPendingIntent(), true); - when(mAppStateTracker.isUidInForeground(TEST_CALLING_UID)).thenReturn(false); - mTestTimer.expire(); - final long lastAwiDispatch = mNowElapsedTest; - // Unrestricted, so should not be changed. - assertEquals(mNowElapsedTest + 1, mTestTimer.getElapsed()); + // Refresh the state + mService.removeLocked(TEST_CALLING_UID); + mService.mAllowWhileIdleHistory.removeForPackage(TEST_CALLING_PACKAGE, TEST_CALLING_USER); - mNowElapsedTest = mTestTimer.getElapsed(); - // AWI_unrestricted should not affect normal AWI bookkeeping. - // The next alarm is after the short delay but before the long delay. - setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, lastAwiDispatch + shortDelay + 1, + firstTrigger = mNowElapsedTest + 10; + for (int i = 0; i < quota; i++) { + setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, + getNewMockPendingIntent(), false); + } + // This delivery time maintains the quota invariant. Should not be deferred. + expectedNextTrigger = firstTrigger + ALLOW_WHILE_IDLE_WINDOW + 5; + setAllowWhileIdleAlarm(ELAPSED_REALTIME_WAKEUP, expectedNextTrigger, getNewMockPendingIntent(), false); - mTestTimer.expire(); - assertEquals(lastAwiDispatch + longDelay, mTestTimer.getElapsed()); - - listener.onUidForeground(TEST_CALLING_UID, true); - assertEquals(lastAwiDispatch + shortDelay + 1, mTestTimer.getElapsed()); + for (int i = 0; i < quota; i++) { + mNowElapsedTest = mTestTimer.getElapsed(); + mTestTimer.expire(); + } + assertEquals("Incorrect trigger when no quota available", expectedNextTrigger, + mTestTimer.getElapsed()); } @Test diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java index 90ce6cbb7718..57b0d01b3025 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java @@ -33,6 +33,7 @@ import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.IBiometricService; import android.os.Binder; import android.os.IBinder; +import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import androidx.annotation.NonNull; @@ -267,6 +268,39 @@ public class BiometricSchedulerTest { assertEquals(0, bsp.recentOperations.length); } + @Test + public void testCancelPendingAuth() throws RemoteException { + final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class); + + final TestClientMonitor client1 = new TestClientMonitor(mContext, mToken, lazyDaemon); + final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class); + final TestAuthenticationClient client2 = new TestAuthenticationClient(mContext, lazyDaemon, + mToken, callback); + + // Add a non-cancellable client, then add the auth client + mScheduler.scheduleClientMonitor(client1); + mScheduler.scheduleClientMonitor(client2); + waitForIdle(); + + assertEquals(mScheduler.getCurrentClient(), client1); + assertEquals(Operation.STATE_WAITING_IN_QUEUE, + mScheduler.mPendingOperations.getFirst().mState); + + // Request cancel before the authentication client has started + mScheduler.cancelAuthentication(mToken); + waitForIdle(); + assertEquals(Operation.STATE_WAITING_IN_QUEUE_CANCELING, + mScheduler.mPendingOperations.getFirst().mState); + + // Finish the blocking client. The authentication client should send ERROR_CANCELED + client1.getCallback().onClientFinished(client1, true /* success */); + waitForIdle(); + verify(callback).onError(anyInt(), anyInt(), + eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED), + eq(0) /* vendorCode */); + assertNull(mScheduler.getCurrentClient()); + } + private BiometricSchedulerProto getDump(boolean clearSchedulerBuffer) throws Exception { return BiometricSchedulerProto.parseFrom(mScheduler.dumpProtoState(clearSchedulerBuffer)); } @@ -293,6 +327,29 @@ public class BiometricSchedulerTest { } } + private static class TestAuthenticationClient extends AuthenticationClient<Object> { + + public TestAuthenticationClient(@NonNull Context context, + @NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token, + @NonNull ClientMonitorCallbackConverter listener) { + super(context, lazyDaemon, token, listener, 0 /* targetUserId */, 0 /* operationId */, + false /* restricted */, TAG, 1 /* cookie */, false /* requireConfirmation */, + TEST_SENSOR_ID, true /* isStrongBiometric */, 0 /* statsModality */, + 0 /* statsClient */, null /* taskStackListener */, mock(LockoutTracker.class), + false /* isKeyguard */); + } + + @Override + protected void stopHalOperation() { + + } + + @Override + protected void startHalOperation() { + + } + } + private static class TestClientMonitor2 extends TestClientMonitor { private final int mProtoEnum; diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java index ac8dc341999a..a53ff9bc7fdc 100644 --- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java @@ -44,6 +44,8 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.UUID; @RunWith(AndroidJUnit4.class) @@ -69,6 +71,10 @@ public class CompatConfigTest { os.close(); } + private String readFile(File file) throws IOException { + return new String(Files.readAllBytes(Paths.get(file.toURI()))); + } + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); @@ -499,4 +505,86 @@ public class CompatConfigTest { assertThat(compatConfig.isChangeEnabled(1236L, ApplicationInfoBuilder.create().withTargetSdk(1).build())).isTrue(); } + + @Test + public void testSaveOverrides() throws Exception { + File overridesFile = new File(createTempDir(), "overrides.xml"); + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addDisabledChangeWithId(1L) + .addEnableSinceSdkChangeWithId(2, 2L) + .build(); + compatConfig.forceNonDebuggableFinalForTest(true); + compatConfig.initOverrides(overridesFile); + when(mPackageManager.getApplicationInfo(eq("foo.bar"), anyInt())) + .thenReturn(ApplicationInfoBuilder.create() + .withPackageName("foo.bar") + .debuggable() + .build()); + when(mPackageManager.getApplicationInfo(eq("bar.baz"), anyInt())) + .thenThrow(new NameNotFoundException()); + + compatConfig.addOverride(1L, "foo.bar", true); + compatConfig.addOverride(2L, "bar.baz", false); + + assertThat(readFile(overridesFile)).isEqualTo("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + + "<overrides>\n" + + " <change-overrides changeId=\"1\">\n" + + " <validated>\n" + + " <override-value packageName=\"foo.bar\" enabled=\"true\">\n" + + " </override-value>\n" + + " </validated>\n" + + " <deferred>\n" + + " </deferred>\n" + + " </change-overrides>\n" + + " <change-overrides changeId=\"2\">\n" + + " <validated>\n" + + " </validated>\n" + + " <deferred>\n" + + " <override-value packageName=\"bar.baz\" enabled=\"false\">\n" + + " </override-value>\n" + + " </deferred>\n" + + " </change-overrides>\n" + + "</overrides>\n"); + } + + @Test + public void testLoadOverrides() throws Exception { + File tempDir = createTempDir(); + File overridesFile = new File(tempDir, "overrides.xml"); + // Change 1 is enabled for foo.bar (validated) + // Change 2 is disabled for bar.baz (deferred) + String xmlData = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + + "<overrides>" + + "<change-overrides changeId=\"1\">" + + "<deferred/>" + + "<validated>" + + "<override-value packageName=\"foo.bar\" enabled=\"true\"/>" + + "</validated>" + + "</change-overrides>" + + "<change-overrides changeId=\"2\">" + + "<deferred>" + + "<override-value packageName=\"bar.baz\" enabled=\"false\"/>" + + "</deferred>" + + "<validated/>" + + "</change-overrides>" + + "</overrides>"; + writeToFile(tempDir, "overrides.xml", xmlData); + CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext) + .addDisabledChangeWithId(1L) + .addEnableSinceSdkChangeWithId(2, 2L) + .build(); + compatConfig.forceNonDebuggableFinalForTest(true); + compatConfig.initOverrides(overridesFile); + ApplicationInfo applicationInfo = ApplicationInfoBuilder.create() + .withPackageName("foo.bar") + .debuggable() + .build(); + when(mPackageManager.getApplicationInfo(eq("foo.bar"), anyInt())) + .thenReturn(applicationInfo); + when(mPackageManager.getApplicationInfo(eq("bar.baz"), anyInt())) + .thenThrow(new NameNotFoundException()); + + assertThat(compatConfig.isChangeEnabled(1L, applicationInfo)).isTrue(); + assertThat(compatConfig.willChangeBeEnabled(2L, "bar.baz")).isFalse(); + } } diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java index 26c304f763d6..2e3178b13892 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java @@ -818,10 +818,5 @@ public class DisplayModeDirectorTest { PEAK_REFRESH_RATE_URI); } } - - @Override - public boolean isDeviceInteractive(@NonNull Context context) { - return true; - } } } diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java new file mode 100644 index 000000000000..c10cee9b4c3d --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/PersistentSystemFontConfigTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.graphics.fonts; + +import static com.google.common.truth.Truth.assertThat; + +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; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public final class PersistentSystemFontConfigTest { + + @Test + public void testWriteRead() throws IOException, XmlPullParserException { + long expectedModifiedDate = 1234567890; + PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config(); + config.lastModifiedDate = expectedModifiedDate; + + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + PersistentSystemFontConfig.writeToXml(baos, config); + + byte[] written = baos.toByteArray(); + assertThat(written).isNotEmpty(); + + try (ByteArrayInputStream bais = new ByteArrayInputStream(written)) { + PersistentSystemFontConfig.Config another = new PersistentSystemFontConfig.Config(); + PersistentSystemFontConfig.loadFromXml(bais, another); + + assertThat(another.lastModifiedDate).isEqualTo(expectedModifiedDate); + } + } + } + + @Test + public void testWrongType() throws IOException, XmlPullParserException { + String xml = "<fontConfig>" + + " <lastModifiedDate value=\"string\" />" + + "</fontConfig>"; + + try (ByteArrayInputStream bais = + new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) { + PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config(); + PersistentSystemFontConfig.loadFromXml(bais, config); + assertThat(config.lastModifiedDate).isEqualTo(0); + } + } + +} diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java index 75bf1e6bd485..f437d1f214ea 100644 --- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java +++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java @@ -22,8 +22,10 @@ import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.fail; import android.content.Context; +import android.graphics.fonts.FontManager; import android.os.FileUtils; import android.platform.test.annotations.Presubmit; +import android.system.Os; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -36,6 +38,7 @@ import org.junit.runner.RunWith; import java.io.File; import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -107,6 +110,7 @@ public final class UpdatableFontDirTest { private File mCacheDir; private File mUpdatableFontFilesDir; + private File mConfigFile; private List<File> mPreinstalledFontDirs; @SuppressWarnings("ResultOfMethodCallIgnored") @@ -124,6 +128,7 @@ public final class UpdatableFontDirTest { for (File dir : mPreinstalledFontDirs) { dir.mkdir(); } + mConfigFile = new File(mCacheDir, "config.xml"); } @After @@ -133,19 +138,30 @@ public final class UpdatableFontDirTest { @Test public void construct() throws Exception { + long expectedModifiedDate = 1234567890; FakeFontFileParser parser = new FakeFontFileParser(); FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); + PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config(); + config.lastModifiedDate = expectedModifiedDate; + writeConfig(config, mConfigFile); UpdatableFontDir dirForPreparation = new UpdatableFontDir( - mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil); + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, + mConfigFile); + assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedDate()) + .isEqualTo(expectedModifiedDate); installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE); installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE); installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE); installFontFile(dirForPreparation, "bar,4", GOOD_SIGNATURE); // Four font dirs are created. assertThat(mUpdatableFontFilesDir.list()).hasLength(4); + // + assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedDate()) + .isNotEqualTo(expectedModifiedDate); UpdatableFontDir dir = new UpdatableFontDir( - mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil); + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, + mConfigFile); assertThat(dir.getFontFileMap()).containsKey("foo.ttf"); assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(3); assertThat(dir.getFontFileMap()).containsKey("bar.ttf"); @@ -159,7 +175,8 @@ public final class UpdatableFontDirTest { FakeFontFileParser parser = new FakeFontFileParser(); FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( - mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil); + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, + mConfigFile); assertThat(dir.getFontFileMap()).isEmpty(); } @@ -168,7 +185,8 @@ public final class UpdatableFontDirTest { FakeFontFileParser parser = new FakeFontFileParser(); FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dirForPreparation = new UpdatableFontDir( - mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil); + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, + mConfigFile); installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE); installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE); installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE); @@ -179,7 +197,8 @@ public final class UpdatableFontDirTest { fakeFsverityUtil.remove( dirForPreparation.getFontFileMap().get("foo.ttf").getAbsolutePath()); UpdatableFontDir dir = new UpdatableFontDir( - mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil); + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, + mConfigFile); assertThat(dir.getFontFileMap()).isEmpty(); // All font dirs (including dir for "bar.ttf") should be deleted. assertThat(mUpdatableFontFilesDir.list()).hasLength(0); @@ -190,7 +209,8 @@ public final class UpdatableFontDirTest { FakeFontFileParser parser = new FakeFontFileParser(); FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dirForPreparation = new UpdatableFontDir( - mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil); + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, + mConfigFile); installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE); installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE); installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE); @@ -202,7 +222,8 @@ public final class UpdatableFontDirTest { FileUtils.stringToFile(dirForPreparation.getFontFileMap().get("foo.ttf"), "bar,4"); UpdatableFontDir dir = new UpdatableFontDir( - mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil); + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, + mConfigFile); assertThat(dir.getFontFileMap()).isEmpty(); // All font dirs (including dir for "bar.ttf") should be deleted. assertThat(mUpdatableFontFilesDir.list()).hasLength(0); @@ -213,7 +234,8 @@ public final class UpdatableFontDirTest { FakeFontFileParser parser = new FakeFontFileParser(); FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dirForPreparation = new UpdatableFontDir( - mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil); + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, + mConfigFile); installFontFile(dirForPreparation, "foo,1", GOOD_SIGNATURE); installFontFile(dirForPreparation, "bar,2", GOOD_SIGNATURE); installFontFile(dirForPreparation, "foo,3", GOOD_SIGNATURE); @@ -226,7 +248,8 @@ public final class UpdatableFontDirTest { FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(0), "bar.ttf"), "bar,1"); FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(1), "bar.ttf"), "bar,2"); UpdatableFontDir dir = new UpdatableFontDir( - mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil); + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, + mConfigFile); // For foo.ttf, preinstalled font (revision 5) should be used. assertThat(dir.getFontFileMap()).doesNotContainKey("foo.ttf"); // For bar.ttf, updated font (revision 4) should be used. @@ -239,15 +262,30 @@ public final class UpdatableFontDirTest { } @Test + public void construct_failedToLoadConfig() throws Exception { + FakeFontFileParser parser = new FakeFontFileParser(); + FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); + UpdatableFontDir dir = new UpdatableFontDir( + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, + new File("/dev/null")); + assertThat(dir.getFontFileMap()).isEmpty(); + } + + @Test public void installFontFile() throws Exception { FakeFontFileParser parser = new FakeFontFileParser(); FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( - mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil); + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, + mConfigFile); installFontFile(dir, "test,1", GOOD_SIGNATURE); assertThat(dir.getFontFileMap()).containsKey("test.ttf"); assertThat(parser.getRevision(dir.getFontFileMap().get("test.ttf"))).isEqualTo(1); + File fontFile = dir.getFontFileMap().get("test.ttf"); + assertThat(Os.stat(fontFile.getAbsolutePath()).st_mode & 0777).isEqualTo(0644); + File fontDir = fontFile.getParentFile(); + assertThat(Os.stat(fontDir.getAbsolutePath()).st_mode & 0777).isEqualTo(0711); } @Test @@ -255,7 +293,8 @@ public final class UpdatableFontDirTest { FakeFontFileParser parser = new FakeFontFileParser(); FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( - mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil); + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, + mConfigFile); installFontFile(dir, "test,1", GOOD_SIGNATURE); Map<String, File> mapBeforeUpgrade = dir.getFontFileMap(); @@ -272,14 +311,15 @@ public final class UpdatableFontDirTest { FakeFontFileParser parser = new FakeFontFileParser(); FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( - mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil); + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, + mConfigFile); installFontFile(dir, "test,2", GOOD_SIGNATURE); try { installFontFile(dir, "test,1", GOOD_SIGNATURE); fail("Expect IllegalArgumentException"); - } catch (IllegalArgumentException e) { - // Expect + } catch (FontManagerService.SystemFontException e) { + assertThat(e.getErrorCode()).isEqualTo(FontManager.ERROR_CODE_DOWNGRADING); } assertThat(dir.getFontFileMap()).containsKey("test.ttf"); assertWithMessage("Font should not be downgraded to an older revision") @@ -291,7 +331,8 @@ public final class UpdatableFontDirTest { FakeFontFileParser parser = new FakeFontFileParser(); FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( - mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil); + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, + mConfigFile); installFontFile(dir, "foo,1", GOOD_SIGNATURE); installFontFile(dir, "bar,2", GOOD_SIGNATURE); @@ -302,17 +343,19 @@ public final class UpdatableFontDirTest { } @Test - public void installFontFile_invalidSignature() { + public void installFontFile_invalidSignature() throws Exception { FakeFontFileParser parser = new FakeFontFileParser(); FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); UpdatableFontDir dir = new UpdatableFontDir( - mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil); + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, + mConfigFile); try { installFontFile(dir, "test,1", "Invalid signature"); - fail("Expect IOException"); - } catch (IOException e) { - // Expect + fail("Expect SystemFontException"); + } catch (FontManagerService.SystemFontException e) { + assertThat(e.getErrorCode()) + .isEqualTo(FontManager.ERROR_CODE_VERIFICATION_FAILURE); } assertThat(dir.getFontFileMap()).isEmpty(); } @@ -323,23 +366,155 @@ public final class UpdatableFontDirTest { FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(0), "test.ttf"), "test,1"); UpdatableFontDir dir = new UpdatableFontDir( - mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil); + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, + mConfigFile); try { installFontFile(dir, "test,1", GOOD_SIGNATURE); fail("Expect IllegalArgumentException"); - } catch (IllegalArgumentException e) { - // Expect + } catch (FontManagerService.SystemFontException e) { + assertThat(e.getErrorCode()).isEqualTo(FontManager.ERROR_CODE_DOWNGRADING); + } + assertThat(dir.getFontFileMap()).isEmpty(); + } + + @Test + public void installFontFile_failedToWriteConfigXml() throws Exception { + long expectedModifiedDate = 1234567890; + FakeFontFileParser parser = new FakeFontFileParser(); + FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); + FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(0), "test.ttf"), "test,1"); + + File readonlyDir = new File(mCacheDir, "readonly"); + assertThat(readonlyDir.mkdir()).isTrue(); + File readonlyFile = new File(readonlyDir, "readonly_config.xml"); + + PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config(); + config.lastModifiedDate = expectedModifiedDate; + writeConfig(config, readonlyFile); + + assertThat(readonlyDir.setWritable(false, false)).isTrue(); + try { + UpdatableFontDir dir = new UpdatableFontDir( + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, + readonlyFile); + + try { + installFontFile(dir, "test,2", GOOD_SIGNATURE); + } catch (FontManagerService.SystemFontException e) { + assertThat(e.getErrorCode()) + .isEqualTo(FontManager.ERROR_CODE_FAILED_TO_CREATE_CONFIG_FILE); + } + assertThat(dir.getSystemFontConfig().getLastModifiedDate()) + .isEqualTo(expectedModifiedDate); + assertThat(dir.getFontFileMap()).isEmpty(); + } finally { + assertThat(readonlyDir.setWritable(true, true)).isTrue(); + } + } + + @Test + public void installFontFile_failedToParsePostScript() throws Exception { + FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); + UpdatableFontDir dir = new UpdatableFontDir( + mUpdatableFontFilesDir, mPreinstalledFontDirs, + new UpdatableFontDir.FontFileParser() { + @Override + public String getPostScriptName(File file) throws IOException { + return null; + } + + @Override + public long getRevision(File file) throws IOException { + return 0; + } + }, fakeFsverityUtil, mConfigFile); + + try { + installFontFile(dir, "foo,1", GOOD_SIGNATURE); + fail("Expect SystemFontException"); + } catch (FontManagerService.SystemFontException e) { + assertThat(e.getErrorCode()) + .isEqualTo(FontManager.ERROR_CODE_MISSING_POST_SCRIPT_NAME); + } + assertThat(dir.getFontFileMap()).isEmpty(); + } + + @Test + public void installFontFile_failedToParsePostScriptName_invalidFont() throws Exception { + FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); + UpdatableFontDir dir = new UpdatableFontDir( + mUpdatableFontFilesDir, mPreinstalledFontDirs, + new UpdatableFontDir.FontFileParser() { + @Override + public String getPostScriptName(File file) throws IOException { + throw new IOException(); + } + + @Override + public long getRevision(File file) throws IOException { + return 0; + } + }, fakeFsverityUtil, mConfigFile); + + try { + installFontFile(dir, "foo,1", GOOD_SIGNATURE); + fail("Expect SystemFontException"); + } catch (FontManagerService.SystemFontException e) { + assertThat(e.getErrorCode()) + .isEqualTo(FontManager.ERROR_CODE_INVALID_FONT_FILE); + } + assertThat(dir.getFontFileMap()).isEmpty(); + } + + @Test + public void installFontFile_renameToPsNameFailure() throws Exception { + UpdatableFontDir.FsverityUtil fakeFsverityUtil = new UpdatableFontDir.FsverityUtil() { + private final FakeFsverityUtil mFake = new FakeFsverityUtil(); + + @Override + public boolean hasFsverity(String path) { + return mFake.hasFsverity(path); + } + + @Override + public void setUpFsverity(String path, byte[] pkcs7Signature) throws IOException { + mFake.setUpFsverity(path, pkcs7Signature); + } + + @Override + public boolean rename(File src, File dest) { + return false; + } + }; + FakeFontFileParser parser = new FakeFontFileParser(); + UpdatableFontDir dir = new UpdatableFontDir( + mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil, + mConfigFile); + + try { + installFontFile(dir, "foo,1", GOOD_SIGNATURE); + fail("Expect SystemFontException"); + } catch (FontManagerService.SystemFontException e) { + assertThat(e.getErrorCode()) + .isEqualTo(FontManager.ERROR_CODE_FAILED_TO_WRITE_FONT_FILE); } assertThat(dir.getFontFileMap()).isEmpty(); } private void installFontFile(UpdatableFontDir dir, String content, String signature) - throws IOException { + throws Exception { File file = File.createTempFile("font", "ttf", mCacheDir); FileUtils.stringToFile(file, content); try (FileInputStream in = new FileInputStream(file)) { dir.installFontFile(in.getFD(), signature.getBytes()); } } + + private void writeConfig(PersistentSystemFontConfig.Config config, + File file) throws IOException { + try (FileOutputStream fos = new FileOutputStream(file)) { + PersistentSystemFontConfig.writeToXml(fos, config); + } + } } diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/OWNERS b/services/tests/servicestests/src/com/android/server/location/timezone/OWNERS deleted file mode 100644 index 28aff188dbd8..000000000000 --- a/services/tests/servicestests/src/com/android/server/location/timezone/OWNERS +++ /dev/null @@ -1,3 +0,0 @@ -# Bug component: 847766 -nfuller@google.com -include /core/java/android/app/timedetector/OWNERS diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java index 1581d9ac1811..691d174f55f8 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java @@ -82,6 +82,11 @@ public class LockSettingsStorageTestable extends LockSettingsStorage { } @Override + String getRebootEscrowServerBlob() { + return makeDirs(mStorageDir, super.getRebootEscrowServerBlob()).getAbsolutePath(); + } + + @Override protected File getSyntheticPasswordDirectoryForUser(int userId) { return makeDirs(mStorageDir, super.getSyntheticPasswordDirectoryForUser( userId).getAbsolutePath()); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java index f74e45b6e59b..a4ba4c86a8fd 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java @@ -26,6 +26,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doNothing; @@ -52,6 +53,7 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.widget.RebootEscrowListener; +import com.android.server.locksettings.ResumeOnRebootServiceProvider.ResumeOnRebootServiceConnection; import org.junit.Before; import org.junit.Test; @@ -92,6 +94,7 @@ public class RebootEscrowManagerTests { private UserManager mUserManager; private RebootEscrowManager.Callbacks mCallbacks; private IRebootEscrow mRebootEscrow; + private ResumeOnRebootServiceConnection mServiceConnection; private RebootEscrowKeyStoreManager mKeyStoreManager; LockSettingsStorageTestable mStorage; @@ -108,6 +111,7 @@ public class RebootEscrowManagerTests { static class MockInjector extends RebootEscrowManager.Injector { private final IRebootEscrow mRebootEscrow; + private final ResumeOnRebootServiceConnection mServiceConnection; private final RebootEscrowProviderInterface mRebootEscrowProvider; private final UserManager mUserManager; private final MockableRebootEscrowInjected mInjected; @@ -116,10 +120,11 @@ public class RebootEscrowManagerTests { MockInjector(Context context, UserManager userManager, IRebootEscrow rebootEscrow, RebootEscrowKeyStoreManager keyStoreManager, + LockSettingsStorageTestable storage, MockableRebootEscrowInjected injected) { - super(context); + super(context, storage); mRebootEscrow = rebootEscrow; - + mServiceConnection = null; RebootEscrowProviderHalImpl.Injector halInjector = new RebootEscrowProviderHalImpl.Injector() { @Override @@ -133,6 +138,22 @@ public class RebootEscrowManagerTests { mInjected = injected; } + MockInjector(Context context, UserManager userManager, + ResumeOnRebootServiceConnection serviceConnection, + RebootEscrowKeyStoreManager keyStoreManager, + LockSettingsStorageTestable storage, + MockableRebootEscrowInjected injected) { + super(context, storage); + mServiceConnection = serviceConnection; + mRebootEscrow = null; + RebootEscrowProviderServerBasedImpl.Injector injector = + new RebootEscrowProviderServerBasedImpl.Injector(serviceConnection); + mRebootEscrowProvider = new RebootEscrowProviderServerBasedImpl(storage, injector); + mUserManager = userManager; + mKeyStoreManager = keyStoreManager; + mInjected = injected; + } + @Override public UserManager getUserManager() { return mUserManager; @@ -165,6 +186,7 @@ public class RebootEscrowManagerTests { mUserManager = mock(UserManager.class); mCallbacks = mock(RebootEscrowManager.Callbacks.class); mRebootEscrow = mock(IRebootEscrow.class); + mServiceConnection = mock(ResumeOnRebootServiceConnection.class); mKeyStoreManager = mock(RebootEscrowKeyStoreManager.class); mAesKey = new SecretKeySpec(TEST_AES_KEY, "AES"); @@ -186,7 +208,12 @@ public class RebootEscrowManagerTests { when(mCallbacks.isUserSecure(SECURE_SECONDARY_USER_ID)).thenReturn(true); mInjected = mock(MockableRebootEscrowInjected.class); mService = new RebootEscrowManager(new MockInjector(mContext, mUserManager, mRebootEscrow, - mKeyStoreManager, mInjected), mCallbacks, mStorage); + mKeyStoreManager, mStorage, mInjected), mCallbacks, mStorage); + } + + private void setServerBasedRebootEscrowProvider() throws Exception { + mService = new RebootEscrowManager(new MockInjector(mContext, mUserManager, + mServiceConnection, mKeyStoreManager, mStorage, mInjected), mCallbacks, mStorage); } @Test @@ -202,6 +229,19 @@ public class RebootEscrowManagerTests { } @Test + public void prepareRebootEscrowServerBased_Success() throws Exception { + setServerBasedRebootEscrowProvider(); + RebootEscrowListener mockListener = mock(RebootEscrowListener.class); + mService.setRebootEscrowListener(mockListener); + mService.prepareRebootEscrow(); + + mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN); + verify(mockListener).onPreparedForReboot(eq(true)); + verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong()); + assertFalse(mStorage.hasRebootEscrowServerBlob()); + } + + @Test public void prepareRebootEscrow_ClearCredentials_Success() throws Exception { RebootEscrowListener mockListener = mock(RebootEscrowListener.class); mService.setRebootEscrowListener(mockListener); @@ -246,6 +286,28 @@ public class RebootEscrowManagerTests { } @Test + public void armServiceServerBased_Success() throws Exception { + setServerBasedRebootEscrowProvider(); + RebootEscrowListener mockListener = mock(RebootEscrowListener.class); + mService.setRebootEscrowListener(mockListener); + mService.prepareRebootEscrow(); + + clearInvocations(mServiceConnection); + mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN); + verify(mockListener).onPreparedForReboot(eq(true)); + verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong()); + + when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())) + .thenAnswer(invocation -> invocation.getArgument(0)); + assertTrue(mService.armRebootEscrowIfNeeded()); + verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong()); + + assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID)); + assertFalse(mStorage.hasRebootEscrow(NONSECURE_SECONDARY_USER_ID)); + assertTrue(mStorage.hasRebootEscrowServerBlob()); + } + + @Test public void armService_HalFailure_NonFatal() throws Exception { RebootEscrowListener mockListener = mock(RebootEscrowListener.class); mService.setRebootEscrowListener(mockListener); @@ -346,6 +408,40 @@ public class RebootEscrowManagerTests { } @Test + public void loadRebootEscrowDataIfAvailable_ServerBased_Success() throws Exception { + setServerBasedRebootEscrowProvider(); + + when(mInjected.getBootCount()).thenReturn(0); + RebootEscrowListener mockListener = mock(RebootEscrowListener.class); + mService.setRebootEscrowListener(mockListener); + mService.prepareRebootEscrow(); + + clearInvocations(mServiceConnection); + mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN); + verify(mockListener).onPreparedForReboot(eq(true)); + verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong()); + + // Use x -> x for both wrap & unwrap functions. + when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())) + .thenAnswer(invocation -> invocation.getArgument(0)); + assertTrue(mService.armRebootEscrowIfNeeded()); + verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong()); + assertTrue(mStorage.hasRebootEscrowServerBlob()); + + // pretend reboot happens here + when(mInjected.getBootCount()).thenReturn(1); + ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); + doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture()); + + when(mServiceConnection.unwrap(any(), anyLong())) + .thenAnswer(invocation -> invocation.getArgument(0)); + mService.loadRebootEscrowDataIfAvailable(); + verify(mServiceConnection).unwrap(any(), anyLong()); + assertTrue(metricsSuccessCaptor.getValue()); + verify(mKeyStoreManager).clearKeyStoreEncryptionKey(); + } + + @Test public void loadRebootEscrowDataIfAvailable_TooManyBootsInBetween_NoMetrics() throws Exception { when(mInjected.getBootCount()).thenReturn(0); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java new file mode 100644 index 000000000000..bc1e025dd99f --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowProviderServerBasedImplTests.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.locksettings; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.ContextWrapper; +import android.platform.test.annotations.Presubmit; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.stubbing.Answer; + +import java.io.File; +import java.io.IOException; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class RebootEscrowProviderServerBasedImplTests { + private SecretKey mKeyStoreEncryptionKey; + private RebootEscrowKey mRebootEscrowKey; + private ResumeOnRebootServiceProvider.ResumeOnRebootServiceConnection mServiceConnection; + private LockSettingsStorageTestable mStorage; + private RebootEscrowProviderServerBasedImpl mRebootEscrowProvider; + private Answer<byte[]> mFakeEncryption; + + private static final byte[] TEST_AES_KEY = new byte[] { + 0x48, 0x19, 0x12, 0x54, 0x13, 0x13, 0x52, 0x31, + 0x44, 0x74, 0x61, 0x54, 0x29, 0x74, 0x37, 0x61, + 0x70, 0x70, 0x75, 0x25, 0x27, 0x31, 0x49, 0x09, + 0x26, 0x52, 0x72, 0x63, 0x63, 0x61, 0x78, 0x23, + }; + + @Before + public void setUp() throws Exception { + mKeyStoreEncryptionKey = new SecretKeySpec(TEST_AES_KEY, "AES"); + mRebootEscrowKey = RebootEscrowKey.generate(); + mServiceConnection = mock( + ResumeOnRebootServiceProvider.ResumeOnRebootServiceConnection.class); + + Context context = new ContextWrapper(InstrumentationRegistry.getContext()); + mStorage = new LockSettingsStorageTestable(context, + new File(InstrumentationRegistry.getContext().getFilesDir(), "locksettings")); + mRebootEscrowProvider = new RebootEscrowProviderServerBasedImpl(mStorage, + new RebootEscrowProviderServerBasedImpl.Injector(mServiceConnection)); + + mFakeEncryption = invocation -> { + byte[] secret = invocation.getArgument(0); + for (int i = 0; i < secret.length; i++) { + secret[i] = (byte) (secret[i] ^ 0xf); + } + return secret; + }; + } + + @Test + public void getAndClearRebootEscrowKey_loopback_success() throws Exception { + when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())).thenAnswer(mFakeEncryption); + when(mServiceConnection.unwrap(any(), anyLong())).thenAnswer(mFakeEncryption); + + assertTrue(mRebootEscrowProvider.hasRebootEscrowSupport()); + mRebootEscrowProvider.storeRebootEscrowKey(mRebootEscrowKey, mKeyStoreEncryptionKey); + assertTrue(mStorage.hasRebootEscrowServerBlob()); + + + RebootEscrowKey ks = mRebootEscrowProvider.getAndClearRebootEscrowKey( + mKeyStoreEncryptionKey); + assertThat(ks.getKeyBytes(), is(mRebootEscrowKey.getKeyBytes())); + assertFalse(mStorage.hasRebootEscrowServerBlob()); + } + + @Test + public void getAndClearRebootEscrowKey_WrongDecryptionMethod_failure() throws Exception { + when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())).thenAnswer(mFakeEncryption); + when(mServiceConnection.unwrap(any(), anyLong())).thenAnswer( + invocation -> { + byte[] secret = invocation.getArgument(0); + for (int i = 0; i < secret.length; i++) { + secret[i] = (byte) (secret[i] ^ 0xe); + } + return secret; + } + ); + + assertTrue(mRebootEscrowProvider.hasRebootEscrowSupport()); + mRebootEscrowProvider.storeRebootEscrowKey(mRebootEscrowKey, mKeyStoreEncryptionKey); + assertTrue(mStorage.hasRebootEscrowServerBlob()); + + // Expect to get wrong key bytes + RebootEscrowKey ks = mRebootEscrowProvider.getAndClearRebootEscrowKey( + mKeyStoreEncryptionKey); + assertNotEquals(ks.getKeyBytes(), mRebootEscrowKey.getKeyBytes()); + assertFalse(mStorage.hasRebootEscrowServerBlob()); + } + + @Test + public void getAndClearRebootEscrowKey_ServiceConnectionException_failure() throws Exception { + when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())).thenAnswer(mFakeEncryption); + doThrow(IOException.class).when(mServiceConnection).unwrap(any(), anyLong()); + + assertTrue(mRebootEscrowProvider.hasRebootEscrowSupport()); + mRebootEscrowProvider.storeRebootEscrowKey(mRebootEscrowKey, mKeyStoreEncryptionKey); + assertTrue(mStorage.hasRebootEscrowServerBlob()); + + // Expect to get null key bytes when the server service fails to unwrap the blob. + RebootEscrowKey ks = mRebootEscrowProvider.getAndClearRebootEscrowKey( + mKeyStoreEncryptionKey); + assertNull(ks); + assertFalse(mStorage.hasRebootEscrowServerBlob()); + } +} diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java index eaf62cbb8201..0dcd608e6bbd 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java @@ -23,12 +23,11 @@ import static org.junit.Assert.fail; import android.content.Context; import android.content.pm.PackageManager; -import android.content.pm.PackageParser; -import android.content.pm.PackageParser.ApkLite; -import android.content.pm.PackageParser.PackageLite; import android.content.pm.PackageParser.PackageParserException; import android.content.pm.dex.DexMetadataHelper; +import android.content.pm.parsing.ApkLite; import android.content.pm.parsing.ApkLiteParseUtils; +import android.content.pm.parsing.PackageLite; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.os.FileUtils; @@ -38,6 +37,7 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.frameworks.servicestests.R; +import com.android.server.pm.PackageManagerException; import com.android.server.pm.parsing.TestPackageParser2; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; import com.android.server.pm.parsing.pkg.ParsedPackage; @@ -207,28 +207,35 @@ public class DexMetadataHelperTest { } @Test - public void testPackageSizeWithDmFile() - throws IOException, PackageParserException { + public void testPackageSizeWithDmFile() throws IOException { copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base); - File dm = createDexMetadataFile("install_split_base.apk"); - ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite( + final File dm = createDexMetadataFile("install_split_base.apk"); + final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite( ParseTypeImpl.forDefaultParsing().reset(), mTmpDir, 0 /* flags */); if (result.isError()) { throw new IllegalStateException(result.getErrorMessage(), result.getException()); } - PackageParser.PackageLite pkg = result.getResult(); + final PackageLite pkg = result.getResult(); Assert.assertEquals(dm.length(), DexMetadataHelper.getPackageDexMetadataSize(pkg)); } // This simulates the 'adb shell pm install' flow. @Test - public void testPackageSizeWithPartialPackageLite() throws IOException, PackageParserException { - File base = copyApkToToTmpDir("install_split_base", R.raw.install_split_base); - File dm = createDexMetadataFile("install_split_base.apk"); + public void testPackageSizeWithPartialPackageLite() throws IOException, + PackageManagerException { + final File base = copyApkToToTmpDir("install_split_base", R.raw.install_split_base); + final File dm = createDexMetadataFile("install_split_base.apk"); try (FileInputStream is = new FileInputStream(base)) { - ApkLite baseApk = PackageParser.parseApkLite(is.getFD(), base.getAbsolutePath(), 0); - PackageLite pkgLite = new PackageLite(null, baseApk.codePath, baseApk, null, null, null, - null, null, null); + final ParseResult<ApkLite> result = ApkLiteParseUtils.parseApkLite( + ParseTypeImpl.forDefaultParsing().reset(), is.getFD(), + base.getAbsolutePath(), /* flags */ 0); + if (result.isError()) { + throw new PackageManagerException(result.getErrorCode(), + result.getErrorMessage(), result.getException()); + } + final ApkLite baseApk = result.getResult(); + final PackageLite pkgLite = new PackageLite(null, baseApk.getPath(), baseApk, null, + null, null, null, null, null); Assert.assertEquals(dm.length(), DexMetadataHelper.getPackageDexMetadataSize(pkgLite)); } diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java index 972b3bb8b459..4284240c72b4 100644 --- a/services/tests/servicestests/src/com/android/server/location/timezone/ControllerImplTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/ControllerImplTest.java @@ -13,18 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.server.location.timezone; - -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED; -import static com.android.server.location.timezone.TestSupport.USER1_CONFIG_GEO_DETECTION_DISABLED; -import static com.android.server.location.timezone.TestSupport.USER1_CONFIG_GEO_DETECTION_ENABLED; -import static com.android.server.location.timezone.TestSupport.USER2_CONFIG_GEO_DETECTION_ENABLED; -import static com.android.server.location.timezone.TimeZoneProviderEvent.createPermanentFailureEvent; -import static com.android.server.location.timezone.TimeZoneProviderEvent.createUncertainEvent; +package com.android.server.timezonedetector.location; + +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED; +import static com.android.server.timezonedetector.location.TestSupport.USER1_CONFIG_GEO_DETECTION_DISABLED; +import static com.android.server.timezonedetector.location.TestSupport.USER1_CONFIG_GEO_DETECTION_ENABLED; +import static com.android.server.timezonedetector.location.TestSupport.USER2_CONFIG_GEO_DETECTION_ENABLED; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -40,10 +38,10 @@ import android.platform.test.annotations.Presubmit; import android.service.timezone.TimeZoneProviderSuggestion; import android.util.IndentingPrintWriter; -import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.ProviderStateEnum; import com.android.server.timezonedetector.ConfigurationInternal; import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion; import com.android.server.timezonedetector.TestState; +import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.ProviderStateEnum; import org.junit.Before; import org.junit.Test; @@ -66,9 +64,9 @@ public class ControllerImplTest { private static final TimeZoneProviderEvent USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2 = createSuggestionEvent(asList("Europe/Paris")); private static final TimeZoneProviderEvent USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT = - createUncertainEvent(); + TimeZoneProviderEvent.createUncertainEvent(); private static final TimeZoneProviderEvent USER1_PERM_FAILURE_LOCATION_TIME_ZONE_EVENT = - createPermanentFailureEvent("Test"); + TimeZoneProviderEvent.createPermanentFailureEvent("Test"); private TestThreadingDomain mTestThreadingDomain; private TestCallback mTestCallback; diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/HandlerThreadingDomainTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/HandlerThreadingDomainTest.java index 02de24de435e..e7dd97949bb0 100644 --- a/services/tests/servicestests/src/com/android/server/location/timezone/HandlerThreadingDomainTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/HandlerThreadingDomainTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.server.location.timezone; +package com.android.server.timezonedetector.location; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertSame; @@ -24,7 +24,7 @@ import android.os.Handler; import android.os.HandlerThread; import android.platform.test.annotations.Presubmit; -import com.android.server.location.timezone.ThreadingDomain.SingleRunnableQueue; +import com.android.server.timezonedetector.location.ThreadingDomain.SingleRunnableQueue; import org.junit.After; import org.junit.Before; diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/LocationTimeZoneProviderTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java index cb292db50115..095c868fc74c 100644 --- a/services/tests/servicestests/src/com/android/server/location/timezone/LocationTimeZoneProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java @@ -13,17 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.server.location.timezone; +package com.android.server.timezonedetector.location; import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_ERROR_KEY; import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_SUCCESS_KEY; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN; -import static com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED; -import static com.android.server.location.timezone.TestSupport.USER1_CONFIG_GEO_DETECTION_ENABLED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_INITIALIZING; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_UNCERTAIN; +import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STOPPED; +import static com.android.server.timezonedetector.location.TestSupport.USER1_CONFIG_GEO_DETECTION_ENABLED; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -40,10 +40,10 @@ import android.platform.test.annotations.Presubmit; import android.service.timezone.TimeZoneProviderSuggestion; import android.util.IndentingPrintWriter; -import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderListener; -import com.android.server.location.timezone.LocationTimeZoneProvider.ProviderState; import com.android.server.timezonedetector.ConfigurationInternal; import com.android.server.timezonedetector.TestState; +import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderListener; +import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState; import org.junit.Before; import org.junit.Test; diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/TestSupport.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java index 48105634c69d..d319488ba73b 100644 --- a/services/tests/servicestests/src/com/android/server/location/timezone/TestSupport.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.server.location.timezone; +package com.android.server.timezonedetector.location; import android.annotation.UserIdInt; diff --git a/services/tests/servicestests/src/com/android/server/location/timezone/TestThreadingDomain.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestThreadingDomain.java index b1a5ff9b549c..e08fea083d81 100644 --- a/services/tests/servicestests/src/com/android/server/location/timezone/TestThreadingDomain.java +++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestThreadingDomain.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.server.location.timezone; +package com.android.server.timezonedetector.location; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; 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 37fb0e930acc..82ffa765cc27 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java @@ -123,15 +123,6 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { updateDisplayFrames(); } - void addWindowWithRawInsetsState(WindowState win) { - addWindow(win); - // Without mPerformLayout in display content, the window cannot see any insets. Override the - // insets state with the global one. - final InsetsState insetsState = - win.getDisplayContent().getInsetsStateController().getRawInsetsState(); - win.mAboveInsetsState = insetsState; - } - public void setRotation(int rotation, boolean includingWindows) { mRotation = rotation; updateDisplayFrames(); @@ -281,7 +272,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { @Test public void layoutWindowLw_fitStatusBars() { mWindow.mAttrs.setFitInsetsTypes(Type.statusBars()); - addWindowWithRawInsetsState(mWindow); + addWindow(mWindow); mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); @@ -292,7 +283,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { @Test public void layoutWindowLw_fitNavigationBars() { mWindow.mAttrs.setFitInsetsTypes(Type.navigationBars()); - addWindowWithRawInsetsState(mWindow); + addWindow(mWindow); mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); @@ -303,7 +294,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { @Test public void layoutWindowLw_fitAllSides() { mWindow.mAttrs.setFitInsetsSides(Side.all()); - addWindowWithRawInsetsState(mWindow); + addWindow(mWindow); mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); @@ -314,7 +305,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { @Test public void layoutWindowLw_fitTopOnly() { mWindow.mAttrs.setFitInsetsSides(Side.TOP); - addWindowWithRawInsetsState(mWindow); + addWindow(mWindow); mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); @@ -324,12 +315,11 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { @Test public void layoutWindowLw_fitInsetsIgnoringVisibility() { - final InsetsState state = - mDisplayContent.getInsetsStateController().getRawInsetsState(); + final InsetsState state = mWindow.getInsetsState(); state.getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false); state.getSource(InsetsState.ITYPE_NAVIGATION_BAR).setVisible(false); mWindow.mAttrs.setFitInsetsIgnoringVisibility(true); - addWindowWithRawInsetsState(mWindow); + addWindow(mWindow); mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); @@ -339,12 +329,11 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { @Test public void layoutWindowLw_fitInsetsNotIgnoringVisibility() { - final InsetsState state = - mDisplayContent.getInsetsStateController().getRawInsetsState(); + final InsetsState state = mWindow.getInsetsState(); state.getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false); state.getSource(InsetsState.ITYPE_NAVIGATION_BAR).setVisible(false); mWindow.mAttrs.setFitInsetsIgnoringVisibility(false); - addWindowWithRawInsetsState(mWindow); + addWindow(mWindow); mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); @@ -360,7 +349,8 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { state.getSource(InsetsState.ITYPE_IME).setFrame( 0, DISPLAY_HEIGHT - IME_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT); mWindow.mAttrs.privateFlags |= PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME; - addWindowWithRawInsetsState(mWindow); + mWindow.mBehindIme = true; + addWindow(mWindow); mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); @@ -374,7 +364,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.setFitInsetsTypes(Type.displayCutout()); mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - addWindowWithRawInsetsState(mWindow); + addWindow(mWindow); mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); @@ -389,7 +379,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; mWindow.mAttrs.setFitInsetsTypes(0 /* types */); - addWindowWithRawInsetsState(mWindow); + addWindow(mWindow); mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); @@ -405,7 +395,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; - addWindowWithRawInsetsState(mWindow); + addWindow(mWindow); mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); @@ -421,7 +411,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; - addWindowWithRawInsetsState(mWindow); + addWindow(mWindow); mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); @@ -437,7 +427,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - addWindowWithRawInsetsState(mWindow); + addWindow(mWindow); mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); @@ -452,7 +442,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; mWindow.mAttrs.setFitInsetsTypes(0 /* types */); - addWindowWithRawInsetsState(mWindow); + addWindow(mWindow); mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); @@ -467,12 +457,11 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; mWindow.mAttrs.setFitInsetsTypes(0 /* types */); - mDisplayContent.getInsetsStateController().getRawInsetsState() - .getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false); + mWindow.getInsetsState().getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false); final InsetsState requestedState = new InsetsState(); requestedState.getSource(ITYPE_STATUS_BAR).setVisible(false); mWindow.updateRequestedVisibility(requestedState); - addWindowWithRawInsetsState(mWindow); + addWindow(mWindow); mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); @@ -487,13 +476,12 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; mWindow.mAttrs.setFitInsetsTypes(0 /* types */); - mDisplayContent.getInsetsStateController().getRawInsetsState() - .getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false); + mWindow.getInsetsState().getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false); final InsetsState requestedState = new InsetsState(); requestedState.getSource(ITYPE_STATUS_BAR).setVisible(false); mWindow.updateRequestedVisibility(requestedState); mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - addWindowWithRawInsetsState(mWindow); + addWindow(mWindow); mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); @@ -509,7 +497,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; mWindow.mAttrs.setFitInsetsTypes(0 /* types */); - addWindowWithRawInsetsState(mWindow); + addWindow(mWindow); mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); @@ -525,7 +513,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; mWindow.mAttrs.setFitInsetsTypes(0 /* types */); - addWindowWithRawInsetsState(mWindow); + addWindow(mWindow); mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); @@ -541,7 +529,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; mWindow.mAttrs.setFitInsetsTypes(0 /* types */); - addWindowWithRawInsetsState(mWindow); + addWindow(mWindow); mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); @@ -557,7 +545,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.type = TYPE_APPLICATION_OVERLAY; mWindow.mAttrs.width = DISPLAY_WIDTH; mWindow.mAttrs.height = DISPLAY_HEIGHT; - addWindowWithRawInsetsState(mWindow); + addWindow(mWindow); mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); @@ -574,7 +562,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - addWindowWithRawInsetsState(mWindow); + addWindow(mWindow); mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); @@ -588,7 +576,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { mWindow.mAttrs.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; mWindow.mAttrs.setFitInsetsTypes(0 /* types */); - addWindowWithRawInsetsState(mWindow); + addWindow(mWindow); mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); @@ -604,7 +592,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; - addWindowWithRawInsetsState(mWindow); + addWindow(mWindow); mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); @@ -620,7 +608,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; - addWindowWithRawInsetsState(mWindow); + addWindow(mWindow); mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); @@ -636,7 +624,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - addWindowWithRawInsetsState(mWindow); + addWindow(mWindow); mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); @@ -650,7 +638,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; mWindow.mAttrs.setFitInsetsTypes(0 /* types */); mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_NOTHING; - addWindowWithRawInsetsState(mWindow); + addWindow(mWindow); final int forwardedInsetBottom = 50; mDisplayPolicy.setForwardedInsets(Insets.of(0, 0, 0, forwardedInsetBottom)); @@ -788,13 +776,9 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { public void testFixedRotationInsetsSourceFrame() { doReturn((mDisplayContent.getRotation() + 1) % 4).when(mDisplayContent) .rotationForActivityInDifferentOrientation(eq(mWindow.mActivityRecord)); - mWindow.mAboveInsetsState.addSource(mDisplayContent.getInsetsStateController() - .getRawInsetsState().peekSource(ITYPE_STATUS_BAR)); - final Rect frame = mDisplayPolicy.getInsetsPolicy().getInsetsForWindow(mWindow) - .getSource(ITYPE_STATUS_BAR).getFrame(); + final Rect frame = mWindow.getInsetsState().getSource(ITYPE_STATUS_BAR).getFrame(); mDisplayContent.rotateInDifferentOrientationIfNeeded(mWindow.mActivityRecord); - final Rect rotatedFrame = mDisplayPolicy.getInsetsPolicy().getInsetsForWindow(mWindow) - .getSource(ITYPE_STATUS_BAR).getFrame(); + final Rect rotatedFrame = mWindow.getInsetsState().getSource(ITYPE_STATUS_BAR).getFrame(); assertEquals(DISPLAY_WIDTH, frame.width()); assertEquals(DISPLAY_HEIGHT, rotatedFrame.width()); 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 499507e969cf..77537a9de6be 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java @@ -300,7 +300,6 @@ public class DisplayPolicyTests extends WindowTestsBase { displayPolicy.addWindowLw(mNavBarWindow, mNavBarWindow.mAttrs); mNavBarWindow.getControllableInsetProvider().setServerVisible(true); final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState(); - mImeWindow.mAboveInsetsState = state; mDisplayContent.mDisplayFrames = new DisplayFrames(mDisplayContent.getDisplayId(), state, displayInfo, null /* displayCutout */); diff --git a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java index 032edde61bec..325bca418d13 100644 --- a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java @@ -18,15 +18,24 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import android.platform.test.annotations.Presubmit; +import android.view.Display.Mode; +import android.view.DisplayInfo; +import android.view.Surface; +import android.view.SurfaceControl; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -40,16 +49,40 @@ import org.junit.runner.RunWith; @Presubmit @RunWith(WindowTestRunner.class) public class FrameRateSelectionPriorityTests extends WindowTestsBase { + private static final float FLOAT_TOLERANCE = 0.01f; + private static final int LOW_MODE_ID = 3; + + private DisplayPolicy mDisplayPolicy = mock(DisplayPolicy.class); + private RefreshRatePolicy mRefreshRatePolicy; + private HighRefreshRateDenylist mDenylist = mock(HighRefreshRateDenylist.class); + + @Before + public void setUp() { + DisplayInfo di = new DisplayInfo(mDisplayInfo); + Mode defaultMode = di.getDefaultMode(); + di.supportedModes = new Mode[] { + new Mode(1, defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 90), + new Mode(2, defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 70), + new Mode(LOW_MODE_ID, + defaultMode.getPhysicalWidth(), defaultMode.getPhysicalHeight(), 60), + }; + di.defaultModeId = 1; + mRefreshRatePolicy = new RefreshRatePolicy(mWm, di, mDenylist); + when(mDisplayPolicy.getRefreshRatePolicy()).thenReturn(mRefreshRatePolicy); + } @Test public void basicTest() { final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow"); assertNotNull("Window state is created", appWindow); + assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET); + assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE); appWindow.updateFrameRateSelectionPriorityIfNeeded(); // Priority doesn't change. assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET); + assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE); // Call the function a few times. appWindow.updateFrameRateSelectionPriorityIfNeeded(); @@ -57,7 +90,9 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { // Since nothing changed in the priority state, the transaction should not be updating. verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority( - appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET); + any(SurfaceControl.class), anyInt()); + verify(appWindow.getPendingTransaction(), never()).setFrameRate( + any(SurfaceControl.class), anyInt(), anyInt()); } @Test @@ -66,10 +101,16 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET); assertEquals(appWindow.getDisplayContent().getDisplayPolicy().getRefreshRatePolicy() .getPreferredModeId(appWindow), 0); + assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE); + assertEquals(appWindow.getDisplayContent().getDisplayPolicy().getRefreshRatePolicy() + .getPreferredRefreshRate(appWindow), 0, FLOAT_TOLERANCE); + + assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE); appWindow.updateFrameRateSelectionPriorityIfNeeded(); // Priority stays MAX_VALUE. assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET); + assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE); verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority( appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET); @@ -78,31 +119,38 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { appWindow.updateFrameRateSelectionPriorityIfNeeded(); // Priority changes to 1. assertEquals(appWindow.mFrameRateSelectionPriority, 1); + assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE); verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority( appWindow.getSurfaceControl(), 1); + verify(appWindow.getPendingTransaction(), never()).setFrameRate( + any(SurfaceControl.class), anyInt(), anyInt()); } @Test public void testApplicationInFocusWithModeId() { final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow"); assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET); + assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE); // Application is in focus. appWindow.mToken.mDisplayContent.mCurrentFocus = appWindow; appWindow.updateFrameRateSelectionPriorityIfNeeded(); // Priority changes. assertEquals(appWindow.mFrameRateSelectionPriority, 1); + assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE); // Update the mode ID to a requested number. appWindow.mAttrs.preferredDisplayModeId = 1; appWindow.updateFrameRateSelectionPriorityIfNeeded(); // Priority changes. assertEquals(appWindow.mFrameRateSelectionPriority, 0); + assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE); // Remove the mode ID request. appWindow.mAttrs.preferredDisplayModeId = 0; appWindow.updateFrameRateSelectionPriorityIfNeeded(); // Priority changes. assertEquals(appWindow.mFrameRateSelectionPriority, 1); + assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE); // Verify we called actions on Transactions correctly. verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority( @@ -111,12 +159,15 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { appWindow.getSurfaceControl(), 0); verify(appWindow.getPendingTransaction(), times(2)).setFrameRateSelectionPriority( appWindow.getSurfaceControl(), 1); + verify(appWindow.getPendingTransaction(), never()).setFrameRate( + any(SurfaceControl.class), anyInt(), anyInt()); } @Test public void testApplicationNotInFocusWithModeId() { final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow"); assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET); + assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE); final WindowState inFocusWindow = createWindow(null, TYPE_APPLICATION, "inFocus"); appWindow.mToken.mDisplayContent.mCurrentFocus = inFocusWindow; @@ -124,23 +175,28 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { appWindow.updateFrameRateSelectionPriorityIfNeeded(); // The window is not in focus. assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET); + assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE); // Update the mode ID to a requested number. appWindow.mAttrs.preferredDisplayModeId = 1; appWindow.updateFrameRateSelectionPriorityIfNeeded(); // Priority changes. assertEquals(appWindow.mFrameRateSelectionPriority, 2); + assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE); verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority( appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET); verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority( appWindow.getSurfaceControl(), 2); + verify(appWindow.getPendingTransaction(), never()).setFrameRate( + any(SurfaceControl.class), anyInt(), anyInt()); } @Test public void testApplicationNotInFocusWithoutModeId() { final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow"); assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET); + assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE); final WindowState inFocusWindow = createWindow(null, TYPE_APPLICATION, "inFocus"); appWindow.mToken.mDisplayContent.mCurrentFocus = inFocusWindow; @@ -148,14 +204,45 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { appWindow.updateFrameRateSelectionPriorityIfNeeded(); // The window is not in focus. assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET); + assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE); // Make sure that the mode ID is not set. appWindow.mAttrs.preferredDisplayModeId = 0; appWindow.updateFrameRateSelectionPriorityIfNeeded(); // Priority doesn't change. assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET); + assertEquals(appWindow.mDenyListFrameRate, 0, FLOAT_TOLERANCE); verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority( appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET); + verify(appWindow.getPendingTransaction(), never()).setFrameRate( + any(SurfaceControl.class), anyInt(), anyInt()); + } + + @Test + public void testPreferredRefreshRate() { + final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow"); + assertNotNull("Window state is created", appWindow); + when(appWindow.getDisplayContent().getDisplayPolicy()).thenReturn(mDisplayPolicy); + + appWindow.mAttrs.packageName = "com.android.test"; + when(mDenylist.isDenylisted("com.android.test")).thenReturn(true); + + assertEquals(0, mRefreshRatePolicy.getPreferredModeId(appWindow)); + assertEquals(60, mRefreshRatePolicy.getPreferredRefreshRate(appWindow), FLOAT_TOLERANCE); + + appWindow.updateFrameRateSelectionPriorityIfNeeded(); + assertEquals(RefreshRatePolicy.LAYER_PRIORITY_UNSET, appWindow.mFrameRateSelectionPriority); + assertEquals(60, appWindow.mDenyListFrameRate, FLOAT_TOLERANCE); + + // Call the function a few times. + appWindow.updateFrameRateSelectionPriorityIfNeeded(); + appWindow.updateFrameRateSelectionPriorityIfNeeded(); + + // Since nothing changed in the priority state, the transaction should not be updating. + verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority( + any(SurfaceControl.class), anyInt()); + verify(appWindow.getPendingTransaction(), times(1)).setFrameRate( + appWindow.getSurfaceControl(), 60, Surface.FRAME_RATE_COMPATIBILITY_EXACT); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java index bf3ed692dc8e..e0fd3796f2aa 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java @@ -45,7 +45,6 @@ import static org.mockito.Mockito.verify; import android.app.StatusBarManager; import android.platform.test.annotations.Presubmit; -import android.view.InsetsSource; import android.view.InsetsSourceControl; import android.view.InsetsState; @@ -273,6 +272,7 @@ public class InsetsPolicyTest extends WindowTestsBase { final WindowState navBar = addNonFocusableWindow(TYPE_NAVIGATION_BAR, "navBar"); navBar.setHasSurface(true); navBar.getControllableInsetProvider().setServerVisible(true); + final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy()); doNothing().when(policy).startAnimation(anyBoolean(), any()); @@ -337,14 +337,11 @@ public class InsetsPolicyTest extends WindowTestsBase { @UseTestDisplay(addWindows = W_ACTIVITY) @Test public void testAbortTransientBars_bothCanBeAborted_appGetsBothRealControls() { - final InsetsSource statusBarSource = addNonFocusableWindow(TYPE_STATUS_BAR, "statusBar") - .getControllableInsetProvider().getSource(); - final InsetsSource navBarSource = addNonFocusableWindow(TYPE_NAVIGATION_BAR, "navBar") - .getControllableInsetProvider().getSource(); - statusBarSource.setVisible(false); - navBarSource.setVisible(false); - mAppWindow.mAboveInsetsState.addSource(navBarSource); - mAppWindow.mAboveInsetsState.addSource(statusBarSource); + addNonFocusableWindow(TYPE_STATUS_BAR, "statusBar") + .getControllableInsetProvider().getSource().setVisible(false); + addNonFocusableWindow(TYPE_NAVIGATION_BAR, "navBar") + .getControllableInsetProvider().getSource().setVisible(false); + final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy()); doNothing().when(policy).startAnimation(anyBoolean(), any()); policy.updateBarControlTarget(mAppWindow); diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java index 2107ab1eeeea..276643847712 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java @@ -59,6 +59,25 @@ import org.junit.runner.RunWith; public class InsetsStateControllerTest extends WindowTestsBase { @Test + public void testStripForDispatch_notOwn() { + final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); + final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null); + statusBar.setControllableInsetProvider(getController().getSourceProvider(ITYPE_STATUS_BAR)); + assertNotNull(getController().getInsetsForWindow(app).peekSource(ITYPE_STATUS_BAR)); + } + + @Test + public void testStripForDispatch_own() { + final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); + mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_STATUS_BAR) + .setWindow(statusBar, null, null); + statusBar.setControllableInsetProvider(getController().getSourceProvider(ITYPE_STATUS_BAR)); + final InsetsState state = getController().getInsetsForWindow(statusBar); + assertNull(state.peekSource(ITYPE_STATUS_BAR)); + } + + @Test public void testStripForDispatch_navBar() { final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar"); final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); @@ -123,15 +142,14 @@ public class InsetsStateControllerTest extends WindowTestsBase { getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null); final WindowState app1 = createWindow(null, TYPE_APPLICATION, "app1"); - final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2"); + app1.mBehindIme = true; - app1.mAboveInsetsState.addSource(getController().getRawInsetsState().getSource(ITYPE_IME)); + final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2"); + app2.mBehindIme = false; getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true); - assertFalse(getController().getInsetsForWindow(app2).getSource(ITYPE_IME) - .isVisible()); - assertTrue(getController().getInsetsForWindow(app1).getSource(ITYPE_IME) - .isVisible()); + assertFalse(getController().getInsetsForWindow(app2).getSource(ITYPE_IME).isVisible()); + assertTrue(getController().getInsetsForWindow(app1).getSource(ITYPE_IME).isVisible()); } @UseTestDisplay(addWindows = W_INPUT_METHOD) @@ -140,8 +158,7 @@ public class InsetsStateControllerTest extends WindowTestsBase { getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null); final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); - app.mAboveInsetsState.getSource(ITYPE_IME).setVisible(true); - app.mAboveInsetsState.getSource(ITYPE_IME).setFrame(mImeWindow.getFrame()); + app.mBehindIme = true; getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true); assertTrue(getController().getInsetsForWindow(app).getSource(ITYPE_IME).isVisible()); @@ -153,10 +170,10 @@ public class InsetsStateControllerTest extends WindowTestsBase { getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null); final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + app.mBehindIme = false; getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true); - assertFalse(getController().getInsetsForWindow(app).getSource(ITYPE_IME) - .isVisible()); + assertFalse(getController().getInsetsForWindow(app).getSource(ITYPE_IME).isVisible()); } @UseTestDisplay(addWindows = W_INPUT_METHOD) @@ -193,8 +210,7 @@ public class InsetsStateControllerTest extends WindowTestsBase { // app won't get visible IME insets while above IME even when IME is visible. assertTrue(getController().getRawInsetsState().getSourceOrDefaultVisibility(ITYPE_IME)); - assertFalse(getController().getInsetsForWindow(app).getSource(ITYPE_IME) - .isVisible()); + assertFalse(getController().getInsetsForWindow(app).getSource(ITYPE_IME).isVisible()); // Reset invocation counter. clearInvocations(app); @@ -203,8 +219,6 @@ public class InsetsStateControllerTest extends WindowTestsBase { app.mAttrs.flags &= ~FLAG_NOT_FOCUSABLE; mDisplayContent.computeImeTarget(true); mDisplayContent.applySurfaceChangesTransaction(); - app.mAboveInsetsState.getSource(ITYPE_IME).setVisible(true); - app.mAboveInsetsState.getSource(ITYPE_IME).setFrame(mImeWindow.getFrame()); // Make sure app got notified. verify(app, atLeast(1)).notifyInsetsChanged(); @@ -220,8 +234,6 @@ public class InsetsStateControllerTest extends WindowTestsBase { final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); final WindowState child = createWindow(app, TYPE_APPLICATION, "child"); - app.mAboveInsetsState.set(getController().getRawInsetsState()); - child.mAboveInsetsState.set(getController().getRawInsetsState()); child.mAttrs.flags |= FLAG_ALT_FOCUSABLE_IM; mDisplayContent.computeImeTarget(true); @@ -230,8 +242,7 @@ public class InsetsStateControllerTest extends WindowTestsBase { getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true); assertTrue(getController().getInsetsForWindow(app).getSource(ITYPE_IME).isVisible()); - assertFalse(getController().getInsetsForWindow(child).getSource(ITYPE_IME) - .isVisible()); + assertFalse(getController().getInsetsForWindow(child).getSource(ITYPE_IME).isVisible()); } @UseTestDisplay(addWindows = W_INPUT_METHOD) @@ -241,7 +252,6 @@ public class InsetsStateControllerTest extends WindowTestsBase { final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); final WindowState child = createWindow(app, TYPE_APPLICATION, "child"); - app.mAboveInsetsState.addSource(getController().getRawInsetsState().peekSource(ITYPE_IME)); child.mAttrs.flags |= FLAG_NOT_FOCUSABLE; child.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); @@ -251,8 +261,7 @@ public class InsetsStateControllerTest extends WindowTestsBase { getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true); assertTrue(getController().getInsetsForWindow(app).getSource(ITYPE_IME).isVisible()); - assertFalse(getController().getInsetsForWindow(child).getSource(ITYPE_IME) - .isVisible()); + assertFalse(getController().getInsetsForWindow(child).getSource(ITYPE_IME).isVisible()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java index 77a4b0507a42..ef3c7ae91fed 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java @@ -44,7 +44,7 @@ import org.junit.runner.RunWith; @RunWith(WindowTestRunner.class) @FlakyTest public class RefreshRatePolicyTest extends WindowTestsBase { - + private static final float FLOAT_TOLERANCE = 0.01f; private static final int LOW_MODE_ID = 3; private RefreshRatePolicy mPolicy; @@ -70,28 +70,34 @@ public class RefreshRatePolicyTest extends WindowTestsBase { "cameraUsingWindow"); cameraUsingWindow.mAttrs.packageName = "com.android.test"; assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow)); + assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); mPolicy.addNonHighRefreshRatePackage("com.android.test"); assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(cameraUsingWindow)); + assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); mPolicy.removeNonHighRefreshRatePackage("com.android.test"); assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow)); + assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); } @Test - public void testBlacklist() { - final WindowState blacklistedWindow = createWindow(null, TYPE_BASE_APPLICATION, - "blacklistedWindow"); - blacklistedWindow.mAttrs.packageName = "com.android.test"; + public void testDenyList() { + final WindowState denylistedWindow = createWindow(null, TYPE_BASE_APPLICATION, + "denylistedWindow"); + denylistedWindow.mAttrs.packageName = "com.android.test"; when(mDenylist.isDenylisted("com.android.test")).thenReturn(true); - assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(blacklistedWindow)); + assertEquals(0, mPolicy.getPreferredModeId(denylistedWindow)); + assertEquals(60, mPolicy.getPreferredRefreshRate(denylistedWindow), FLOAT_TOLERANCE); } @Test public void testAppOverride_blacklist() { final WindowState overrideWindow = createWindow(null, TYPE_BASE_APPLICATION, "overrideWindow"); + overrideWindow.mAttrs.packageName = "com.android.test"; overrideWindow.mAttrs.preferredDisplayModeId = LOW_MODE_ID; when(mDenylist.isDenylisted("com.android.test")).thenReturn(true); assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(overrideWindow)); + assertEquals(60, mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE); } @Test @@ -102,6 +108,7 @@ public class RefreshRatePolicyTest extends WindowTestsBase { overrideWindow.mAttrs.preferredDisplayModeId = LOW_MODE_ID; mPolicy.addNonHighRefreshRatePackage("com.android.test"); assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(overrideWindow)); + assertEquals(0, mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE); } @Test @@ -115,6 +122,7 @@ public class RefreshRatePolicyTest extends WindowTestsBase { false /* hidden */, ANIMATION_TYPE_APP_TRANSITION); mPolicy.addNonHighRefreshRatePackage("com.android.test"); assertEquals(0, mPolicy.getPreferredModeId(overrideWindow)); + assertEquals(0, mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE); } @Test @@ -125,10 +133,12 @@ public class RefreshRatePolicyTest extends WindowTestsBase { mPolicy.addNonHighRefreshRatePackage("com.android.test"); assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(cameraUsingWindow)); + assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); cameraUsingWindow.mActivityRecord.mSurfaceAnimator.startAnimation( cameraUsingWindow.getPendingTransaction(), mock(AnimationAdapter.class), false /* hidden */, ANIMATION_TYPE_APP_TRANSITION); assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow)); + assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index db773241f063..371e6802ced7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -109,7 +109,7 @@ public class SizeCompatTests extends WindowTestsBase { final Rect originalOverrideBounds = new Rect(mActivity.getBounds()); resizeDisplay(mTask.mDisplayContent, 600, 1200); // The visible activity should recompute configuration according to the last parent bounds. - mAtm.restartActivityProcessIfVisible(mActivity.appToken); + mAtm.mActivityClientController.restartActivityProcessIfVisible(mActivity.appToken); assertEquals(Task.ActivityState.RESTARTING_PROCESS, mActivity.getState()); assertNotEquals(originalOverrideBounds, mActivity.getBounds()); diff --git a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java index c308fdbf8491..b8d44f605bca 100644 --- a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java +++ b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java @@ -245,6 +245,12 @@ public class StubTransaction extends SurfaceControl.Transaction { } @Override + public SurfaceControl.Transaction setFrameRate(SurfaceControl sc, float frameRate, + int compatibility) { + return this; + } + + @Override public SurfaceControl.Transaction unsetColor(SurfaceControl sc) { return this; } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java index a1f89ec75784..1607f013ee81 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java @@ -275,7 +275,7 @@ public class WindowFrameTests extends WindowTestsBase { imeSource.setFrame(imeFrame); imeSource.setVisible(true); w.updateRequestedVisibility(state); - w.mAboveInsetsState.addSource(imeSource); + w.mBehindIme = true; // With no insets or system decor all the frames incoming from PhoneWindowManager // are identical. diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index b3092b92989d..137543a8147c 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -64,6 +64,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY; import static android.net.NetworkCapabilities.NET_CAPABILITY_RCS; @@ -5963,23 +5964,18 @@ public class ConnectivityServiceTest { callback.expectCapabilitiesThat(mMockVpn, nc -> nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) && nc.hasTransport(TRANSPORT_WIFI)); - - // BUG: the VPN is no longer suspended, so a RESUMED callback should have been sent. - // callback.expectCallback(CallbackEntry.RESUMED, mMockVpn); + callback.expectCallback(CallbackEntry.RESUMED, mMockVpn); callback.assertNoCallback(); assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) .hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); - assertNetworkInfo(TYPE_VPN, DetailedState.SUSPENDED); // BUG: VPN caps have NOT_SUSPENDED. + assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); - // BUG: the device has connectivity, so this should return true. - assertGetNetworkInfoOfGetActiveNetworkIsConnected(false); + assertGetNetworkInfoOfGetActiveNetworkIsConnected(true); - // Unsuspend cellular and then switch back to it. - // The same bug happens in the opposite direction: the VPN's capabilities correctly have - // NOT_SUSPENDED, but the VPN's NetworkInfo is in state SUSPENDED. + // Unsuspend cellular and then switch back to it. The VPN remains not suspended. mCellNetworkAgent.resume(); callback.assertNoCallback(); mWiFiNetworkAgent.disconnect(); @@ -5996,12 +5992,11 @@ public class ConnectivityServiceTest { .hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); assertNetworkInfo(TYPE_WIFI, DetailedState.DISCONNECTED); - assertNetworkInfo(TYPE_VPN, DetailedState.SUSPENDED); // BUG: VPN caps have NOT_SUSPENDED. + assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); - // BUG: the device has connectivity, so this should return true. - assertGetNetworkInfoOfGetActiveNetworkIsConnected(false); + assertGetNetworkInfoOfGetActiveNetworkIsConnected(true); - // Re-suspending the current network fixes the problem. + // Suspend cellular and expect no connectivity. mCellNetworkAgent.suspend(); callback.expectCapabilitiesThat(mMockVpn, nc -> !nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) @@ -6017,6 +6012,7 @@ public class ConnectivityServiceTest { assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.SUSPENDED); assertGetNetworkInfoOfGetActiveNetworkIsConnected(false); + // Resume cellular and expect that connectivity comes back. mCellNetworkAgent.resume(); callback.expectCapabilitiesThat(mMockVpn, nc -> nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) @@ -6407,10 +6403,7 @@ public class ConnectivityServiceTest { && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) && !caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); - // While the SUSPENDED callback should in theory be sent here, it is not. This is - // a bug in ConnectivityService, but as the SUSPENDED and RESUMED callbacks have never - // been public and are deprecated and slated for removal, there is no sense in spending - // resources fixing this bug now. + vpnNetworkCallback.expectCallback(CallbackEntry.SUSPENDED, mMockVpn); assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent); // Use both again. @@ -6422,8 +6415,7 @@ public class ConnectivityServiceTest { && caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI) && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); - // As above, the RESUMED callback not being sent here is a bug, but not a bug that's - // worth anybody's time to fix. + vpnNetworkCallback.expectCallback(CallbackEntry.RESUMED, mMockVpn); assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent); // Disconnect cell. Receive update without even removing the dead network from the @@ -7335,39 +7327,68 @@ public class ConnectivityServiceTest { b2.expectBroadcast(); } + /** + * Test mutable and requestable network capabilities such as + * {@link NetworkCapabilities#NET_CAPABILITY_TRUSTED} and + * {@link NetworkCapabilities#NET_CAPABILITY_NOT_VCN_MANAGED}. Verify that the + * {@code ConnectivityService} re-assign the networks accordingly. + */ @Test - public final void testLoseTrusted() throws Exception { - final NetworkRequest trustedRequest = new NetworkRequest.Builder() - .addCapability(NET_CAPABILITY_TRUSTED) - .build(); - final TestNetworkCallback trustedCallback = new TestNetworkCallback(); - mCm.requestNetwork(trustedRequest, trustedCallback); - - mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); - mCellNetworkAgent.connect(true); - trustedCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); - verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId)); - reset(mMockNetd); - - mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); - mWiFiNetworkAgent.connect(true); - trustedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); - verify(mMockNetd).networkSetDefault(eq(mWiFiNetworkAgent.getNetwork().netId)); - reset(mMockNetd); + public final void testLoseMutableAndRequestableCaps() throws Exception { + final int[] testCaps = new int [] { + NET_CAPABILITY_TRUSTED, + NET_CAPABILITY_NOT_VCN_MANAGED + }; + for (final int testCap : testCaps) { + // Create requests with and without the testing capability. + final TestNetworkCallback callbackWithCap = new TestNetworkCallback(); + final TestNetworkCallback callbackWithoutCap = new TestNetworkCallback(); + mCm.requestNetwork(new NetworkRequest.Builder().addCapability(testCap).build(), + callbackWithCap); + mCm.requestNetwork(new NetworkRequest.Builder().removeCapability(testCap).build(), + callbackWithoutCap); + + // Setup networks with testing capability and verify the default network changes. + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.addCapability(testCap); + mCellNetworkAgent.connect(true); + callbackWithCap.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + callbackWithoutCap.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId)); + reset(mMockNetd); - mWiFiNetworkAgent.removeCapability(NET_CAPABILITY_TRUSTED); - trustedCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); - verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId)); - reset(mMockNetd); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); + mWiFiNetworkAgent.addCapability(testCap); + mWiFiNetworkAgent.connect(true); + callbackWithCap.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + callbackWithoutCap.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + verify(mMockNetd).networkSetDefault(eq(mWiFiNetworkAgent.getNetwork().netId)); + reset(mMockNetd); + + // Remove the testing capability on wifi, verify the callback and default network + // changes back to cellular. + mWiFiNetworkAgent.removeCapability(testCap); + callbackWithCap.expectAvailableCallbacksValidated(mCellNetworkAgent); + callbackWithoutCap.expectCapabilitiesWithout(testCap, mWiFiNetworkAgent); + // TODO: Test default network changes for NOT_VCN_MANAGED once the default request has + // it. + if (testCap == NET_CAPABILITY_TRUSTED) { + verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId)); + reset(mMockNetd); + } - mCellNetworkAgent.removeCapability(NET_CAPABILITY_TRUSTED); - trustedCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); - verify(mMockNetd).networkClearDefault(); + mCellNetworkAgent.removeCapability(testCap); + callbackWithCap.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); + callbackWithoutCap.assertNoCallback(); + if (testCap == NET_CAPABILITY_TRUSTED) { + verify(mMockNetd).networkClearDefault(); + } - mCm.unregisterNetworkCallback(trustedCallback); + mCm.unregisterNetworkCallback(callbackWithCap); + mCm.unregisterNetworkCallback(callbackWithoutCap); + } } - @Ignore // 40%+ flakiness : figure out why and re-enable. @Test public final void testBatteryStatsNetworkType() throws Exception { final LinkProperties cellLp = new LinkProperties(); @@ -7375,8 +7396,8 @@ public class ConnectivityServiceTest { mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp); mCellNetworkAgent.connect(true); waitForIdle(); - verify(mBatteryStatsService).noteNetworkInterfaceType(cellLp.getInterfaceName(), - TYPE_MOBILE); + verify(mBatteryStatsService).noteNetworkInterfaceForTransports(cellLp.getInterfaceName(), + new int[] { TRANSPORT_CELLULAR }); reset(mBatteryStatsService); final LinkProperties wifiLp = new LinkProperties(); @@ -7384,18 +7405,20 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp); mWiFiNetworkAgent.connect(true); waitForIdle(); - verify(mBatteryStatsService).noteNetworkInterfaceType(wifiLp.getInterfaceName(), - TYPE_WIFI); + verify(mBatteryStatsService).noteNetworkInterfaceForTransports(wifiLp.getInterfaceName(), + new int[] { TRANSPORT_WIFI }); reset(mBatteryStatsService); mCellNetworkAgent.disconnect(); + mWiFiNetworkAgent.disconnect(); cellLp.setInterfaceName("wifi0"); mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp); mCellNetworkAgent.connect(true); waitForIdle(); - verify(mBatteryStatsService).noteNetworkInterfaceType(cellLp.getInterfaceName(), - TYPE_MOBILE); + verify(mBatteryStatsService).noteNetworkInterfaceForTransports(cellLp.getInterfaceName(), + new int[] { TRANSPORT_CELLULAR }); + mCellNetworkAgent.disconnect(); } /** @@ -7468,8 +7491,8 @@ public class ConnectivityServiceTest { assertRoutesAdded(cellNetId, ipv6Subnet, defaultRoute); verify(mMockDnsResolver, times(1)).createNetworkCache(eq(cellNetId)); verify(mMockNetd, times(1)).networkAddInterface(cellNetId, MOBILE_IFNAME); - verify(mBatteryStatsService).noteNetworkInterfaceType(cellLp.getInterfaceName(), - TYPE_MOBILE); + verify(mBatteryStatsService).noteNetworkInterfaceForTransports(cellLp.getInterfaceName(), + new int[] { TRANSPORT_CELLULAR }); networkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId); @@ -7489,7 +7512,8 @@ public class ConnectivityServiceTest { // Make sure BatteryStats was not told about any v4- interfaces, as none should have // come online yet. waitForIdle(); - verify(mBatteryStatsService, never()).noteNetworkInterfaceType(startsWith("v4-"), anyInt()); + verify(mBatteryStatsService, never()).noteNetworkInterfaceForTransports(startsWith("v4-"), + any()); verifyNoMoreInteractions(mMockNetd); verifyNoMoreInteractions(mMockDnsResolver); @@ -7542,8 +7566,8 @@ public class ConnectivityServiceTest { assertTrue(ArrayUtils.contains(resolvrParams.servers, "8.8.8.8")); for (final LinkProperties stackedLp : stackedLpsAfterChange) { - verify(mBatteryStatsService).noteNetworkInterfaceType(stackedLp.getInterfaceName(), - TYPE_MOBILE); + verify(mBatteryStatsService).noteNetworkInterfaceForTransports( + stackedLp.getInterfaceName(), new int[] { TRANSPORT_CELLULAR }); } reset(mMockNetd); when(mMockNetd.interfaceGetCfg(CLAT_PREFIX + MOBILE_IFNAME)) diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java new file mode 100644 index 000000000000..d0fec55a6827 --- /dev/null +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vcn; + +import static com.android.server.vcn.VcnGatewayConnection.TEARDOWN_TIMEOUT_SECONDS; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.verify; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.TimeUnit; + +/** Tests for VcnGatewayConnection.DisconnectedState */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class VcnGatewayConnectionDisconnectingStateTest extends VcnGatewayConnectionTestBase { + @Before + public void setUp() throws Exception { + super.setUp(); + + mGatewayConnection.setIkeSession(mGatewayConnection.buildIkeSession()); + + mGatewayConnection.transitionTo(mGatewayConnection.mDisconnectingState); + mTestLooper.dispatchAll(); + } + + @Test + public void testIkeSessionClosed() throws Exception { + getIkeSessionCallback().onClosed(); + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mDisconnectedState, mGatewayConnection.getCurrentState()); + } + + @Test + public void testTimeoutExpired() throws Exception { + mTestLooper.moveTimeForward(TimeUnit.SECONDS.toMillis(TEARDOWN_TIMEOUT_SECONDS)); + mTestLooper.dispatchAll(); + + verify(mMockIkeSession).kill(); + } + + @Test + public void testTeardown() throws Exception { + mGatewayConnection.teardownAsynchronously(); + mTestLooper.dispatchAll(); + + // Should do nothing; already tearing down. + assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); + } +} diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java index 1725dd983115..346785907fcf 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java @@ -17,11 +17,13 @@ package com.android.server.vcn; import static com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord; +import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession; import static com.android.server.vcn.VcnTestUtils.setupIpSecManager; import static org.mockito.Matchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import android.annotation.NonNull; import android.content.Context; @@ -30,6 +32,7 @@ import android.net.IpSecTunnelInterfaceResponse; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; +import android.net.ipsec.ike.IkeSessionCallback; import android.net.vcn.VcnGatewayConnectionConfig; import android.net.vcn.VcnGatewayConnectionConfigTest; import android.os.ParcelUuid; @@ -38,6 +41,7 @@ import android.os.test.TestLooper; import com.android.server.IpSecService; import org.junit.Before; +import org.mockito.ArgumentCaptor; import java.util.UUID; @@ -68,6 +72,7 @@ public class VcnGatewayConnectionTestBase { @NonNull protected final IpSecService mIpSecSvc; + protected VcnIkeSession mMockIkeSession; protected VcnGatewayConnection mGatewayConnection; public VcnGatewayConnectionTestBase() { @@ -100,6 +105,16 @@ public class VcnGatewayConnectionTestBase { TEST_IPSEC_TUNNEL_IFACE); doReturn(resp).when(mIpSecSvc).createTunnelInterface(any(), any(), any(), any(), any()); + mMockIkeSession = mock(VcnIkeSession.class); + doReturn(mMockIkeSession).when(mDeps).newIkeSession(any(), any(), any(), any(), any()); + mGatewayConnection = new VcnGatewayConnection(mVcnContext, TEST_SUB_GRP, mConfig, mDeps); } + + protected IkeSessionCallback getIkeSessionCallback() { + ArgumentCaptor<IkeSessionCallback> captor = + ArgumentCaptor.forClass(IkeSessionCallback.class); + verify(mDeps).newIkeSession(any(), any(), any(), captor.capture(), any()); + return captor.getValue(); + } } |