diff options
350 files changed, 8195 insertions, 8293 deletions
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java index 452bb0ab5909..06207215b7be 100644 --- a/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java +++ b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java @@ -19,6 +19,7 @@ package android.wm; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; +import android.graphics.Rect; import android.os.RemoteException; import android.os.SystemClock; import android.perftests.utils.ManualBenchmarkState; @@ -86,6 +87,7 @@ public class WindowAddRemovePerfTest extends WindowManagerPerfTestBase final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities(); final InsetsState mOutInsetsState = new InsetsState(); final InsetsSourceControl[] mOutControls = new InsetsSourceControl[0]; + final Rect mOutAttachedFrame = new Rect(); TestWindow() { mLayoutParams.setTitle(TestWindow.class.getName()); @@ -104,7 +106,7 @@ public class WindowAddRemovePerfTest extends WindowManagerPerfTestBase long startTime = SystemClock.elapsedRealtimeNanos(); session.addToDisplay(this, mLayoutParams, View.VISIBLE, Display.DEFAULT_DISPLAY, mRequestedVisibilities, inputChannel, - mOutInsetsState, mOutControls); + mOutInsetsState, mOutControls, mOutAttachedFrame); final long elapsedTimeNsOfAdd = SystemClock.elapsedRealtimeNanos() - startTime; state.addExtraResult("add", elapsedTimeNsOfAdd); diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java index 2fcab59cdec7..78214dc27a9f 100644 --- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java +++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java @@ -385,6 +385,12 @@ public class PowerExemptionManager { */ public static final int REASON_ACTIVE_DEVICE_ADMIN = 324; + /** + * Media notification re-generate during transferring. + * @hide + */ + public static final int REASON_MEDIA_NOTIFICATION_TRANSFER = 325; + /** @hide The app requests out-out. */ public static final int REASON_OPT_OUT_REQUESTED = 1000; @@ -465,6 +471,7 @@ public class PowerExemptionManager { REASON_DPO_PROTECTED_APP, REASON_DISALLOW_APPS_CONTROL, REASON_ACTIVE_DEVICE_ADMIN, + REASON_MEDIA_NOTIFICATION_TRANSFER, }) @Retention(RetentionPolicy.SOURCE) public @interface ReasonCode {} @@ -830,6 +837,8 @@ public class PowerExemptionManager { return "ACTIVE_DEVICE_ADMIN"; case REASON_OPT_OUT_REQUESTED: return "REASON_OPT_OUT_REQUESTED"; + case REASON_MEDIA_NOTIFICATION_TRANSFER: + return "REASON_MEDIA_NOTIFICATION_TRANSFER"; default: return "(unknown:" + reasonCode + ")"; } diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java index 2eb86c1b720f..40d1b4c9b267 100644 --- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java @@ -73,6 +73,7 @@ import android.os.ServiceManager; import android.os.ShellCallback; import android.os.ShellCommand; import android.os.SystemClock; +import android.os.Trace; import android.os.UserHandle; import android.provider.DeviceConfig; import android.util.ArrayMap; @@ -2329,8 +2330,8 @@ public class DeviceIdleController extends SystemService // a battery update the next time the level drops. mCharging = true; mActiveReason = ACTIVE_REASON_UNKNOWN; - mState = STATE_ACTIVE; - mLightState = LIGHT_STATE_ACTIVE; + moveToStateLocked(STATE_ACTIVE, "boot"); + moveToLightStateLocked(LIGHT_STATE_ACTIVE, "boot"); mInactiveTimeout = mConstants.INACTIVE_TIMEOUT; mPreIdleFactor = 1.0f; mLastPreIdleFactor = 1.0f; @@ -3173,8 +3174,7 @@ public class DeviceIdleController extends SystemService + ", changeLightIdle=" + changeLightIdle); } if (mState != STATE_ACTIVE || mLightState != STATE_ACTIVE) { - EventLogTags.writeDeviceIdle(STATE_ACTIVE, activeReason); - mState = STATE_ACTIVE; + moveToStateLocked(STATE_ACTIVE, activeReason); mInactiveTimeout = newInactiveTimeout; resetIdleManagementLocked(); // Don't reset maintenance window start time if we're in a light idle maintenance window @@ -3184,8 +3184,7 @@ public class DeviceIdleController extends SystemService } if (changeLightIdle) { - EventLogTags.writeDeviceIdleLight(LIGHT_STATE_ACTIVE, activeReason); - mLightState = LIGHT_STATE_ACTIVE; + moveToLightStateLocked(LIGHT_STATE_ACTIVE, activeReason); resetLightIdleManagementLocked(); // Only report active if light is also ACTIVE. scheduleReportActiveLocked(activeReason, activeUid); @@ -3258,11 +3257,7 @@ public class DeviceIdleController extends SystemService // values, so returning here is safe. return; } - if (DEBUG) { - Slog.d(TAG, "Moved from " - + stateToString(mState) + " to STATE_QUICK_DOZE_DELAY"); - } - mState = STATE_QUICK_DOZE_DELAY; + moveToStateLocked(STATE_QUICK_DOZE_DELAY, "no activity"); // Make sure any motion sensing or locating is stopped. resetIdleManagementLocked(); if (isUpcomingAlarmClock()) { @@ -3277,10 +3272,8 @@ public class DeviceIdleController extends SystemService // recently closed app) needs to finish running. scheduleAlarmLocked(mConstants.QUICK_DOZE_DELAY_TIMEOUT, false); } - EventLogTags.writeDeviceIdle(mState, "no activity"); } else if (mState == STATE_ACTIVE) { - mState = STATE_INACTIVE; - if (DEBUG) Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE"); + moveToStateLocked(STATE_INACTIVE, "no activity"); resetIdleManagementLocked(); long delay = mInactiveTimeout; if (shouldUseIdleTimeoutFactorLocked()) { @@ -3296,12 +3289,10 @@ public class DeviceIdleController extends SystemService } else { scheduleAlarmLocked(delay, false); } - EventLogTags.writeDeviceIdle(mState, "no activity"); } } if (mLightState == LIGHT_STATE_ACTIVE && mLightEnabled) { - mLightState = LIGHT_STATE_INACTIVE; - if (DEBUG) Slog.d(TAG, "Moved from LIGHT_STATE_ACTIVE to LIGHT_STATE_INACTIVE"); + moveToLightStateLocked(LIGHT_STATE_INACTIVE, "no activity"); resetLightIdleManagementLocked(); scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT, mConstants.FLEX_TIME_SHORT); @@ -3309,7 +3300,6 @@ public class DeviceIdleController extends SystemService // timeout and a single light idle period. scheduleLightMaintenanceAlarmLocked( mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT + mConstants.LIGHT_IDLE_TIMEOUT); - EventLogTags.writeDeviceIdleLight(mLightState, "no activity"); } } @@ -3429,12 +3419,7 @@ public class DeviceIdleController extends SystemService // time from now. scheduleLightAlarmLocked(mCurLightIdleBudget, mConstants.FLEX_TIME_SHORT); scheduleLightMaintenanceAlarmLocked(mCurLightIdleBudget + mNextLightIdleDelay); - if (DEBUG) { - Slog.d(TAG, "Moved from " + lightStateToString(mLightState) - + " to LIGHT_STATE_IDLE_MAINTENANCE"); - } - mLightState = LIGHT_STATE_IDLE_MAINTENANCE; - EventLogTags.writeDeviceIdleLight(mLightState, reason); + moveToLightStateLocked(LIGHT_STATE_IDLE_MAINTENANCE, reason); addEvent(EVENT_LIGHT_MAINTENANCE, null); mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF); } else { @@ -3443,9 +3428,7 @@ public class DeviceIdleController extends SystemService // We'll only wait for another full idle period, however, and then give up. scheduleLightMaintenanceAlarmLocked(mNextLightIdleDelay); cancelLightAlarmLocked(); - if (DEBUG) Slog.d(TAG, "Moved to LIGHT_WAITING_FOR_NETWORK."); - mLightState = LIGHT_STATE_WAITING_FOR_NETWORK; - EventLogTags.writeDeviceIdleLight(mLightState, reason); + moveToLightStateLocked(LIGHT_STATE_WAITING_FOR_NETWORK, reason); } } else { if (mMaintenanceStartTime != 0) { @@ -3469,9 +3452,7 @@ public class DeviceIdleController extends SystemService // maintenance window, so reschedule the alarm starting from now. scheduleLightMaintenanceAlarmLocked(mNextLightIdleDelay); cancelLightAlarmLocked(); - if (DEBUG) Slog.d(TAG, "Moved to LIGHT_STATE_IDLE."); - mLightState = LIGHT_STATE_IDLE; - EventLogTags.writeDeviceIdleLight(mLightState, reason); + moveToLightStateLocked(LIGHT_STATE_IDLE, reason); addEvent(EVENT_LIGHT_IDLE, null); mGoingIdleWakeLock.acquire(); mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON_LIGHT); @@ -3535,12 +3516,11 @@ public class DeviceIdleController extends SystemService moveToStateLocked(STATE_IDLE_PENDING, reason); break; case STATE_IDLE_PENDING: - moveToStateLocked(STATE_SENSING, reason); cancelLocatingLocked(); mLocated = false; mLastGenericLocation = null; mLastGpsLocation = null; - updateActiveConstraintsLocked(); + moveToStateLocked(STATE_SENSING, reason); // Wait for open constraints and an accelerometer reading before moving on. if (mUseMotionSensor && mAnyMotionDetector.hasSensor()) { @@ -3609,7 +3589,7 @@ public class DeviceIdleController extends SystemService } moveToStateLocked(STATE_IDLE, reason); if (mLightState != LIGHT_STATE_OVERRIDE) { - mLightState = LIGHT_STATE_OVERRIDE; + moveToLightStateLocked(LIGHT_STATE_OVERRIDE, "deep"); cancelAllLightAlarmsLocked(); } addEvent(EVENT_DEEP_IDLE, null); @@ -3637,14 +3617,27 @@ public class DeviceIdleController extends SystemService } @GuardedBy("this") + private void moveToLightStateLocked(int state, String reason) { + if (DEBUG) { + Slog.d(TAG, String.format("Moved from LIGHT_STATE_%s to LIGHT_STATE_%s.", + lightStateToString(mLightState), lightStateToString(state))); + } + mLightState = state; + EventLogTags.writeDeviceIdleLight(mLightState, reason); + // This is currently how to set the current state in a trace. + Trace.traceCounter(Trace.TRACE_TAG_SYSTEM_SERVER, "DozeLightState", state); + } + + @GuardedBy("this") private void moveToStateLocked(int state, String reason) { - final int oldState = mState; - mState = state; if (DEBUG) { Slog.d(TAG, String.format("Moved from STATE_%s to STATE_%s.", - stateToString(oldState), stateToString(mState))); + stateToString(mState), stateToString(state))); } + mState = state; EventLogTags.writeDeviceIdle(mState, reason); + // This is currently how to set the current state in a trace. + Trace.traceCounter(Trace.TRACE_TAG_SYSTEM_SERVER, "DozeDeepState", state); updateActiveConstraintsLocked(); } diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java index f7fe9cab60ae..68801bcac4ba 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -43,6 +43,7 @@ import android.os.Looper; import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; +import android.os.Trace; import android.os.UserHandle; import android.util.EventLog; import android.util.IndentingPrintWriter; @@ -361,6 +362,9 @@ public final class JobServiceContext implements ServiceConnection { job.getJob().getPriority(), job.getEffectivePriority(), job.getNumFailures()); + // Use the context's ID to distinguish traces since there'll only be one job running + // per context. + Trace.asyncTraceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, job.getBatteryName(), getId()); try { mBatteryStats.noteJobStart(job.getBatteryName(), job.getSourceUid()); } catch (RemoteException e) { @@ -1024,6 +1028,7 @@ public final class JobServiceContext implements ServiceConnection { completedJob.getJob().getPriority(), completedJob.getEffectivePriority(), completedJob.getNumFailures()); + Trace.asyncTraceEnd(Trace.TRACE_TAG_SYSTEM_SERVER, completedJob.getBatteryName(), getId()); try { mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(), mRunningJob.getSourceUid(), internalStopReason); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index 251cf5640de7..e0f58e3fa6e3 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -496,7 +496,7 @@ public final class JobStatus { this.batteryName = this.sourceTag != null ? this.sourceTag + ":" + job.getService().getPackageName() : job.getService().flattenToShortString(); - this.tag = "*job*/" + this.batteryName; + this.tag = "*job*/" + this.batteryName + "#" + job.getId(); this.earliestRunTimeElapsedMillis = earliestRunTimeElapsedMillis; this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis; diff --git a/boot/Android.bp b/boot/Android.bp index 5b265a5dc9de..3f14ebc4fd92 100644 --- a/boot/Android.bp +++ b/boot/Android.bp @@ -60,8 +60,8 @@ platform_bootclasspath { module: "art-bootclasspath-fragment", }, { - apex: "com.android.bluetooth", - module: "com.android.bluetooth-bootclasspath-fragment", + apex: "com.android.btservices", + module: "com.android.btservices-bootclasspath-fragment", }, { apex: "com.android.conscrypt", diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 02c9f6862e53..e1ce5bd71423 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -387,6 +387,7 @@ package android { public static final class R.bool { field public static final int config_enableQrCodeScannerOnLockScreen = 17891336; // 0x1110008 + field public static final int config_safetyProtectionEnabled; field public static final int config_sendPackageName = 17891328; // 0x1110000 field public static final int config_showDefaultAssistant = 17891329; // 0x1110001 field public static final int config_showDefaultEmergency = 17891330; // 0x1110002 diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING index 908d3f802764..5b0bd9614f83 100644 --- a/core/java/android/app/TEST_MAPPING +++ b/core/java/android/app/TEST_MAPPING @@ -121,6 +121,9 @@ "include-annotation": "android.platform.test.annotations.Presubmit" }, { + "exclude-annotation": "android.platform.test.annotations.LargeTest" + }, + { "exclude-annotation": "androidx.test.filters.FlakyTest" }, { diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java index f7f0235cd508..93748f81ffa1 100644 --- a/core/java/android/companion/AssociationInfo.java +++ b/core/java/android/companion/AssociationInfo.java @@ -55,6 +55,14 @@ public final class AssociationInfo implements Parcelable { private final boolean mSelfManaged; private final boolean mNotifyOnDeviceNearby; + + /** + * Indicates that the association has been revoked (removed), but we keep the association + * record for final clean up (e.g. removing the app from the list of the role holders). + * + * @see CompanionDeviceManager#disassociate(int) + */ + private final boolean mRevoked; private final long mTimeApprovedMs; /** * A long value indicates the last time connected reported by selfManaged devices @@ -71,7 +79,7 @@ public final class AssociationInfo implements Parcelable { public AssociationInfo(int id, @UserIdInt int userId, @NonNull String packageName, @Nullable MacAddress macAddress, @Nullable CharSequence displayName, @Nullable String deviceProfile, boolean selfManaged, boolean notifyOnDeviceNearby, - long timeApprovedMs, long lastTimeConnectedMs) { + boolean revoked, long timeApprovedMs, long lastTimeConnectedMs) { if (id <= 0) { throw new IllegalArgumentException("Association ID should be greater than 0"); } @@ -91,6 +99,7 @@ public final class AssociationInfo implements Parcelable { mSelfManaged = selfManaged; mNotifyOnDeviceNearby = notifyOnDeviceNearby; + mRevoked = revoked; mTimeApprovedMs = timeApprovedMs; mLastTimeConnectedMs = lastTimeConnectedMs; } @@ -176,6 +185,14 @@ public final class AssociationInfo implements Parcelable { } /** + * @return if the association has been revoked (removed). + * @hide + */ + public boolean isRevoked() { + return mRevoked; + } + + /** * @return the last time self reported disconnected for selfManaged only. * @hide */ @@ -244,6 +261,7 @@ public final class AssociationInfo implements Parcelable { + ", mDeviceProfile='" + mDeviceProfile + '\'' + ", mSelfManaged=" + mSelfManaged + ", mNotifyOnDeviceNearby=" + mNotifyOnDeviceNearby + + ", mRevoked=" + mRevoked + ", mTimeApprovedMs=" + new Date(mTimeApprovedMs) + ", mLastTimeConnectedMs=" + ( mLastTimeConnectedMs == Long.MAX_VALUE @@ -260,6 +278,7 @@ public final class AssociationInfo implements Parcelable { && mUserId == that.mUserId && mSelfManaged == that.mSelfManaged && mNotifyOnDeviceNearby == that.mNotifyOnDeviceNearby + && mRevoked == that.mRevoked && mTimeApprovedMs == that.mTimeApprovedMs && mLastTimeConnectedMs == that.mLastTimeConnectedMs && Objects.equals(mPackageName, that.mPackageName) @@ -271,7 +290,7 @@ public final class AssociationInfo implements Parcelable { @Override public int hashCode() { return Objects.hash(mId, mUserId, mPackageName, mDeviceMacAddress, mDisplayName, - mDeviceProfile, mSelfManaged, mNotifyOnDeviceNearby, mTimeApprovedMs, + mDeviceProfile, mSelfManaged, mNotifyOnDeviceNearby, mRevoked, mTimeApprovedMs, mLastTimeConnectedMs); } @@ -293,6 +312,7 @@ public final class AssociationInfo implements Parcelable { dest.writeBoolean(mSelfManaged); dest.writeBoolean(mNotifyOnDeviceNearby); + dest.writeBoolean(mRevoked); dest.writeLong(mTimeApprovedMs); dest.writeLong(mLastTimeConnectedMs); } @@ -309,6 +329,7 @@ public final class AssociationInfo implements Parcelable { mSelfManaged = in.readBoolean(); mNotifyOnDeviceNearby = in.readBoolean(); + mRevoked = in.readBoolean(); mTimeApprovedMs = in.readLong(); mLastTimeConnectedMs = in.readLong(); } @@ -352,11 +373,13 @@ public final class AssociationInfo implements Parcelable { @NonNull private final AssociationInfo mOriginalInfo; private boolean mNotifyOnDeviceNearby; + private boolean mRevoked; private long mLastTimeConnectedMs; private Builder(@NonNull AssociationInfo info) { mOriginalInfo = info; mNotifyOnDeviceNearby = info.mNotifyOnDeviceNearby; + mRevoked = info.mRevoked; mLastTimeConnectedMs = info.mLastTimeConnectedMs; } @@ -388,6 +411,17 @@ public final class AssociationInfo implements Parcelable { } /** + * Should only be used by the CompanionDeviceManagerService. + * @hide + */ + @Override + @NonNull + public Builder setRevoked(boolean revoked) { + mRevoked = revoked; + return this; + } + + /** * @hide */ @NonNull @@ -401,6 +435,7 @@ public final class AssociationInfo implements Parcelable { mOriginalInfo.mDeviceProfile, mOriginalInfo.mSelfManaged, mNotifyOnDeviceNearby, + mRevoked, mOriginalInfo.mTimeApprovedMs, mLastTimeConnectedMs ); @@ -433,5 +468,12 @@ public final class AssociationInfo implements Parcelable { */ @NonNull Builder setLastTimeConnected(long lastTimeConnectedMs); + + /** + * Should only be used by the CompanionDeviceManagerService. + * @hide + */ + @NonNull + Builder setRevoked(boolean revoked); } } diff --git a/core/java/android/content/TEST_MAPPING b/core/java/android/content/TEST_MAPPING index 5bb845d5a1a1..dac79e7124bd 100644 --- a/core/java/android/content/TEST_MAPPING +++ b/core/java/android/content/TEST_MAPPING @@ -7,6 +7,9 @@ "include-annotation": "android.platform.test.annotations.Presubmit" }, { + "exclude-annotation": "android.platform.test.annotations.LargeTest" + }, + { "exclude-annotation": "androidx.test.filters.FlakyTest" }, { diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java index 439c6396f1d0..608e34bd07b4 100644 --- a/core/java/android/content/res/CompatibilityInfo.java +++ b/core/java/android/content/res/CompatibilityInfo.java @@ -420,7 +420,10 @@ public class CompatibilityInfo implements Parcelable { * Translate a Rect in screen coordinates into the app window's coordinates. */ @UnsupportedAppUsage - public void translateRectInScreenToAppWindow(Rect rect) { + public void translateRectInScreenToAppWindow(@Nullable Rect rect) { + if (rect == null) { + return; + } rect.scale(applicationInvertedScale); } diff --git a/core/java/android/debug/AdbManagerInternal.java b/core/java/android/debug/AdbManagerInternal.java index d730129507d7..e448706fabfe 100644 --- a/core/java/android/debug/AdbManagerInternal.java +++ b/core/java/android/debug/AdbManagerInternal.java @@ -55,6 +55,12 @@ public abstract class AdbManagerInternal { public abstract File getAdbTempKeysFile(); /** + * Notify the AdbManager that the key files have changed and any in-memory state should be + * reloaded. + */ + public abstract void notifyKeyFilesUpdated(); + + /** * Starts adbd for a transport. */ public abstract void startAdbdForTransport(byte transportType); diff --git a/core/java/android/debug/OWNERS b/core/java/android/debug/OWNERS new file mode 100644 index 000000000000..b97f7956d115 --- /dev/null +++ b/core/java/android/debug/OWNERS @@ -0,0 +1 @@ +include platform/packages/modules/adb:/OWNERS diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index b505395a091a..8bc11cbc61de 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -1448,5 +1448,15 @@ public final class DisplayManager { * @hide */ String KEY_HIGH_REFRESH_RATE_BLACKLIST = "high_refresh_rate_blacklist"; + + /** + * Key for the brightness throttling data as a String formatted: + * <displayId>,<no of throttling levels>,[<severity as string>,<brightness cap>] + * Where the latter part is repeated for each throttling level, and the entirety is repeated + * for each display, separated by a semicolon. + * For example: + * 123,1,critical,0.8;456,2,moderate,0.9,critical,0.7 + */ + String KEY_BRIGHTNESS_THROTTLING_DATA = "brightness_throttling_data"; } } diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index ee34ffe33ff6..b47e92d09989 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -195,15 +195,14 @@ class IInputMethodWrapper extends IInputMethod.Stub case DO_START_INPUT: { final SomeArgs args = (SomeArgs) msg.obj; final IBinder startInputToken = (IBinder) args.arg1; - final IRemoteInputConnection remoteIc = - (IRemoteInputConnection) ((SomeArgs) args.arg2).arg1; - final ImeOnBackInvokedDispatcher imeDispatcher = - (ImeOnBackInvokedDispatcher) ((SomeArgs) args.arg2).arg2; + final IRemoteInputConnection remoteIc = (IRemoteInputConnection) args.arg2; final EditorInfo info = (EditorInfo) args.arg3; - final CancellationGroup cancellationGroup = (CancellationGroup) args.arg4; - final boolean restarting = args.argi5 == 1; + final ImeOnBackInvokedDispatcher imeDispatcher = + (ImeOnBackInvokedDispatcher) args.arg4; + final CancellationGroup cancellationGroup = (CancellationGroup) args.arg5; + final boolean restarting = args.argi1 == 1; @InputMethodNavButtonFlags - final int navButtonFlags = args.argi6; + final int navButtonFlags = args.argi2; final InputConnection ic = remoteIc != null ? new RemoteInputConnection(mTarget, remoteIc, cancellationGroup) : null; @@ -352,18 +351,22 @@ class IInputMethodWrapper extends IInputMethod.Stub @BinderThread @Override public void startInput(IBinder startInputToken, IRemoteInputConnection inputConnection, - EditorInfo attribute, boolean restarting, + EditorInfo editorInfo, boolean restarting, @InputMethodNavButtonFlags int navButtonFlags, @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { if (mCancellationGroup == null) { Log.e(TAG, "startInput must be called after bindInput."); mCancellationGroup = new CancellationGroup(); } - SomeArgs args = SomeArgs.obtain(); - args.arg1 = inputConnection; - args.arg2 = imeDispatcher; - mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOII(DO_START_INPUT, startInputToken, - args, attribute, mCancellationGroup, restarting ? 1 : 0, navButtonFlags)); + final SomeArgs args = SomeArgs.obtain(); + args.arg1 = startInputToken; + args.arg2 = inputConnection; + args.arg3 = editorInfo; + args.argi1 = restarting ? 1 : 0; + args.argi2 = navButtonFlags; + args.arg4 = imeDispatcher; + args.arg5 = mCancellationGroup; + mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_START_INPUT, args)); } @BinderThread diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 9c146b58de49..e442e6dcd8a8 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -366,7 +366,7 @@ public class InputMethodService extends AbstractInputMethodService { **/ private RingBuffer<MotionEvent> mPendingEvents; private ImeOnBackInvokedDispatcher mImeDispatcher; - private Boolean mBackCallbackRegistered = false; + private boolean mBackCallbackRegistered = false; private final CompatOnBackInvokedCallback mCompatBackCallback = this::compatHandleBack; private Runnable mImeSurfaceRemoverRunnable; private Runnable mFinishHwRunnable; @@ -796,10 +796,10 @@ public class InputMethodService extends AbstractInputMethodService { */ @MainThread @Override - public void startInput(InputConnection ic, EditorInfo attribute) { - if (DEBUG) Log.v(TAG, "startInput(): editor=" + attribute); + public void startInput(InputConnection ic, EditorInfo editorInfo) { + if (DEBUG) Log.v(TAG, "startInput(): editor=" + editorInfo); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.startInput"); - doStartInput(ic, attribute, false); + doStartInput(ic, editorInfo, false); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } @@ -808,10 +808,10 @@ public class InputMethodService extends AbstractInputMethodService { */ @MainThread @Override - public void restartInput(InputConnection ic, EditorInfo attribute) { - if (DEBUG) Log.v(TAG, "restartInput(): editor=" + attribute); + public void restartInput(InputConnection ic, EditorInfo editorInfo) { + if (DEBUG) Log.v(TAG, "restartInput(): editor=" + editorInfo); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.restartInput"); - doStartInput(ic, attribute, true); + doStartInput(ic, editorInfo, true); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } @@ -2315,11 +2315,11 @@ public class InputMethodService extends AbstractInputMethodService { * setup here. You are guaranteed that {@link #onCreateInputView()} will * have been called some time before this function is called. * - * @param info Description of the type of text being edited. + * @param editorInfo Description of the type of text being edited. * @param restarting Set to true if we are restarting input on the * same text field as before. */ - public void onStartInputView(EditorInfo info, boolean restarting) { + public void onStartInputView(EditorInfo editorInfo, boolean restarting) { // Intentionally empty } @@ -2360,11 +2360,11 @@ public class InputMethodService extends AbstractInputMethodService { * editor is hidden but wants to show its candidates UI as text is * entered through some other mechanism. * - * @param info Description of the type of text being edited. + * @param editorInfo Description of the type of text being edited. * @param restarting Set to true if we are restarting input on the * same text field as before. */ - public void onStartCandidatesView(EditorInfo info, boolean restarting) { + public void onStartCandidatesView(EditorInfo editorInfo, boolean restarting) { // Intentionally empty } @@ -2902,7 +2902,7 @@ public class InputMethodService extends AbstractInputMethodService { unregisterCompatOnBackInvokedCallback(); } - void doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting) { + void doStartInput(InputConnection ic, EditorInfo editorInfo, boolean restarting) { if (!restarting && mInputStarted) { doFinishInput(); } @@ -2910,13 +2910,13 @@ public class InputMethodService extends AbstractInputMethodService { null /* icProto */); mInputStarted = true; mStartedInputConnection = ic; - mInputEditorInfo = attribute; + mInputEditorInfo = editorInfo; initialize(); mInlineSuggestionSessionController.notifyOnStartInput( - attribute == null ? null : attribute.packageName, - attribute == null ? null : attribute.autofillId); + editorInfo == null ? null : editorInfo.packageName, + editorInfo == null ? null : editorInfo.autofillId); if (DEBUG) Log.v(TAG, "CALL: onStartInput"); - onStartInput(attribute, restarting); + onStartInput(editorInfo, restarting); if (mDecorViewVisible) { if (mShowInputRequested) { if (DEBUG) Log.v(TAG, "CALL: onStartInputView"); @@ -2987,11 +2987,6 @@ public class InputMethodService extends AbstractInputMethodService { * the text. This is called whether or not the input method has requested * extracted text updates, although if so it will not receive this call * if the extracted text has changed as well. - * - * <p>Be careful about changing the text in reaction to this call with - * methods such as setComposingText, commitText or - * deleteSurroundingText. If the cursor moves as a result, this method - * will be called again, which may result in an infinite loop. * * <p>The default implementation takes care of updating the cursor in * the extract text, if it is being shown. diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index 5177cb4f8549..ffa9507ebc2a 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -39,9 +39,9 @@ import android.util.Log; import java.io.File; import java.io.IOException; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; -import java.util.LinkedList; import java.util.List; import java.util.Objects; @@ -977,7 +977,7 @@ public class Environment { } private static boolean hasInterestingFiles(File dir) { - final LinkedList<File> explore = new LinkedList<>(); + final ArrayDeque<File> explore = new ArrayDeque<>(); explore.add(dir); while (!explore.isEmpty()) { dir = explore.pop(); diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index c68fb8544569..1dedc2666582 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -247,6 +247,7 @@ public final class Parcel { private ArrayMap<Class, Object> mClassCookies; private RuntimeException mStack; + private boolean mRecycled = false; /** @hide */ @TestApi @@ -528,6 +529,7 @@ public final class Parcel { if (res == null) { res = new Parcel(0); } else { + res.mRecycled = false; if (DEBUG_RECYCLE) { res.mStack = new RuntimeException(); } @@ -556,7 +558,15 @@ public final class Parcel { * the object after this call. */ public final void recycle() { - if (DEBUG_RECYCLE) mStack = null; + if (mRecycled) { + Log.w(TAG, "Recycle called on unowned Parcel. (recycle twice?)", mStack); + } + mRecycled = true; + + // We try to reset the entire object here, but in order to be + // able to print a stack when a Parcel is recycled twice, that + // is cleared in obtain instead. + mClassCookies = null; freeBuffer(); @@ -5112,6 +5122,7 @@ public final class Parcel { if (res == null) { res = new Parcel(obj); } else { + res.mRecycled = false; if (DEBUG_RECYCLE) { res.mStack = new RuntimeException(); } @@ -5160,7 +5171,8 @@ public final class Parcel { @Override protected void finalize() throws Throwable { if (DEBUG_RECYCLE) { - if (mStack != null) { + // we could always have this log on, but it's spammy + if (!mRecycled) { Log.w(TAG, "Client did not call Parcel.recycle()", mStack); } } diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING index 22ddbccbaaae..e02c45ddf5a8 100644 --- a/core/java/android/os/TEST_MAPPING +++ b/core/java/android/os/TEST_MAPPING @@ -53,8 +53,7 @@ "name": "FrameworksServicesTests", "options": [ { "include-filter": "com.android.server.am.BatteryStatsServiceTest" }, - { "include-filter": "com.android.server.am.MeasuredEnergySnapshotTest" }, - { "include-filter": "com.android.server.am.BatteryExternalStatsWorkerTest" } + { "include-filter": "com.android.server.power.stats.BatteryStatsTests" } ] }, { diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java index bf2898137967..3cb5c60259eb 100644 --- a/core/java/android/os/ZygoteProcess.java +++ b/core/java/android/os/ZygoteProcess.java @@ -82,13 +82,6 @@ public class ZygoteProcess { private static final String LOG_TAG = "ZygoteProcess"; /** - * The default value for enabling the unspecialized app process (USAP) pool. This value will - * not be used if the devices has a DeviceConfig profile pushed to it that contains a value for - * this key. - */ - private static final String USAP_POOL_ENABLED_DEFAULT = "false"; - - /** * The name of the socket used to communicate with the primary zygote. */ private final LocalSocketAddress mZygoteSocketAddress; @@ -793,14 +786,8 @@ public class ZygoteProcess { private boolean fetchUsapPoolEnabledProp() { boolean origVal = mUsapPoolEnabled; - final String propertyString = Zygote.getConfigurationProperty( - ZygoteConfig.USAP_POOL_ENABLED, USAP_POOL_ENABLED_DEFAULT); - - if (!propertyString.isEmpty()) { - mUsapPoolEnabled = Zygote.getConfigurationPropertyBoolean( - ZygoteConfig.USAP_POOL_ENABLED, - Boolean.parseBoolean(USAP_POOL_ENABLED_DEFAULT)); - } + mUsapPoolEnabled = ZygoteConfig.getBool( + ZygoteConfig.USAP_POOL_ENABLED, ZygoteConfig.USAP_POOL_ENABLED_DEFAULT); boolean valueChanged = origVal != mUsapPoolEnabled; diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 22672131a85b..a513d5eb961b 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -17701,7 +17701,7 @@ public final class Settings { public @interface SyncDisabledMode {} /** - * Sync is not not disabled. + * Sync is not disabled. * * @hide */ diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java index 024660bde6fe..a59f026c4182 100644 --- a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java +++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java @@ -45,9 +45,9 @@ import android.util.Log; import com.android.internal.widget.LockPatternUtils; import java.io.IOException; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.HashMap; -import java.util.LinkedList; import java.util.Map; import java.util.Queue; import java.util.UUID; @@ -85,7 +85,7 @@ public class QuickAccessWalletClientImpl implements QuickAccessWalletClient, Ser mServiceInfo = QuickAccessWalletServiceInfo.tryCreate(context); mHandler = new Handler(Looper.getMainLooper()); mLifecycleExecutor = (bgExecutor == null) ? Runnable::run : bgExecutor; - mRequestQueue = new LinkedList<>(); + mRequestQueue = new ArrayDeque<>(); mEventListeners = new HashMap<>(1); } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 9679a6ab3acb..1df7dbc0cd0b 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -24,7 +24,6 @@ import static android.graphics.Matrix.MSKEW_X; import static android.graphics.Matrix.MSKEW_Y; import static android.view.SurfaceControl.METADATA_WINDOW_TYPE; import static android.view.View.SYSTEM_UI_FLAG_VISIBLE; -import static android.view.ViewRootImpl.LOCAL_LAYOUT; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import android.animation.AnimationHandler; @@ -41,7 +40,6 @@ import android.app.Service; import android.app.WallpaperColors; import android.app.WallpaperInfo; import android.app.WallpaperManager; -import android.app.WindowConfiguration; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.Intent; @@ -260,8 +258,6 @@ public abstract class WallpaperService extends Service { private final Point mLastSurfaceSize = new Point(); private final Matrix mTmpMatrix = new Matrix(); private final float[] mTmpValues = new float[9]; - private final WindowLayout mWindowLayout = new WindowLayout(); - private final Rect mTempRect = new Rect(); final WindowManager.LayoutParams mLayout = new WindowManager.LayoutParams(); @@ -1100,8 +1096,7 @@ public abstract class WallpaperService extends Service { | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; final Configuration config = mMergedConfiguration.getMergedConfiguration(); - final WindowConfiguration winConfig = config.windowConfiguration; - final Rect maxBounds = winConfig.getMaxBounds(); + final Rect maxBounds = config.windowConfiguration.getMaxBounds(); if (myWidth == ViewGroup.LayoutParams.MATCH_PARENT && myHeight == ViewGroup.LayoutParams.MATCH_PARENT) { mLayout.width = myWidth; @@ -1139,7 +1134,7 @@ public abstract class WallpaperService extends Service { if (mSession.addToDisplay(mWindow, mLayout, View.VISIBLE, mDisplay.getDisplayId(), mRequestedVisibilities, inputChannel, - mInsetsState, mTempControls) < 0) { + mInsetsState, mTempControls, new Rect()) < 0) { Log.w(TAG, "Failed to add window while updating wallpaper surface."); return; } @@ -1158,29 +1153,9 @@ public abstract class WallpaperService extends Service { } else { mLayout.surfaceInsets.set(0, 0, 0, 0); } - - int relayoutResult = 0; - if (LOCAL_LAYOUT) { - if (!mSurfaceControl.isValid()) { - relayoutResult = mSession.updateVisibility(mWindow, mLayout, - View.VISIBLE, mMergedConfiguration, mSurfaceControl, - mInsetsState, mTempControls); - } - - final Rect displayCutoutSafe = mTempRect; - mInsetsState.getDisplayCutoutSafe(displayCutoutSafe); - mWindowLayout.computeFrames(mLayout, mInsetsState, displayCutoutSafe, - winConfig.getBounds(), winConfig.getWindowingMode(), mWidth, - mHeight, mRequestedVisibilities, null /* attachedWindowFrame */, - 1f /* compatScale */, mWinFrames); - - mSession.updateLayout(mWindow, mLayout, 0 /* flags */, mWinFrames, mWidth, - mHeight); - } else { - relayoutResult = mSession.relayout(mWindow, mLayout, mWidth, mHeight, - View.VISIBLE, 0, mWinFrames, mMergedConfiguration, - mSurfaceControl, mInsetsState, mTempControls, mSyncSeqIdBundle); - } + final int relayoutResult = mSession.relayout(mWindow, mLayout, mWidth, mHeight, + View.VISIBLE, 0, mWinFrames, mMergedConfiguration, mSurfaceControl, + mInsetsState, mTempControls, mSyncSeqIdBundle); final int transformHint = SurfaceControl.rotationToBufferTransform( (mDisplayInstallOrientation + mDisplay.getRotation()) % 4); @@ -1229,7 +1204,7 @@ public abstract class WallpaperService extends Service { null /* ignoringVisibilityState */, config.isScreenRound(), false /* alwaysConsumeSystemBars */, mLayout.softInputMode, mLayout.flags, SYSTEM_UI_FLAG_VISIBLE, mLayout.type, - winConfig.getWindowingMode(), null /* typeSideMap */); + config.windowConfiguration.getWindowingMode(), null /* typeSideMap */); if (!fixedSize) { final Rect padding = mIWallpaperEngine.mDisplayPadding; diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index bbfe18cbfa44..f1ce9c3aa04b 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -57,6 +57,17 @@ public class FeatureFlagUtils { */ public static final String SETTINGS_APP_LANGUAGE_SELECTION = "settings_app_language_selection"; + + /** + * Feature flag to allow/restrict intent redirection from/to clone profile. + * Default value is false,this is to ensure that framework is not impacted by intent redirection + * till we are ready to launch. + * From Android U onwards, this would be set to true and eventually removed. + * @hide + */ + public static final String SETTINGS_ALLOW_INTENT_REDIRECTION_FOR_CLONE_PROFILE = + "settings_allow_intent_redirection_for_clone_profile"; + /** * Support locale opt-out and opt-in switch for per app's language. * @hide @@ -119,6 +130,7 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_SUPPORT_LARGE_SCREEN, "true"); DEFAULT_FLAGS.put("settings_search_always_expand", "true"); DEFAULT_FLAGS.put(SETTINGS_APP_LANGUAGE_SELECTION, "true"); + DEFAULT_FLAGS.put(SETTINGS_ALLOW_INTENT_REDIRECTION_FOR_CLONE_PROFILE, "false"); DEFAULT_FLAGS.put(SETTINGS_APP_LOCALE_OPT_IN_ENABLED, "true"); DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true"); DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true"); @@ -132,6 +144,7 @@ public class FeatureFlagUtils { static { PERSISTENT_FLAGS = new HashSet<>(); PERSISTENT_FLAGS.add(SETTINGS_APP_LANGUAGE_SELECTION); + PERSISTENT_FLAGS.add(SETTINGS_ALLOW_INTENT_REDIRECTION_FOR_CLONE_PROFILE); PERSISTENT_FLAGS.add(SETTINGS_APP_LOCALE_OPT_IN_ENABLED); PERSISTENT_FLAGS.add(SETTINGS_SUPPORT_LARGE_SCREEN); PERSISTENT_FLAGS.add(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS); diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java index 7f8f50b7768e..14691b3cc5d8 100644 --- a/core/java/android/view/HandwritingInitiator.java +++ b/core/java/android/view/HandwritingInitiator.java @@ -60,7 +60,7 @@ public class HandwritingInitiator { */ private final long mHandwritingTimeoutInMillis; - private final State mState = new State(); + private State mState; private final HandwritingAreaTracker mHandwritingAreasTracker = new HandwritingAreaTracker(); /** The reference to the View that currently has the input connection. */ @@ -86,17 +86,27 @@ public class HandwritingInitiator { /** * Notify the HandwritingInitiator that a new MotionEvent has arrived. - * This method is non-block, and the event passed to this method should be dispatched to the - * View tree as usual. If HandwritingInitiator triggers the handwriting mode, an fabricated - * ACTION_CANCEL event will be sent to the ViewRootImpl. - * @param motionEvent the stylus MotionEvent. + * + * <p>The return value indicates whether the event has been fully handled by the + * HandwritingInitiator and should not be dispatched to the view tree. This will be true for + * ACTION_MOVE events from a stylus gesture after handwriting mode has been initiated, in order + * to suppress other actions such as scrolling. + * + * <p>If HandwritingInitiator triggers the handwriting mode, a fabricated ACTION_CANCEL event + * will be sent to the ViewRootImpl. + * + * @param motionEvent the stylus {@link MotionEvent} + * @return true if the event has been fully handled by the {@link HandwritingInitiator} and + * should not be dispatched to the {@link View} tree, or false if the event should be dispatched + * to the {@link View} tree as usual */ @VisibleForTesting - public void onTouchEvent(@NonNull MotionEvent motionEvent) { + public boolean onTouchEvent(@NonNull MotionEvent motionEvent) { final int maskedAction = motionEvent.getActionMasked(); switch (maskedAction) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_POINTER_DOWN: + mState = null; final int actionIndex = motionEvent.getActionIndex(); final int toolType = motionEvent.getToolType(actionIndex); // TOOL_TYPE_ERASER is also from stylus. This indicates that the user is holding @@ -104,42 +114,44 @@ public class HandwritingInitiator { if (toolType != MotionEvent.TOOL_TYPE_STYLUS && toolType != MotionEvent.TOOL_TYPE_ERASER) { // The motion event is not from a stylus event, ignore it. - return; + return false; + } + if (!mImm.isStylusHandwritingAvailable()) { + return false; } - mState.mStylusPointerId = motionEvent.getPointerId(actionIndex); - mState.mStylusDownTimeInMillis = motionEvent.getEventTime(); - mState.mStylusDownX = motionEvent.getX(actionIndex); - mState.mStylusDownY = motionEvent.getY(actionIndex); - mState.mStylusDownCandidateView = new WeakReference<>( - findBestCandidateView(mState.mStylusDownX, mState.mStylusDownY)); - mState.mShouldInitHandwriting = true; - mState.mExceedHandwritingSlop = false; + mState = new State(motionEvent); break; case MotionEvent.ACTION_POINTER_UP: final int pointerId = motionEvent.getPointerId(motionEvent.getActionIndex()); - if (pointerId != mState.mStylusPointerId) { + if (mState == null || pointerId != mState.mStylusPointerId) { // ACTION_POINTER_UP is from another stylus pointer, ignore the event. - return; + return false; } // Deliberately fall through. case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: // If it's ACTION_CANCEL or ACTION_UP, all the pointers go up. There is no need to // check whether the stylus we are tracking goes up. - mState.mShouldInitHandwriting = false; - break; + if (mState != null) { + mState.mShouldInitHandwriting = false; + } + return false; case MotionEvent.ACTION_MOVE: + if (mState == null) { + return false; + } + // Either we've already tried to initiate handwriting, or the ongoing MotionEvent // sequence is considered to be tap, long-click or other gestures. if (!mState.mShouldInitHandwriting || mState.mExceedHandwritingSlop) { - return; + return mState.mHasInitiatedHandwriting; } final long timeElapsed = motionEvent.getEventTime() - mState.mStylusDownTimeInMillis; if (timeElapsed > mHandwritingTimeoutInMillis) { mState.mShouldInitHandwriting = false; - return; + return mState.mHasInitiatedHandwriting; } final int pointerIndex = motionEvent.findPointerIndex(mState.mStylusPointerId); @@ -147,13 +159,8 @@ public class HandwritingInitiator { final float y = motionEvent.getY(pointerIndex); if (largerThanTouchSlop(x, y, mState.mStylusDownX, mState.mStylusDownY)) { mState.mExceedHandwritingSlop = true; - View candidateView = mState.mStylusDownCandidateView.get(); - if (candidateView == null || !candidateView.isAttachedToWindow()) { - // If there was no candidate view found in the stylus down event, or if that - // candidate view is no longer attached, search again for a candidate view. - candidateView = findBestCandidateView(mState.mStylusDownX, - mState.mStylusDownY); - } + View candidateView = + findBestCandidateView(mState.mStylusDownX, mState.mStylusDownY); if (candidateView != null) { if (candidateView == getConnectedView()) { startHandwriting(candidateView); @@ -162,7 +169,9 @@ public class HandwritingInitiator { } } } + return mState.mHasInitiatedHandwriting; } + return false; } @Nullable @@ -195,7 +204,7 @@ public class HandwritingInitiator { } else { mConnectedView = new WeakReference<>(view); mConnectionCount = 1; - if (mState.mShouldInitHandwriting) { + if (mState != null && mState.mShouldInitHandwriting) { tryStartHandwriting(); } } @@ -259,6 +268,7 @@ public class HandwritingInitiator { @VisibleForTesting public void startHandwriting(@NonNull View view) { mImm.startStylusHandwriting(view); + mState.mHasInitiatedHandwriting = true; mState.mShouldInitHandwriting = false; } @@ -438,28 +448,38 @@ public class HandwritingInitiator { * b) If the MotionEvent sequence is considered to be tap, long-click or other gestures. * This boolean will be set to false, and it won't request to start handwriting. */ - private boolean mShouldInitHandwriting = false; + private boolean mShouldInitHandwriting; + /** + * Whether handwriting mode has already been initiated for the current MotionEvent sequence. + */ + private boolean mHasInitiatedHandwriting; /** * Whether the current ongoing stylus MotionEvent sequence already exceeds the * handwriting slop. * It's used for the case where the stylus exceeds handwriting slop before the target View * built InputConnection. */ - private boolean mExceedHandwritingSlop = false; + private boolean mExceedHandwritingSlop; /** The pointer id of the stylus pointer that is being tracked. */ - private int mStylusPointerId = -1; + private final int mStylusPointerId; /** The time stamp when the stylus pointer goes down. */ - private long mStylusDownTimeInMillis = -1; + private final long mStylusDownTimeInMillis; /** The initial location where the stylus pointer goes down. */ - private float mStylusDownX = Float.NaN; - private float mStylusDownY = Float.NaN; - /** - * The best candidate view to initialize handwriting mode based on the initial location - * where the stylus pointer goes down, or null if the location was not within any candidate - * view's handwriting area. - */ - private WeakReference<View> mStylusDownCandidateView = new WeakReference<>(null); + private final float mStylusDownX; + private final float mStylusDownY; + + private State(MotionEvent motionEvent) { + final int actionIndex = motionEvent.getActionIndex(); + mStylusPointerId = motionEvent.getPointerId(actionIndex); + mStylusDownTimeInMillis = motionEvent.getEventTime(); + mStylusDownX = motionEvent.getX(actionIndex); + mStylusDownY = motionEvent.getY(actionIndex); + + mShouldInitHandwriting = true; + mHasInitiatedHandwriting = false; + mExceedHandwritingSlop = false; + } } /** The helper method to check if the given view is still active for handwriting. */ diff --git a/core/java/android/view/IRemoteAnimationRunner.aidl b/core/java/android/view/IRemoteAnimationRunner.aidl index 1f64fb8ca2ec..1981c9d66c8b 100644 --- a/core/java/android/view/IRemoteAnimationRunner.aidl +++ b/core/java/android/view/IRemoteAnimationRunner.aidl @@ -46,5 +46,5 @@ oneway interface IRemoteAnimationRunner { * won't have any effect anymore. */ @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) - void onAnimationCancelled(); + void onAnimationCancelled(boolean isKeyguardOccluded); } diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index acdff4fb52fd..58a041aec894 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -749,6 +749,11 @@ interface IWindowManager void setLayerTracingFlags(int flags); /** + * Toggle active SurfaceFlinger transaction tracing. + */ + void setActiveTransactionTracing(boolean active); + + /** * Forwards a scroll capture request to the appropriate window, if available. * * @param displayId the id of the display to target diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 649accd1126d..ef57b1ddd4b3 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -50,13 +50,15 @@ interface IWindowSession { int addToDisplay(IWindow window, in WindowManager.LayoutParams attrs, in int viewVisibility, in int layerStackId, in InsetsVisibilities requestedVisibilities, out InputChannel outInputChannel, out InsetsState insetsState, - out InsetsSourceControl[] activeControls); + out InsetsSourceControl[] activeControls, out Rect attachedFrame); int addToDisplayAsUser(IWindow window, in WindowManager.LayoutParams attrs, in int viewVisibility, in int layerStackId, in int userId, in InsetsVisibilities requestedVisibilities, out InputChannel outInputChannel, - out InsetsState insetsState, out InsetsSourceControl[] activeControls); + out InsetsState insetsState, out InsetsSourceControl[] activeControls, + out Rect attachedFrame); int addToDisplayWithoutInputChannel(IWindow window, in WindowManager.LayoutParams attrs, - in int viewVisibility, in int layerStackId, out InsetsState insetsState); + in int viewVisibility, in int layerStackId, out InsetsState insetsState, + out Rect attachedFrame); @UnsupportedAppUsage void remove(IWindow window); @@ -107,41 +109,6 @@ interface IWindowSession { out InsetsState insetsState, out InsetsSourceControl[] activeControls, out Bundle bundle); - /** - * Changes the view visibility and the attributes of a window. This should only be called when - * the visibility of the root view is changed. This returns a valid surface if the root view is - * visible. This also returns the latest information for the caller to compute its window frame. - * - * @param window The window being updated. - * @param attrs If non-null, new attributes to apply to the window. - * @param viewVisibility Window root view's visibility. - * @param outMergedConfiguration New config container that holds global, override and merged - * config for window, if it is now becoming visible and the merged configuration has changed - * since it was last displayed. - * @param outSurfaceControl Object in which is placed the new display surface. - * @param outInsetsState The current insets state in the system. - * @param outActiveControls The insets source controls for the caller to override the insets - * state in the system. - * - * @return int Result flags: {@link WindowManagerGlobal#RELAYOUT_FIRST_TIME}. - */ - int updateVisibility(IWindow window, in WindowManager.LayoutParams attrs, int viewVisibility, - out MergedConfiguration outMergedConfiguration, out SurfaceControl outSurfaceControl, - out InsetsState outInsetsState, out InsetsSourceControl[] outActiveControls); - - /** - * Reports the layout results and the attributes of a window to the server. - * - * @param window The window being reported. - * @param attrs If non-null, new attributes to apply to the window. - * @param flags Request flags: {@link WindowManagerGlobal#RELAYOUT_INSETS_PENDING}. - * @param clientFrames the window frames computed by the client. - * @param requestedWidth The width the window wants to be. - * @param requestedHeight The height the window wants to be. - */ - oneway void updateLayout(IWindow window, in WindowManager.LayoutParams attrs, int flags, - in ClientWindowFrames clientFrames, int requestedWidth, int requestedHeight); - /* * Notify the window manager that an application is relaunching and * windows should be prepared for replacement. @@ -384,4 +351,9 @@ interface IWindowSession { * Clears a touchable region set by {@link #setInsets}. */ void clearTouchableRegion(IWindow window); + + /** + * Returns whether this window needs to cancel draw and retry later. + */ + boolean cancelDraw(IWindow window); } diff --git a/core/java/android/view/TEST_MAPPING b/core/java/android/view/TEST_MAPPING index 50d69f78688a..ecb98f9ce801 100644 --- a/core/java/android/view/TEST_MAPPING +++ b/core/java/android/view/TEST_MAPPING @@ -10,6 +10,9 @@ "include-annotation": "android.platform.test.annotations.Presubmit" }, { + "exclude-annotation": "android.platform.test.annotations.LargeTest" + }, + { "exclude-annotation": "androidx.test.filters.FlakyTest" }, { diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 0b742e7f2eff..bc7da13b66db 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -60,13 +60,11 @@ import static android.view.WindowInsetsController.BEHAVIOR_DEFAULT; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; import static android.view.WindowLayout.UNSPECIFIED_LENGTH; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; -import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION; import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; -import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_APPEARANCE_CONTROLLED; @@ -77,13 +75,12 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY; +import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW; import static android.view.WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS; import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER; @@ -566,8 +563,6 @@ public final class ViewRootImpl implements ViewParent, private final WindowLayout mWindowLayout; - private ViewRootImpl mParentViewRoot; - // This is used to reduce the race between window focus changes being dispatched from // the window manager and input events coming through the input system. @GuardedBy("this") @@ -601,6 +596,14 @@ public final class ViewRootImpl implements ViewParent, */ private boolean mSyncBuffer = false; + /** + * Flag to determine whether the client needs to check with WMS if it can draw. WMS will notify + * the client that it can't draw if we're still in the middle of a sync set that includes this + * window. Once the sync is complete, the window can resume drawing. This is to ensure we don't + * deadlock the client by trying to request draws when there may not be any buffers available. + */ + private boolean mCheckIfCanDraw = false; + int mSyncSeqId = 0; int mLastSyncSeqId = 0; @@ -1187,7 +1190,6 @@ public final class ViewRootImpl implements ViewParent, if (panelParentView != null) { mAttachInfo.mPanelParentWindowToken = panelParentView.getApplicationWindowToken(); - mParentViewRoot = panelParentView.getViewRootImpl(); } mAdded = true; int res; /* = WindowManagerImpl.ADD_OKAY; */ @@ -1218,14 +1220,21 @@ public final class ViewRootImpl implements ViewParent, collectViewAttributes(); adjustLayoutParamsForCompatibility(mWindowAttributes); controlInsetsForCompatibility(mWindowAttributes); + + Rect attachedFrame = new Rect(); res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), userId, mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets, - mTempControls); + mTempControls, attachedFrame); + if (!attachedFrame.isValid()) { + attachedFrame = null; + } if (mTranslator != null) { mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets); mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls); + mTranslator.translateRectInScreenToAppWindow(attachedFrame); } + mTmpFrames.attachedFrame = attachedFrame; } catch (RemoteException e) { mAdded = false; mView = null; @@ -1252,8 +1261,8 @@ public final class ViewRootImpl implements ViewParent, mWindowLayout.computeFrames(mWindowAttributes, state, displayCutoutSafe, winConfig.getBounds(), winConfig.getWindowingMode(), UNSPECIFIED_LENGTH, UNSPECIFIED_LENGTH, - mInsetsController.getRequestedVisibilities(), - getAttachedWindowFrame(), 1f /* compactScale */, mTmpFrames); + mInsetsController.getRequestedVisibilities(), 1f /* compactScale */, + mTmpFrames); setFrame(mTmpFrames.frame); registerBackCallbackOnWindow(); if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) { @@ -1378,14 +1387,6 @@ public final class ViewRootImpl implements ViewParent, } } - private Rect getAttachedWindowFrame() { - final int type = mWindowAttributes.type; - final boolean layoutAttached = (mParentViewRoot != null - && type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW - && type != TYPE_APPLICATION_ATTACHED_DIALOG); - return layoutAttached ? mParentViewRoot.mWinFrame : null; - } - /** * Register any kind of listeners if setView was success. */ @@ -1743,16 +1744,20 @@ public final class ViewRootImpl implements ViewParent, final Rect frame = frames.frame; final Rect displayFrame = frames.displayFrame; + final Rect attachedFrame = frames.attachedFrame; if (mTranslator != null) { mTranslator.translateRectInScreenToAppWindow(frame); mTranslator.translateRectInScreenToAppWindow(displayFrame); + mTranslator.translateRectInScreenToAppWindow(attachedFrame); } final boolean frameChanged = !mWinFrame.equals(frame); final boolean configChanged = !mLastReportedMergedConfiguration.equals(mergedConfiguration); + final boolean attachedFrameChanged = LOCAL_LAYOUT + && !Objects.equals(mTmpFrames.attachedFrame, attachedFrame); final boolean displayChanged = mDisplay.getDisplayId() != displayId; final boolean resizeModeChanged = mResizeMode != resizeMode; - if (msg == MSG_RESIZED && !frameChanged && !configChanged && !displayChanged - && !resizeModeChanged && !forceNextWindowRelayout) { + if (msg == MSG_RESIZED && !frameChanged && !configChanged && !attachedFrameChanged + && !displayChanged && !resizeModeChanged && !forceNextWindowRelayout) { return; } @@ -1770,6 +1775,9 @@ public final class ViewRootImpl implements ViewParent, setFrame(frame); mTmpFrames.displayFrame.set(displayFrame); + if (mTmpFrames.attachedFrame != null && attachedFrame != null) { + mTmpFrames.attachedFrame.set(attachedFrame); + } if (mDragResizing && mUseMTRenderer) { boolean fullscreen = frame.equals(mPendingBackDropFrame); @@ -2703,6 +2711,9 @@ public final class ViewRootImpl implements ViewParent, mIsInTraversal = true; mWillDrawSoon = true; + boolean cancelDraw = false; + boolean isSyncRequest = false; + boolean windowSizeMayChange = false; WindowManager.LayoutParams lp = mWindowAttributes; @@ -2972,6 +2983,8 @@ public final class ViewRootImpl implements ViewParent, mViewFrameInfo.flags |= FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED; } relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); + cancelDraw = (relayoutResult & RELAYOUT_RES_CANCEL_AND_REDRAW) + == RELAYOUT_RES_CANCEL_AND_REDRAW; final boolean dragResizing = mPendingDragResizing; if (mSyncSeqId > mLastSyncSeqId) { mLastSyncSeqId = mSyncSeqId; @@ -2980,6 +2993,7 @@ public final class ViewRootImpl implements ViewParent, } reportNextDraw(); mSyncBuffer = true; + isSyncRequest = true; } final boolean surfaceControlChanged = @@ -3268,6 +3282,19 @@ public final class ViewRootImpl implements ViewParent, } } } else { + // If a relayout isn't going to happen, we still need to check if this window can draw + // when mCheckIfCanDraw is set. This is because it means we had a sync in the past, but + // have not been told by WMS that the sync is complete and that we can continue to draw + if (mCheckIfCanDraw) { + try { + cancelDraw = mWindowSession.cancelDraw(mWindow); + if (DEBUG_BLAST) { + Log.d(mTag, "cancelDraw returned " + cancelDraw); + } + } catch (RemoteException e) { + } + } + // Not the first pass and no window/insets/visibility change but the window // may have moved and we need check that and if so to update the left and right // in the attach info. We translate only the window frame since on window move @@ -3486,7 +3513,9 @@ public final class ViewRootImpl implements ViewParent, reportNextDraw(); } - boolean cancelAndRedraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw(); + mCheckIfCanDraw = isSyncRequest || cancelDraw; + + boolean cancelAndRedraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || cancelDraw; if (!cancelAndRedraw) { createSyncIfNeeded(); } @@ -6554,11 +6583,13 @@ public final class ViewRootImpl implements ViewParent, private int processPointerEvent(QueuedInputEvent q) { final MotionEvent event = (MotionEvent)q.mEvent; - mHandwritingInitiator.onTouchEvent(event); + boolean handled = mHandwritingInitiator.onTouchEvent(event); mAttachInfo.mUnbufferedDispatchRequested = false; mAttachInfo.mHandlingPointerEvent = true; - boolean handled = mView.dispatchPointerEvent(event); + // If the event was fully handled by the handwriting initiator, then don't dispatch it + // to the view tree. + handled = handled || mView.dispatchPointerEvent(event); maybeUpdatePointerIcon(event); maybeUpdateTooltip(event); mAttachInfo.mHandlingPointerEvent = false; @@ -8011,69 +8042,20 @@ public final class ViewRootImpl implements ViewParent, final int requestedWidth = (int) (mView.getMeasuredWidth() * appScale + 0.5f); final int requestedHeight = (int) (mView.getMeasuredHeight() * appScale + 0.5f); - int relayoutResult = 0; - WindowConfiguration winConfig = getConfiguration().windowConfiguration; - if (LOCAL_LAYOUT) { - if (mFirst || viewVisibility != mViewVisibility) { - relayoutResult = mWindowSession.updateVisibility(mWindow, params, viewVisibility, - mPendingMergedConfiguration, mSurfaceControl, mTempInsets, mTempControls); - if (mTranslator != null) { - mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets); - mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls); - } - mInsetsController.onStateChanged(mTempInsets); - mInsetsController.onControlsChanged(mTempControls); - - mPendingAlwaysConsumeSystemBars = - (relayoutResult & RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS) != 0; - } - final InsetsState state = mInsetsController.getState(); - final Rect displayCutoutSafe = mTempRect; - state.getDisplayCutoutSafe(displayCutoutSafe); - if (mWindowAttributes.type == TYPE_APPLICATION_STARTING) { - // TODO(b/210378379): Remove the special logic. - // Letting starting window use the window bounds from the pending config is for the - // fixed rotation, because the config is not overridden before the starting window - // is created. - winConfig = mPendingMergedConfiguration.getMergedConfiguration() - .windowConfiguration; - } - mWindowLayout.computeFrames(mWindowAttributes, state, displayCutoutSafe, - winConfig.getBounds(), winConfig.getWindowingMode(), requestedWidth, - requestedHeight, mInsetsController.getRequestedVisibilities(), - getAttachedWindowFrame(), 1f /* compatScale */, mTmpFrames); - - mWindowSession.updateLayout(mWindow, params, - insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, mTmpFrames, - requestedWidth, requestedHeight); - - } else { - relayoutResult = mWindowSession.relayout(mWindow, params, - requestedWidth, requestedHeight, viewVisibility, - insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, - mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets, - mTempControls, mRelayoutBundle); - final int maybeSyncSeqId = mRelayoutBundle.getInt("seqid"); - if (maybeSyncSeqId > 0) { - mSyncSeqId = maybeSyncSeqId; - } - - if (mTranslator != null) { - mTranslator.translateRectInScreenToAppWindow(mTmpFrames.frame); - mTranslator.translateRectInScreenToAppWindow(mTmpFrames.displayFrame); - mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets); - mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls); - } - mInsetsController.onStateChanged(mTempInsets); - mInsetsController.onControlsChanged(mTempControls); - - mPendingAlwaysConsumeSystemBars = - (relayoutResult & RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS) != 0; + int relayoutResult = mWindowSession.relayout(mWindow, params, + requestedWidth, requestedHeight, viewVisibility, + insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, + mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets, + mTempControls, mRelayoutBundle); + final int maybeSyncSeqId = mRelayoutBundle.getInt("seqid"); + if (maybeSyncSeqId > 0) { + mSyncSeqId = maybeSyncSeqId; } final int transformHint = SurfaceControl.rotationToBufferTransform( (mDisplayInstallOrientation + mDisplay.getRotation()) % 4); + final WindowConfiguration winConfig = getConfiguration().windowConfiguration; WindowLayout.computeSurfaceSize(mWindowAttributes, winConfig.getMaxBounds(), requestedWidth, requestedHeight, mTmpFrames.frame, mPendingDragResizing, mSurfaceSize); @@ -8122,10 +8104,23 @@ public final class ViewRootImpl implements ViewParent, destroySurface(); } + mPendingAlwaysConsumeSystemBars = + (relayoutResult & RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS) != 0; + if (restore) { params.restore(); } + + if (mTranslator != null) { + mTranslator.translateRectInScreenToAppWindow(mTmpFrames.frame); + mTranslator.translateRectInScreenToAppWindow(mTmpFrames.displayFrame); + mTranslator.translateRectInScreenToAppWindow(mTmpFrames.attachedFrame); + mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets); + mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls); + } setFrame(mTmpFrames.frame); + mInsetsController.onStateChanged(mTempInsets); + mInsetsController.onControlsChanged(mTempControls); return relayoutResult; } diff --git a/core/java/android/view/WindowLayout.java b/core/java/android/view/WindowLayout.java index c320b262ebd7..9b6b2b906e8e 100644 --- a/core/java/android/view/WindowLayout.java +++ b/core/java/android/view/WindowLayout.java @@ -66,14 +66,15 @@ public class WindowLayout { public void computeFrames(WindowManager.LayoutParams attrs, InsetsState state, Rect displayCutoutSafe, Rect windowBounds, @WindowingMode int windowingMode, int requestedWidth, int requestedHeight, InsetsVisibilities requestedVisibilities, - Rect attachedWindowFrame, float compatScale, ClientWindowFrames outFrames) { + float compatScale, ClientWindowFrames frames) { final int type = attrs.type; final int fl = attrs.flags; final int pfl = attrs.privateFlags; final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) == FLAG_LAYOUT_IN_SCREEN; - final Rect outDisplayFrame = outFrames.displayFrame; - final Rect outParentFrame = outFrames.parentFrame; - final Rect outFrame = outFrames.frame; + final Rect attachedWindowFrame = frames.attachedFrame; + final Rect outDisplayFrame = frames.displayFrame; + final Rect outParentFrame = frames.parentFrame; + final Rect outFrame = frames.frame; // Compute bounds restricted by insets final Insets insets = state.calculateInsets(windowBounds, attrs.getFitInsetsTypes(), @@ -104,7 +105,7 @@ public class WindowLayout { final DisplayCutout cutout = state.getDisplayCutout(); final Rect displayCutoutSafeExceptMaybeBars = mTempDisplayCutoutSafeExceptMaybeBarsRect; displayCutoutSafeExceptMaybeBars.set(displayCutoutSafe); - outFrames.isParentFrameClippedByDisplayCutout = false; + frames.isParentFrameClippedByDisplayCutout = false; if (cutoutMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS && !cutout.isEmpty()) { // Ensure that windows with a non-ALWAYS display cutout mode are laid out in // the cutout safe zone. @@ -167,7 +168,7 @@ public class WindowLayout { if (!attachedInParent && !floatingInScreenWindow) { mTempRect.set(outParentFrame); outParentFrame.intersectUnchecked(displayCutoutSafeExceptMaybeBars); - outFrames.isParentFrameClippedByDisplayCutout = !mTempRect.equals(outParentFrame); + frames.isParentFrameClippedByDisplayCutout = !mTempRect.equals(outParentFrame); } outDisplayFrame.intersectUnchecked(displayCutoutSafeExceptMaybeBars); } @@ -287,12 +288,9 @@ public class WindowLayout { } } - if (DEBUG) Log.d(TAG, "computeWindowFrames " + attrs.getTitle() - + " outFrames=" + outFrames + if (DEBUG) Log.d(TAG, "computeFrames " + attrs.getTitle() + + " frames=" + frames + " windowBounds=" + windowBounds.toShortString() - + " attachedWindowFrame=" + (attachedWindowFrame != null - ? attachedWindowFrame.toShortString() - : "null") + " requestedWidth=" + requestedWidth + " requestedHeight=" + requestedHeight + " compatScale=" + compatScale diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 792eedec388e..13ea7af7a623 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -2598,6 +2598,7 @@ public interface WindowManager extends ViewManager { PRIVATE_FLAG_SYSTEM_ERROR, PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS, PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR, + PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT, PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY, PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH, PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME, @@ -2659,6 +2660,10 @@ public interface WindowManager extends ViewManager { equals = PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR, name = "FORCE_STATUS_BAR_VISIBLE"), @ViewDebug.FlagToString( + mask = PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT, + equals = PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT, + name = "LAYOUT_SIZE_EXTENDED_BY_CUTOUT"), + @ViewDebug.FlagToString( mask = PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY, equals = PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY, name = "FORCE_DECOR_VIEW_VISIBILITY"), diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index 85a5762f7f3d..25445abefca2 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -83,6 +83,11 @@ public final class WindowManagerGlobal { public static final int RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS = 1 << 3; /** + * The window manager has told the window it cannot draw this frame and should retry again. + */ + public static final int RELAYOUT_RES_CANCEL_AND_REDRAW = 1 << 4; + + /** * Flag for relayout: the client will be later giving * internal insets; as a result, the window will not impact other window * layouts until the insets are given. diff --git a/core/java/android/view/WindowlessWindowLayout.java b/core/java/android/view/WindowlessWindowLayout.java index 7cc50c579d0d..5bec5b6b6722 100644 --- a/core/java/android/view/WindowlessWindowLayout.java +++ b/core/java/android/view/WindowlessWindowLayout.java @@ -30,10 +30,10 @@ public class WindowlessWindowLayout extends WindowLayout { public void computeFrames(WindowManager.LayoutParams attrs, InsetsState state, Rect displayCutoutSafe, Rect windowBounds, @WindowingMode int windowingMode, int requestedWidth, int requestedHeight, InsetsVisibilities requestedVisibilities, - Rect attachedWindowFrame, float compatScale, ClientWindowFrames outFrames) { - outFrames.frame.set(0, 0, attrs.width, attrs.height); - outFrames.displayFrame.set(outFrames.frame); - outFrames.parentFrame.set(outFrames.frame); + float compatScale, ClientWindowFrames frames) { + frames.frame.set(0, 0, attrs.width, attrs.height); + frames.displayFrame.set(frames.frame); + frames.parentFrame.set(frames.frame); } } diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index a212348a2315..94da2741f71a 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -149,7 +149,7 @@ public class WindowlessWindowManager implements IWindowSession { public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, InsetsVisibilities requestedVisibilities, InputChannel outInputChannel, InsetsState outInsetsState, - InsetsSourceControl[] outActiveControls) { + InsetsSourceControl[] outActiveControls, Rect outAttachedFrame) { final SurfaceControl.Builder b = new SurfaceControl.Builder(mSurfaceSession) .setFormat(attrs.format) .setBLASTLayer() @@ -181,6 +181,7 @@ public class WindowlessWindowManager implements IWindowSession { synchronized (this) { mStateForWindow.put(window.asBinder(), state); } + outAttachedFrame.set(0, 0, -1, -1); final int res = WindowManagerGlobal.ADD_OKAY | WindowManagerGlobal.ADD_FLAG_APP_VISIBLE | WindowManagerGlobal.ADD_FLAG_USE_BLAST; @@ -196,15 +197,15 @@ public class WindowlessWindowManager implements IWindowSession { public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities, InputChannel outInputChannel, InsetsState outInsetsState, - InsetsSourceControl[] outActiveControls) { + InsetsSourceControl[] outActiveControls, Rect outAttachedFrame) { return addToDisplay(window, attrs, viewVisibility, displayId, requestedVisibilities, - outInputChannel, outInsetsState, outActiveControls); + outInputChannel, outInsetsState, outActiveControls, outAttachedFrame); } @Override public int addToDisplayWithoutInputChannel(android.view.IWindow window, android.view.WindowManager.LayoutParams attrs, int viewVisibility, int layerStackId, - android.view.InsetsState insetsState) { + android.view.InsetsState insetsState, Rect outAttachedFrame) { return 0; } @@ -337,21 +338,6 @@ public class WindowlessWindowManager implements IWindowSession { } @Override - public int updateVisibility(IWindow window, WindowManager.LayoutParams inAttrs, - int viewVisibility, MergedConfiguration outMergedConfiguration, - SurfaceControl outSurfaceControl, InsetsState outInsetsState, - InsetsSourceControl[] outActiveControls) { - // TODO(b/161810301): Finish the implementation. - return 0; - } - - @Override - public void updateLayout(IWindow window, WindowManager.LayoutParams inAttrs, int flags, - ClientWindowFrames clientWindowFrames, int requestedWidth, int requestedHeight) { - // TODO(b/161810301): Finish the implementation. - } - - @Override public void prepareToReplaceWindows(android.os.IBinder appToken, boolean childrenOnly) { } @@ -552,4 +538,9 @@ public class WindowlessWindowManager implements IWindowSession { } } } + + @Override + public boolean cancelDraw(IWindow window) { + return false; + } } diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java index 827c3ceb07b3..54ff11ccac9d 100644 --- a/core/java/android/view/inputmethod/InputMethod.java +++ b/core/java/android/view/inputmethod/InputMethod.java @@ -182,13 +182,13 @@ public interface InputMethod { * @param inputConnection Optional specific input connection for * communicating with the text box; if null, you should use the generic * bound input connection. - * @param info Information about the text box (typically, an EditText) + * @param editorInfo Information about the text box (typically, an EditText) * that requests input. * * @see EditorInfo */ @MainThread - public void startInput(InputConnection inputConnection, EditorInfo info); + public void startInput(InputConnection inputConnection, EditorInfo editorInfo); /** * This method is called when the state of this input method needs to be @@ -201,13 +201,13 @@ public interface InputMethod { * @param inputConnection Optional specific input connection for * communicating with the text box; if null, you should use the generic * bound input connection. - * @param attribute The attribute of the text box (typically, a EditText) + * @param editorInfo The attribute of the text box (typically, a EditText) * that requests input. * * @see EditorInfo */ @MainThread - public void restartInput(InputConnection inputConnection, EditorInfo attribute); + public void restartInput(InputConnection inputConnection, EditorInfo editorInfo); /** * This method is called when {@code {@link #startInput(InputConnection, EditorInfo)} or @@ -233,7 +233,7 @@ public interface InputMethod { * long as your implementation of {@link InputMethod} relies on such * IPCs * @param navButtonFlags {@link InputMethodNavButtonFlags} in the initial state of this session. - * @param imeDispatcher The {@link ImeOnBackInvokedDispatcher }} to be set on the + * @param imeDispatcher The {@link ImeOnBackInvokedDispatcher} to be set on the * IME's {@link android.window.WindowOnBackInvokedDispatcher}, so that IME * {@link android.window.OnBackInvokedCallback}s can be forwarded to * the client requesting to start input. diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 73d6241c21d1..50f3e0cfbcf3 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -446,7 +446,7 @@ public final class InputMethodManager { * the attributes that were last retrieved from the served view and given * to the input connection. */ - EditorInfo mCurrentTextBoxAttribute; + EditorInfo mCurrentEditorInfo; /** * The InputConnection that was last retrieved from the served view. */ @@ -541,7 +541,9 @@ public final class InputMethodManager { /** * The monitor mode for {@link #updateCursorAnchorInfo(View, CursorAnchorInfo)}. + * @deprecated This is kept for {@link UnsupportedAppUsage}. Must not be used. */ + @Deprecated private int mRequestUpdateCursorAnchorInfoMonitorMode = REQUEST_UPDATE_CURSOR_ANCHOR_INFO_NONE; /** @@ -652,15 +654,13 @@ public final class InputMethodManager { public boolean startInput(@StartInputReason int startInputReason, View focusedView, @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode, int windowFlags) { - final View servedView; ImeTracing.getInstance().triggerClientDump( "InputMethodManager.DelegateImpl#startInput", InputMethodManager.this, null /* icProto */); synchronized (mH) { - mCurrentTextBoxAttribute = null; + mCurrentEditorInfo = null; mCompletions = null; mServedConnecting = true; - servedView = getServedViewLocked(); } return startInputInner(startInputReason, focusedView != null ? focusedView.getWindowToken() : null, startInputFlags, @@ -1131,9 +1131,7 @@ public final class InputMethodManager { || mServedInputConnection == null) { return; } - final boolean isMonitoring = (mRequestUpdateCursorAnchorInfoMonitorMode - & InputConnection.CURSOR_UPDATE_MONITOR) != 0; - if (!isMonitoring) { + if (!mServedInputConnection.isCursorAnchorInfoMonitoring()) { return; } // Since the host VirtualDisplay is moved, we need to issue @@ -1600,7 +1598,7 @@ public final class InputMethodManager { checkFocus(); synchronized (mH) { - return hasServedByInputMethodLocked(view) && mCurrentTextBoxAttribute != null; + return hasServedByInputMethodLocked(view) && mCurrentEditorInfo != null; } } @@ -1610,7 +1608,7 @@ public final class InputMethodManager { public boolean isActive() { checkFocus(); synchronized (mH) { - return getServedViewLocked() != null && mCurrentTextBoxAttribute != null; + return getServedViewLocked() != null && mCurrentEditorInfo != null; } } @@ -1694,7 +1692,7 @@ public final class InputMethodManager { * to an input method */ void clearConnectionLocked() { - mCurrentTextBoxAttribute = null; + mCurrentEditorInfo = null; if (mServedInputConnection != null) { mServedInputConnection.deactivate(); mServedInputConnection = null; @@ -2192,7 +2190,7 @@ public final class InputMethodManager { public boolean doInvalidateInput(@NonNull RemoteInputConnectionImpl inputConnection, @NonNull TextSnapshot textSnapshot, int sessionId) { synchronized (mH) { - if (mServedInputConnection != inputConnection || mCurrentTextBoxAttribute == null) { + if (mServedInputConnection != inputConnection || mCurrentEditorInfo == null) { // OK to ignore because the calling InputConnection is already abandoned. return true; } @@ -2200,7 +2198,7 @@ public final class InputMethodManager { // IME is not yet bound to the client. Need to fall back to the restartInput(). return false; } - final EditorInfo editorInfo = mCurrentTextBoxAttribute.createCopyInternal(); + final EditorInfo editorInfo = mCurrentEditorInfo.createCopyInternal(); editorInfo.initialSelStart = mCursorSelStart = textSnapshot.getSelectionStart(); editorInfo.initialSelEnd = mCursorSelEnd = textSnapshot.getSelectionEnd(); mCursorCandStart = textSnapshot.getCompositionStart(); @@ -2339,7 +2337,7 @@ public final class InputMethodManager { // This is not an error. Once IME binds (MSG_BIND), InputConnection is fully // established. So we report this to interested recipients. reportInputConnectionOpened( - mServedInputConnection.getInputConnection(), mCurrentTextBoxAttribute, + mServedInputConnection.getInputConnection(), mCurrentEditorInfo, mServedInputConnectionHandler, view); } return false; @@ -2347,12 +2345,12 @@ public final class InputMethodManager { // If we already have a text box, then this view is already // connected so we want to restart it. - if (mCurrentTextBoxAttribute == null) { + if (mCurrentEditorInfo == null) { startInputFlags |= StartInputFlags.INITIAL_CONNECTION; } // Hook 'em up and let 'er rip. - mCurrentTextBoxAttribute = tba.createCopyInternal(); + mCurrentEditorInfo = tba.createCopyInternal(); mServedConnecting = false; if (mServedInputConnection != null) { @@ -2658,7 +2656,7 @@ public final class InputMethodManager { checkFocus(); synchronized (mH) { - if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null + if (!hasServedByInputMethodLocked(view) || mCurrentEditorInfo == null || mCurrentInputMethodSession == null) { return; } @@ -2675,19 +2673,14 @@ public final class InputMethodManager { if (DEBUG) { Log.v(TAG, "SELECTION CHANGE: " + mCurrentInputMethodSession); } - final int oldSelStart = mCursorSelStart; - final int oldSelEnd = mCursorSelEnd; - // Update internal values before sending updateSelection to the IME, because - // if it changes the text within its onUpdateSelection handler in a way that - // does not move the cursor we don't want to call it again with the same values. + mCurrentInputMethodSession.updateSelection(mCursorSelStart, mCursorSelEnd, selStart, + selEnd, candidatesStart, candidatesEnd); + forAccessibilitySessionsLocked(wrapper -> wrapper.updateSelection(mCursorSelStart, + mCursorSelEnd, selStart, selEnd, candidatesStart, candidatesEnd)); mCursorSelStart = selStart; mCursorSelEnd = selEnd; mCursorCandStart = candidatesStart; mCursorCandEnd = candidatesEnd; - mCurrentInputMethodSession.updateSelection( - oldSelStart, oldSelEnd, selStart, selEnd, candidatesStart, candidatesEnd); - forAccessibilitySessionsLocked(wrapper -> wrapper.updateSelection(oldSelStart, - oldSelEnd, selStart, selEnd, candidatesStart, candidatesEnd)); } } } @@ -2720,7 +2713,7 @@ public final class InputMethodManager { final boolean focusChanged = servedView != nextServedView; checkFocus(); synchronized (mH) { - if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null + if (!hasServedByInputMethodLocked(view) || mCurrentEditorInfo == null || mCurrentInputMethodSession == null) { return; } @@ -2744,8 +2737,10 @@ public final class InputMethodManager { * Return true if the current input method wants to be notified when cursor/anchor location * is changed. * + * @deprecated This method is kept for {@link UnsupportedAppUsage}. Must not be used. * @hide */ + @Deprecated @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean isCursorAnchorInfoEnabled() { synchronized (mH) { @@ -2760,8 +2755,10 @@ public final class InputMethodManager { /** * Set the requested mode for {@link #updateCursorAnchorInfo(View, CursorAnchorInfo)}. * + * @deprecated This method is kept for {@link UnsupportedAppUsage}. Must not be used. * @hide */ + @Deprecated @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void setUpdateCursorAnchorInfoMode(int flags) { synchronized (mH) { @@ -2770,17 +2767,6 @@ public final class InputMethodManager { } /** - * Get the requested mode for {@link #updateCursorAnchorInfo(View, CursorAnchorInfo)}. - * - * @hide - */ - public int getUpdateCursorAnchorInfoMode() { - synchronized (mH) { - return mRequestUpdateCursorAnchorInfoMonitorMode; - } - } - - /** * Report the current cursor location in its window. * * @deprecated Use {@link #updateCursorAnchorInfo(View, CursorAnchorInfo)} instead. @@ -2796,7 +2782,7 @@ public final class InputMethodManager { checkFocus(); synchronized (mH) { - if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null + if (!hasServedByInputMethodLocked(view) || mCurrentEditorInfo == null || mCurrentInputMethodSession == null) { return; } @@ -2828,14 +2814,14 @@ public final class InputMethodManager { checkFocus(); synchronized (mH) { - if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null + if (!hasServedByInputMethodLocked(view) || mCurrentEditorInfo == null || mCurrentInputMethodSession == null) { return; } // If immediate bit is set, we will call updateCursorAnchorInfo() even when the data has // not been changed from the previous call. - final boolean isImmediate = (mRequestUpdateCursorAnchorInfoMonitorMode & - CURSOR_UPDATE_IMMEDIATE) != 0; + final boolean isImmediate = mServedInputConnection != null + && mServedInputConnection.resetHasPendingImmediateCursorAnchorInfoUpdate(); if (!isImmediate && Objects.equals(mCursorAnchorInfo, cursorAnchorInfo)) { // TODO: Consider always emitting this message once we have addressed redundant // calls of this method from android.widget.Editor. @@ -2854,8 +2840,6 @@ public final class InputMethodManager { mCurrentInputMethodSession.updateCursorAnchorInfo(cursorAnchorInfo); } mCursorAnchorInfo = cursorAnchorInfo; - // Clear immediate bit (if any). - mRequestUpdateCursorAnchorInfoMonitorMode &= ~CURSOR_UPDATE_IMMEDIATE; } } @@ -2880,7 +2864,7 @@ public final class InputMethodManager { checkFocus(); synchronized (mH) { - if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null + if (!hasServedByInputMethodLocked(view) || mCurrentEditorInfo == null || mCurrentInputMethodSession == null) { return; } @@ -3606,11 +3590,11 @@ public final class InputMethodManager { p.println(" mServedView=" + getServedViewLocked()); p.println(" mNextServedView=" + getNextServedViewLocked()); p.println(" mServedConnecting=" + mServedConnecting); - if (mCurrentTextBoxAttribute != null) { - p.println(" mCurrentTextBoxAttribute:"); - mCurrentTextBoxAttribute.dump(p, " ", false /* dumpExtras */); + if (mCurrentEditorInfo != null) { + p.println(" mCurrentEditorInfo:"); + mCurrentEditorInfo.dump(p, " ", false /* dumpExtras */); } else { - p.println(" mCurrentTextBoxAttribute: null"); + p.println(" mCurrentEditorInfo: null"); } p.println(" mServedInputConnection=" + mServedInputConnection); p.println(" mServedInputConnectionHandler=" + mServedInputConnectionHandler); @@ -3733,8 +3717,8 @@ public final class InputMethodManager { if (mCurRootView != null) { mCurRootView.dumpDebug(proto, VIEW_ROOT_IMPL); } - if (mCurrentTextBoxAttribute != null) { - mCurrentTextBoxAttribute.dumpDebug(proto, EDITOR_INFO); + if (mCurrentEditorInfo != null) { + mCurrentEditorInfo.dumpDebug(proto, EDITOR_INFO); } if (mImeInsetsConsumer != null) { mImeInsetsConsumer.dumpDebug(proto, IME_INSETS_SOURCE_CONSUMER); diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index bb4b1c868bb9..fb298c75c909 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -4599,20 +4599,22 @@ public class Editor { return; } // Skip if the IME has not requested the cursor/anchor position. - if (!imm.isCursorAnchorInfoEnabled()) { + final int knownCursorAnchorInfoModes = + InputConnection.CURSOR_UPDATE_IMMEDIATE | InputConnection.CURSOR_UPDATE_MONITOR; + if ((mInputMethodState.mUpdateCursorAnchorInfoMode & knownCursorAnchorInfoModes) == 0) { return; } Layout layout = mTextView.getLayout(); if (layout == null) { return; } - int mode = imm.getUpdateCursorAnchorInfoMode(); + final int filter = mInputMethodState.mUpdateCursorAnchorInfoFilter; boolean includeEditorBounds = - (mode & InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS) != 0; + (filter & InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS) != 0; boolean includeCharacterBounds = - (mode & InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS) != 0; + (filter & InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS) != 0; boolean includeInsertionMarker = - (mode & InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER) != 0; + (filter & InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER) != 0; boolean includeAll = (!includeEditorBounds && !includeCharacterBounds && !includeInsertionMarker); @@ -4713,6 +4715,10 @@ public class Editor { } imm.updateCursorAnchorInfo(mTextView, builder.build()); + + // Drop the immediate flag if any. + mInputMethodState.mUpdateCursorAnchorInfoMode &= + ~InputConnection.CURSOR_UPDATE_IMMEDIATE; } } @@ -7203,6 +7209,10 @@ public class Editor { boolean mSelectionModeChanged; boolean mContentChanged; int mChangedStart, mChangedEnd, mChangedDelta; + @InputConnection.CursorUpdateMode + int mUpdateCursorAnchorInfoMode; + @InputConnection.CursorUpdateFilter + int mUpdateCursorAnchorInfoFilter; } /** diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index cd072b0cfb31..83d77172031e 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -9005,6 +9005,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public InputConnection onCreateInputConnection(EditorInfo outAttrs) { if (onCheckIsTextEditor() && isEnabled()) { mEditor.createInputMethodStateIfNeeded(); + mEditor.mInputMethodState.mUpdateCursorAnchorInfoMode = 0; + mEditor.mInputMethodState.mUpdateCursorAnchorInfoFilter = 0; + outAttrs.inputType = getInputType(); if (mEditor.mInputContentType != null) { outAttrs.imeOptions = mEditor.mInputContentType.imeOptions; @@ -9061,6 +9064,33 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** + * Called back by the system to handle {@link InputConnection#requestCursorUpdates(int, int)}. + * + * @param cursorUpdateMode modes defined in {@link InputConnection.CursorUpdateMode}. + * @param cursorUpdateFilter modes defined in {@link InputConnection.CursorUpdateFilter}. + * + * @hide + */ + public void onRequestCursorUpdatesInternal( + @InputConnection.CursorUpdateMode int cursorUpdateMode, + @InputConnection.CursorUpdateFilter int cursorUpdateFilter) { + mEditor.mInputMethodState.mUpdateCursorAnchorInfoMode = cursorUpdateMode; + mEditor.mInputMethodState.mUpdateCursorAnchorInfoFilter = cursorUpdateFilter; + if ((cursorUpdateMode & InputConnection.CURSOR_UPDATE_IMMEDIATE) == 0) { + return; + } + if (isInLayout()) { + // In this case, the view hierarchy is currently undergoing a layout pass. + // IMM#updateCursorAnchorInfo is supposed to be called soon after the layout + // pass is finished. + } else { + // This will schedule a layout pass of the view tree, and the layout event + // eventually triggers IMM#updateCursorAnchorInfo. + requestLayout(); + } + } + + /** * If this TextView contains editable content, extract a portion of it * based on the information in <var>request</var> in to <var>outText</var>. * @return Returns true if the text was successfully extracted, else false. diff --git a/core/java/android/window/ClientWindowFrames.java b/core/java/android/window/ClientWindowFrames.java index 51f3fe2551ad..929e81ed9044 100644 --- a/core/java/android/window/ClientWindowFrames.java +++ b/core/java/android/window/ClientWindowFrames.java @@ -17,6 +17,7 @@ package android.window; import android.annotation.NonNull; +import android.annotation.Nullable; import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; @@ -40,6 +41,12 @@ public class ClientWindowFrames implements Parcelable { */ public final @NonNull Rect parentFrame = new Rect(); + /** + * The frame this window attaches to. If this is not null, this is the frame of the parent + * window. + */ + public @Nullable Rect attachedFrame; + public boolean isParentFrameClippedByDisplayCutout; public ClientWindowFrames() { @@ -49,6 +56,9 @@ public class ClientWindowFrames implements Parcelable { frame.set(other.frame); displayFrame.set(other.displayFrame); parentFrame.set(other.parentFrame); + if (other.attachedFrame != null) { + attachedFrame = new Rect(other.attachedFrame); + } isParentFrameClippedByDisplayCutout = other.isParentFrameClippedByDisplayCutout; } @@ -61,6 +71,7 @@ public class ClientWindowFrames implements Parcelable { frame.readFromParcel(in); displayFrame.readFromParcel(in); parentFrame.readFromParcel(in); + attachedFrame = in.readTypedObject(Rect.CREATOR); isParentFrameClippedByDisplayCutout = in.readBoolean(); } @@ -69,6 +80,7 @@ public class ClientWindowFrames implements Parcelable { frame.writeToParcel(dest, flags); displayFrame.writeToParcel(dest, flags); parentFrame.writeToParcel(dest, flags); + dest.writeTypedObject(attachedFrame, flags); dest.writeBoolean(isParentFrameClippedByDisplayCutout); } @@ -78,6 +90,7 @@ public class ClientWindowFrames implements Parcelable { return "ClientWindowFrames{frame=" + frame.toShortString(sb) + " display=" + displayFrame.toShortString(sb) + " parentFrame=" + parentFrame.toShortString(sb) + + (attachedFrame != null ? " attachedFrame=" + attachedFrame.toShortString() : "") + " parentClippedByDisplayCutout=" + isParentFrameClippedByDisplayCutout + "}"; } diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java index 874e3f4ae26a..50afb3ee0a03 100644 --- a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java +++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java @@ -28,6 +28,7 @@ import static com.android.internal.accessibility.util.AccessibilityUtils.isUserS import android.annotation.Nullable; import android.app.Activity; import android.app.AlertDialog; +import android.content.Context; import android.content.DialogInterface; import android.content.res.TypedArray; import android.os.Bundle; @@ -102,16 +103,7 @@ public class AccessibilityShortcutChooserActivity extends Activity { final AccessibilityTarget target = mTargets.get(position); if ((target instanceof AccessibilityServiceTarget) && !target.isShortcutEnabled()) { - mPermissionDialog = new AlertDialog.Builder(this) - .setView(createEnableDialogContentView(this, - (AccessibilityServiceTarget) target, - v -> { - mPermissionDialog.dismiss(); - mTargetAdapter.notifyDataSetChanged(); - }, - v -> mPermissionDialog.dismiss())) - .create(); - mPermissionDialog.show(); + showPermissionDialogIfNeeded(this, (AccessibilityServiceTarget) target, mTargetAdapter); return; } @@ -119,6 +111,24 @@ public class AccessibilityShortcutChooserActivity extends Activity { mTargetAdapter.notifyDataSetChanged(); } + private void showPermissionDialogIfNeeded(Context context, + AccessibilityServiceTarget serviceTarget, ShortcutTargetAdapter targetAdapter) { + if (mPermissionDialog != null) { + return; + } + + mPermissionDialog = new AlertDialog.Builder(context) + .setView(createEnableDialogContentView(context, serviceTarget, + v -> { + mPermissionDialog.dismiss(); + targetAdapter.notifyDataSetChanged(); + }, + v -> mPermissionDialog.dismiss())) + .setOnDismissListener(dialog -> mPermissionDialog = null) + .create(); + mPermissionDialog.show(); + } + private void onDoneButtonClicked() { mTargets.clear(); mTargets.addAll(getTargets(this, mShortcutType)); diff --git a/core/java/com/android/internal/inputmethod/EditableInputConnection.java b/core/java/com/android/internal/inputmethod/EditableInputConnection.java index f8b268fbc1f2..be7501fea7d0 100644 --- a/core/java/com/android/internal/inputmethod/EditableInputConnection.java +++ b/core/java/com/android/internal/inputmethod/EditableInputConnection.java @@ -215,13 +215,15 @@ public final class EditableInputConnection extends BaseInputConnection public boolean requestCursorUpdates(int cursorUpdateMode) { if (DEBUG) Log.v(TAG, "requestUpdateCursorAnchorInfo " + cursorUpdateMode); - // It is possible that any other bit is used as a valid flag in a future release. - // We should reject the entire request in such a case. - final int knownFlagMask = InputConnection.CURSOR_UPDATE_IMMEDIATE - | InputConnection.CURSOR_UPDATE_MONITOR - | InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS + final int knownModeFlags = InputConnection.CURSOR_UPDATE_IMMEDIATE + | InputConnection.CURSOR_UPDATE_MONITOR; + final int knownFilterFlags = InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS | InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER | InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS; + + // It is possible that any other bit is used as a valid flag in a future release. + // We should reject the entire request in such a case. + final int knownFlagMask = knownModeFlags | knownFilterFlags; final int unknownFlags = cursorUpdateMode & ~knownFlagMask; if (unknownFlags != 0) { if (DEBUG) { @@ -237,21 +239,10 @@ public final class EditableInputConnection extends BaseInputConnection // CursorAnchorInfo is temporarily unavailable. return false; } - mIMM.setUpdateCursorAnchorInfoMode(cursorUpdateMode); - if ((cursorUpdateMode & InputConnection.CURSOR_UPDATE_IMMEDIATE) != 0) { - if (mTextView == null) { - // In this case, FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE is silently ignored. - // TODO: Return some notification code for the input method that indicates - // FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE is ignored. - } else if (mTextView.isInLayout()) { - // In this case, the view hierarchy is currently undergoing a layout pass. - // IMM#updateCursorAnchorInfo is supposed to be called soon after the layout - // pass is finished. - } else { - // This will schedule a layout pass of the view tree, and the layout event - // eventually triggers IMM#updateCursorAnchorInfo. - mTextView.requestLayout(); - } + mIMM.setUpdateCursorAnchorInfoMode(cursorUpdateMode); // for UnsupportedAppUsage + if (mTextView != null) { + mTextView.onRequestCursorUpdatesInternal(cursorUpdateMode & knownModeFlags, + cursorUpdateMode & knownFilterFlags); } return true; } diff --git a/core/java/com/android/internal/inputmethod/IInputMethod.aidl b/core/java/com/android/internal/inputmethod/IInputMethod.aidl index 49eafd017687..b8196157ffd9 100644 --- a/core/java/com/android/internal/inputmethod/IInputMethod.aidl +++ b/core/java/com/android/internal/inputmethod/IInputMethod.aidl @@ -46,7 +46,7 @@ oneway interface IInputMethod { void unbindInput(); void startInput(in IBinder startInputToken, in IRemoteInputConnection inputConnection, - in EditorInfo attribute, boolean restarting, int navigationBarFlags, + in EditorInfo editorInfo, boolean restarting, int navigationBarFlags, in ImeOnBackInvokedDispatcher imeDispatcher); void onNavButtonFlagsChanged(int navButtonFlags); diff --git a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java index cb18ccc9788a..2bef10f1aee5 100644 --- a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java +++ b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java @@ -176,6 +176,10 @@ public final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub private final AtomicInteger mCurrentSessionId = new AtomicInteger(0); private final AtomicBoolean mHasPendingInvalidation = new AtomicBoolean(); + private final AtomicBoolean mIsCursorAnchorInfoMonitoring = new AtomicBoolean(false); + private final AtomicBoolean mHasPendingImmediateCursorAnchorInfoUpdate = + new AtomicBoolean(false); + public RemoteInputConnectionImpl(@NonNull Looper looper, @NonNull InputConnection inputConnection, @NonNull InputMethodManager inputMethodManager, @Nullable View servedView) { @@ -223,6 +227,33 @@ public final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub } /** + * Gets and resets {@link #mHasPendingImmediateCursorAnchorInfoUpdate}. + * + * <p>Calling this method resets {@link #mHasPendingImmediateCursorAnchorInfoUpdate}. This + * means that the second call of this method returns {@code false} unless the IME requests + * {@link android.view.inputmethod.CursorAnchorInfo} again with + * {@link InputConnection#CURSOR_UPDATE_IMMEDIATE} flag.</p> + * + * @return {@code true} if there is any pending request for + * {@link android.view.inputmethod.CursorAnchorInfo} with + * {@link InputConnection#CURSOR_UPDATE_IMMEDIATE} flag. + */ + @AnyThread + public boolean resetHasPendingImmediateCursorAnchorInfoUpdate() { + return mHasPendingImmediateCursorAnchorInfoUpdate.getAndSet(false); + } + + /** + * @return {@code true} if there is any active request for + * {@link android.view.inputmethod.CursorAnchorInfo} with + * {@link InputConnection#CURSOR_UPDATE_MONITOR} flag. + */ + @AnyThread + public boolean isCursorAnchorInfoMonitoring() { + return mIsCursorAnchorInfoMonitoring.get(); + } + + /** * Schedule a task to execute * {@link InputMethodManager#doInvalidateInput(RemoteInputConnectionImpl, TextSnapshot, int)} * on the associated Handler if not yet scheduled. @@ -998,11 +1029,20 @@ public final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub // requestCursorUpdates() is not currently supported across displays. return false; } + final boolean hasImmediate = + (cursorUpdateMode & InputConnection.CURSOR_UPDATE_IMMEDIATE) != 0; + final boolean hasMonitoring = + (cursorUpdateMode & InputConnection.CURSOR_UPDATE_MONITOR) != 0; + boolean result = false; try { - return ic.requestCursorUpdates(cursorUpdateMode, cursorUpdateFilter); + result = ic.requestCursorUpdates(cursorUpdateMode, cursorUpdateFilter); + return result; } catch (AbstractMethodError ignored) { // TODO(b/199934664): See if we can remove this by providing a default impl. return false; + } finally { + mHasPendingImmediateCursorAnchorInfoUpdate.set(result && hasImmediate); + mIsCursorAnchorInfoMonitoring.set(result && hasMonitoring); } } diff --git a/core/java/com/android/internal/os/TEST_MAPPING b/core/java/com/android/internal/os/TEST_MAPPING index 5a5165ddd7e2..60b160ad18c2 100644 --- a/core/java/com/android/internal/os/TEST_MAPPING +++ b/core/java/com/android/internal/os/TEST_MAPPING @@ -39,8 +39,7 @@ "name": "FrameworksServicesTests", "options": [ { "include-filter": "com.android.server.am.BatteryStatsServiceTest" }, - { "include-filter": "com.android.server.am.MeasuredEnergySnapshotTest" }, - { "include-filter": "com.android.server.am.BatteryExternalStatsWorkerTest" } + { "include-filter": "com.android.server.power.stats.BatteryStatsTests" } ] }, { @@ -75,13 +74,11 @@ "name": "FrameworksCoreTests", "options": [ { - "include-filter": "com.android.internal.os.BstatsCpuTimesValidationTest" + "include-filter": "com.android.server.power.stats.BstatsCpuTimesValidationTest" } ], "file_patterns": [ - "BatteryStatsImpl\\.java", - "KernelCpuUidFreqTimeReader\\.java", - "KernelSingleUidTimeReader\\.java" + "Kernel[^/]*\\.java" ] } ] diff --git a/core/java/com/android/internal/os/ZygoteConfig.java b/core/java/com/android/internal/os/ZygoteConfig.java index 6ebcae182b11..e5dc874d1f90 100644 --- a/core/java/com/android/internal/os/ZygoteConfig.java +++ b/core/java/com/android/internal/os/ZygoteConfig.java @@ -16,6 +16,9 @@ package com.android.internal.os; +import android.os.SystemProperties; +import android.provider.DeviceConfig; + /** * Flag names for configuring the zygote. * @@ -26,15 +29,87 @@ public class ZygoteConfig { /** If {@code true}, enables the unspecialized app process (USAP) pool feature */ public static final String USAP_POOL_ENABLED = "usap_pool_enabled"; + /** + * The default value for enabling the unspecialized app process (USAP) pool. This value will + * not be used if the devices has a DeviceConfig profile pushed to it that contains a value for + * this key or if the System Property dalvik.vm.usap_pool_enabled is set. + */ + public static final boolean USAP_POOL_ENABLED_DEFAULT = false; + + + /** The threshold used to determine if the pool should be refilled */ public static final String USAP_POOL_REFILL_THRESHOLD = "usap_refill_threshold"; + public static final int USAP_POOL_REFILL_THRESHOLD_DEFAULT = 1; + + + /** The maximum number of processes to keep in the USAP pool */ public static final String USAP_POOL_SIZE_MAX = "usap_pool_size_max"; + public static final int USAP_POOL_SIZE_MAX_DEFAULT = 3; + + /** + * The maximim value that will be accepted from the USAP_POOL_SIZE_MAX device property. + * is a mirror of USAP_POOL_MAX_LIMIT found in com_android_internal_os_Zygote.cpp. + */ + public static final int USAP_POOL_SIZE_MAX_LIMIT = 100; + + + /** The minimum number of processes to keep in the USAP pool */ public static final String USAP_POOL_SIZE_MIN = "usap_pool_size_min"; + public static final int USAP_POOL_SIZE_MIN_DEFAULT = 1; + + /** + * The minimum value that will be accepted from the USAP_POOL_SIZE_MIN device property. + */ + public static final int USAP_POOL_SIZE_MIN_LIMIT = 1; + + + /** The number of milliseconds to delay before refilling the USAP pool */ public static final String USAP_POOL_REFILL_DELAY_MS = "usap_pool_refill_delay_ms"; + + public static final int USAP_POOL_REFILL_DELAY_MS_DEFAULT = 3000; + + public static final String PROPERTY_PREFIX_DEVICE_CONFIG = "persist.device_config"; + public static final String PROPERTY_PREFIX_SYSTEM = "dalvik.vm."; + + private static String getDeviceConfig(String name) { + return SystemProperties.get( + String.join( + ".", + PROPERTY_PREFIX_DEVICE_CONFIG, + DeviceConfig.NAMESPACE_RUNTIME_NATIVE, + name)); + } + + /** + * Get a property value from SystemProperties and convert it to an integer value. + */ + public static int getInt(String name, int defaultValue) { + final String propString = getDeviceConfig(name); + + if (!propString.isEmpty()) { + return Integer.parseInt(propString); + } else { + return SystemProperties.getInt(PROPERTY_PREFIX_SYSTEM + name, defaultValue); + } + } + + /** + * Get a property value from SystemProperties and convert it to a Boolean value. + */ + public static boolean getBool(String name, boolean defaultValue) { + final String propString = getDeviceConfig(name); + + if (!propString.isEmpty()) { + return Boolean.parseBoolean(propString); + } else { + return SystemProperties.getBoolean(PROPERTY_PREFIX_SYSTEM + name, defaultValue); + } + } } diff --git a/core/java/com/android/internal/os/ZygoteServer.java b/core/java/com/android/internal/os/ZygoteServer.java index 4d2266b2eba5..f8598f2f471a 100644 --- a/core/java/com/android/internal/os/ZygoteServer.java +++ b/core/java/com/android/internal/os/ZygoteServer.java @@ -49,26 +49,6 @@ class ZygoteServer { // TODO (chriswailes): Change this so it is set with Zygote or ZygoteSecondary as appropriate public static final String TAG = "ZygoteServer"; - /** - * The maximim value that will be accepted from the USAP_POOL_SIZE_MAX device property. - * is a mirror of USAP_POOL_MAX_LIMIT found in com_android_internal_os_Zygote.cpp. - */ - private static final int USAP_POOL_SIZE_MAX_LIMIT = 100; - - /** - * The minimum value that will be accepted from the USAP_POOL_SIZE_MIN device property. - */ - private static final int USAP_POOL_SIZE_MIN_LIMIT = 1; - - /** The default value used for the USAP_POOL_SIZE_MAX device property */ - private static final String USAP_POOL_SIZE_MAX_DEFAULT = "10"; - - /** The default value used for the USAP_POOL_SIZE_MIN device property */ - private static final String USAP_POOL_SIZE_MIN_DEFAULT = "1"; - - /** The default value used for the USAP_REFILL_DELAY_MS device property */ - private static final String USAP_POOL_REFILL_DELAY_MS_DEFAULT = "3000"; - /** The "not a timestamp" value for the refill delay timestamp mechanism. */ private static final int INVALID_TIMESTAMP = -1; @@ -264,46 +244,35 @@ class ZygoteServer { private void fetchUsapPoolPolicyProps() { if (mUsapPoolSupported) { - final String usapPoolSizeMaxPropString = Zygote.getConfigurationProperty( - ZygoteConfig.USAP_POOL_SIZE_MAX, USAP_POOL_SIZE_MAX_DEFAULT); - - if (!usapPoolSizeMaxPropString.isEmpty()) { - mUsapPoolSizeMax = Integer.min(Integer.parseInt( - usapPoolSizeMaxPropString), USAP_POOL_SIZE_MAX_LIMIT); - } - - final String usapPoolSizeMinPropString = Zygote.getConfigurationProperty( - ZygoteConfig.USAP_POOL_SIZE_MIN, USAP_POOL_SIZE_MIN_DEFAULT); - - if (!usapPoolSizeMinPropString.isEmpty()) { - mUsapPoolSizeMin = Integer.max( - Integer.parseInt(usapPoolSizeMinPropString), USAP_POOL_SIZE_MIN_LIMIT); - } - - final String usapPoolRefillThresholdPropString = Zygote.getConfigurationProperty( + mUsapPoolSizeMax = Integer.min( + ZygoteConfig.getInt( + ZygoteConfig.USAP_POOL_SIZE_MAX, + ZygoteConfig.USAP_POOL_SIZE_MAX_DEFAULT), + ZygoteConfig.USAP_POOL_SIZE_MAX_LIMIT); + + mUsapPoolSizeMin = Integer.max( + ZygoteConfig.getInt( + ZygoteConfig.USAP_POOL_SIZE_MIN, + ZygoteConfig.USAP_POOL_SIZE_MIN_DEFAULT), + ZygoteConfig.USAP_POOL_SIZE_MIN_LIMIT); + + mUsapPoolRefillThreshold = Integer.min( + ZygoteConfig.getInt( ZygoteConfig.USAP_POOL_REFILL_THRESHOLD, - Integer.toString(mUsapPoolSizeMax / 2)); + ZygoteConfig.USAP_POOL_REFILL_THRESHOLD_DEFAULT), + mUsapPoolSizeMax); - if (!usapPoolRefillThresholdPropString.isEmpty()) { - mUsapPoolRefillThreshold = Integer.min( - Integer.parseInt(usapPoolRefillThresholdPropString), - mUsapPoolSizeMax); - } - - final String usapPoolRefillDelayMsPropString = Zygote.getConfigurationProperty( - ZygoteConfig.USAP_POOL_REFILL_DELAY_MS, USAP_POOL_REFILL_DELAY_MS_DEFAULT); - - if (!usapPoolRefillDelayMsPropString.isEmpty()) { - mUsapPoolRefillDelayMs = Integer.parseInt(usapPoolRefillDelayMsPropString); - } + mUsapPoolRefillDelayMs = ZygoteConfig.getInt( + ZygoteConfig.USAP_POOL_REFILL_DELAY_MS, + ZygoteConfig.USAP_POOL_REFILL_DELAY_MS_DEFAULT); // Validity check if (mUsapPoolSizeMin >= mUsapPoolSizeMax) { Log.w(TAG, "The max size of the USAP pool must be greater than the minimum size." + " Restoring default values."); - mUsapPoolSizeMax = Integer.parseInt(USAP_POOL_SIZE_MAX_DEFAULT); - mUsapPoolSizeMin = Integer.parseInt(USAP_POOL_SIZE_MIN_DEFAULT); + mUsapPoolSizeMax = ZygoteConfig.USAP_POOL_SIZE_MAX_DEFAULT; + mUsapPoolSizeMin = ZygoteConfig.USAP_POOL_SIZE_MIN_DEFAULT; mUsapPoolRefillThreshold = mUsapPoolSizeMax / 2; } } diff --git a/core/java/com/android/internal/power/TEST_MAPPING b/core/java/com/android/internal/power/TEST_MAPPING index 96f31bcbe5b2..c6cab183d970 100644 --- a/core/java/com/android/internal/power/TEST_MAPPING +++ b/core/java/com/android/internal/power/TEST_MAPPING @@ -11,8 +11,7 @@ "name": "FrameworksServicesTests", "options": [ { "include-filter": "com.android.server.am.BatteryStatsServiceTest" }, - { "include-filter": "com.android.server.am.MeasuredEnergySnapshotTest" }, - { "include-filter": "com.android.server.am.BatteryExternalStatsWorkerTest" } + { "include-filter": "com.android.server.power.stats.BatteryStatsTests" } ] } ] diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 9b19e021cc4f..508e4450d0f9 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -49,14 +49,14 @@ interface IInputMethodManager { boolean hideSoftInput(in IInputMethodClient client, IBinder windowToken, int flags, in ResultReceiver resultReceiver, int reason); // 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. + // has gained focus, and if 'editorInfo' is non-null then also does startInput. // @NonNull InputBindResult startInputOrWindowGainedFocus( /* @StartInputReason */ int startInputReason, in IInputMethodClient client, in IBinder windowToken, /* @StartInputFlags */ int startInputFlags, /* @android.view.WindowManager.LayoutParams.SoftInputModeFlags */ int softInputMode, - int windowFlags, in EditorInfo attribute, in IRemoteInputConnection inputConnection, + int windowFlags, in EditorInfo editorInfo, in IRemoteInputConnection inputConnection, in IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, int unverifiedTargetSdkVersion, in ImeOnBackInvokedDispatcher imeDispatcher); diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java index 61536e8960e2..409ab01bf74b 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java +++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java @@ -94,6 +94,11 @@ public class ActionMenuItemView extends TextView updateTextButtonVisibility(); } + @Override + public CharSequence getAccessibilityClassName() { + return android.widget.Button.class.getName(); + } + /** * Whether action menu items should obey the "withText" showAsAction flag. This may be set to * false for situations where space is extremely limited. --> diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 34fd478baeda..5cb0de324106 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -94,6 +94,10 @@ #include "nativebridge/native_bridge.h" +#if defined(__BIONIC__) +extern "C" void android_reset_stack_guards(); +#endif + namespace { // TODO (chriswailes): Add a function to initialize native Zygote data. @@ -412,6 +416,7 @@ static void sendSigChildStatus(const pid_t pid, const uid_t uid, const int statu } // This signal handler is for zygote mode, since the zygote must reap its children +NO_STACK_PROTECTOR static void SigChldHandler(int /*signal_number*/, siginfo_t* info, void* /*ucontext*/) { pid_t pid; int status; @@ -2042,6 +2047,7 @@ static std::set<int>* gPreloadFds = nullptr; static bool gPreloadFdsExtracted = false; // Utility routine to fork a process from the zygote. +NO_STACK_PROTECTOR pid_t zygote::ForkCommon(JNIEnv* env, bool is_system_server, const std::vector<int>& fds_to_close, const std::vector<int>& fds_to_ignore, @@ -2098,6 +2104,11 @@ pid_t zygote::ForkCommon(JNIEnv* env, bool is_system_server, setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_MIN); } +#if defined(__BIONIC__) + // Reset the stack guard for the new process. + android_reset_stack_guards(); +#endif + // The child process. PreApplicationInit(); @@ -2130,6 +2141,7 @@ static void com_android_internal_os_Zygote_nativePreApplicationInit(JNIEnv*, jcl PreApplicationInit(); } +NO_STACK_PROTECTOR static jint com_android_internal_os_Zygote_nativeForkAndSpecialize( JNIEnv* env, jclass, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, @@ -2184,6 +2196,7 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize( return pid; } +NO_STACK_PROTECTOR static jint com_android_internal_os_Zygote_nativeForkSystemServer( JNIEnv* env, jclass, uid_t uid, gid_t gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jlong permitted_capabilities, @@ -2255,6 +2268,7 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer( * @param is_priority_fork Controls the nice level assigned to the newly created process * @return child pid in the parent, 0 in the child */ +NO_STACK_PROTECTOR static jint com_android_internal_os_Zygote_nativeForkApp(JNIEnv* env, jclass, jint read_pipe_fd, @@ -2269,6 +2283,7 @@ static jint com_android_internal_os_Zygote_nativeForkApp(JNIEnv* env, args_known == JNI_TRUE, is_priority_fork == JNI_TRUE, true); } +NO_STACK_PROTECTOR int zygote::forkApp(JNIEnv* env, int read_pipe_fd, int write_pipe_fd, diff --git a/core/jni/com_android_internal_os_Zygote.h b/core/jni/com_android_internal_os_Zygote.h index b87396cbd5f5..15f53e0814e3 100644 --- a/core/jni/com_android_internal_os_Zygote.h +++ b/core/jni/com_android_internal_os_Zygote.h @@ -20,6 +20,14 @@ #define LOG_TAG "Zygote" #define ATRACE_TAG ATRACE_TAG_DALVIK +/* + * All functions that lead to ForkCommon must be marked with the + * no_stack_protector attributed. Because ForkCommon changes the stack + * protector cookie, all of the guard checks on the frames above ForkCommon + * would fail when they are popped. + */ +#define NO_STACK_PROTECTOR __attribute__((no_stack_protector)) + #include <jni.h> #include <vector> #include <android-base/stringprintf.h> diff --git a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp index add645dee718..2b5b8f7a108e 100644 --- a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp +++ b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp @@ -377,6 +377,7 @@ void com_android_internal_os_ZygoteCommandBuffer_nativeReadFullyAndReset(JNIEnv* // We only process fork commands if the peer uid matches expected_uid. // For every fork command after the first, we check that the requested uid is at // least minUid. +NO_STACK_PROTECTOR jboolean com_android_internal_os_ZygoteCommandBuffer_nativeForkRepeatedly( JNIEnv* env, jclass, diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml index 0756d68063f5..fd787f6ea470 100644 --- a/core/res/res/layout/notification_template_material_base.xml +++ b/core/res/res/layout/notification_template_material_base.xml @@ -138,7 +138,7 @@ </LinearLayout> - <ImageView + <com.android.internal.widget.CachingIconView android:id="@+id/right_icon" android:layout_width="@dimen/notification_right_icon_size" android:layout_height="@dimen/notification_right_icon_size" @@ -150,6 +150,8 @@ android:clipToOutline="true" android:importantForAccessibility="no" android:scaleType="centerCrop" + android:maxDrawableWidth="@dimen/notification_right_icon_size" + android:maxDrawableHeight="@dimen/notification_right_icon_size" /> <FrameLayout diff --git a/core/res/res/layout/notification_template_right_icon.xml b/core/res/res/layout/notification_template_right_icon.xml index f163ed5f955a..8b3b795f7473 100644 --- a/core/res/res/layout/notification_template_right_icon.xml +++ b/core/res/res/layout/notification_template_right_icon.xml @@ -13,7 +13,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License --> -<ImageView +<com.android.internal.widget.CachingIconView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/right_icon" android:layout_width="@dimen/notification_right_icon_size" @@ -25,4 +25,6 @@ android:clipToOutline="true" android:importantForAccessibility="no" android:scaleType="centerCrop" + android:maxDrawableWidth="@dimen/notification_right_icon_size" + android:maxDrawableHeight="@dimen/notification_right_icon_size" /> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index dac13ff62e3b..2876161628c8 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2905,7 +2905,7 @@ <!-- System bluetooth stack package name --> <string name="config_systemBluetoothStack" translatable="false"> - com.android.bluetooth.services + com.android.bluetooth </string> <!-- Flag indicating that the media framework should not allow changes or mute on any @@ -5144,11 +5144,11 @@ <!-- Whether displaying letterbox education is enabled for letterboxed fullscreen apps. --> <bool name="config_letterboxIsEducationEnabled">false</bool> - <!-- Default min aspect ratio for unresizable apps which is used when an app doesn't specify - android:minAspectRatio in accordance with CDD 7.1.1.2 requirement: - https://source.android.com/compatibility/12/android-12-cdd#7112_screen_aspect_ratio. - An exception will be thrown if the given aspect ratio < 4:3. --> - <item name="config_letterboxDefaultMinAspectRatioForUnresizableApps" format="float" type="dimen">1.5</item> + <!-- Default min aspect ratio for unresizable apps which are eligible for size compat mode. + Values <= 1.0 will be ignored. Activity min/max aspect ratio restrictions will still be + espected so this override can control the maximum screen area that can be occupied by + the app in the letterbox mode. --> + <item name="config_letterboxDefaultMinAspectRatioForUnresizableApps" format="float" type="dimen">0.0</item> <!-- Whether using split screen aspect ratio as a default aspect ratio for unresizable apps. --> <bool name="config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled">false</bool> @@ -5657,7 +5657,7 @@ <bool name="config_enableSafetyCenter">true</bool> <!-- Config for whether Safety Protection is enabled. --> - <bool name="config_safetyProtectionEnabled">false</bool> + <bool name="config_safetyProtectionEnabled">true</bool> <!-- Flag indicating if help links for Settings app should be enabled. --> <bool name="config_settingsHelpLinksEnabled">false</bool> diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml index edebc7a8b604..11c245b4c85c 100644 --- a/core/res/res/values/public-staging.xml +++ b/core/res/res/values/public-staging.xml @@ -162,6 +162,8 @@ </staging-public-group> <staging-public-group type="bool" first-id="0x01be0000"> + <!-- @hide @SystemApi --> + <public name="config_safetyProtectionEnabled" /> </staging-public-group> <staging-public-group type="fraction" first-id="0x01bd0000"> diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index 3e4b1cc87ef8..e15458680e66 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -161,6 +161,9 @@ <!-- ChooserActivityTest permissions--> <uses-permission android:name="android.permission.SET_CLIP_SOURCE" /> + <!-- AccessibilityShortcutChooserActivityTest permissions --> + <uses-permission android:name="android.permission.MANAGE_ACCESSIBILITY" /> + <application android:theme="@style/Theme" android:supportsRtl="true" @@ -1414,6 +1417,7 @@ <activity android:name="com.android.internal.app.ChooserWrapperActivity"/> <activity android:name="com.android.internal.app.ResolverWrapperActivity"/> <activity android:name="com.android.internal.app.IntentForwarderActivityTest$IntentForwarderWrapperActivity"/> + <activity android:name="com.android.internal.accessibility.AccessibilityShortcutChooserActivityTest$TestAccessibilityShortcutChooserActivity"/> <receiver android:name="android.app.activity.AbortReceiver" android:exported="true"> @@ -1680,6 +1684,17 @@ android:resizeableActivity="true" android:exported="true"> </activity> + + <service android:name="android.view.stylus.HandwritingImeService" + android:label="Handwriting IME" + android:permission="android.permission.BIND_INPUT_METHOD" + android:exported="true"> + <intent-filter> + <action android:name="android.view.InputMethod"/> + </intent-filter> + <meta-data android:name="android.view.im" + android:resource="@xml/ime_meta_handwriting"/> + </service> </application> <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" diff --git a/core/tests/coretests/res/xml/ime_meta_handwriting.xml b/core/tests/coretests/res/xml/ime_meta_handwriting.xml new file mode 100644 index 000000000000..24c0c255c2f3 --- /dev/null +++ b/core/tests/coretests/res/xml/ime_meta_handwriting.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<input-method + xmlns:android="http://schemas.android.com/apk/res/android" + android:settingsActivity="com.android.inputmethod.latin.settings.SettingsActivity" + android:supportsStylusHandwriting="true"/> diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingImeService.java b/core/tests/coretests/src/android/view/stylus/HandwritingImeService.java new file mode 100644 index 000000000000..98cf6b25152d --- /dev/null +++ b/core/tests/coretests/src/android/view/stylus/HandwritingImeService.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.stylus; + +import android.content.ComponentName; +import android.inputmethodservice.InputMethodService; + +public class HandwritingImeService extends InputMethodService { + private static final String PACKAGE_NAME = "com.android.frameworks.coretests"; + + private static ComponentName getComponentName() { + return new ComponentName(PACKAGE_NAME, HandwritingImeService.class.getName()); + } + + static String getImeId() { + return getComponentName().flattenToShortString(); + } +} diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java index 647e410d8c28..8d3ee2a15dce 100644 --- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java +++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java @@ -43,10 +43,15 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.compatibility.common.util.PollingCheck; + +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.concurrent.TimeUnit; + /** * Tests for {@link HandwritingInitiator} * @@ -60,7 +65,7 @@ public class HandwritingInitiatorTest { private static final long TIMEOUT = ViewConfiguration.getLongPressTimeout(); private static final int HW_BOUNDS_OFFSETS_LEFT_PX = 10; private static final int HW_BOUNDS_OFFSETS_TOP_PX = 20; - private static final int HW_BOUNDS_OFFSETS_RIGHT_PX = 30; + private static final int HW_BOUNDS_OFFSETS_RIGHT_PX = 30; private static final int HW_BOUNDS_OFFSETS_BOTTOM_PX = 40; private int mHandwritingSlop = 4; @@ -71,9 +76,17 @@ public class HandwritingInitiatorTest { private Context mContext; @Before - public void setup() { - final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation(); - mContext = mInstrumentation.getTargetContext(); + public void setup() throws Exception { + final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); + mContext = instrumentation.getTargetContext(); + + String imeId = HandwritingImeService.getImeId(); + instrumentation.getUiAutomation().executeShellCommand("ime enable " + imeId); + instrumentation.getUiAutomation().executeShellCommand("ime set " + imeId); + PollingCheck.check("Check that stylus handwriting is available", + TimeUnit.SECONDS.toMillis(10), + () -> mContext.getSystemService(InputMethodManager.class) + .isStylusHandwritingAvailable()); final ViewConfiguration viewConfiguration = ViewConfiguration.get(mContext); mHandwritingSlop = viewConfiguration.getScaledHandwritingSlop(); @@ -90,22 +103,32 @@ public class HandwritingInitiatorTest { mHandwritingInitiator.updateHandwritingAreasForView(mTestView); } + @After + public void tearDown() throws Exception { + InstrumentationRegistry.getInstrumentation().getUiAutomation() + .executeShellCommand("ime reset"); + } + @Test public void onTouchEvent_startHandwriting_when_stylusMoveOnce_withinHWArea() { mHandwritingInitiator.onInputConnectionCreated(mTestView); final int x1 = (sHwArea.left + sHwArea.right) / 2; final int y1 = (sHwArea.top + sHwArea.bottom) / 2; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); - mHandwritingInitiator.onTouchEvent(stylusEvent1); + boolean onTouchEventResult1 = mHandwritingInitiator.onTouchEvent(stylusEvent1); final int x2 = x1 + mHandwritingSlop * 2; final int y2 = y1; MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0); - mHandwritingInitiator.onTouchEvent(stylusEvent2); + boolean onTouchEventResult2 = mHandwritingInitiator.onTouchEvent(stylusEvent2); // Stylus movement within HandwritingArea should trigger IMM.startHandwriting once. verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView); + assertThat(onTouchEventResult1).isFalse(); + // After IMM.startHandwriting is triggered, onTouchEvent should return true for ACTION_MOVE + // events so that the events are not dispatched to the view tree. + assertThat(onTouchEventResult2).isTrue(); } @Test @@ -114,24 +137,38 @@ public class HandwritingInitiatorTest { final int x1 = (sHwArea.left + sHwArea.right) / 2; final int y1 = (sHwArea.top + sHwArea.bottom) / 2; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); - mHandwritingInitiator.onTouchEvent(stylusEvent1); + boolean onTouchEventResult1 = mHandwritingInitiator.onTouchEvent(stylusEvent1); - final int x2 = x1 + mHandwritingSlop * 2; + final int x2 = x1 + mHandwritingSlop / 2; final int y2 = y1; MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0); - mHandwritingInitiator.onTouchEvent(stylusEvent2); - + boolean onTouchEventResult2 = mHandwritingInitiator.onTouchEvent(stylusEvent2); final int x3 = x2 + mHandwritingSlop * 2; - final int y3 = y2; + final int y3 = y1; MotionEvent stylusEvent3 = createStylusEvent(ACTION_MOVE, x3, y3, 0); - mHandwritingInitiator.onTouchEvent(stylusEvent3); + boolean onTouchEventResult3 = mHandwritingInitiator.onTouchEvent(stylusEvent3); + + final int x4 = x3 + mHandwritingSlop * 2; + final int y4 = y1; + MotionEvent stylusEvent4 = createStylusEvent(ACTION_MOVE, x4, y4, 0); + boolean onTouchEventResult4 = mHandwritingInitiator.onTouchEvent(stylusEvent4); - MotionEvent stylusEvent4 = createStylusEvent(ACTION_UP, x2, y2, 0); - mHandwritingInitiator.onTouchEvent(stylusEvent4); + MotionEvent stylusEvent5 = createStylusEvent(ACTION_UP, x4, y4, 0); + boolean onTouchEventResult5 = mHandwritingInitiator.onTouchEvent(stylusEvent5); // It only calls startHandwriting once for each ACTION_DOWN. verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView); + assertThat(onTouchEventResult1).isFalse(); + // stylusEvent2 does not trigger IMM.startHandwriting since the touch slop distance has not + // been exceeded. onTouchEvent should return false so that the event is dispatched to the + // view tree. + assertThat(onTouchEventResult2).isFalse(); + // After IMM.startHandwriting is triggered by stylusEvent3, onTouchEvent should return true + // for ACTION_MOVE events so that the events are not dispatched to the view tree. + assertThat(onTouchEventResult3).isTrue(); + assertThat(onTouchEventResult4).isTrue(); + assertThat(onTouchEventResult5).isFalse(); } @Test @@ -189,6 +226,32 @@ public class HandwritingInitiatorTest { } @Test + public void onTouchEvent_notStartHandwriting_whenHandwritingNotAvailable() throws Exception { + InstrumentationRegistry.getInstrumentation().getUiAutomation() + .executeShellCommand("ime reset"); + PollingCheck.check("Check that stylus handwriting is unavailable", + TimeUnit.SECONDS.toMillis(10), + () -> !mContext.getSystemService(InputMethodManager.class) + .isStylusHandwritingAvailable()); + + mHandwritingInitiator.onInputConnectionCreated(mTestView); + final int x1 = (sHwArea.left + sHwArea.right) / 2; + final int y1 = (sHwArea.top + sHwArea.bottom) / 2; + MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); + mHandwritingInitiator.onTouchEvent(stylusEvent1); + + final int x2 = x1 + mHandwritingSlop * 2; + final int y2 = y1; + + MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0); + mHandwritingInitiator.onTouchEvent(stylusEvent2); + + // Stylus movement within HandwritingArea should not trigger IMM.startHandwriting since + // the current IME doesn't support handwriting. + verify(mHandwritingInitiator, never()).startHandwriting(mTestView); + } + + @Test public void onTouchEvent_notStartHandwriting_when_stylusTap_withinHWArea() { mHandwritingInitiator.onInputConnectionCreated(mTestView); final int x1 = 200; diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java new file mode 100644 index 000000000000..728757929cfd --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.accessibility; + +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.action.ViewActions.doubleClick; +import static androidx.test.espresso.action.ViewActions.scrollTo; +import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.matcher.RootMatchers.isDialog; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static androidx.test.espresso.matcher.ViewMatchers.withText; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.accessibilityservice.AccessibilityServiceInfo; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.os.Bundle; +import android.os.Handler; +import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.IAccessibilityManager; + +import androidx.lifecycle.Lifecycle; +import androidx.test.core.app.ActivityScenario; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.R; +import com.android.internal.accessibility.dialog.AccessibilityShortcutChooserActivity; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Collections; + +/** + * Tests for {@link AccessibilityShortcutChooserActivity}. + */ +@RunWith(AndroidJUnit4.class) +public class AccessibilityShortcutChooserActivityTest { + private static final String TEST_LABEL = "TEST_LABEL"; + private static final Context sContext = + InstrumentationRegistry.getInstrumentation().getContext(); + private ActivityScenario<TestAccessibilityShortcutChooserActivity> mScenario; + private static IAccessibilityManager sAccessibilityManagerService; + + @Mock + private AccessibilityServiceInfo mAccessibilityServiceInfo; + + @Mock + private ResolveInfo mResolveInfo; + + @Mock + private ServiceInfo mServiceInfo; + + @Mock + private ApplicationInfo mApplicationInfo; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + sAccessibilityManagerService = mock(IAccessibilityManager.class); + + when(mAccessibilityServiceInfo.getResolveInfo()).thenReturn(mResolveInfo); + mResolveInfo.serviceInfo = mServiceInfo; + mServiceInfo.applicationInfo = mApplicationInfo; + when(mResolveInfo.loadLabel(any(PackageManager.class))).thenReturn(TEST_LABEL); + when(mAccessibilityServiceInfo.getComponentName()).thenReturn( + new ComponentName("package", "class")); + when(sAccessibilityManagerService.getInstalledAccessibilityServiceList( + anyInt())).thenReturn(Collections.singletonList(mAccessibilityServiceInfo)); + + mScenario = ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class); + } + + @Test + public void doubleClickServiceTargetAndClickDenyButton_permissionDialogDoesNotExist() { + mScenario.moveToState(Lifecycle.State.CREATED); + mScenario.moveToState(Lifecycle.State.STARTED); + mScenario.moveToState(Lifecycle.State.RESUMED); + onView(withText(R.string.accessibility_select_shortcut_menu_title)).inRoot( + isDialog()).check(matches(isDisplayed())); + onView(withText(R.string.edit_accessibility_shortcut_menu_button)).perform(click()); + + onView(withText(TEST_LABEL)).perform(scrollTo(), doubleClick()); + onView(withId(R.id.accessibility_permission_enable_deny_button)).perform(scrollTo(), + click()); + + onView(withId(R.id.accessibility_permissionDialog_title)).inRoot(isDialog()).check( + doesNotExist()); + mScenario.moveToState(Lifecycle.State.DESTROYED); + } + + /** + * Used for testing. + */ + public static class TestAccessibilityShortcutChooserActivity extends + AccessibilityShortcutChooserActivity { + private AccessibilityManager mAccessibilityManager; + + @Override + protected void onCreate(Bundle savedInstanceState) { + mAccessibilityManager = new AccessibilityManager(sContext, new Handler(getMainLooper()), + sAccessibilityManagerService, /* userId= */ 0, /* serviceConnect= */ true); + super.onCreate(savedInstanceState); + } + + @Override + public Object getSystemService(String name) { + if (Context.ACCESSIBILITY_SERVICE.equals(name)) { + return mAccessibilityManager; + } + + return super.getSystemService(name); + } + } +} diff --git a/data/keyboards/Vendor_0957_Product_0006.idc b/data/keyboards/Vendor_0957_Product_0006.idc new file mode 100644 index 000000000000..842307893448 --- /dev/null +++ b/data/keyboards/Vendor_0957_Product_0006.idc @@ -0,0 +1,25 @@ +# Copyright (C) 2022 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Input Device Configuration file for Google Reference RCU Remote. +# +# + +# Basic Parameters +# Depending on the FLASH configurations, RCUs may have PID 0006 instead +# of 0001. +keyboard.layout = Vendor_0957_Product_0001 +keyboard.doNotWakeByDefault = 1 +audio.mic = 1 diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 242e9ab6beee..41791afa45a3 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -24,9 +24,9 @@ import static androidx.window.extensions.embedding.SplitContainer.getFinishSecon import static androidx.window.extensions.embedding.SplitContainer.isStickyPlaceholderRule; import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenAdjacent; import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked; -import static androidx.window.extensions.embedding.SplitPresenter.boundsSmallerThanMinDimensions; +import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO; import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair; -import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions; +import static androidx.window.extensions.embedding.SplitPresenter.getNonEmbeddedActivityBounds; import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSideBySide; import android.app.Activity; @@ -381,6 +381,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * in a state that the caller shouldn't handle. */ @VisibleForTesting + @GuardedBy("mLock") boolean resolveActivityToContainer(@NonNull Activity activity, boolean isOnReparent) { if (isInPictureInPicture(activity) || activity.isFinishing()) { // We don't embed activity when it is in PIP, or finishing. Return true since we don't @@ -581,8 +582,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } /** Finds the activity below the given activity. */ + @VisibleForTesting @Nullable - private Activity findActivityBelow(@NonNull Activity activity) { + Activity findActivityBelow(@NonNull Activity activity) { Activity activityBelow = null; final TaskFragmentContainer container = getContainerWithActivity(activity); if (container != null) { @@ -606,6 +608,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * Checks if there is a rule to split the two activities. If there is one, puts them into split * and returns {@code true}. Otherwise, returns {@code false}. */ + @GuardedBy("mLock") private boolean putActivitiesIntoSplitIfNecessary(@NonNull Activity primaryActivity, @NonNull Activity secondaryActivity) { final SplitPairRule splitRule = getSplitRule(primaryActivity, secondaryActivity); @@ -616,25 +619,25 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen primaryActivity); final SplitContainer splitContainer = getActiveSplitForContainer(primaryContainer); if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer() - && canReuseContainer(splitRule, splitContainer.getSplitRule()) - && !boundsSmallerThanMinDimensions(primaryContainer.getLastRequestedBounds(), - getMinDimensions(primaryActivity))) { + && canReuseContainer(splitRule, splitContainer.getSplitRule())) { // Can launch in the existing secondary container if the rules share the same // presentation. final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer(); - if (secondaryContainer == getContainerWithActivity(secondaryActivity) - && !boundsSmallerThanMinDimensions(secondaryContainer.getLastRequestedBounds(), - getMinDimensions(secondaryActivity))) { + if (secondaryContainer == getContainerWithActivity(secondaryActivity)) { // The activity is already in the target TaskFragment. return true; } secondaryContainer.addPendingAppearedActivity(secondaryActivity); final WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.reparentActivityToTaskFragment( - secondaryContainer.getTaskFragmentToken(), - secondaryActivity.getActivityToken()); - mPresenter.applyTransaction(wct); - return true; + if (mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity, + secondaryActivity, null /* secondaryIntent */) + != RESULT_EXPAND_FAILED_NO_TF_INFO) { + wct.reparentActivityToTaskFragment( + secondaryContainer.getTaskFragmentToken(), + secondaryActivity.getActivityToken()); + mPresenter.applyTransaction(wct); + return true; + } } // Create new split pair. mPresenter.createNewSplitContainer(primaryActivity, secondaryActivity, splitRule); @@ -642,6 +645,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } private void onActivityConfigurationChanged(@NonNull Activity activity) { + if (activity.isFinishing()) { + // Do nothing if the activity is currently finishing. + return; + } + if (isInPictureInPicture(activity)) { // We don't embed activity when it is in PIP. return; @@ -787,6 +795,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * Returns a container for the new activity intent to launch into as splitting with the primary * activity. */ + @GuardedBy("mLock") @Nullable private TaskFragmentContainer getSecondaryContainerForSplitIfAny( @NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, @@ -800,16 +809,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (splitContainer != null && existingContainer == splitContainer.getPrimaryContainer() && (canReuseContainer(splitRule, splitContainer.getSplitRule()) // TODO(b/231845476) we should always respect clearTop. - || !respectClearTop)) { - final Rect secondaryBounds = splitContainer.getSecondaryContainer() - .getLastRequestedBounds(); - if (secondaryBounds.isEmpty() - || !boundsSmallerThanMinDimensions(secondaryBounds, - getMinDimensions(intent))) { - // Can launch in the existing secondary container if the rules share the same - // presentation. - return splitContainer.getSecondaryContainer(); - } + || !respectClearTop) + && mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity, + null /* secondaryActivity */, intent) != RESULT_EXPAND_FAILED_NO_TF_INFO) { + // Can launch in the existing secondary container if the rules share the same + // presentation. + return splitContainer.getSecondaryContainer(); } // Create a new TaskFragment to split with the primary activity for the new activity. return mPresenter.createNewSplitWithEmptySideContainer(wct, primaryActivity, intent, @@ -863,6 +868,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * if needed. * @param taskId parent Task of the new TaskFragment. */ + @GuardedBy("mLock") TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity, @Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId) { if (activityInTask == null) { @@ -876,7 +882,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen pendingAppearedIntent, taskContainer, this); if (!taskContainer.isTaskBoundsInitialized()) { // Get the initial bounds before the TaskFragment has appeared. - final Rect taskBounds = SplitPresenter.getTaskBoundsFromActivity(activityInTask); + final Rect taskBounds = getNonEmbeddedActivityBounds(activityInTask); if (!taskContainer.setTaskBounds(taskBounds)) { Log.w(TAG, "Can't find bounds from activity=" + activityInTask); } @@ -1119,6 +1125,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } boolean launchPlaceholderIfNecessary(@NonNull Activity activity, boolean isOnCreated) { + if (activity.isFinishing()) { + return false; + } + final TaskFragmentContainer container = getContainerWithActivity(activity); // Don't launch placeholder if the container is occluded. if (container != null && container != getTopActiveContainer(container.getTaskId())) { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index 1b79ad999435..a89847a30d20 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -65,6 +65,41 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { }) private @interface Position {} + /** + * Result of {@link #expandSplitContainerIfNeeded(WindowContainerTransaction, SplitContainer, + * Activity, Activity, Intent)}. + * No need to expand the splitContainer because screen is big enough to + * {@link #shouldShowSideBySide(Rect, SplitRule, Pair)} and minimum dimensions is satisfied. + */ + static final int RESULT_NOT_EXPANDED = 0; + /** + * Result of {@link #expandSplitContainerIfNeeded(WindowContainerTransaction, SplitContainer, + * Activity, Activity, Intent)}. + * The splitContainer should be expanded. It is usually because minimum dimensions is not + * satisfied. + * @see #shouldShowSideBySide(Rect, SplitRule, Pair) + */ + static final int RESULT_EXPANDED = 1; + /** + * Result of {@link #expandSplitContainerIfNeeded(WindowContainerTransaction, SplitContainer, + * Activity, Activity, Intent)}. + * The splitContainer should be expanded, but the client side hasn't received + * {@link android.window.TaskFragmentInfo} yet. Fallback to create new expanded SplitContainer + * instead. + */ + static final int RESULT_EXPAND_FAILED_NO_TF_INFO = 2; + + /** + * Result of {@link #expandSplitContainerIfNeeded(WindowContainerTransaction, SplitContainer, + * Activity, Activity, Intent)} + */ + @IntDef(value = { + RESULT_NOT_EXPANDED, + RESULT_EXPANDED, + RESULT_EXPAND_FAILED_NO_TF_INFO, + }) + private @interface ResultCode {} + private final SplitController mController; SplitPresenter(@NonNull Executor executor, SplitController controller) { @@ -396,6 +431,44 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { super.updateWindowingMode(wct, fragmentToken, windowingMode); } + /** + * Expands the split container if the current split bounds are smaller than the Activity or + * Intent that is added to the container. + * + * @return the {@link ResultCode} based on {@link #shouldShowSideBySide(Rect, SplitRule, Pair)} + * and if {@link android.window.TaskFragmentInfo} has reported to the client side. + */ + @ResultCode + int expandSplitContainerIfNeeded(@NonNull WindowContainerTransaction wct, + @NonNull SplitContainer splitContainer, @NonNull Activity primaryActivity, + @Nullable Activity secondaryActivity, @Nullable Intent secondaryIntent) { + if (secondaryActivity == null && secondaryIntent == null) { + throw new IllegalArgumentException("Either secondaryActivity or secondaryIntent must be" + + " non-null."); + } + final Rect taskBounds = getParentContainerBounds(primaryActivity); + final Pair<Size, Size> minDimensionsPair; + if (secondaryActivity != null) { + minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity, secondaryActivity); + } else { + minDimensionsPair = getActivityIntentMinDimensionsPair(primaryActivity, + secondaryIntent); + } + // Expand the splitContainer if minimum dimensions are not satisfied. + if (!shouldShowSideBySide(taskBounds, splitContainer.getSplitRule(), minDimensionsPair)) { + // If the client side hasn't received TaskFragmentInfo yet, we can't change TaskFragment + // bounds. Return failure to create a new SplitContainer which fills task bounds. + if (splitContainer.getPrimaryContainer().getInfo() == null + || splitContainer.getSecondaryContainer().getInfo() == null) { + return RESULT_EXPAND_FAILED_NO_TF_INFO; + } + expandTaskFragment(wct, splitContainer.getPrimaryContainer().getTaskFragmentToken()); + expandTaskFragment(wct, splitContainer.getSecondaryContainer().getTaskFragmentToken()); + return RESULT_EXPANDED; + } + return RESULT_NOT_EXPANDED; + } + static boolean shouldShowSideBySide(@NonNull Rect parentBounds, @NonNull SplitRule rule) { return shouldShowSideBySide(parentBounds, rule, null /* minimumDimensionPair */); } @@ -565,11 +638,19 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { if (container != null) { return getParentContainerBounds(container); } - return getTaskBoundsFromActivity(activity); + // Obtain bounds from Activity instead because the Activity hasn't been embedded yet. + return getNonEmbeddedActivityBounds(activity); } + /** + * Obtains the bounds from a non-embedded Activity. + * <p> + * Note that callers should use {@link #getParentContainerBounds(Activity)} instead for most + * cases unless we want to obtain task bounds before + * {@link TaskContainer#isTaskBoundsInitialized()}. + */ @NonNull - static Rect getTaskBoundsFromActivity(@NonNull Activity activity) { + static Rect getNonEmbeddedActivityBounds(@NonNull Activity activity) { final WindowConfiguration windowConfiguration = activity.getResources().getConfiguration().windowConfiguration; if (!activity.isInMultiWindowMode()) { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java index 1ac33173668b..c4f37091a491 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java @@ -83,9 +83,9 @@ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub { } @Override - public void onAnimationCancelled() { + public void onAnimationCancelled(boolean isKeyguardOccluded) { if (TaskFragmentAnimationController.DEBUG) { - Log.v(TAG, "onAnimationCancelled"); + Log.v(TAG, "onAnimationCancelled: isKeyguardOccluded=" + isKeyguardOccluded); } mHandler.post(this::cancelAnimation); } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java index cfb32050e32f..18086f552ea3 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java @@ -25,7 +25,10 @@ import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect; import android.annotation.Nullable; import android.app.Activity; +import android.app.ActivityManager; +import android.app.ActivityManager.AppTask; import android.app.Application; +import android.app.WindowConfiguration; import android.content.Context; import android.graphics.Rect; import android.os.Bundle; @@ -180,7 +183,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { if (displayId != DEFAULT_DISPLAY) { Log.w(TAG, "This sample doesn't support display features on secondary displays"); return features; - } else if (activity.isInMultiWindowMode()) { + } else if (isTaskInMultiWindowMode(activity)) { // It is recommended not to report any display features in multi-window mode, since it // won't be possible to synchronize the display feature positions with window movement. return features; @@ -204,6 +207,32 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { } /** + * Checks whether the task associated with the activity is in multi-window. If task info is not + * available it defaults to {@code true}. + */ + private boolean isTaskInMultiWindowMode(@NonNull Activity activity) { + final ActivityManager am = activity.getSystemService(ActivityManager.class); + if (am == null) { + return true; + } + + final List<AppTask> appTasks = am.getAppTasks(); + final int taskId = activity.getTaskId(); + AppTask task = null; + for (AppTask t : appTasks) { + if (t.getTaskInfo().taskId == taskId) { + task = t; + break; + } + } + if (task == null) { + // The task might be removed on the server already. + return true; + } + return WindowConfiguration.inMultiWindowMode(task.getTaskInfo().getWindowingMode()); + } + + /** * Returns {@link true} if a {@link Rect} has zero width and zero height, * {@code false} otherwise. */ diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java index 835c40365cda..effc1a3ef3ea 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.mock; import android.annotation.NonNull; import android.app.Activity; import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Point; import android.graphics.Rect; @@ -57,13 +58,21 @@ public class EmbeddingTestUtils { /** Creates a rule to always split the given activity and the given intent. */ static SplitRule createSplitRule(@NonNull Activity primaryActivity, @NonNull Intent secondaryIntent) { + return createSplitRule(primaryActivity, secondaryIntent, true /* clearTop */); + } + + /** Creates a rule to always split the given activity and the given intent. */ + static SplitRule createSplitRule(@NonNull Activity primaryActivity, + @NonNull Intent secondaryIntent, boolean clearTop) { final Pair<Activity, Intent> targetPair = new Pair<>(primaryActivity, secondaryIntent); return new SplitPairRule.Builder( activityPair -> false, targetPair::equals, w -> true) .setSplitRatio(SPLIT_RATIO) - .setShouldClearTop(true) + .setShouldClearTop(clearTop) + .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY) + .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY) .build(); } @@ -75,6 +84,14 @@ public class EmbeddingTestUtils { true /* clearTop */); } + /** Creates a rule to always split the given activities. */ + static SplitRule createSplitRule(@NonNull Activity primaryActivity, + @NonNull Activity secondaryActivity, boolean clearTop) { + return createSplitRule(primaryActivity, secondaryActivity, + DEFAULT_FINISH_PRIMARY_WITH_SECONDARY, DEFAULT_FINISH_SECONDARY_WITH_PRIMARY, + clearTop); + } + /** Creates a rule to always split the given activities with the given finish behaviors. */ static SplitRule createSplitRule(@NonNull Activity primaryActivity, @NonNull Activity secondaryActivity, int finishPrimaryWithSecondary, @@ -105,4 +122,12 @@ public class EmbeddingTestUtils { false /* isTaskFragmentClearedForPip */, new Point()); } + + static ActivityInfo createActivityInfoWithMinDimensions() { + ActivityInfo aInfo = new ActivityInfo(); + final Rect primaryBounds = getSplitBounds(true /* isPrimary */); + aInfo.windowLayout = new ActivityInfo.WindowLayout(0, 0, 0, 0, 0, + primaryBounds.width() + 1, primaryBounds.height() + 1); + return aInfo; + } } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index ef7728cec387..042547fd30f2 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static androidx.window.extensions.embedding.EmbeddingTestUtils.SPLIT_RATIO; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityInfoWithMinDimensions; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule; import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds; @@ -34,6 +35,7 @@ import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; @@ -436,6 +438,50 @@ public class SplitControllerTest { } @Test + public void testResolveStartActivityIntent_shouldExpandSplitContainer() { + final Intent intent = new Intent().setComponent( + new ComponentName(ApplicationProvider.getApplicationContext(), + MinimumDimensionActivity.class)); + setupSplitRule(mActivity, intent, false /* clearTop */); + final Activity secondaryActivity = createMockActivity(); + addSplitTaskFragments(mActivity, secondaryActivity, false /* clearTop */); + + final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent( + mTransaction, TASK_ID, intent, mActivity); + final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity( + mActivity); + + assertNotNull(mSplitController.getActiveSplitForContainers(primaryContainer, container)); + assertTrue(primaryContainer.areLastRequestedBoundsEqual(null)); + assertTrue(container.areLastRequestedBoundsEqual(null)); + assertEquals(container, mSplitController.getContainerWithActivity(secondaryActivity)); + } + + @Test + public void testResolveStartActivityIntent_noInfo_shouldCreateSplitContainer() { + final Intent intent = new Intent().setComponent( + new ComponentName(ApplicationProvider.getApplicationContext(), + MinimumDimensionActivity.class)); + setupSplitRule(mActivity, intent, false /* clearTop */); + final Activity secondaryActivity = createMockActivity(); + addSplitTaskFragments(mActivity, secondaryActivity, false /* clearTop */); + + final TaskFragmentContainer secondaryContainer = mSplitController + .getContainerWithActivity(secondaryActivity); + secondaryContainer.mInfo = null; + + final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent( + mTransaction, TASK_ID, intent, mActivity); + final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity( + mActivity); + + assertNotNull(mSplitController.getActiveSplitForContainers(primaryContainer, container)); + assertTrue(primaryContainer.areLastRequestedBoundsEqual(null)); + assertTrue(container.areLastRequestedBoundsEqual(null)); + assertNotEquals(container, secondaryContainer); + } + + @Test public void testPlaceActivityInTopContainer() { mSplitController.placeActivityInTopContainer(mActivity); @@ -787,11 +833,7 @@ public class SplitControllerTest { final Activity activityBelow = createMockActivity(); setupSplitRule(mActivity, activityBelow); - ActivityInfo aInfo = new ActivityInfo(); - final Rect primaryBounds = getSplitBounds(true /* isPrimary */); - aInfo.windowLayout = new ActivityInfo.WindowLayout(0, 0, 0, 0, 0, - primaryBounds.width() + 1, primaryBounds.height() + 1); - doReturn(aInfo).when(mActivity).getActivityInfo(); + doReturn(createActivityInfoWithMinDimensions()).when(mActivity).getActivityInfo(); final TaskFragmentContainer container = mSplitController.newContainer(activityBelow, TASK_ID); @@ -810,17 +852,12 @@ public class SplitControllerTest { final Activity activityBelow = createMockActivity(); setupSplitRule(activityBelow, mActivity); - ActivityInfo aInfo = new ActivityInfo(); - final Rect secondaryBounds = getSplitBounds(false /* isPrimary */); - aInfo.windowLayout = new ActivityInfo.WindowLayout(0, 0, 0, 0, 0, - secondaryBounds.width() + 1, secondaryBounds.height() + 1); - doReturn(aInfo).when(mActivity).getActivityInfo(); + doReturn(createActivityInfoWithMinDimensions()).when(mActivity).getActivityInfo(); final TaskFragmentContainer container = mSplitController.newContainer(activityBelow, TASK_ID); container.addPendingAppearedActivity(mActivity); - // Allow to split as primary. boolean result = mSplitController.resolveActivityToContainer(mActivity, false /* isOnReparent */); @@ -828,6 +865,29 @@ public class SplitControllerTest { assertSplitPair(activityBelow, mActivity, true /* matchParentBounds */); } + // Suppress GuardedBy warning on unit tests + @SuppressWarnings("GuardedBy") + @Test + public void testResolveActivityToContainer_minDimensions_shouldExpandSplitContainer() { + final Activity primaryActivity = createMockActivity(); + final Activity secondaryActivity = createMockActivity(); + addSplitTaskFragments(primaryActivity, secondaryActivity, false /* clearTop */); + + setupSplitRule(primaryActivity, mActivity, false /* clearTop */); + doReturn(createActivityInfoWithMinDimensions()).when(mActivity).getActivityInfo(); + doReturn(secondaryActivity).when(mSplitController).findActivityBelow(eq(mActivity)); + + clearInvocations(mSplitPresenter); + boolean result = mSplitController.resolveActivityToContainer(mActivity, + false /* isOnReparent */); + + assertTrue(result); + assertSplitPair(primaryActivity, mActivity, true /* matchParentBounds */); + assertEquals(mSplitController.getContainerWithActivity(secondaryActivity), + mSplitController.getContainerWithActivity(mActivity)); + verify(mSplitPresenter, never()).createNewSplitContainer(any(), any(), any()); + } + @Test public void testResolveActivityToContainer_inUnknownTaskFragment() { doReturn(new Binder()).when(mSplitController).getInitialTaskFragmentToken(mActivity); @@ -944,23 +1004,41 @@ public class SplitControllerTest { /** Setups a rule to always split the given activities. */ private void setupSplitRule(@NonNull Activity primaryActivity, @NonNull Intent secondaryIntent) { - final SplitRule splitRule = createSplitRule(primaryActivity, secondaryIntent); + setupSplitRule(primaryActivity, secondaryIntent, true /* clearTop */); + } + + /** Setups a rule to always split the given activities. */ + private void setupSplitRule(@NonNull Activity primaryActivity, + @NonNull Intent secondaryIntent, boolean clearTop) { + final SplitRule splitRule = createSplitRule(primaryActivity, secondaryIntent, clearTop); mSplitController.setEmbeddingRules(Collections.singleton(splitRule)); } /** Setups a rule to always split the given activities. */ private void setupSplitRule(@NonNull Activity primaryActivity, @NonNull Activity secondaryActivity) { - final SplitRule splitRule = createSplitRule(primaryActivity, secondaryActivity); + setupSplitRule(primaryActivity, secondaryActivity, true /* clearTop */); + } + + /** Setups a rule to always split the given activities. */ + private void setupSplitRule(@NonNull Activity primaryActivity, + @NonNull Activity secondaryActivity, boolean clearTop) { + final SplitRule splitRule = createSplitRule(primaryActivity, secondaryActivity, clearTop); mSplitController.setEmbeddingRules(Collections.singleton(splitRule)); } /** Adds a pair of TaskFragments as split for the given activities. */ private void addSplitTaskFragments(@NonNull Activity primaryActivity, @NonNull Activity secondaryActivity) { + addSplitTaskFragments(primaryActivity, secondaryActivity, true /* clearTop */); + } + + /** Adds a pair of TaskFragments as split for the given activities. */ + private void addSplitTaskFragments(@NonNull Activity primaryActivity, + @NonNull Activity secondaryActivity, boolean clearTop) { registerSplitPair(createMockTaskFragmentContainer(primaryActivity), createMockTaskFragmentContainer(secondaryActivity), - createSplitRule(primaryActivity, secondaryActivity)); + createSplitRule(primaryActivity, secondaryActivity, clearTop)); } /** Registers the two given TaskFragments as split pair. */ @@ -1011,16 +1089,18 @@ public class SplitControllerTest { if (primaryContainer.mInfo != null) { final Rect primaryBounds = matchParentBounds ? new Rect() : getSplitBounds(true /* isPrimary */); + final int windowingMode = matchParentBounds ? WINDOWING_MODE_UNDEFINED + : WINDOWING_MODE_MULTI_WINDOW; assertTrue(primaryContainer.areLastRequestedBoundsEqual(primaryBounds)); - assertTrue(primaryContainer.isLastRequestedWindowingModeEqual( - WINDOWING_MODE_MULTI_WINDOW)); + assertTrue(primaryContainer.isLastRequestedWindowingModeEqual(windowingMode)); } if (secondaryContainer.mInfo != null) { final Rect secondaryBounds = matchParentBounds ? new Rect() : getSplitBounds(false /* isPrimary */); + final int windowingMode = matchParentBounds ? WINDOWING_MODE_UNDEFINED + : WINDOWING_MODE_MULTI_WINDOW; assertTrue(secondaryContainer.areLastRequestedBoundsEqual(secondaryBounds)); - assertTrue(secondaryContainer.isLastRequestedWindowingModeEqual( - WINDOWING_MODE_MULTI_WINDOW)); + assertTrue(secondaryContainer.isLastRequestedWindowingModeEqual(windowingMode)); } } } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java index acc398a27baf..d79319666c01 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java @@ -20,11 +20,16 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityInfoWithMinDimensions; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule; import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds; import static androidx.window.extensions.embedding.SplitPresenter.POSITION_END; import static androidx.window.extensions.embedding.SplitPresenter.POSITION_FILL; import static androidx.window.extensions.embedding.SplitPresenter.POSITION_START; +import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPANDED; +import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO; +import static androidx.window.extensions.embedding.SplitPresenter.RESULT_NOT_EXPANDED; import static androidx.window.extensions.embedding.SplitPresenter.getBoundsForPosition; import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions; import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSideBySide; @@ -34,6 +39,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -49,6 +55,7 @@ import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Rect; +import android.os.IBinder; import android.platform.test.annotations.Presubmit; import android.util.Pair; import android.util.Size; @@ -195,6 +202,52 @@ public class SplitPresenterTest { splitRule, mActivity, minDimensionsPair)); } + @Test + public void testExpandSplitContainerIfNeeded() { + SplitContainer splitContainer = mock(SplitContainer.class); + Activity secondaryActivity = createMockActivity(); + SplitRule splitRule = createSplitRule(mActivity, secondaryActivity); + TaskFragmentContainer primaryTf = mController.newContainer(mActivity, TASK_ID); + TaskFragmentContainer secondaryTf = mController.newContainer(secondaryActivity, TASK_ID); + doReturn(splitRule).when(splitContainer).getSplitRule(); + doReturn(primaryTf).when(splitContainer).getPrimaryContainer(); + doReturn(secondaryTf).when(splitContainer).getSecondaryContainer(); + + assertThrows(IllegalArgumentException.class, () -> + mPresenter.expandSplitContainerIfNeeded(mTransaction, splitContainer, mActivity, + null /* secondaryActivity */, null /* secondaryIntent */)); + + assertEquals(RESULT_NOT_EXPANDED, mPresenter.expandSplitContainerIfNeeded(mTransaction, + splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */)); + verify(mPresenter, never()).expandTaskFragment(any(), any()); + + doReturn(createActivityInfoWithMinDimensions()).when(secondaryActivity).getActivityInfo(); + assertEquals(RESULT_EXPAND_FAILED_NO_TF_INFO, mPresenter.expandSplitContainerIfNeeded( + mTransaction, splitContainer, mActivity, secondaryActivity, + null /* secondaryIntent */)); + + primaryTf.setInfo(createMockTaskFragmentInfo(primaryTf, mActivity)); + secondaryTf.setInfo(createMockTaskFragmentInfo(secondaryTf, secondaryActivity)); + + assertEquals(RESULT_EXPANDED, mPresenter.expandSplitContainerIfNeeded(mTransaction, + splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */)); + verify(mPresenter).expandTaskFragment(eq(mTransaction), + eq(primaryTf.getTaskFragmentToken())); + verify(mPresenter).expandTaskFragment(eq(mTransaction), + eq(secondaryTf.getTaskFragmentToken())); + + clearInvocations(mPresenter); + + assertEquals(RESULT_EXPANDED, mPresenter.expandSplitContainerIfNeeded(mTransaction, + splitContainer, mActivity, null /* secondaryActivity */, + new Intent(ApplicationProvider.getApplicationContext(), + MinimumDimensionActivity.class))); + verify(mPresenter).expandTaskFragment(eq(mTransaction), + eq(primaryTf.getTaskFragmentToken())); + verify(mPresenter).expandTaskFragment(eq(mTransaction), + eq(secondaryTf.getTaskFragmentToken())); + } + private Activity createMockActivity() { final Activity activity = mock(Activity.class); final Configuration activityConfig = new Configuration(); @@ -203,6 +256,7 @@ public class SplitPresenterTest { doReturn(mActivityResources).when(activity).getResources(); doReturn(activityConfig).when(mActivityResources).getConfiguration(); doReturn(new ActivityInfo()).when(activity).getActivityInfo(); + doReturn(mock(IBinder.class)).when(activity).getActivityToken(); return activity; } } diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml index b2f09895d7d8..68a08513e7f5 100644 --- a/libs/WindowManager/Shell/res/values/strings.xml +++ b/libs/WindowManager/Shell/res/values/strings.xml @@ -157,7 +157,7 @@ <string name="accessibility_bubble_dismissed">Bubble dismissed.</string> <!-- Description of the restart button in the hint of size compatibility mode. [CHAR LIMIT=NONE] --> - <string name="restart_button_description">Tap to restart this app and go full screen.</string> + <string name="restart_button_description">Tap to restart this app for a better view.</string> <!-- Description of the camera compat button for applying stretched issues treatment in the hint for compatibility control. [CHAR LIMIT=NONE] --> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java index e71a59d26740..8c0affb0a432 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java @@ -31,13 +31,15 @@ public interface BackAnimation { /** * Called when a {@link MotionEvent} is generated by a back gesture. * - * @param event the original {@link MotionEvent} - * @param action the original {@link KeyEvent#getAction()} when the event was dispatched to + * @param touchX the X touch position of the {@link MotionEvent}. + * @param touchY the Y touch position of the {@link MotionEvent}. + * @param keyAction the original {@link KeyEvent#getAction()} when the event was dispatched to * the process. This is forwarded separately because the input pipeline may mutate * the {#event} action state later. * @param swipeEdge the edge from which the swipe begins. */ - void onBackMotion(MotionEvent event, int action, @BackEvent.SwipeEdge int swipeEdge); + void onBackMotion(float touchX, float touchY, int keyAction, + @BackEvent.SwipeEdge int swipeEdge); /** * Sets whether the back gesture is past the trigger threshold or not. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index 89d262b17b59..f061f8bc178a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -187,8 +187,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont @Override public void onBackMotion( - MotionEvent event, int action, @BackEvent.SwipeEdge int swipeEdge) { - mShellExecutor.execute(() -> onMotionEvent(event, action, swipeEdge)); + float touchX, float touchY, int keyAction, @BackEvent.SwipeEdge int swipeEdge) { + mShellExecutor.execute(() -> onMotionEvent(touchX, touchY, keyAction, swipeEdge)); } @Override @@ -259,33 +259,34 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont * Called when a new motion event needs to be transferred to this * {@link BackAnimationController} */ - public void onMotionEvent(MotionEvent event, int action, @BackEvent.SwipeEdge int swipeEdge) { + public void onMotionEvent(float touchX, float touchY, int keyAction, + @BackEvent.SwipeEdge int swipeEdge) { if (mTransitionInProgress) { return; } - if (action == MotionEvent.ACTION_MOVE) { + if (keyAction == MotionEvent.ACTION_MOVE) { if (!mBackGestureStarted) { // Let the animation initialized here to make sure the onPointerDownOutsideFocus // could be happened when ACTION_DOWN, it may change the current focus that we // would access it when startBackNavigation. - initAnimation(event); + initAnimation(touchX, touchY); } - onMove(event, swipeEdge); - } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { + onMove(touchX, touchY, swipeEdge); + } else if (keyAction == MotionEvent.ACTION_UP || keyAction == MotionEvent.ACTION_CANCEL) { ProtoLog.d(WM_SHELL_BACK_PREVIEW, - "Finishing gesture with event action: %d", action); + "Finishing gesture with event action: %d", keyAction); onGestureFinished(); } } - private void initAnimation(MotionEvent event) { + private void initAnimation(float touchX, float touchY) { ProtoLog.d(WM_SHELL_BACK_PREVIEW, "initAnimation mMotionStarted=%b", mBackGestureStarted); if (mBackGestureStarted || mBackNavigationInfo != null) { Log.e(TAG, "Animation is being initialized but is already started."); finishAnimation(); } - mInitTouchLocation.set(event.getX(), event.getY()); + mInitTouchLocation.set(touchX, touchY); mBackGestureStarted = true; try { @@ -354,18 +355,18 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mTransaction.setVisibility(screenshotSurface, true); } - private void onMove(MotionEvent event, @BackEvent.SwipeEdge int swipeEdge) { + private void onMove(float touchX, float touchY, @BackEvent.SwipeEdge int swipeEdge) { if (!mBackGestureStarted || mBackNavigationInfo == null) { return; } - int deltaX = Math.round(event.getX() - mInitTouchLocation.x); + int deltaX = Math.round(touchX - mInitTouchLocation.x); float progressThreshold = PROGRESS_THRESHOLD >= 0 ? PROGRESS_THRESHOLD : mProgressThreshold; float progress = Math.min(Math.max(Math.abs(deltaX) / progressThreshold, 0), 1); int backType = mBackNavigationInfo.getType(); RemoteAnimationTarget animationTarget = mBackNavigationInfo.getDepartingAnimationTarget(); BackEvent backEvent = new BackEvent( - event.getX(), event.getY(), progress, swipeEdge, animationTarget); + touchX, touchY, progress, swipeEdge, animationTarget); IOnBackInvokedCallback targetCallback = null; if (shouldDispatchToLauncher(backType)) { targetCallback = mBackToLauncherCallback; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 1e369899e354..a8c1071eb69e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -68,15 +68,15 @@ import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.transition.Transitions; -import com.android.wm.shell.unfold.UnfoldAnimationController; import com.android.wm.shell.unfold.ShellUnfoldProgressProvider; +import com.android.wm.shell.unfold.UnfoldAnimationController; import com.android.wm.shell.unfold.UnfoldBackgroundController; import com.android.wm.shell.unfold.UnfoldTransitionHandler; import com.android.wm.shell.unfold.animation.FullscreenUnfoldTaskAnimator; import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator; import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator; -import com.android.wm.shell.unfold.qualifier.UnfoldTransition; import com.android.wm.shell.unfold.qualifier.UnfoldShellTransition; +import com.android.wm.shell.unfold.qualifier.UnfoldTransition; import com.android.wm.shell.windowdecor.CaptionWindowDecorViewModel; import com.android.wm.shell.windowdecor.WindowDecorViewModel; @@ -218,6 +218,7 @@ public abstract class WMShellModule { PipKeepClearAlgorithm pipKeepClearAlgorithm, PipBoundsState pipBoundsState, PipMotionHelper pipMotionHelper, PipMediaController pipMediaController, PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer, + PipTransitionState pipTransitionState, PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController, WindowManagerShellWrapper windowManagerShellWrapper, TaskStackListenerImpl taskStackListener, @@ -227,7 +228,7 @@ public abstract class WMShellModule { return Optional.ofNullable(PipController.create(context, displayController, pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper, - pipMediaController, phonePipMenuController, pipTaskOrganizer, + pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController, windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder, oneHandedController, mainExecutor)); } 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 3b3091a9caf3..bbc47e47afc5 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 @@ -86,12 +86,12 @@ public interface Pip { } /** - * Registers the pinned stack animation listener. + * Set the callback when {@link PipTaskOrganizer#isInPip()} state is changed. * - * @param callback The callback of pinned stack animation. + * @param callback The callback accepts the result of {@link PipTaskOrganizer#isInPip()} + * when it's changed. */ - default void setPinnedStackAnimationListener(Consumer<Boolean> callback) { - } + default void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {} /** * Set the pinned stack with {@link PipAnimationController.AnimationType} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index 28427a808d90..c80c14f353d0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -42,6 +42,7 @@ import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SP import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP; import static com.android.wm.shell.transition.Transitions.isOpeningType; +import android.animation.Animator; import android.app.ActivityManager; import android.app.TaskInfo; import android.content.Context; @@ -248,6 +249,13 @@ public class PipTransition extends PipTransitionController { return false; } + @Override + public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + end(); + } + /** Helper to identify whether this handler is currently the one playing an animation */ private boolean isAnimatingLocally() { return mFinishTransaction != null; @@ -283,6 +291,13 @@ public class PipTransition extends PipTransitionController { } @Override + public void end() { + Animator animator = mPipAnimationController.getCurrentAnimator(); + if (animator == null) return; + animator.end(); + } + + @Override public boolean handleRotateDisplay(int startRotation, int endRotation, WindowContainerTransaction wct) { if (mRequestedEnterTransition != null && mOneShotAnimationType == ANIM_TYPE_ALPHA) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java index d3f69f6762f9..a43b6043908b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java @@ -237,6 +237,10 @@ public abstract class PipTransitionController implements Transitions.TransitionH @NonNull final Transitions.TransitionFinishCallback finishCallback) { } + /** End the currently-playing PiP animation. */ + public void end() { + } + /** * Callback interface for PiP transitions (both from and to PiP mode) */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java index 85e56b7dd99f..1a4be3b41911 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java @@ -17,12 +17,15 @@ package com.android.wm.shell.pip; import android.annotation.IntDef; +import android.annotation.NonNull; import android.app.PictureInPictureParams; import android.content.ComponentName; import android.content.pm.ActivityInfo; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; /** * Used to keep track of PiP leash state as it appears and animates by {@link PipTaskOrganizer} and @@ -37,6 +40,9 @@ public class PipTransitionState { public static final int ENTERED_PIP = 4; public static final int EXITING_PIP = 5; + private final List<OnPipTransitionStateChangedListener> mOnPipTransitionStateChangedListeners = + new ArrayList<>(); + /** * If set to {@code true}, no entering PiP transition would be kicked off and most likely * it's due to the fact that Launcher is handling the transition directly when swiping @@ -65,7 +71,13 @@ public class PipTransitionState { } public void setTransitionState(@TransitionState int state) { - mState = state; + if (mState != state) { + for (int i = 0; i < mOnPipTransitionStateChangedListeners.size(); i++) { + mOnPipTransitionStateChangedListeners.get(i).onPipTransitionStateChanged( + mState, state); + } + mState = state; + } } public @TransitionState int getTransitionState() { @@ -73,8 +85,7 @@ public class PipTransitionState { } public boolean isInPip() { - return mState >= TASK_APPEARED - && mState != EXITING_PIP; + return isInPip(mState); } public void setInSwipePipToHomeTransition(boolean inSwipePipToHomeTransition) { @@ -94,4 +105,23 @@ public class PipTransitionState { return mState < ENTERING_PIP || mState == EXITING_PIP; } + + public void addOnPipTransitionStateChangedListener( + @NonNull OnPipTransitionStateChangedListener listener) { + mOnPipTransitionStateChangedListeners.add(listener); + } + + public void removeOnPipTransitionStateChangedListener( + @NonNull OnPipTransitionStateChangedListener listener) { + mOnPipTransitionStateChangedListeners.remove(listener); + } + + public static boolean isInPip(@TransitionState int state) { + return state >= TASK_APPEARED && state != EXITING_PIP; + } + + public interface OnPipTransitionStateChangedListener { + void onPipTransitionStateChanged(@TransitionState int oldState, + @TransitionState int newState); + } } 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 c3e6d82df781..3000998f210d 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 @@ -84,6 +84,7 @@ import com.android.wm.shell.pip.PipParamsChangedForwarder; import com.android.wm.shell.pip.PipSnapAlgorithm; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; +import com.android.wm.shell.pip.PipTransitionState; import com.android.wm.shell.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.transition.Transitions; @@ -128,11 +129,14 @@ public class PipController implements PipTransitionController.PipTransitionCallb protected PhonePipMenuController mMenuController; protected PipTaskOrganizer mPipTaskOrganizer; + private PipTransitionState mPipTransitionState; protected PinnedStackListenerForwarder.PinnedTaskListener mPinnedTaskListener = new PipControllerPinnedTaskListener(); private boolean mIsKeyguardShowingOrAnimating; + private Consumer<Boolean> mOnIsInPipStateChangedListener; + private interface PipAnimationListener { /** * Notifies the listener that the Pip animation is started. @@ -291,6 +295,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb PipKeepClearAlgorithm pipKeepClearAlgorithm, PipBoundsState pipBoundsState, PipMotionHelper pipMotionHelper, PipMediaController pipMediaController, PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer, + PipTransitionState pipTransitionState, PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController, WindowManagerShellWrapper windowManagerShellWrapper, TaskStackListenerImpl taskStackListener, @@ -305,7 +310,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb return new PipController(context, displayController, pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper, pipMediaController, - phonePipMenuController, pipTaskOrganizer, pipTouchHandler, pipTransitionController, + phonePipMenuController, pipTaskOrganizer, pipTransitionState, + pipTouchHandler, pipTransitionController, windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder, oneHandedController, mainExecutor) .mImpl; @@ -321,6 +327,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb PipMediaController pipMediaController, PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer, + PipTransitionState pipTransitionState, PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController, WindowManagerShellWrapper windowManagerShellWrapper, @@ -344,6 +351,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb mPipBoundsState = pipBoundsState; mPipMotionHelper = pipMotionHelper; mPipTaskOrganizer = pipTaskOrganizer; + mPipTransitionState = pipTransitionState; mMainExecutor = mainExecutor; mMediaController = pipMediaController; mMenuController = phonePipMenuController; @@ -370,6 +378,15 @@ public class PipController implements PipTransitionController.PipTransitionCallb onDisplayChanged(mDisplayController.getDisplayLayout(displayId), false /* saveRestoreSnapFraction */); }); + mPipTransitionState.addOnPipTransitionStateChangedListener((oldState, newState) -> { + if (mOnIsInPipStateChangedListener != null) { + final boolean wasInPip = PipTransitionState.isInPip(oldState); + final boolean nowInPip = PipTransitionState.isInPip(newState); + if (nowInPip != wasInPip) { + mOnIsInPipStateChangedListener.accept(nowInPip); + } + } + }); mPipBoundsState.setOnMinimalSizeChangeCallback( () -> { // The minimal size drives the normal bounds, so they need to be recalculated. @@ -664,6 +681,13 @@ public class PipController implements PipTransitionController.PipTransitionCallb } } + private void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) { + mOnIsInPipStateChangedListener = callback; + if (mOnIsInPipStateChangedListener != null) { + callback.accept(mPipTransitionState.isInPip()); + } + } + private void setShelfHeightLocked(boolean visible, int height) { final int shelfHeight = visible ? height : 0; mPipBoundsState.setShelfVisibility(visible, shelfHeight); @@ -941,6 +965,13 @@ public class PipController implements PipTransitionController.PipTransitionCallb } @Override + public void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) { + mMainExecutor.execute(() -> { + PipController.this.setOnIsInPipStateChangedListener(callback); + }); + } + + @Override public void setPinnedStackAnimationType(int animationType) { mMainExecutor.execute(() -> { PipController.this.setPinnedStackAnimationType(animationType); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java index f7057d454df9..e55729a883e0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java @@ -225,9 +225,25 @@ class SplitScreenTransitions { void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback) { - if (mergeTarget == mAnimatingTransition && mActiveRemoteHandler != null) { + if (mergeTarget != mAnimatingTransition) return; + if (mActiveRemoteHandler != null) { mActiveRemoteHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); + } else { + for (int i = mAnimations.size() - 1; i >= 0; --i) { + final Animator anim = mAnimations.get(i); + mTransitions.getAnimExecutor().execute(anim::end); + } + } + } + + boolean end() { + // If its remote, there's nothing we can do right now. + if (mActiveRemoteHandler != null) return false; + for (int i = mAnimations.size() - 1; i >= 0; --i) { + final Animator anim = mAnimations.get(i); + mTransitions.getAnimExecutor().execute(anim::end); } + return true; } void onTransitionMerged(@NonNull IBinder transition) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 6cfb700fc16a..59b0afe22acb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -457,10 +457,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } @Override - public void onAnimationCancelled() { + public void onAnimationCancelled(boolean isKeyguardOccluded) { onRemoteAnimationFinishedOrCancelled(evictWct); try { - adapter.getRunner().onAnimationCancelled(); + adapter.getRunner().onAnimationCancelled(isKeyguardOccluded); } catch (RemoteException e) { Slog.e(TAG, "Error starting remote animation", e); } @@ -1521,6 +1521,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitTransitions.mergeAnimation(transition, info, t, mergeTarget, finishCallback); } + /** Jump the current transition animation to the end. */ + public boolean end() { + return mSplitTransitions.end(); + } + @Override public void onTransitionMerged(@NonNull IBinder transition) { mSplitTransitions.onTransitionMerged(transition); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java index 95bc579a4a51..19d3acbf28d4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java @@ -20,10 +20,8 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.graphics.Color.WHITE; import static android.graphics.Color.alpha; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; -import static android.view.ViewRootImpl.LOCAL_LAYOUT; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; -import static android.view.WindowLayout.UNSPECIFIED_LENGTH; import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; import static android.view.WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES; @@ -53,7 +51,6 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManager.TaskDescription; import android.app.ActivityThread; -import android.app.WindowConfiguration; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; @@ -80,7 +77,6 @@ import android.view.SurfaceSession; import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; -import android.view.WindowLayout; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.window.ClientWindowFrames; @@ -212,8 +208,6 @@ public class TaskSnapshotWindow { final IWindowSession session = WindowManagerGlobal.getWindowSession(); final SurfaceControl surfaceControl = new SurfaceControl(); final ClientWindowFrames tmpFrames = new ClientWindowFrames(); - final WindowLayout windowLayout = new WindowLayout(); - final Rect displayCutoutSafe = new Rect(); final InsetsSourceControl[] tmpControls = new InsetsSourceControl[0]; final MergedConfiguration tmpMergedConfiguration = new MergedConfiguration(); @@ -238,7 +232,8 @@ public class TaskSnapshotWindow { try { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#addToDisplay"); final int res = session.addToDisplay(window, layoutParams, View.GONE, displayId, - info.requestedVisibilities, tmpInputChannel, tmpInsetsState, tmpControls); + info.requestedVisibilities, tmpInputChannel, tmpInsetsState, tmpControls, + new Rect()); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); if (res < 0) { Slog.w(TAG, "Failed to add snapshot starting window res=" + res); @@ -250,25 +245,9 @@ public class TaskSnapshotWindow { window.setOuter(snapshotSurface); try { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#relayout"); - if (LOCAL_LAYOUT) { - if (!surfaceControl.isValid()) { - session.updateVisibility(window, layoutParams, View.VISIBLE, - tmpMergedConfiguration, surfaceControl, tmpInsetsState, tmpControls); - } - tmpInsetsState.getDisplayCutoutSafe(displayCutoutSafe); - final WindowConfiguration winConfig = - tmpMergedConfiguration.getMergedConfiguration().windowConfiguration; - windowLayout.computeFrames(layoutParams, tmpInsetsState, displayCutoutSafe, - winConfig.getBounds(), winConfig.getWindowingMode(), UNSPECIFIED_LENGTH, - UNSPECIFIED_LENGTH, info.requestedVisibilities, - null /* attachedWindowFrame */, 1f /* compatScale */, tmpFrames); - session.updateLayout(window, layoutParams, 0 /* flags */, tmpFrames, - UNSPECIFIED_LENGTH, UNSPECIFIED_LENGTH); - } else { - session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, - tmpFrames, tmpMergedConfiguration, surfaceControl, tmpInsetsState, - tmpControls, new Bundle()); - } + session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, + tmpFrames, tmpMergedConfiguration, surfaceControl, tmpInsetsState, + tmpControls, new Bundle()); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } catch (RemoteException e) { snapshotSurface.clearWindowSynced(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index 1ffe26df729f..7234d559e153 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -53,10 +53,18 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { private static class MixedTransition { static final int TYPE_ENTER_PIP_FROM_SPLIT = 1; + /** The default animation for this mixed transition. */ + static final int ANIM_TYPE_DEFAULT = 0; + + /** For ENTER_PIP_FROM_SPLIT, indicates that this is a to-home animation. */ + static final int ANIM_TYPE_GOING_HOME = 1; + final int mType; + int mAnimType = 0; final IBinder mTransition; Transitions.TransitionFinishCallback mFinishCallback = null; + Transitions.TransitionHandler mLeftoversHandler = null; /** * Mixed transitions are made up of multiple "parts". This keeps track of how many @@ -128,7 +136,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { MixedTransition mixed = null; for (int i = mActiveTransitions.size() - 1; i >= 0; --i) { if (mActiveTransitions.get(i).mTransition != transition) continue; - mixed = mActiveTransitions.remove(i); + mixed = mActiveTransitions.get(i); break; } if (mixed == null) return false; @@ -137,6 +145,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { return animateEnterPipFromSplit(mixed, info, startTransaction, finishTransaction, finishCallback); } else { + mActiveTransitions.remove(mixed); throw new IllegalStateException("Starting mixed animation without a known mixed type? " + mixed.mType); } @@ -178,6 +187,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> { --mixed.mInFlightSubAnimations; if (mixed.mInFlightSubAnimations > 0) return; + mActiveTransitions.remove(mixed); if (isGoingHome) { mSplitHandler.onTransitionAnimationComplete(); } @@ -216,8 +226,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { finishCB); // Dispatch the rest of the transition normally. This will most-likely be taken by // recents or default handler. - mPlayer.dispatchTransition(mixed.mTransition, everythingElse, otherStartT, - finishTransaction, finishCB, this); + mixed.mLeftoversHandler = mPlayer.dispatchTransition(mixed.mTransition, everythingElse, + otherStartT, finishTransaction, finishCB, this); } else { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Not leaving split, so just " + "forward animation to Pip-Handler."); @@ -235,6 +245,32 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @NonNull Transitions.TransitionFinishCallback finishCallback) { + for (int i = 0; i < mActiveTransitions.size(); ++i) { + if (mActiveTransitions.get(i) != mergeTarget) continue; + MixedTransition mixed = mActiveTransitions.get(i); + if (mixed.mInFlightSubAnimations <= 0) { + // Already done, so no need to end it. + return; + } + if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) { + if (mixed.mAnimType == MixedTransition.ANIM_TYPE_GOING_HOME) { + boolean ended = mSplitHandler.end(); + // If split couldn't end (because it is remote), then don't end everything else + // since we have to play out the animation anyways. + if (!ended) return; + mPipHandler.end(); + if (mixed.mLeftoversHandler != null) { + mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, + finishCallback); + } + } else { + mPipHandler.end(); + } + } else { + throw new IllegalStateException("Playing a mixed transition with unknown type? " + + mixed.mType); + } + } } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index c3eaa8ee1da0..05e5b8e66a00 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -523,6 +523,18 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { return true; } + @Override + public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + ArrayList<Animator> anims = mAnimations.get(mergeTarget); + if (anims == null) return; + for (int i = anims.size() - 1; i >= 0; --i) { + final Animator anim = anims.get(i); + mAnimExecutor.execute(anim::end); + } + } + private void edgeExtendWindow(TransitionInfo.Change change, Animation a, SurfaceControl.Transaction startTransaction, SurfaceControl.Transaction finishTransaction) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java index 61e11e877b90..61e92f355dc2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java @@ -107,7 +107,7 @@ public class LegacyTransitions { } @Override - public void onAnimationCancelled() throws RemoteException { + public void onAnimationCancelled(boolean isKeyguardOccluded) throws RemoteException { mCancelled = true; mApps = mWallpapers = mNonApps = null; checkApply(); diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt index 2514e3be2f78..e7d641e9c66e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.flicker.pip -import android.platform.test.annotations.FlakyTest import androidx.test.filters.RequiresDevice import com.android.launcher3.tapl.LauncherInstrumentation import com.android.server.wm.flicker.FlickerParametersRunnerFactory @@ -51,7 +50,6 @@ import org.junit.runners.Parameterized @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group3 -@FlakyTest(bugId = 234848637) class EnterPipOnUserLeaveHintTest(testSpec: FlickerTestParameter) : EnterPipTest(testSpec) { protected val taplInstrumentation = LauncherInstrumentation() /** diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index fcfcbfa091db..e7c5cb2183db 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -298,7 +298,7 @@ public class BackAnimationControllerTest { private void doMotionEvent(int actionDown, int coordinate) { mController.onMotionEvent( - MotionEvent.obtain(0, mEventTime, actionDown, coordinate, coordinate, 0), + coordinate, coordinate, actionDown, BackEvent.EDGE_LEFT); mEventTime += 10; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java index abd55dd7d606..babc9707ef9c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java @@ -53,6 +53,7 @@ import com.android.wm.shell.pip.PipParamsChangedForwarder; import com.android.wm.shell.pip.PipSnapAlgorithm; import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; +import com.android.wm.shell.pip.PipTransitionState; import org.junit.Before; import org.junit.Test; @@ -80,6 +81,7 @@ public class PipControllerTest extends ShellTestCase { @Mock private PipSnapAlgorithm mMockPipSnapAlgorithm; @Mock private PipMediaController mMockPipMediaController; @Mock private PipTaskOrganizer mMockPipTaskOrganizer; + @Mock private PipTransitionState mMockPipTransitionState; @Mock private PipTransitionController mMockPipTransitionController; @Mock private PipTouchHandler mMockPipTouchHandler; @Mock private PipMotionHelper mMockPipMotionHelper; @@ -104,8 +106,8 @@ public class PipControllerTest extends ShellTestCase { mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm, mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController, - mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTouchHandler, - mMockPipTransitionController, mMockWindowManagerShellWrapper, + mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState, + mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper, mMockTaskStackListener, mPipParamsChangedForwarder, mMockOneHandedController, mMockExecutor); when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm); @@ -138,8 +140,8 @@ public class PipControllerTest extends ShellTestCase { mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm, mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController, - mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTouchHandler, - mMockPipTransitionController, mMockWindowManagerShellWrapper, + mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState, + mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper, mMockTaskStackListener, mPipParamsChangedForwarder, mMockOneHandedController, mMockExecutor)); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java index 630d0d2c827c..14d8ce4682c6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java @@ -249,7 +249,8 @@ public class StartingSurfaceDrawerTests { any() /* window */, any() /* attrs */, anyInt() /* viewVisibility */, anyInt() /* displayId */, any() /* requestedVisibility */, any() /* outInputChannel */, - any() /* outInsetsState */, any() /* outActiveControls */); + any() /* outInsetsState */, any() /* outActiveControls */, + any() /* outAttachedFrame */); TaskSnapshotWindow mockSnapshotWindow = TaskSnapshotWindow.create(windowInfo, mBinder, snapshot, mTestExecutor, () -> { diff --git a/libs/androidfw/CursorWindow.cpp b/libs/androidfw/CursorWindow.cpp index 87ae45bedd67..3527eeead1d5 100644 --- a/libs/androidfw/CursorWindow.cpp +++ b/libs/androidfw/CursorWindow.cpp @@ -227,7 +227,6 @@ status_t CursorWindow::writeToParcel(Parcel* parcel) { if (!dest) goto fail; memcpy(static_cast<uint8_t*>(dest), static_cast<uint8_t*>(mData), mAllocOffset); - memset(static_cast<uint8_t*>(dest) + mAllocOffset, 0, 4); memcpy(static_cast<uint8_t*>(dest) + compactedSize - slotsSize, static_cast<uint8_t*>(mData) + mSlotsOffset, slotsSize); } diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index fd0c8948c321..f59650203889 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -2394,8 +2394,8 @@ public class LocationManager { * @return true if the listener was successfully added * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present * - * @deprecated use {@link #registerGnssStatusCallback(GnssStatus.Callback)} instead. No longer - * supported in apps targeting S and above. + * @deprecated Use {@link #registerGnssStatusCallback(GnssStatus.Callback, Handler)} or {@link + * #registerGnssStatusCallback(Executor, GnssStatus.Callback)} instead. */ @Deprecated @RequiresPermission(ACCESS_FINE_LOCATION) @@ -2505,7 +2505,8 @@ public class LocationManager { /** * No-op method to keep backward-compatibility. * - * @deprecated Use {@link #addNmeaListener} instead. + * @deprecated Use {@link #addNmeaListener(OnNmeaMessageListener, Handler)} or {@link + * #addNmeaListener(Executor, OnNmeaMessageListener)} instead. */ @Deprecated @RequiresPermission(ACCESS_FINE_LOCATION) @@ -2667,6 +2668,13 @@ public class LocationManager { * * <p>Not all GNSS chipsets support measurements updates, see {@link #getGnssCapabilities()}. * + * <p class="caution">On Android R devices that have not yet upgraded to Android R QPR1, using + * this API will cause unavoidable crashes in the client application when GNSS measurements + * are received. If a client needs to receive GNSS measurements on Android R devices that have + * not been upgraded to QPR1, clients are instead encouraged to use + * <a href="https://developer.android.com/reference/androidx/core/location/LocationManagerCompat#registerGnssMeasurementsCallback(android.location.LocationManager,java.util.concurrent.Executor,android.location.GnssMeasurementsEvent.Callback)">LocationManagerCompat.registerGnssMeasurementsCallback()</a> + * from the compat libraries instead to avoid this crash. + * * @param executor the executor that the callback runs on * @param callback the callback to register * @return {@code true} always diff --git a/location/java/android/location/package.html b/location/java/android/location/package.html index 20c5c54d6921..9e048d6f5f07 100644 --- a/location/java/android/location/package.html +++ b/location/java/android/location/package.html @@ -1,17 +1,13 @@ <html> <body> - -<p>Contains the framework API classes that define Android location-based and - related services.</p> -<p class="warning"> -<strong>This API is not the recommended method for accessing Android location.</strong><br> -The -<a href="https://developers.google.com/android/reference/com/google/android/gms/location/package-summary">Google Location Services API</a>, -part of Google Play services, is the preferred way to add location-awareness to -your app. It offers a simpler API, higher accuracy, low-power geofencing, and -more. If you are currently using the android.location API, you are strongly -encouraged to switch to the Google Location Services API as soon as -possible. +<p>Contains framework API classes for accessing a variety of location related services.</p> +<p class="note"><strong>Note:</strong> The +<a href="https://developers.google.com/android/reference/com/google/android/gms/location/package-summary">Google Location Services APIs</a>, +part of Google Play services, is the preferred way to access location services for apps. These APIs +are both simpler to use and offer more capabilities as compared to the traditional Android location +APIs found here. They also offer new services, such as low-power geofencing, activity recognition, +and more. Clients of the traditional Android location APIs are encouraged to switch to the Google +Location Services APIs wherever possible. <br><br> To learn more about the Google Location Services API, see the <a href="{@docRoot}google/play-services/location.html">Location API overview</a>. diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 78fd944dc22b..6922637350d9 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -4004,7 +4004,7 @@ public class AudioManager { * Timeout duration in ms when waiting on an external focus policy for the result for a * focus request */ - private static final int EXT_FOCUS_POLICY_TIMEOUT_MS = 200; + private static final int EXT_FOCUS_POLICY_TIMEOUT_MS = 250; private static final String FOCUS_CLIENT_ID_STRING = "android_audio_focus_client_id"; @@ -4284,8 +4284,17 @@ public class AudioManager { } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } + if (status != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) { + // default path with no external focus policy + return status; + } + + BlockingFocusResultReceiver focusReceiver; + synchronized (mFocusRequestsLock) { + focusReceiver = addClientIdToFocusReceiverLocked(clientFakeId); + } - return handleExternalAudioPolicyWaitIfNeeded(clientFakeId, status); + return handleExternalAudioPolicyWaitIfNeeded(clientFakeId, focusReceiver); } /** @@ -4368,7 +4377,9 @@ public class AudioManager { } final String clientId = getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener()); + BlockingFocusResultReceiver focusReceiver; synchronized (mFocusRequestsLock) { + try { // TODO status contains result and generation counter for ext policy status = service.requestAudioFocus(afr.getAudioAttributes(), @@ -4383,29 +4394,30 @@ public class AudioManager { } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } + if (status != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) { + // default path with no external focus policy + return status; + } + focusReceiver = addClientIdToFocusReceiverLocked(clientId); } - return handleExternalAudioPolicyWaitIfNeeded(clientId, status); + return handleExternalAudioPolicyWaitIfNeeded(clientId, focusReceiver); } - private @FocusRequestResult int handleExternalAudioPolicyWaitIfNeeded(String clientId, - @FocusRequestResult int results) { - if (results != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) { - // default path with no external focus policy - return results; - } - + @GuardedBy("mFocusRequestsLock") + private BlockingFocusResultReceiver addClientIdToFocusReceiverLocked(String clientId) { BlockingFocusResultReceiver focusReceiver; - - synchronized (mFocusRequestsLock) { - if (mFocusRequestsAwaitingResult == null) { - mFocusRequestsAwaitingResult = - new HashMap<String, BlockingFocusResultReceiver>(1); - } - focusReceiver = new BlockingFocusResultReceiver(clientId); - mFocusRequestsAwaitingResult.put(clientId, focusReceiver); + if (mFocusRequestsAwaitingResult == null) { + mFocusRequestsAwaitingResult = + new HashMap<String, BlockingFocusResultReceiver>(1); } + focusReceiver = new BlockingFocusResultReceiver(clientId); + mFocusRequestsAwaitingResult.put(clientId, focusReceiver); + return focusReceiver; + } + private @FocusRequestResult int handleExternalAudioPolicyWaitIfNeeded(String clientId, + BlockingFocusResultReceiver focusReceiver) { focusReceiver.waitForResult(EXT_FOCUS_POLICY_TIMEOUT_MS); if (DEBUG && !focusReceiver.receivedResult()) { Log.e(TAG, "handleExternalAudioPolicyWaitIfNeeded" diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java index 2a04ebb1efec..762dea1393a0 100644 --- a/media/java/android/media/MediaDrm.java +++ b/media/java/android/media/MediaDrm.java @@ -2867,7 +2867,7 @@ public final class MediaDrm implements AutoCloseable { = "drm.mediadrm.get_device_unique_id.error.list"; /** - * Key to extraact the count of {@link KeyStatus#STATUS_EXPIRED} events + * Key to extract the count of {@link KeyStatus#STATUS_EXPIRED} events * that occured. The count is extracted from the * {@link PersistableBundle} returned from a {@link #getMetrics} call. * The count is a Long value ({@link android.os.BaseBundle#getLong}). diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java index 89e10c4b5e11..fc70ba40fb47 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java @@ -20,15 +20,19 @@ import android.bluetooth.BluetoothCsipSetCoordinator; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothUuid; +import android.os.Build; import android.os.ParcelUuid; import android.util.Log; +import androidx.annotation.ChecksSdkIntAtLeast; + import com.android.internal.annotations.VisibleForTesting; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; /** * CsipDeviceManager manages the set of remote CSIP Bluetooth devices. @@ -126,32 +130,84 @@ public class CsipDeviceManager { } } + @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.TIRAMISU) + private static boolean isAtLeastT() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU; + } + // Group devices by groupId @VisibleForTesting void onGroupIdChanged(int groupId) { - int firstMatchedIndex = -1; - CachedBluetoothDevice mainDevice = null; + if (!isValidGroupId(groupId)) { + log("onGroupIdChanged: groupId is invalid"); + return; + } + log("onGroupIdChanged: mCachedDevices list =" + mCachedDevices.toString()); + final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager(); + final CachedBluetoothDeviceManager deviceManager = mBtManager.getCachedDeviceManager(); + final LeAudioProfile leAudioProfile = profileManager.getLeAudioProfile(); + final BluetoothDevice mainBluetoothDevice = (leAudioProfile != null && isAtLeastT()) ? + leAudioProfile.getConnectedGroupLeadDevice(groupId) : null; + CachedBluetoothDevice newMainDevice = + mainBluetoothDevice != null ? deviceManager.findDevice(mainBluetoothDevice) : null; + if (newMainDevice != null) { + final CachedBluetoothDevice finalNewMainDevice = newMainDevice; + final List<CachedBluetoothDevice> memberDevices = mCachedDevices.stream() + .filter(cachedDevice -> !cachedDevice.equals(finalNewMainDevice) + && cachedDevice.getGroupId() == groupId) + .collect(Collectors.toList()); + if (memberDevices == null || memberDevices.isEmpty()) { + log("onGroupIdChanged: There is no member device in list."); + return; + } + log("onGroupIdChanged: removed from UI device =" + memberDevices + + ", with groupId=" + groupId + " mainDevice= " + newMainDevice); + for (CachedBluetoothDevice memberDeviceItem : memberDevices) { + Set<CachedBluetoothDevice> memberSet = memberDeviceItem.getMemberDevice(); + if (!memberSet.isEmpty()) { + log("onGroupIdChanged: Transfer the member list into new main device."); + for (CachedBluetoothDevice memberListItem : memberSet) { + if (!memberListItem.equals(newMainDevice)) { + newMainDevice.addMemberDevice(memberListItem); + } + } + memberSet.clear(); + } - for (int i = mCachedDevices.size() - 1; i >= 0; i--) { - final CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); - if (cachedDevice.getGroupId() != groupId) { - continue; + newMainDevice.addMemberDevice(memberDeviceItem); + mCachedDevices.remove(memberDeviceItem); + mBtManager.getEventManager().dispatchDeviceRemoved(memberDeviceItem); } - if (firstMatchedIndex == -1) { - // Found the first one - firstMatchedIndex = i; - mainDevice = cachedDevice; - continue; + if (!mCachedDevices.contains(newMainDevice)) { + mCachedDevices.add(newMainDevice); + mBtManager.getEventManager().dispatchDeviceAdded(newMainDevice); } + } else { + log("onGroupIdChanged: There is no main device from the LE profile."); + int firstMatchedIndex = -1; - log("onGroupIdChanged: removed from UI device =" + cachedDevice - + ", with groupId=" + groupId + " firstMatchedIndex=" + firstMatchedIndex); + for (int i = mCachedDevices.size() - 1; i >= 0; i--) { + final CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); + if (cachedDevice.getGroupId() != groupId) { + continue; + } + + if (firstMatchedIndex == -1) { + // Found the first one + firstMatchedIndex = i; + newMainDevice = cachedDevice; + continue; + } + + log("onGroupIdChanged: removed from UI device =" + cachedDevice + + ", with groupId=" + groupId + " firstMatchedIndex=" + firstMatchedIndex); - mainDevice.addMemberDevice(cachedDevice); - mCachedDevices.remove(i); - mBtManager.getEventManager().dispatchDeviceRemoved(cachedDevice); - break; + newMainDevice.addMemberDevice(cachedDevice); + mCachedDevices.remove(i); + mBtManager.getEventManager().dispatchDeviceRemoved(cachedDevice); + break; + } } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java index 19df1e9c0730..0f57d8785de9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java @@ -21,6 +21,7 @@ import static android.bluetooth.BluetoothAdapter.ACTIVE_DEVICE_ALL; import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; +import android.annotation.Nullable; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothCodecConfig; @@ -183,6 +184,37 @@ public class LeAudioProfile implements LocalBluetoothProfile { return mBluetoothAdapter.getActiveDevices(BluetoothProfile.LE_AUDIO); } + /** + * Get Lead device for the group. + * + * Lead device is the device that can be used as an active device in the system. + * Active devices points to the Audio Device for the Le Audio group. + * This method returns the Lead devices for the connected LE Audio + * group and this device should be used in the setActiveDevice() method by other parts + * of the system, which wants to set to active a particular Le Audio group. + * + * Note: getActiveDevice() returns the Lead device for the currently active LE Audio group. + * Note: When Lead device gets disconnected while Le Audio group is active and has more devices + * in the group, then Lead device will not change. If Lead device gets disconnected, for the + * Le Audio group which is not active, a new Lead device will be chosen + * + * @param groupId The group id. + * @return group lead device. + * + * @hide + */ + @RequiresApi(Build.VERSION_CODES.TIRAMISU) + public @Nullable BluetoothDevice getConnectedGroupLeadDevice(int groupId) { + if (DEBUG) { + Log.d(TAG,"getConnectedGroupLeadDevice"); + } + if (mService == null) { + Log.e(TAG,"No service."); + return null; + } + return mService.getConnectedGroupLeadDevice(groupId); + } + @Override public boolean isEnabled(BluetoothDevice device) { if (mService == null || device == null) { diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java index e8cbab8197b2..a040e28169e8 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java @@ -32,7 +32,7 @@ public abstract class MediaManager { private static final String TAG = "MediaManager"; protected final Collection<MediaDeviceCallback> mCallbacks = new CopyOnWriteArrayList<>(); - protected final List<MediaDevice> mMediaDevices = new ArrayList<>(); + protected final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>(); protected Context mContext; protected Notification mNotification; diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt index 5b47ae525919..fbe33565f11d 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt @@ -656,7 +656,7 @@ class ActivityLaunchAnimator( controller.onLaunchAnimationCancelled() } - override fun onAnimationCancelled() { + override fun onAnimationCancelled(isKeyguardOccluded: Boolean) { if (timedOut) { return } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt index 20fac93df0af..db14fdf5930b 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt @@ -474,7 +474,13 @@ class TextInterpolator(layout: Layout) { val out = mutableListOf<List<PositionedGlyphs>>() for (lineNo in 0 until layout.lineCount) { // Shape all lines. val lineStart = layout.getLineStart(lineNo) - val count = layout.getLineEnd(lineNo) - lineStart + var count = layout.getLineEnd(lineNo) - lineStart + // Do not render the last character in the line if it's a newline and unprintable + val last = lineStart + count - 1 + if (last > lineStart && last < layout.text.length && layout.text[last] == '\n') { + count-- + } + val runs = mutableListOf<PositionedGlyphs>() TextShaper.shapeText( layout.text, diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt index 0c1916074e0c..cafdc8676173 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt @@ -13,9 +13,9 @@ */ package com.android.systemui.plugins +import android.content.res.Resources import android.graphics.drawable.Drawable import android.view.View -import com.android.internal.colorextraction.ColorExtractor import com.android.systemui.plugins.annotations.ProvidesInterface import java.io.PrintWriter import java.util.Locale @@ -57,7 +57,15 @@ interface Clock { val events: ClockEvents /** Triggers for various animations */ - val animation: ClockAnimation + val animations: ClockAnimations + + /** Initializes various rendering parameters. If never called, provides reasonable defaults. */ + fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) { + events.onColorPaletteChanged(resources) + animations.doze(dozeFraction) + animations.fold(foldFraction) + events.onTimeTick() + } /** Optional method for dumping debug information */ fun dump(pw: PrintWriter) { } @@ -80,15 +88,12 @@ interface ClockEvents { /** Call whenever font settings change */ fun onFontSettingChanged() { } - /** Call whenever the color pallete should update */ - fun onColorPaletteChanged(palette: ColorExtractor.GradientColors) { } + /** Call whenever the color palette should update */ + fun onColorPaletteChanged(resources: Resources) { } } /** Methods which trigger various clock animations */ -interface ClockAnimation { - /** Initializes the doze & fold animation positions. Defaults to neither folded nor dozing. */ - fun initialize(dozeFraction: Float, foldFraction: Float) { } - +interface ClockAnimations { /** Runs an enter animation (if any) */ fun enter() { } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java index 0f10589dfcf9..bd628ccb3c08 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java @@ -29,7 +29,7 @@ import java.lang.annotation.RetentionPolicy; /** * Interface that decides whether a touch on the phone was accidental. i.e. Pocket Dialing. * - * {@see com.android.systemui.classifier.FalsingManagerImpl} + * {@see com.android.systemui.classifier.BrightLineFalsingManager} */ @ProvidesInterface(version = FalsingManager.VERSION) public interface FalsingManager { diff --git a/packages/SystemUI/res-keyguard/font/clock.xml b/packages/SystemUI/res-keyguard/font/clock.xml deleted file mode 100644 index 0137dc39921f..000000000000 --- a/packages/SystemUI/res-keyguard/font/clock.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -** -** Copyright 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. -*/ ---> - -<!-- -** AOD/LockScreen Clock font. -** Should include all numeric glyphs in all supported locales. -** Recommended: font with variable width to support AOD => LS animations ---> -<!-- TODO: Remove when clock migration complete --> -<font-family xmlns:android="http://schemas.android.com/apk/res/android"> - <font android:typeface="monospace"/> -</font-family>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml index 6a38507b2ad7..8b8ebf00e190 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml @@ -31,42 +31,14 @@ android:layout_alignParentStart="true" android:layout_alignParentTop="true" android:paddingStart="@dimen/clock_padding_start"> - <com.android.systemui.shared.clocks.AnimatableClockView - android:id="@+id/animatable_clock_view" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="start" - android:gravity="start" - android:textSize="@dimen/clock_text_size" - android:fontFamily="@font/clock" - android:elegantTextHeight="false" - android:singleLine="true" - android:fontFeatureSettings="pnum" - chargeAnimationDelay="350" - dozeWeight="200" - lockScreenWeight="400" - /> </FrameLayout> <FrameLayout android:id="@+id/lockscreen_clock_view_large" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="match_parent" android:layout_below="@id/keyguard_slice_view" + android:paddingTop="@dimen/keyguard_large_clock_top_padding" android:visibility="gone"> - <com.android.systemui.shared.clocks.AnimatableClockView - android:id="@+id/animatable_clock_view_large" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:gravity="center_horizontal" - android:textSize="@dimen/large_clock_text_size" - android:fontFamily="@font/clock" - android:typeface="monospace" - android:elegantTextHeight="false" - chargeAnimationDelay="200" - dozeWeight="200" - lockScreenWeight="400" - /> </FrameLayout> <!-- Not quite optimal but needed to translate these items as a group. The diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index 77f1803523a8..acf3e4dcf02a 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -102,12 +102,13 @@ screen. --> <item name="half_opened_bouncer_height_ratio" type="dimen" format="float">0.0</item> - <!-- The actual amount of translation that is applied to the bouncer when it animates from one - side of the screen to the other in one-handed mode. Note that it will always translate from - the side of the screen to the other (it will "jump" closer to the destination while the - opacity is zero), but this controls how much motion will actually be applied to it while - animating. Larger values will cause it to move "faster" while fading out/in. --> - <dimen name="one_handed_bouncer_move_animation_translation">120dp</dimen> + <!-- The actual amount of translation that is applied to the security when it animates from one + side of the screen to the other in one-handed or user switcher mode. Note that it will + always translate from the side of the screen to the other (it will "jump" closer to the + destination while the opacity is zero), but this controls how much motion will actually be + applied to it while animating. Larger values will cause it to move "faster" while + fading out/in. --> + <dimen name="security_shift_animation_translation">120dp</dimen> <dimen name="bouncer_user_switcher_header_text_size">20sp</dimen> diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml index 3f56bafef134..efbdd1af3644 100644 --- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml +++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml @@ -20,5 +20,5 @@ style="@style/clock_subtitle" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:format12Hour="EEE, MMM d" - android:format24Hour="EEE, MMM d"/> + android:format12Hour="@string/dream_date_complication_date_format" + android:format24Hour="@string/dream_date_complication_date_format"/> diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml index e066d38e446f..5f4e310f975c 100644 --- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml +++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml @@ -19,11 +19,11 @@ android:id="@+id/time_view" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:fontFamily="@font/clock" + android:fontFamily="@*android:string/config_clockFontFamily" android:includeFontPadding="false" android:textColor="@android:color/white" - android:format12Hour="h:mm" - android:format24Hour="kk:mm" + android:format12Hour="@string/dream_time_complication_12_hr_time_format" + android:format24Hour="@string/dream_time_complication_24_hr_time_format" android:shadowColor="@color/keyguard_shadow_color" android:shadowRadius="?attr/shadowRadius" android:textSize="@dimen/dream_overlay_complication_clock_time_text_size"/> diff --git a/packages/SystemUI/res/layout/hybrid_conversation_notification.xml b/packages/SystemUI/res/layout/hybrid_conversation_notification.xml index 43b16618d615..a313833e2a66 100644 --- a/packages/SystemUI/res/layout/hybrid_conversation_notification.xml +++ b/packages/SystemUI/res/layout/hybrid_conversation_notification.xml @@ -57,7 +57,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:singleLine="true" - style="?attr/hybridNotificationTextStyle" + android:paddingEnd="4dp" + style="@*android:style/Widget.DeviceDefault.Notification.Text" /> <TextView @@ -65,6 +66,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" - style="?attr/hybridNotificationTextStyle" + android:paddingEnd="4dp" + style="@*android:style/Widget.DeviceDefault.Notification.Text" /> </com.android.systemui.statusbar.notification.row.HybridConversationNotificationView> diff --git a/packages/SystemUI/res/layout/hybrid_notification.xml b/packages/SystemUI/res/layout/hybrid_notification.xml index e8d77511e53c..9ea7be50adec 100644 --- a/packages/SystemUI/res/layout/hybrid_notification.xml +++ b/packages/SystemUI/res/layout/hybrid_notification.xml @@ -20,19 +20,22 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="bottom|start" - style="?attr/hybridNotificationStyle"> + android:paddingStart="@*android:dimen/notification_content_margin_start" + android:paddingEnd="12dp"> <TextView android:id="@+id/notification_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:singleLine="true" - style="?attr/hybridNotificationTitleStyle" + android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title" + android:paddingEnd="4dp" /> <TextView android:id="@+id/notification_text" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" - style="?attr/hybridNotificationTextStyle" + android:paddingEnd="4dp" + style="@*android:style/Widget.DeviceDefault.Notification.Text" /> </com.android.systemui.statusbar.notification.row.HybridNotificationView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/media_ttt_chip.xml b/packages/SystemUI/res/layout/media_ttt_chip.xml index 4d24140abbf4..d88680669fe0 100644 --- a/packages/SystemUI/res/layout/media_ttt_chip.xml +++ b/packages/SystemUI/res/layout/media_ttt_chip.xml @@ -31,6 +31,8 @@ android:padding="@dimen/media_ttt_chip_outer_padding" android:background="@drawable/media_ttt_chip_background" android:layout_marginTop="20dp" + android:layout_marginStart="@dimen/notification_side_paddings" + android:layout_marginEnd="@dimen/notification_side_paddings" android:clipToPadding="false" android:gravity="center_vertical" android:alpha="0.0" @@ -46,8 +48,9 @@ <TextView android:id="@+id/text" - android:layout_width="wrap_content" + android:layout_width="0dp" android:layout_height="wrap_content" + android:layout_weight="1" android:textSize="@dimen/media_ttt_text_size" android:textColor="?android:attr/textColorPrimary" android:alpha="0.0" diff --git a/packages/SystemUI/res/values-h800dp/dimens.xml b/packages/SystemUI/res/values-h800dp/dimens.xml index e6af6f46ae69..94fe20955ce6 100644 --- a/packages/SystemUI/res/values-h800dp/dimens.xml +++ b/packages/SystemUI/res/values-h800dp/dimens.xml @@ -15,9 +15,6 @@ --> <resources> - <!-- Minimum margin between clock and top of screen or ambient indication --> - <dimen name="keyguard_clock_top_margin">26dp</dimen> - <!-- Large clock maximum font size (dp is intentional, to prevent any further scaling) --> <dimen name="large_clock_text_size">200dp</dimen> diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml index 70a72ad23d7e..9a71995383ac 100644 --- a/packages/SystemUI/res/values/attrs.xml +++ b/packages/SystemUI/res/values/attrs.xml @@ -108,12 +108,6 @@ <attr name="android:layout" /> </declare-styleable> - <declare-styleable name="HybridNotificationTheme"> - <attr name="hybridNotificationStyle" format="reference" /> - <attr name="hybridNotificationTitleStyle" format="reference" /> - <attr name="hybridNotificationTextStyle" format="reference" /> - </declare-styleable> - <declare-styleable name="PluginInflateContainer"> <attr name="viewType" format="string" /> </declare-styleable> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 3b8505015f3e..1210b79d3ff5 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -661,13 +661,7 @@ <!-- When large clock is showing, offset the smartspace by this amount --> <dimen name="keyguard_smartspace_top_offset">12dp</dimen> <!-- With the large clock, move up slightly from the center --> - <dimen name="keyguard_large_clock_top_margin">-60dp</dimen> - - <!-- TODO: Remove during migration --> - <!-- Default line spacing multiplier between hours and minutes of the keyguard clock --> - <item name="keyguard_clock_line_spacing_scale" type="dimen" format="float">.7</item> - <!-- Burmese line spacing multiplier between hours and minutes of the keyguard clock --> - <item name="keyguard_clock_line_spacing_scale_burmese" type="dimen" format="float">1</item> + <dimen name="keyguard_large_clock_top_padding">100dp</dimen> <dimen name="notification_scrim_corner_radius">32dp</dimen> @@ -890,11 +884,6 @@ burn-in on AOD. --> <dimen name="burn_in_prevention_offset_y_clock">42dp</dimen> - <!-- Clock maximum font size (dp is intentional, to prevent any further scaling) --> - <!-- TODO: Remove when clock migration complete --> - <dimen name="large_clock_text_size">150dp</dimen> - <dimen name="clock_text_size">86dp</dimen> - <!-- The maximum offset in either direction that icons move to prevent burn-in on AOD. --> <dimen name="default_burn_in_prevention_offset">15dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index d88428f972cb..343ec4f67964 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2581,4 +2581,12 @@ <!-- [CHAR LIMIT=NONE] Le audio broadcast dialog, media app is unknown --> <string name="bt_le_audio_broadcast_dialog_unknown_name">Unknown</string> + <!-- Date format for the Dream Date Complication [CHAR LIMIT=NONE] --> + <string name="dream_date_complication_date_format">EEE, MMM d</string> + + <!-- Time format for the Dream Time Complication for 12-hour time format [CHAR LIMIT=NONE] --> + <string name="dream_time_complication_12_hr_time_format">h:mm</string> + + <!-- Time format for the Dream Time Complication for 24-hour time format [CHAR LIMIT=NONE] --> + <string name="dream_time_complication_24_hr_time_format">kk:mm</string> </resources> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 4ca6e3ab3dc1..758c16d15bcf 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -17,30 +17,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> - <!-- HybridNotification themes and styles --> - - <style name="HybridNotification"> - <item name="hybridNotificationStyle">@style/hybrid_notification</item> - <item name="hybridNotificationTitleStyle">@style/hybrid_notification_title</item> - <item name="hybridNotificationTextStyle">@style/hybrid_notification_text</item> - </style> - - <style name="hybrid_notification"> - <item name="android:paddingStart">@*android:dimen/notification_content_margin_start</item> - <item name="android:paddingEnd">12dp</item> - </style> - - <style name="hybrid_notification_title"> - <item name="android:paddingEnd">4dp</item> - <item name="android:textAppearance">@*android:style/TextAppearance.DeviceDefault.Notification.Title</item> - </style> - - <style name="hybrid_notification_text" - parent="@*android:style/Widget.DeviceDefault.Notification.Text"> - <item name="android:paddingEnd">4dp</item> - </style> - - <style name="TextAppearance.StatusBar.Clock" parent="@*android:style/TextAppearance.StatusBar.Icon"> <item name="android:textSize">@dimen/status_bar_clock_size</item> <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item> diff --git a/packages/SystemUI/screenshot/Android.bp b/packages/SystemUI/screenshot/Android.bp new file mode 100644 index 000000000000..a79fd9040db3 --- /dev/null +++ b/packages/SystemUI/screenshot/Android.bp @@ -0,0 +1,48 @@ +// Copyright (C) 2022 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], +} + +android_library { + name: "SystemUIScreenshotLib", + manifest: "AndroidManifest.xml", + + srcs: [ + // All files in this library should be in Kotlin besides some exceptions. + "src/**/*.kt", + + // This file was forked from google3, so exceptionally it can be in Java. + "src/com/android/systemui/testing/screenshot/DynamicColorsTestUtils.java", + ], + + resource_dirs: [ + "res", + ], + + static_libs: [ + "SystemUI-core", + "androidx.test.espresso.core", + "androidx.appcompat_appcompat", + "platform-screenshot-diff-core", + ], + + kotlincflags: ["-Xjvm-default=all"], +} diff --git a/packages/SystemUI/screenshot/AndroidManifest.xml b/packages/SystemUI/screenshot/AndroidManifest.xml new file mode 100644 index 000000000000..3b703be34e5d --- /dev/null +++ b/packages/SystemUI/screenshot/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.systemui.testing.screenshot"> + <application> + <activity + android:name="com.android.systemui.testing.screenshot.ScreenshotActivity" + android:exported="true" + android:theme="@style/Theme.SystemUI.Screenshot" /> + </application> + + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> +</manifest> diff --git a/packages/SystemUI/screenshot/res/values/themes.xml b/packages/SystemUI/screenshot/res/values/themes.xml new file mode 100644 index 000000000000..40e50bbb6bbf --- /dev/null +++ b/packages/SystemUI/screenshot/res/values/themes.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources> + <style name="Theme.SystemUI.Screenshot" parent="Theme.SystemUI"> + <item name="android:windowActionBar">false</item> + <item name="android:windowNoTitle">true</item> + + <!-- Make sure that device specific cutouts don't impact the outcome of screenshot tests --> + <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> + </style> +</resources>
\ No newline at end of file diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/DynamicColorsTestUtils.java b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/DynamicColorsTestUtils.java new file mode 100644 index 000000000000..96ec4c543474 --- /dev/null +++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/DynamicColorsTestUtils.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.testing.screenshot; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import android.app.UiAutomation; +import android.content.Context; +import android.provider.Settings; +import android.util.Log; + +import androidx.annotation.ColorInt; +import androidx.annotation.ColorRes; +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; +import androidx.core.content.ContextCompat; +import androidx.test.espresso.Espresso; +import androidx.test.espresso.IdlingRegistry; +import androidx.test.espresso.IdlingResource; + +import org.json.JSONObject; +import org.junit.function.ThrowingRunnable; + +import java.util.HashMap; +import java.util.Map; + +/* + * Note: This file was forked from + * google3/third_party/java_src/android_libs/material_components/screenshot_tests/java/android/ + * support/design/scuba/color/DynamicColorsTestUtils.java. + */ + +/** Utility that helps change the dynamic system colors for testing. */ +@RequiresApi(32) +public class DynamicColorsTestUtils { + + private static final String TAG = DynamicColorsTestUtils.class.getSimpleName(); + + private static final String THEME_CUSTOMIZATION_KEY = "theme_customization_overlay_packages"; + private static final String THEME_CUSTOMIZATION_SYSTEM_PALETTE_KEY = + "android.theme.customization.system_palette"; + + private static final int ORANGE_SYSTEM_SEED_COLOR = 0xA66800; + private static final int ORANGE_EXPECTED_SYSTEM_ACCENT1_600_COLOR = -8235756; + + private DynamicColorsTestUtils() { + } + + /** + * Update system dynamic colors (e.g., android.R.color.system_accent1_600) based on an orange + * seed color, and then wait for the change to propagate to the app by comparing + * android.R.color.system_accent1_600 to the expected orange value. + */ + public static void updateSystemColorsToOrange() { + updateSystemColors(ORANGE_SYSTEM_SEED_COLOR, ORANGE_EXPECTED_SYSTEM_ACCENT1_600_COLOR); + } + + /** + * Update system dynamic colors (e.g., android.R.color.system_accent1_600) based on the provided + * {@code seedColor}, and then wait for the change to propagate to the app by comparing + * android.R.color.system_accent1_600 to {@code expectedSystemAccent1600}. + */ + public static void updateSystemColors( + @ColorInt int seedColor, @ColorInt int expectedSystemAccent1600) { + Context context = getInstrumentation().getTargetContext(); + + int actualSystemAccent1600 = + ContextCompat.getColor(context, android.R.color.system_accent1_600); + + if (expectedSystemAccent1600 == actualSystemAccent1600) { + String expectedColorString = Integer.toHexString(expectedSystemAccent1600); + Log.d( + TAG, + "Skipped updating system colors since system_accent1_600 is already equal to " + + "expected: " + + expectedColorString); + return; + } + + updateSystemColors(seedColor); + } + + /** + * Update system dynamic colors (e.g., android.R.color.system_accent1_600) based on the provided + * {@code seedColor}, and then wait for the change to propagate to the app by checking + * android.R.color.system_accent1_600 for any change. + */ + public static void updateSystemColors(@ColorInt int seedColor) { + Context context = getInstrumentation().getTargetContext(); + + // Initialize system color idling resource with original system_accent1_600 value. + ColorChangeIdlingResource systemColorIdlingResource = + new ColorChangeIdlingResource(context, android.R.color.system_accent1_600); + + // Update system theme color setting to trigger fabricated resource overlay. + runWithShellPermissionIdentity( + () -> + Settings.Secure.putString( + context.getContentResolver(), + THEME_CUSTOMIZATION_KEY, + buildThemeCustomizationString(seedColor))); + + // Wait for system color update to propagate to app. + IdlingRegistry idlingRegistry = IdlingRegistry.getInstance(); + idlingRegistry.register(systemColorIdlingResource); + Espresso.onIdle(); + idlingRegistry.unregister(systemColorIdlingResource); + + Log.d(TAG, + Settings.Secure.getString(context.getContentResolver(), THEME_CUSTOMIZATION_KEY)); + } + + private static String buildThemeCustomizationString(@ColorInt int seedColor) { + String seedColorHex = Integer.toHexString(seedColor); + Map<String, String> themeCustomizationMap = new HashMap<>(); + themeCustomizationMap.put(THEME_CUSTOMIZATION_SYSTEM_PALETTE_KEY, seedColorHex); + return new JSONObject(themeCustomizationMap).toString(); + } + + private static void runWithShellPermissionIdentity(@NonNull ThrowingRunnable runnable) { + UiAutomation uiAutomation = getInstrumentation().getUiAutomation(); + uiAutomation.adoptShellPermissionIdentity(); + try { + runnable.run(); + } catch (Throwable e) { + throw new RuntimeException(e); + } finally { + uiAutomation.dropShellPermissionIdentity(); + } + } + + private static class ColorChangeIdlingResource implements IdlingResource { + + private final Context mContext; + private final int mColorResId; + private final int mInitialColorInt; + + private ResourceCallback mResourceCallback; + private boolean mIdleNow; + + ColorChangeIdlingResource(Context context, @ColorRes int colorResId) { + this.mContext = context; + this.mColorResId = colorResId; + this.mInitialColorInt = ContextCompat.getColor(context, colorResId); + } + + @Override + public String getName() { + return ColorChangeIdlingResource.class.getName(); + } + + @Override + public boolean isIdleNow() { + if (mIdleNow) { + return true; + } + + int currentColorInt = ContextCompat.getColor(mContext, mColorResId); + + String initialColorString = Integer.toHexString(mInitialColorInt); + String currentColorString = Integer.toHexString(currentColorInt); + Log.d(TAG, String.format("Initial=%s, Current=%s", initialColorString, + currentColorString)); + + mIdleNow = currentColorInt != mInitialColorInt; + Log.d(TAG, String.format("idleNow=%b", mIdleNow)); + + if (mIdleNow) { + mResourceCallback.onTransitionToIdle(); + } + return mIdleNow; + } + + @Override + public void registerIdleTransitionCallback(ResourceCallback resourceCallback) { + this.mResourceCallback = resourceCallback; + } + } +} diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt new file mode 100644 index 000000000000..2a55a80eb7f4 --- /dev/null +++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.testing.screenshot + +import androidx.activity.ComponentActivity + +/** The Activity that is launched and whose content is set for screenshot tests. */ +class ScreenshotActivity : ComponentActivity() diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotTestRule.kt new file mode 100644 index 000000000000..363ce10fa36c --- /dev/null +++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotTestRule.kt @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.testing.screenshot + +import android.app.UiModeManager +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.os.UserHandle +import android.view.Display +import android.view.View +import android.view.WindowManagerGlobal +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement +import platform.test.screenshot.GoldenImagePathManager +import platform.test.screenshot.PathConfig +import platform.test.screenshot.PathElementNoContext +import platform.test.screenshot.ScreenshotTestRule +import platform.test.screenshot.matchers.PixelPerfectMatcher + +/** + * A base rule for screenshot diff tests. + * + * This rules takes care of setting up the activity according to [testSpec] by: + * - emulating the display size and density. + * - setting the dark/light mode. + * - setting the system (Material You) colors to a fixed value. + * + * @see ComposeScreenshotTestRule + * @see ViewScreenshotTestRule + */ +class ScreenshotTestRule(private val testSpec: ScreenshotTestSpec) : TestRule { + private var currentDisplay: DisplaySpec? = null + private var currentGoldenIdentifier: String? = null + + private val pathConfig = + PathConfig( + PathElementNoContext("model", isDir = true) { + currentDisplay?.name ?: error("currentDisplay is null") + }, + ) + private val defaultMatcher = PixelPerfectMatcher() + + private val screenshotRule = + ScreenshotTestRule( + SystemUIGoldenImagePathManager( + pathConfig, + currentGoldenIdentifier = { + currentGoldenIdentifier ?: error("currentGoldenIdentifier is null") + }, + ) + ) + + override fun apply(base: Statement, description: Description): Statement { + // The statement which call beforeTest() before running the test and afterTest() afterwards. + val statement = + object : Statement() { + override fun evaluate() { + try { + beforeTest() + base.evaluate() + } finally { + afterTest() + } + } + } + + return screenshotRule.apply(statement, description) + } + + private fun beforeTest() { + // Update the system colors to a fixed color, so that tests don't depend on the host device + // extracted colors. Note that we don't restore the default device colors at the end of the + // test because changing the colors (and waiting for them to be applied) is costly and makes + // the screenshot tests noticeably slower. + DynamicColorsTestUtils.updateSystemColorsToOrange() + + // Emulate the display size and density. + val display = testSpec.display + val density = display.densityDpi + val wm = WindowManagerGlobal.getWindowManagerService() + val (width, height) = getEmulatedDisplaySize() + wm.setForcedDisplayDensityForUser(Display.DEFAULT_DISPLAY, density, UserHandle.myUserId()) + wm.setForcedDisplaySize(Display.DEFAULT_DISPLAY, width, height) + + // Force the dark/light theme. + val uiModeManager = + InstrumentationRegistry.getInstrumentation() + .targetContext + .getSystemService(Context.UI_MODE_SERVICE) as UiModeManager + uiModeManager.setApplicationNightMode( + if (testSpec.isDarkTheme) { + UiModeManager.MODE_NIGHT_YES + } else { + UiModeManager.MODE_NIGHT_NO + } + ) + } + + private fun afterTest() { + // Reset the density and display size. + val wm = WindowManagerGlobal.getWindowManagerService() + wm.clearForcedDisplayDensityForUser(Display.DEFAULT_DISPLAY, UserHandle.myUserId()) + wm.clearForcedDisplaySize(Display.DEFAULT_DISPLAY) + + // Reset the dark/light theme. + val uiModeManager = + InstrumentationRegistry.getInstrumentation() + .targetContext + .getSystemService(Context.UI_MODE_SERVICE) as UiModeManager + uiModeManager.setApplicationNightMode(UiModeManager.MODE_NIGHT_AUTO) + } + + /** + * Compare the content of [view] with the golden image identified by [goldenIdentifier] in the + * context of [testSpec]. + */ + fun screenshotTest(goldenIdentifier: String, view: View) { + val bitmap = drawIntoBitmap(view) + + // Compare bitmap against golden asset. + val isDarkTheme = testSpec.isDarkTheme + val isLandscape = testSpec.isLandscape + val identifierWithSpec = buildString { + append(goldenIdentifier) + if (isDarkTheme) append("_dark") + if (isLandscape) append("_landscape") + } + + // TODO(b/230832101): Provide a way to pass a PathConfig and override the file name on + // device to assertBitmapAgainstGolden instead? + currentDisplay = testSpec.display + currentGoldenIdentifier = goldenIdentifier + screenshotRule.assertBitmapAgainstGolden(bitmap, identifierWithSpec, defaultMatcher) + currentDisplay = null + currentGoldenIdentifier = goldenIdentifier + } + + /** Draw [view] into a [Bitmap]. */ + private fun drawIntoBitmap(view: View): Bitmap { + val bitmap = + Bitmap.createBitmap( + view.measuredWidth, + view.measuredHeight, + Bitmap.Config.ARGB_8888, + ) + val canvas = Canvas(bitmap) + view.draw(canvas) + return bitmap + } + + /** Get the emulated display size for [testSpec]. */ + private fun getEmulatedDisplaySize(): Pair<Int, Int> { + val display = testSpec.display + val isPortraitNaturalPosition = display.width < display.height + return if (testSpec.isLandscape) { + if (isPortraitNaturalPosition) { + display.height to display.width + } else { + display.width to display.height + } + } else { + if (isPortraitNaturalPosition) { + display.width to display.height + } else { + display.height to display.width + } + } + } +} + +private class SystemUIGoldenImagePathManager( + pathConfig: PathConfig, + private val currentGoldenIdentifier: () -> String, +) : + GoldenImagePathManager( + appContext = InstrumentationRegistry.getInstrumentation().context, + deviceLocalPath = + InstrumentationRegistry.getInstrumentation() + .targetContext + .filesDir + .absolutePath + .toString() + "/sysui_screenshots", + pathConfig = pathConfig, + ) { + // This string is appended to all actual/expected screenshots on the device. We append the + // golden identifier so that our pull_golden.py scripts can map a screenshot on device to its + // asset (and automatically update it, if necessary). + override fun toString() = currentGoldenIdentifier() +} diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotTestSpec.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotTestSpec.kt new file mode 100644 index 000000000000..7fc624554738 --- /dev/null +++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotTestSpec.kt @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.testing.screenshot + +/** The specification of a device display to be used in a screenshot test. */ +data class DisplaySpec( + val name: String, + val width: Int, + val height: Int, + val densityDpi: Int, +) + +/** The specification of a screenshot diff test. */ +class ScreenshotTestSpec( + val display: DisplaySpec, + val isDarkTheme: Boolean = false, + val isLandscape: Boolean = false, +) { + companion object { + /** + * Return a list of [ScreenshotTestSpec] for each of the [displays]. + * + * If [isDarkTheme] is null, this will create a spec for both light and dark themes, for + * each of the orientation. + * + * If [isLandscape] is null, this will create a spec for both portrait and landscape, for + * each of the light/dark themes. + */ + fun forDisplays( + vararg displays: DisplaySpec, + isDarkTheme: Boolean? = null, + isLandscape: Boolean? = null, + ): List<ScreenshotTestSpec> { + return displays.flatMap { display -> + buildList { + fun addDisplay(isLandscape: Boolean) { + if (isDarkTheme != true) { + add(ScreenshotTestSpec(display, isDarkTheme = false, isLandscape)) + } + + if (isDarkTheme != false) { + add(ScreenshotTestSpec(display, isDarkTheme = true, isLandscape)) + } + } + + if (isLandscape != true) { + addDisplay(isLandscape = false) + } + + if (isLandscape != false) { + addDisplay(isLandscape = true) + } + } + } + } + } + + override fun toString(): String = buildString { + // This string is appended to PNGs stored in the device, so let's keep it simple. + append(display.name) + if (isDarkTheme) append("_dark") + if (isLandscape) append("_landscape") + } +} diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt new file mode 100644 index 000000000000..2c3ff2c75c72 --- /dev/null +++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt @@ -0,0 +1,51 @@ +package com.android.systemui.testing.screenshot + +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.view.ViewGroup.LayoutParams +import androidx.test.ext.junit.rules.ActivityScenarioRule +import org.junit.Assert.assertEquals +import org.junit.rules.RuleChain +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +/** A rule for View screenshot diff tests. */ +class ViewScreenshotTestRule(testSpec: ScreenshotTestSpec) : TestRule { + private val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java) + private val screenshotRule = ScreenshotTestRule(testSpec) + + private val delegate = RuleChain.outerRule(screenshotRule).around(activityRule) + + override fun apply(base: Statement, description: Description): Statement { + return delegate.apply(base, description) + } + + /** + * Compare the content of [view] with the golden image identified by [goldenIdentifier] in the + * context of [testSpec]. + */ + fun screenshotTest( + goldenIdentifier: String, + layoutParams: LayoutParams = + LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT), + view: (Activity) -> View, + ) { + activityRule.scenario.onActivity { activity -> + // Make sure that the activity draws full screen and fits the whole display instead of + // the system bars. + activity.window.setDecorFitsSystemWindows(false) + activity.setContentView(view(activity), layoutParams) + } + + // We call onActivity again because it will make sure that our Activity is done measuring, + // laying out and drawing its content (that we set in the previous onActivity lambda). + activityRule.scenario.onActivity { activity -> + // Check that the content is what we expected. + val content = activity.requireViewById<ViewGroup>(android.R.id.content) + assertEquals(1, content.childCount) + screenshotRule.screenshotTest(goldenIdentifier, content.getChildAt(0)) + } + } +} diff --git a/packages/SystemUI/shared/res/layout/clock_default_large.xml b/packages/SystemUI/shared/res/layout/clock_default_large.xml index 8510a0a8b550..0139d50dcfba 100644 --- a/packages/SystemUI/shared/res/layout/clock_default_large.xml +++ b/packages/SystemUI/shared/res/layout/clock_default_large.xml @@ -18,7 +18,6 @@ --> <com.android.systemui.shared.clocks.AnimatableClockView xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/animatable_clock_view_large" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" diff --git a/packages/SystemUI/shared/res/layout/clock_default_small.xml b/packages/SystemUI/shared/res/layout/clock_default_small.xml index ec0e427e6a4d..390ff5e3ff78 100644 --- a/packages/SystemUI/shared/res/layout/clock_default_small.xml +++ b/packages/SystemUI/shared/res/layout/clock_default_small.xml @@ -18,7 +18,6 @@ --> <com.android.systemui.shared.clocks.AnimatableClockView xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/animatable_clock_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="start" @@ -26,6 +25,7 @@ android:textSize="@dimen/small_clock_text_size" android:fontFamily="@*android:string/config_clockFontFamily" android:elegantTextHeight="false" + android:ellipsize="none" android:singleLine="true" android:fontFeatureSettings="pnum" chargeAnimationDelay="350" diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt index 2739d59dbf00..8f1959e884cf 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt @@ -20,12 +20,15 @@ import android.annotation.ColorInt import android.annotation.FloatRange import android.annotation.IntRange import android.annotation.SuppressLint +import android.app.compat.ChangeIdStateCache.invalidate import android.content.Context import android.graphics.Canvas import android.text.TextUtils import android.text.format.DateFormat import android.util.AttributeSet import android.widget.TextView +import com.android.internal.R.attr.contentDescription +import com.android.internal.R.attr.format import com.android.systemui.animation.GlyphCallback import com.android.systemui.animation.Interpolators import com.android.systemui.animation.TextAnimator @@ -75,6 +78,12 @@ class AnimatableClockView @JvmOverloads constructor( val lockScreenWeight: Int get() = if (useBoldedVersion()) lockScreenWeightInternal + 100 else lockScreenWeightInternal + /** + * The number of pixels below the baseline. For fonts that support languages such as + * Burmese, this space can be significant and should be accounted for when computing layout. + */ + val bottom get() = paint?.fontMetrics?.bottom ?: 0f + init { val animatableClockViewAttributes = context.obtainStyledAttributes( attrs, R.styleable.AnimatableClockView, defStyleAttr, defStyleRes @@ -133,6 +142,15 @@ class AnimatableClockView @JvmOverloads constructor( // relayout if the text didn't actually change. if (!TextUtils.equals(text, formattedText)) { text = formattedText + + // Because the TextLayout may mutate under the hood as a result of the new text, we + // notify the TextAnimator that it may have changed and request a measure/layout. A + // crash will occur on the next invocation of setTextStyle if the layout is mutated + // without being notified TextInterpolator being notified. + if (layout != null) { + textAnimator?.updateLayout(layout) + } + requestLayout() } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt index a4c03b0b57c8..06247c6c9523 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt @@ -35,8 +35,6 @@ import javax.inject.Inject private val TAG = ClockRegistry::class.simpleName private val DEBUG = true -typealias ClockChangeListener = () -> Unit - /** ClockRegistry aggregates providers and plugins */ open class ClockRegistry( val context: Context, @@ -51,12 +49,19 @@ open class ClockRegistry( defaultClockProvider: DefaultClockProvider ) : this(context, pluginManager, handler, defaultClockProvider as ClockProvider) { } + // Usually this would be a typealias, but a SAM provides better java interop + fun interface ClockChangeListener { + fun onClockChanged() + } + + var isEnabled: Boolean = false + private val gson = Gson() private val availableClocks = mutableMapOf<ClockId, ClockInfo>() private val clockChangeListeners = mutableListOf<ClockChangeListener>() private val settingObserver = object : ContentObserver(handler) { override fun onChange(selfChange: Boolean, uris: Collection<Uri>, flags: Int, userId: Int) = - clockChangeListeners.forEach { it() } + clockChangeListeners.forEach { it.onClockChanged() } } private val pluginListener = object : PluginListener<ClockProviderPlugin> { @@ -73,7 +78,7 @@ open class ClockRegistry( context.contentResolver, Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE ) - return gson.fromJson(json, ClockSetting::class.java).clockId + return gson.fromJson(json, ClockSetting::class.java)?.clockId ?: DEFAULT_CLOCK_ID } set(value) { val json = gson.toJson(ClockSetting(value, System.currentTimeMillis())) @@ -85,6 +90,12 @@ open class ClockRegistry( init { connectClocks(defaultClockProvider) + if (!availableClocks.containsKey(DEFAULT_CLOCK_ID)) { + throw IllegalArgumentException( + "$defaultClockProvider did not register clock at $DEFAULT_CLOCK_ID" + ) + } + pluginManager.addPluginListener(pluginListener, ClockProviderPlugin::class.java) context.contentResolver.registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE), @@ -100,8 +111,11 @@ open class ClockRegistry( val id = clock.clockId val current = availableClocks[id] if (current != null) { - Log.e(TAG, "Clock Id conflict: $id is registered by both " + - "${provider::class.simpleName} and ${current.provider::class.simpleName}") + Log.e( + TAG, + "Clock Id conflict: $id is registered by both " + + "${provider::class.simpleName} and ${current.provider::class.simpleName}" + ) return } @@ -110,7 +124,7 @@ open class ClockRegistry( if (DEBUG) { Log.i(TAG, "Current clock ($currentId) was connected") } - clockChangeListeners.forEach { it() } + clockChangeListeners.forEach { it.onClockChanged() } } } } @@ -122,12 +136,17 @@ open class ClockRegistry( if (currentId == clock.clockId) { Log.w(TAG, "Current clock ($currentId) was disconnected") - clockChangeListeners.forEach { it() } + clockChangeListeners.forEach { it.onClockChanged() } } } } - fun getClocks(): List<ClockMetadata> = availableClocks.map { (_, clock) -> clock.metadata } + fun getClocks(): List<ClockMetadata> { + if (!isEnabled) { + return listOf(availableClocks[DEFAULT_CLOCK_ID]!!.metadata) + } + return availableClocks.map { (_, clock) -> clock.metadata } + } fun getClockThumbnail(clockId: ClockId): Drawable? = availableClocks[clockId]?.provider?.getClockThumbnail(clockId) @@ -142,7 +161,7 @@ open class ClockRegistry( fun createCurrentClock(): Clock { val clockId = currentClockId - if (!clockId.isNullOrEmpty()) { + if (isEnabled && clockId.isNotEmpty()) { val clock = createClock(clockId) if (clock != null) { return clock diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt index 5d8da5985768..1d8abe3fdf42 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt @@ -19,10 +19,9 @@ import android.graphics.drawable.Drawable import android.icu.text.NumberFormat import android.util.TypedValue import android.view.LayoutInflater -import com.android.internal.colorextraction.ColorExtractor import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.Clock -import com.android.systemui.plugins.ClockAnimation +import com.android.systemui.plugins.ClockAnimations import com.android.systemui.plugins.ClockEvents import com.android.systemui.plugins.ClockId import com.android.systemui.plugins.ClockMetadata @@ -102,10 +101,13 @@ class DefaultClock( TypedValue.COMPLEX_UNIT_PX, resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat() ) + recomputePadding() } - override fun onColorPaletteChanged(palette: ColorExtractor.GradientColors) = - clocks.forEach { it.setColors(DOZE_COLOR, palette.mainColor) } + override fun onColorPaletteChanged(resources: Resources) { + val color = resources.getColor(android.R.color.system_accent1_100) + clocks.forEach { it.setColors(DOZE_COLOR, color) } + } override fun onLocaleChanged(locale: Locale) { val nf = NumberFormat.getInstance(locale) @@ -119,8 +121,17 @@ class DefaultClock( } } - override val animation = object : ClockAnimation { - override fun initialize(dozeFraction: Float, foldFraction: Float) { + override var animations = DefaultClockAnimations(0f, 0f) + private set + + inner class DefaultClockAnimations( + dozeFraction: Float, + foldFraction: Float + ) : ClockAnimations { + private var foldState = AnimationState(0f) + private var dozeState = AnimationState(0f) + + init { dozeState = AnimationState(dozeFraction) foldState = AnimationState(foldFraction) @@ -132,14 +143,13 @@ class DefaultClock( } override fun enter() { - if (dozeState.isActive) { + if (!dozeState.isActive) { clocks.forEach { it.animateAppearOnLockscreen() } } } override fun charge() = clocks.forEach { it.animateCharge { dozeState.isActive } } - private var foldState = AnimationState(0f) override fun fold(fraction: Float) { val (hasChanged, hasJumped) = foldState.update(fraction) if (hasChanged) { @@ -147,7 +157,6 @@ class DefaultClock( } } - private var dozeState = AnimationState(0f) override fun doze(fraction: Float) { val (hasChanged, hasJumped) = dozeState.update(fraction) if (hasChanged) { @@ -172,6 +181,19 @@ class DefaultClock( init { events.onLocaleChanged(Locale.getDefault()) + clocks.forEach { it.setColors(DOZE_COLOR, DOZE_COLOR) } + } + + override fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) { + recomputePadding() + animations = DefaultClockAnimations(dozeFraction, foldFraction) + events.onColorPaletteChanged(resources) + events.onTimeTick() + } + + private fun recomputePadding() { + val topPadding = -1 * (largeClock.bottom.toInt() - 180) + largeClock.setPadding(0, topPadding, 0, 0) } override fun dump(pw: PrintWriter) = clocks.forEach { it.dump(pw) } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java index 76a09b38036c..e629f749ee96 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java @@ -105,7 +105,7 @@ public class RemoteAnimationAdapterCompat { } @Override - public void onAnimationCancelled() { + public void onAnimationCancelled(boolean isKeyguardOccluded) { remoteAnimationAdapter.onAnimationCancelled(); } }; @@ -114,6 +114,8 @@ public class RemoteAnimationAdapterCompat { private static IRemoteTransition.Stub wrapRemoteTransition( final RemoteAnimationRunnerCompat remoteAnimationAdapter) { return new IRemoteTransition.Stub() { + final ArrayMap<IBinder, Runnable> mFinishRunnables = new ArrayMap<>(); + @Override public void startAnimation(IBinder token, TransitionInfo info, SurfaceControl.Transaction t, @@ -229,19 +231,32 @@ public class RemoteAnimationAdapterCompat { } } }; + synchronized (mFinishRunnables) { + mFinishRunnables.put(token, animationFinishedCallback); + } // TODO(bc-unlcok): Pass correct transit type. - remoteAnimationAdapter.onAnimationStart( - TRANSIT_OLD_NONE, - appsCompat, wallpapersCompat, nonAppsCompat, - animationFinishedCallback); + remoteAnimationAdapter.onAnimationStart(TRANSIT_OLD_NONE, + appsCompat, wallpapersCompat, nonAppsCompat, () -> { + synchronized (mFinishRunnables) { + if (mFinishRunnables.remove(token) == null) return; + } + animationFinishedCallback.run(); + }); } @Override public void mergeAnimation(IBinder token, TransitionInfo info, SurfaceControl.Transaction t, IBinder mergeTarget, IRemoteTransitionFinishedCallback finishCallback) { - // TODO: hook up merge to recents onTaskAppeared if applicable. Until then, ignore - // any incoming merges. + // TODO: hook up merge to recents onTaskAppeared if applicable. Until then, adapt + // to legacy cancel. + final Runnable finishRunnable; + synchronized (mFinishRunnables) { + finishRunnable = mFinishRunnables.remove(mergeTarget); + } + if (finishRunnable == null) return; + remoteAnimationAdapter.onAnimationCancelled(); + finishRunnable.run(); } }; } diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java index c69ff7ee1cd8..e0b11d83bf75 100644 --- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java +++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java @@ -182,18 +182,6 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie mStatusBarStateController.removeCallback(mStatusBarStateListener); } - /** - * @return the number of pixels below the baseline. For fonts that support languages such as - * Burmese, this space can be significant. - */ - public float getBottom() { - if (mView.getPaint() != null && mView.getPaint().getFontMetrics() != null) { - return mView.getPaint().getFontMetrics().bottom; - } - - return 0f; - } - /** Animate the clock appearance */ public void animateAppear() { if (!mIsDozing) mView.animateAppearOnLockscreen(); diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt new file mode 100644 index 000000000000..efd7bcf10cd2 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.keyguard + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.content.res.Resources +import android.text.format.DateFormat +import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.plugins.Clock +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.statusbar.policy.BatteryController +import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback +import com.android.systemui.statusbar.policy.ConfigurationController +import java.io.PrintWriter +import java.util.Locale +import java.util.TimeZone +import javax.inject.Inject + +/** + * Controller for a Clock provided by the registry and used on the keyguard. Instantiated by + * [KeyguardClockSwitchController]. Functionality is forked from [AnimatableClockController]. + */ +class ClockEventController @Inject constructor( + private val statusBarStateController: StatusBarStateController, + private val broadcastDispatcher: BroadcastDispatcher, + private val batteryController: BatteryController, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + private val configurationController: ConfigurationController, + @Main private val resources: Resources, + private val context: Context +) { + var clock: Clock? = null + set(value) { + field = value + if (value != null) { + value.initialize(resources, dozeAmount, 0f) + } + } + + private var isDozing = false + private set + + private var isCharging = false + private var dozeAmount = 0f + private var isKeyguardShowing = false + + private val configListener = object : ConfigurationController.ConfigurationListener { + override fun onThemeChanged() { + clock?.events?.onColorPaletteChanged(resources) + } + } + + private val batteryCallback = object : BatteryStateChangeCallback { + override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) { + if (isKeyguardShowing && !isCharging && charging) { + clock?.animations?.charge() + } + isCharging = charging + } + } + + private val localeBroadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + clock?.events?.onLocaleChanged(Locale.getDefault()) + } + } + + private val statusBarStateListener = object : StatusBarStateController.StateListener { + override fun onDozeAmountChanged(linear: Float, eased: Float) { + clock?.animations?.doze(linear) + + isDozing = linear > dozeAmount + dozeAmount = linear + } + } + + private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() { + override fun onKeyguardVisibilityChanged(showing: Boolean) { + isKeyguardShowing = showing + if (!isKeyguardShowing) { + clock?.animations?.doze(if (isDozing) 1f else 0f) + } + } + + override fun onTimeFormatChanged(timeFormat: String) { + clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context)) + } + + override fun onTimeZoneChanged(timeZone: TimeZone) { + clock?.events?.onTimeZoneChanged(timeZone) + } + + override fun onUserSwitchComplete(userId: Int) { + clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context)) + } + } + + init { + isDozing = statusBarStateController.isDozing + } + + fun registerListeners() { + dozeAmount = statusBarStateController.dozeAmount + isDozing = statusBarStateController.isDozing || dozeAmount != 0f + + broadcastDispatcher.registerReceiver( + localeBroadcastReceiver, + IntentFilter(Intent.ACTION_LOCALE_CHANGED) + ) + configurationController.addCallback(configListener) + batteryController.addCallback(batteryCallback) + keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) + statusBarStateController.addCallback(statusBarStateListener) + } + + fun unregisterListeners() { + broadcastDispatcher.unregisterReceiver(localeBroadcastReceiver) + configurationController.removeCallback(configListener) + batteryController.removeCallback(batteryCallback) + keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback) + statusBarStateController.removeCallback(statusBarStateListener) + } + + /** + * Dump information for debugging + */ + fun dump(pw: PrintWriter) { + pw.println(this) + clock?.dump(pw) + } + + companion object { + private val TAG = ClockEventController::class.simpleName + private const val FORMAT_NUMBER = 1234567890 + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index 206b8bee323c..e1fabdef3651 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -5,10 +5,8 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; -import android.graphics.Paint; -import android.graphics.Paint.Style; import android.util.AttributeSet; -import android.util.TypedValue; +import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; @@ -17,19 +15,14 @@ import android.widget.RelativeLayout; import androidx.annotation.IntDef; import androidx.annotation.VisibleForTesting; -import com.android.internal.colorextraction.ColorExtractor; import com.android.keyguard.dagger.KeyguardStatusViewScope; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; -import com.android.systemui.plugins.ClockPlugin; -import com.android.systemui.shared.clocks.AnimatableClockView; +import com.android.systemui.plugins.Clock; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.Arrays; -import java.util.TimeZone; - /** * Switch to show plugin clock when plugin is connected, otherwise it will show default clock. */ @@ -50,17 +43,10 @@ public class KeyguardClockSwitch extends RelativeLayout { public static final int SMALL = 1; /** - * Optional/alternative clock injected via plugin. - */ - private ClockPlugin mClockPlugin; - - /** * Frame for small/large clocks */ - private FrameLayout mClockFrame; + private FrameLayout mSmallClockFrame; private FrameLayout mLargeClockFrame; - private AnimatableClockView mClockView; - private AnimatableClockView mLargeClockView; private View mStatusArea; private int mSmartspaceTopOffset; @@ -80,12 +66,6 @@ public class KeyguardClockSwitch extends RelativeLayout { @VisibleForTesting AnimatorSet mClockOutAnim = null; private ObjectAnimator mStatusAreaAnim = null; - /** - * If the Keyguard Slice has a header (big center-aligned text.) - */ - private boolean mSupportsDarkText; - private int[] mColorPalette; - private int mClockSwitchYAmount; @VisibleForTesting boolean mChildrenAreLaidOut = false; @@ -97,97 +77,38 @@ public class KeyguardClockSwitch extends RelativeLayout { * Apply dp changes on font/scale change */ public void onDensityOrFontScaleChanged() { - mLargeClockView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mContext.getResources() - .getDimensionPixelSize(R.dimen.large_clock_text_size)); - mClockView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mContext.getResources() - .getDimensionPixelSize(R.dimen.clock_text_size)); - mClockSwitchYAmount = mContext.getResources().getDimensionPixelSize( R.dimen.keyguard_clock_switch_y_shift); - mSmartspaceTopOffset = mContext.getResources().getDimensionPixelSize( R.dimen.keyguard_smartspace_top_offset); } - /** - * Returns if this view is presenting a custom clock, or the default implementation. - */ - public boolean hasCustomClock() { - return mClockPlugin != null; - } - @Override protected void onFinishInflate() { super.onFinishInflate(); - mClockFrame = findViewById(R.id.lockscreen_clock_view); - mClockView = findViewById(R.id.animatable_clock_view); + mSmallClockFrame = findViewById(R.id.lockscreen_clock_view); mLargeClockFrame = findViewById(R.id.lockscreen_clock_view_large); - mLargeClockView = findViewById(R.id.animatable_clock_view_large); mStatusArea = findViewById(R.id.keyguard_status_area); onDensityOrFontScaleChanged(); } - void setClockPlugin(ClockPlugin plugin, int statusBarState) { + void setClock(Clock clock, int statusBarState) { // Disconnect from existing plugin. - if (mClockPlugin != null) { - View smallClockView = mClockPlugin.getView(); - if (smallClockView != null && smallClockView.getParent() == mClockFrame) { - mClockFrame.removeView(smallClockView); - } - View bigClockView = mClockPlugin.getBigClockView(); - if (bigClockView != null && bigClockView.getParent() == mLargeClockFrame) { - mLargeClockFrame.removeView(bigClockView); - } - mClockPlugin.onDestroyView(); - mClockPlugin = null; - } - if (plugin == null) { - mClockView.setVisibility(View.VISIBLE); - mLargeClockView.setVisibility(View.VISIBLE); - return; - } - // Attach small and big clock views to hierarchy. - View smallClockView = plugin.getView(); - if (smallClockView != null) { - mClockFrame.addView(smallClockView, -1, - new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT)); - mClockView.setVisibility(View.GONE); - } - View bigClockView = plugin.getBigClockView(); - if (bigClockView != null) { - mLargeClockFrame.addView(bigClockView); - mLargeClockView.setVisibility(View.GONE); - } - - // Initialize plugin parameters. - mClockPlugin = plugin; - mClockPlugin.setStyle(getPaint().getStyle()); - mClockPlugin.setTextColor(getCurrentTextColor()); - mClockPlugin.setDarkAmount(mDarkAmount); - if (mColorPalette != null) { - mClockPlugin.setColorPalette(mSupportsDarkText, mColorPalette); - } - } + mSmallClockFrame.removeAllViews(); + mLargeClockFrame.removeAllViews(); - /** - * It will also update plugin setStyle if plugin is connected. - */ - public void setStyle(Style style) { - if (mClockPlugin != null) { - mClockPlugin.setStyle(style); + if (clock == null) { + Log.e(TAG, "No clock being shown"); + return; } - } - /** - * It will also update plugin setTextColor if plugin is connected. - */ - public void setTextColor(int color) { - if (mClockPlugin != null) { - mClockPlugin.setTextColor(color); - } + // Attach small and big clock views to hierarchy. + mSmallClockFrame.addView(clock.getSmallClock(), -1, + new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + mLargeClockFrame.addView(clock.getLargeClock()); } private void updateClockViews(boolean useLargeClock, boolean animate) { @@ -203,14 +124,14 @@ public class KeyguardClockSwitch extends RelativeLayout { int direction = 1; float statusAreaYTranslation; if (useLargeClock) { - out = mClockFrame; + out = mSmallClockFrame; in = mLargeClockFrame; if (indexOfChild(in) == -1) addView(in); direction = -1; - statusAreaYTranslation = mClockFrame.getTop() - mStatusArea.getTop() + statusAreaYTranslation = mSmallClockFrame.getTop() - mStatusArea.getTop() + mSmartspaceTopOffset; } else { - in = mClockFrame; + in = mSmallClockFrame; out = mLargeClockFrame; statusAreaYTranslation = 0f; @@ -269,18 +190,6 @@ public class KeyguardClockSwitch extends RelativeLayout { } /** - * Set the amount (ratio) that the device has transitioned to doze. - * - * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake. - */ - public void setDarkAmount(float darkAmount) { - mDarkAmount = darkAmount; - if (mClockPlugin != null) { - mClockPlugin.setDarkAmount(darkAmount); - } - } - - /** * Display the desired clock and hide the other one * * @return true if desired clock appeared and false if it was already visible @@ -311,64 +220,11 @@ public class KeyguardClockSwitch extends RelativeLayout { mChildrenAreLaidOut = true; } - public Paint getPaint() { - return mClockView.getPaint(); - } - - public int getCurrentTextColor() { - return mClockView.getCurrentTextColor(); - } - - public float getTextSize() { - return mClockView.getTextSize(); - } - - /** - * Refresh the time of the clock, due to either time tick broadcast or doze time tick alarm. - */ - public void refresh() { - if (mClockPlugin != null) { - mClockPlugin.onTimeTick(); - } - } - - /** - * Notifies that the time zone has changed. - */ - public void onTimeZoneChanged(TimeZone timeZone) { - if (mClockPlugin != null) { - mClockPlugin.onTimeZoneChanged(timeZone); - } - } - - /** - * Notifies that the time format has changed. - * - * @param timeFormat "12" for 12-hour format, "24" for 24-hour format - */ - public void onTimeFormatChanged(String timeFormat) { - if (mClockPlugin != null) { - mClockPlugin.onTimeFormatChanged(timeFormat); - } - } - - void updateColors(ColorExtractor.GradientColors colors) { - mSupportsDarkText = colors.supportsDarkText(); - mColorPalette = colors.getColorPalette(); - if (mClockPlugin != null) { - mClockPlugin.setColorPalette(mSupportsDarkText, mColorPalette); - } - } - public void dump(PrintWriter pw, String[] args) { pw.println("KeyguardClockSwitch:"); - pw.println(" mClockPlugin: " + mClockPlugin); - pw.println(" mClockFrame: " + mClockFrame); + pw.println(" mClockFrame: " + mSmallClockFrame); pw.println(" mLargeClockFrame: " + mLargeClockFrame); pw.println(" mStatusArea: " + mStatusArea); - pw.println(" mDarkAmount: " + mDarkAmount); - pw.println(" mSupportsDarkText: " + mSupportsDarkText); - pw.println(" mColorPalette: " + Arrays.toString(mColorPalette)); pw.println(" mDisplayedClockSize: " + mDisplayedClockSize); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 6c32a4910c56..eeab538932c0 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -22,8 +22,6 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.keyguard.KeyguardClockSwitch.LARGE; import static com.android.keyguard.KeyguardClockSwitch.SMALL; -import android.app.WallpaperManager; -import android.content.res.Resources; import android.database.ContentObserver; import android.os.UserHandle; import android.provider.Settings; @@ -32,34 +30,30 @@ import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.LinearLayout; -import android.widget.RelativeLayout; import androidx.annotation.NonNull; -import com.android.internal.colorextraction.ColorExtractor; -import com.android.keyguard.clock.ClockManager; import com.android.systemui.Dumpable; import com.android.systemui.R; -import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; -import com.android.systemui.plugins.ClockPlugin; +import com.android.systemui.plugins.Clock; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shared.clocks.ClockRegistry; import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.stack.AnimationProperties; import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.NotificationIconContainer; -import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.util.ViewController; import com.android.systemui.util.settings.SecureSettings; import java.io.PrintWriter; import java.util.Locale; -import java.util.TimeZone; import java.util.concurrent.Executor; import javax.inject.Inject; @@ -69,48 +63,24 @@ import javax.inject.Inject; */ public class KeyguardClockSwitchController extends ViewController<KeyguardClockSwitch> implements Dumpable { - private static final boolean CUSTOM_CLOCKS_ENABLED = true; - private final StatusBarStateController mStatusBarStateController; - private final SysuiColorExtractor mColorExtractor; - private final ClockManager mClockManager; + private final ClockRegistry mClockRegistry; private final KeyguardSliceViewController mKeyguardSliceViewController; private final NotificationIconAreaController mNotificationIconAreaController; - private final BroadcastDispatcher mBroadcastDispatcher; - private final BatteryController mBatteryController; private final LockscreenSmartspaceController mSmartspaceController; - private final Resources mResources; private final SecureSettings mSecureSettings; private final DumpManager mDumpManager; + private final ClockEventController mClockEventController; - /** - * Clock for both small and large sizes - */ - private AnimatableClockController mClockViewController; - private FrameLayout mClockFrame; // top aligned clock - private AnimatableClockController mLargeClockViewController; + /** Clock frames for both small and large sizes */ + private FrameLayout mSmallClockFrame; // top aligned clock private FrameLayout mLargeClockFrame; // centered clock @KeyguardClockSwitch.ClockSize private int mCurrentClockSize = SMALL; - private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; - private int mKeyguardClockTopMargin = 0; - - /** - * Listener for changes to the color palette. - * - * The color palette changes when the wallpaper is changed. - */ - private final ColorExtractor.OnColorsChangedListener mColorsListener = - (extractor, which) -> { - if ((which & WallpaperManager.FLAG_LOCK) != 0) { - mView.updateColors(getGradientColors()); - } - }; - - private final ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin; + private final ClockRegistry.ClockChangeListener mClockChangedListener; private ViewGroup mStatusArea; // If set will replace keyguard_slice_view @@ -119,9 +89,9 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; private boolean mOnlyClock = false; - private Executor mUiExecutor; + private final Executor mUiExecutor; private boolean mCanShowDoubleLineClock = true; - private ContentObserver mDoubleLineClockObserver = new ContentObserver(null) { + private final ContentObserver mDoubleLineClockObserver = new ContentObserver(null) { @Override public void onChange(boolean change) { updateDoubleLineClock(); @@ -142,34 +112,32 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS public KeyguardClockSwitchController( KeyguardClockSwitch keyguardClockSwitch, StatusBarStateController statusBarStateController, - SysuiColorExtractor colorExtractor, - ClockManager clockManager, + ClockRegistry clockRegistry, KeyguardSliceViewController keyguardSliceViewController, NotificationIconAreaController notificationIconAreaController, - BroadcastDispatcher broadcastDispatcher, - BatteryController batteryController, - KeyguardUpdateMonitor keyguardUpdateMonitor, LockscreenSmartspaceController smartspaceController, KeyguardUnlockAnimationController keyguardUnlockAnimationController, SecureSettings secureSettings, @Main Executor uiExecutor, - @Main Resources resources, - DumpManager dumpManager) { + DumpManager dumpManager, + ClockEventController clockEventController, + FeatureFlags featureFlags) { super(keyguardClockSwitch); mStatusBarStateController = statusBarStateController; - mColorExtractor = colorExtractor; - mClockManager = clockManager; + mClockRegistry = clockRegistry; mKeyguardSliceViewController = keyguardSliceViewController; mNotificationIconAreaController = notificationIconAreaController; - mBroadcastDispatcher = broadcastDispatcher; - mBatteryController = batteryController; - mKeyguardUpdateMonitor = keyguardUpdateMonitor; mSmartspaceController = smartspaceController; - mResources = resources; mSecureSettings = secureSettings; mUiExecutor = uiExecutor; mKeyguardUnlockAnimationController = keyguardUnlockAnimationController; mDumpManager = dumpManager; + mClockEventController = clockEventController; + + mClockRegistry.setEnabled(featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS)); + mClockChangedListener = () -> { + setClock(mClockRegistry.createCurrentClock()); + }; } /** @@ -186,40 +154,18 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS public void onInit() { mKeyguardSliceViewController.init(); - mClockFrame = mView.findViewById(R.id.lockscreen_clock_view); + mSmallClockFrame = mView.findViewById(R.id.lockscreen_clock_view); mLargeClockFrame = mView.findViewById(R.id.lockscreen_clock_view_large); - mClockViewController = - new AnimatableClockController( - mView.findViewById(R.id.animatable_clock_view), - mStatusBarStateController, - mBroadcastDispatcher, - mBatteryController, - mKeyguardUpdateMonitor, - mResources); - mClockViewController.init(); - - mLargeClockViewController = - new AnimatableClockController( - mView.findViewById(R.id.animatable_clock_view_large), - mStatusBarStateController, - mBroadcastDispatcher, - mBatteryController, - mKeyguardUpdateMonitor, - mResources); - mLargeClockViewController.init(); - mDumpManager.unregisterDumpable(getClass().toString()); // unregister previous clocks mDumpManager.registerDumpable(getClass().toString(), this); } @Override protected void onViewAttached() { - if (CUSTOM_CLOCKS_ENABLED) { - mClockManager.addOnClockChangedListener(mClockChangedListener); - } - mColorExtractor.addOnColorsChangedListener(mColorsListener); - mView.updateColors(getGradientColors()); + mClockRegistry.registerClockChangeListener(mClockChangedListener); + setClock(mClockRegistry.createCurrentClock()); + mClockEventController.registerListeners(); mKeyguardClockTopMargin = mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin); @@ -242,7 +188,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS ksv.setVisibility(View.GONE); addSmartspaceView(ksvIndex); - updateClockLayout(); } mSecureSettings.registerContentObserverForUser( @@ -264,11 +209,9 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS @Override protected void onViewDetached() { - if (CUSTOM_CLOCKS_ENABLED) { - mClockManager.removeOnClockChangedListener(mClockChangedListener); - } - mColorExtractor.removeOnColorsChangedListener(mColorsListener); - mView.setClockPlugin(null, mStatusBarStateController.getState()); + mClockRegistry.unregisterClockChangeListener(mClockChangedListener); + mClockEventController.unregisterListeners(); + setClock(null); mSecureSettings.unregisterContentObserver(mDoubleLineClockObserver); @@ -307,18 +250,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mView.onDensityOrFontScaleChanged(); mKeyguardClockTopMargin = mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin); - - updateClockLayout(); - } - - private void updateClockLayout() { - int largeClockTopMargin = getContext().getResources().getDimensionPixelSize( - R.dimen.keyguard_large_clock_top_margin) - - (int) mLargeClockViewController.getBottom(); - RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(MATCH_PARENT, - MATCH_PARENT); - lp.topMargin = largeClockTopMargin; - mLargeClockFrame.setLayoutParams(lp); } /** @@ -334,44 +265,31 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS boolean appeared = mView.switchToClock(clockSize, animate); if (animate && appeared && clockSize == LARGE) { - mLargeClockViewController.animateAppear(); + getClock().getAnimations().enter(); } } - public void animateFoldToAod() { - if (mClockViewController != null) { - mClockViewController.animateFoldAppear(); - mLargeClockViewController.animateFoldAppear(); - } - } - - /** - * If we're presenting a custom clock of just the default one. - */ - public boolean hasCustomClock() { - return mView.hasCustomClock(); - } - /** - * Get the clock text size. + * Animates the clock view between folded and unfolded states */ - public float getClockTextSize() { - return mView.getTextSize(); + public void animateFoldToAod(float foldFraction) { + Clock clock = getClock(); + if (clock != null) { + clock.getAnimations().fold(foldFraction); + } } /** * Refresh clock. Called in response to TIME_TICK broadcasts. */ void refresh() { - if (mClockViewController != null) { - mClockViewController.refreshTime(); - mLargeClockViewController.refreshTime(); - } if (mSmartspaceController != null) { mSmartspaceController.requestSmartspaceUpdate(); } - - mView.refresh(); + Clock clock = getClock(); + if (clock != null) { + clock.getEvents().onTimeTick(); + } } /** @@ -383,7 +301,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS void updatePosition(int x, float scale, AnimationProperties props, boolean animate) { x = getCurrentLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? -x : x; - PropertyAnimator.setProperty(mClockFrame, AnimatableProperty.TRANSLATION_X, + PropertyAnimator.setProperty(mSmallClockFrame, AnimatableProperty.TRANSLATION_X, x, props, animate); PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_X, scale, props, animate); @@ -396,25 +314,39 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS } } - void updateTimeZone(TimeZone timeZone) { - mView.onTimeZoneChanged(timeZone); - } - /** * Get y-bottom position of the currently visible clock on the keyguard. * We can't directly getBottom() because clock changes positions in AOD for burn-in */ int getClockBottom(int statusBarHeaderHeight) { + Clock clock = getClock(); + if (clock == null) { + return 0; + } + if (mLargeClockFrame.getVisibility() == View.VISIBLE) { - View clock = mLargeClockFrame.findViewById( - com.android.systemui.R.id.animatable_clock_view_large); int frameHeight = mLargeClockFrame.getHeight(); - int clockHeight = clock.getHeight(); + int clockHeight = clock.getLargeClock().getHeight(); return frameHeight / 2 + clockHeight / 2; } else { - return mClockFrame.findViewById( - com.android.systemui.R.id.animatable_clock_view).getHeight() - + statusBarHeaderHeight + mKeyguardClockTopMargin; + int clockHeight = clock.getSmallClock().getHeight(); + return clockHeight + statusBarHeaderHeight + mKeyguardClockTopMargin; + } + } + + /** + * Get the height of the currently visible clock on the keyguard. + */ + int getClockHeight() { + Clock clock = getClock(); + if (clock == null) { + return 0; + } + + if (mLargeClockFrame.getVisibility() == View.VISIBLE) { + return clock.getLargeClock().getHeight(); + } else { + return clock.getSmallClock().getHeight(); } } @@ -429,12 +361,13 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mNotificationIconAreaController.setupAodIcons(nic); } - private void setClockPlugin(ClockPlugin plugin) { - mView.setClockPlugin(plugin, mStatusBarStateController.getState()); + private void setClock(Clock clock) { + mClockEventController.setClock(clock); + mView.setClock(clock, mStatusBarStateController.getState()); } - private ColorExtractor.GradientColors getGradientColors() { - return mColorExtractor.getColors(WallpaperManager.FLAG_LOCK); + private Clock getClock() { + return mClockEventController.getClock(); } private int getCurrentLayoutDirection() { @@ -467,8 +400,10 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { pw.println("currentClockSizeLarge=" + (mCurrentClockSize == LARGE)); pw.println("mCanShowDoubleLineClock=" + mCanShowDoubleLineClock); - mClockViewController.dump(pw); - mLargeClockViewController.dump(pw); + Clock clock = getClock(); + if (clock != null) { + clock.dump(pw); + } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java index 83780c8a176d..29e912fdab32 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java @@ -59,6 +59,7 @@ public class KeyguardPasswordViewController private final boolean mShowImeAtScreenOn; private EditText mPasswordEntry; private ImageView mSwitchImeButton; + private boolean mPaused; private final OnEditorActionListener mOnEditorActionListener = (v, actionId, event) -> { // Check if this was the result of hitting the enter key @@ -202,6 +203,7 @@ public class KeyguardPasswordViewController @Override public void onResume(int reason) { super.onResume(reason); + mPaused = false; if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) { showInput(); } @@ -223,6 +225,11 @@ public class KeyguardPasswordViewController @Override public void onPause() { + if (mPaused) { + return; + } + mPaused = true; + if (!mPasswordEntry.isVisibleToUser()) { // Reset all states directly and then hide IME when the screen turned off. super.onPause(); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 65c415b5272b..8fb622a8c55e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -94,6 +94,7 @@ import com.android.systemui.util.settings.GlobalSettings; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; public class KeyguardSecurityContainer extends FrameLayout { static final int USER_TYPE_PRIMARY = 1; @@ -128,12 +129,12 @@ public class KeyguardSecurityContainer extends FrameLayout { private static final long IME_DISAPPEAR_DURATION_MS = 125; - // The duration of the animation to switch bouncer sides. - private static final long BOUNCER_HANDEDNESS_ANIMATION_DURATION_MS = 500; + // The duration of the animation to switch security sides. + private static final long SECURITY_SHIFT_ANIMATION_DURATION_MS = 500; - // How much of the switch sides animation should be dedicated to fading the bouncer out. The + // How much of the switch sides animation should be dedicated to fading the security out. The // remainder will fade it back in again. - private static final float BOUNCER_HANDEDNESS_ANIMATION_FADE_OUT_PROPORTION = 0.2f; + private static final float SECURITY_SHIFT_ANIMATION_FADE_OUT_PROPORTION = 0.2f; @VisibleForTesting KeyguardSecurityViewFlipper mSecurityViewFlipper; @@ -375,10 +376,23 @@ public class KeyguardSecurityContainer extends FrameLayout { mViewMode.updatePositionByTouchX(x); } - /** Returns whether the inner SecurityViewFlipper is left-aligned when in one-handed mode. */ - public boolean isOneHandedModeLeftAligned() { - return mCurrentMode == MODE_ONE_HANDED - && ((OneHandedViewMode) mViewMode).isLeftAligned(); + public boolean isSidedSecurityMode() { + return mViewMode instanceof SidedSecurityMode; + } + + /** Returns whether the inner SecurityViewFlipper is left-aligned when in sided mode. */ + public boolean isSecurityLeftAligned() { + return mViewMode instanceof SidedSecurityMode + && ((SidedSecurityMode) mViewMode).isLeftAligned(); + } + + /** + * Returns whether the touch happened on the other side of security (like bouncer) when in + * sided mode. + */ + public boolean isTouchOnTheOtherSideOfSecurity(MotionEvent ev) { + return mViewMode instanceof SidedSecurityMode + && ((SidedSecurityMode) mViewMode).isTouchOnTheOtherSideOfSecurity(ev); } public void onPause() { @@ -437,12 +451,15 @@ public class KeyguardSecurityContainer extends FrameLayout { @Override public boolean onTouchEvent(MotionEvent event) { final int action = event.getActionMasked(); - mDoubleTapDetector.onTouchEvent(event); boolean result = mMotionEventListeners.stream() .anyMatch(listener -> listener.onTouchEvent(event)) || super.onTouchEvent(event); + // double tap detector should be called after listeners handle touches as listeners are + // helping with ignoring falsing. Otherwise falsing will be activated for some double taps + mDoubleTapDetector.onTouchEvent(event); + switch (action) { case MotionEvent.ACTION_MOVE: mVelocityTracker.addMovement(event); @@ -487,11 +504,16 @@ public class KeyguardSecurityContainer extends FrameLayout { private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onDoubleTap(MotionEvent e) { - if (!mIsDragging) { - mViewMode.handleDoubleTap(e); - } + return handleDoubleTap(e); + } + } + + @VisibleForTesting boolean handleDoubleTap(MotionEvent e) { + if (!mIsDragging) { + mViewMode.handleDoubleTap(e); return true; } + return false; } void addMotionEventListener(Gefingerpoken listener) { @@ -771,6 +793,195 @@ public class KeyguardSecurityContainer extends FrameLayout { } /** + * Base class for modes which support having on left/right side of the screen, used for large + * screen devices + */ + abstract static class SidedSecurityMode implements ViewMode { + @Nullable private ValueAnimator mRunningSecurityShiftAnimator; + private KeyguardSecurityViewFlipper mViewFlipper; + private ViewGroup mView; + private GlobalSettings mGlobalSettings; + private int mDefaultSideSetting; + + public void init(ViewGroup v, KeyguardSecurityViewFlipper viewFlipper, + GlobalSettings globalSettings, boolean leftAlignedByDefault) { + mView = v; + mViewFlipper = viewFlipper; + mGlobalSettings = globalSettings; + mDefaultSideSetting = + leftAlignedByDefault ? Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT + : Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT; + } + + /** + * Determine if a double tap on this view is on the other side. If so, will animate + * positions and record the preference to always show on this side. + */ + @Override + public void handleDoubleTap(MotionEvent event) { + boolean currentlyLeftAligned = isLeftAligned(); + // Did the tap hit the "other" side of the bouncer? + if (isTouchOnTheOtherSideOfSecurity(event, currentlyLeftAligned)) { + boolean willBeLeftAligned = !currentlyLeftAligned; + updateSideSetting(willBeLeftAligned); + + int keyguardState = willBeLeftAligned + ? SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SWITCH_LEFT + : SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SWITCH_RIGHT; + SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, keyguardState); + + updateSecurityViewLocation(willBeLeftAligned, /* animate= */ true); + } + } + + private boolean isTouchOnTheOtherSideOfSecurity(MotionEvent ev, boolean leftAligned) { + float x = ev.getX(); + return (leftAligned && (x > mView.getWidth() / 2f)) + || (!leftAligned && (x < mView.getWidth() / 2f)); + } + + public boolean isTouchOnTheOtherSideOfSecurity(MotionEvent ev) { + return isTouchOnTheOtherSideOfSecurity(ev, isLeftAligned()); + } + + protected abstract void updateSecurityViewLocation(boolean leftAlign, boolean animate); + + protected void translateSecurityViewLocation(boolean leftAlign, boolean animate) { + translateSecurityViewLocation(leftAlign, animate, i -> {}); + } + + /** + * Moves the inner security view to the correct location with animation. This is triggered + * when the user double taps on the side of the screen that is not currently occupied by + * the security view. + */ + protected void translateSecurityViewLocation(boolean leftAlign, boolean animate, + Consumer<Float> securityAlphaListener) { + if (mRunningSecurityShiftAnimator != null) { + mRunningSecurityShiftAnimator.cancel(); + mRunningSecurityShiftAnimator = null; + } + + int targetTranslation = leftAlign + ? 0 : mView.getMeasuredWidth() - mViewFlipper.getWidth(); + + if (animate) { + // This animation is a bit fun to implement. The bouncer needs to move, and fade + // in/out at the same time. The issue is, the bouncer should only move a short + // amount (120dp or so), but obviously needs to go from one side of the screen to + // the other. This needs a pretty custom animation. + // + // This works as follows. It uses a ValueAnimation to simply drive the animation + // progress. This animator is responsible for both the translation of the bouncer, + // and the current fade. It will fade the bouncer out while also moving it along the + // 120dp path. Once the bouncer is fully faded out though, it will "snap" the + // bouncer closer to its destination, then fade it back in again. The effect is that + // the bouncer will move from 0 -> X while fading out, then + // (destination - X) -> destination while fading back in again. + // TODO(b/208250221): Make this animation properly abortable. + Interpolator positionInterpolator = AnimationUtils.loadInterpolator( + mView.getContext(), android.R.interpolator.fast_out_extra_slow_in); + Interpolator fadeOutInterpolator = Interpolators.FAST_OUT_LINEAR_IN; + Interpolator fadeInInterpolator = Interpolators.LINEAR_OUT_SLOW_IN; + + mRunningSecurityShiftAnimator = ValueAnimator.ofFloat(0.0f, 1.0f); + mRunningSecurityShiftAnimator.setDuration(SECURITY_SHIFT_ANIMATION_DURATION_MS); + mRunningSecurityShiftAnimator.setInterpolator(Interpolators.LINEAR); + + int initialTranslation = (int) mViewFlipper.getTranslationX(); + int totalTranslation = (int) mView.getResources().getDimension( + R.dimen.security_shift_animation_translation); + + final boolean shouldRestoreLayerType = mViewFlipper.hasOverlappingRendering() + && mViewFlipper.getLayerType() != View.LAYER_TYPE_HARDWARE; + if (shouldRestoreLayerType) { + mViewFlipper.setLayerType(View.LAYER_TYPE_HARDWARE, /* paint= */null); + } + + float initialAlpha = mViewFlipper.getAlpha(); + + mRunningSecurityShiftAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mRunningSecurityShiftAnimator = null; + } + }); + mRunningSecurityShiftAnimator.addUpdateListener(animation -> { + float switchPoint = SECURITY_SHIFT_ANIMATION_FADE_OUT_PROPORTION; + boolean isFadingOut = animation.getAnimatedFraction() < switchPoint; + + int currentTranslation = (int) (positionInterpolator.getInterpolation( + animation.getAnimatedFraction()) * totalTranslation); + int translationRemaining = totalTranslation - currentTranslation; + + // Flip the sign if we're going from right to left. + if (leftAlign) { + currentTranslation = -currentTranslation; + translationRemaining = -translationRemaining; + } + + float opacity; + if (isFadingOut) { + // The bouncer fades out over the first X%. + float fadeOutFraction = MathUtils.constrainedMap( + /* rangeMin= */1.0f, + /* rangeMax= */0.0f, + /* valueMin= */0.0f, + /* valueMax= */switchPoint, + animation.getAnimatedFraction()); + opacity = fadeOutInterpolator.getInterpolation(fadeOutFraction); + + // When fading out, the alpha needs to start from the initial opacity of the + // view flipper, otherwise we get a weird bit of jank as it ramps back to + // 100%. + mViewFlipper.setAlpha(opacity * initialAlpha); + + // Animate away from the source. + mViewFlipper.setTranslationX(initialTranslation + currentTranslation); + } else { + // And in again over the remaining (100-X)%. + float fadeInFraction = MathUtils.constrainedMap( + /* rangeMin= */0.0f, + /* rangeMax= */1.0f, + /* valueMin= */switchPoint, + /* valueMax= */1.0f, + animation.getAnimatedFraction()); + + opacity = fadeInInterpolator.getInterpolation(fadeInFraction); + mViewFlipper.setAlpha(opacity); + + // Fading back in, animate towards the destination. + mViewFlipper.setTranslationX(targetTranslation - translationRemaining); + } + securityAlphaListener.accept(opacity); + + if (animation.getAnimatedFraction() == 1.0f && shouldRestoreLayerType) { + mViewFlipper.setLayerType(View.LAYER_TYPE_NONE, /* paint= */null); + } + }); + + mRunningSecurityShiftAnimator.start(); + } else { + mViewFlipper.setTranslationX(targetTranslation); + } + } + + + boolean isLeftAligned() { + return mGlobalSettings.getInt(Settings.Global.ONE_HANDED_KEYGUARD_SIDE, + mDefaultSideSetting) + == Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT; + } + + protected void updateSideSetting(boolean leftAligned) { + mGlobalSettings.putInt( + Settings.Global.ONE_HANDED_KEYGUARD_SIDE, + leftAligned ? Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT + : Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT); + } + } + + /** * Default bouncer is centered within the space */ static class DefaultViewMode implements ViewMode { @@ -802,7 +1013,7 @@ public class KeyguardSecurityContainer extends FrameLayout { * User switcher mode will display both the current user icon as well as * a user switcher, in both portrait and landscape modes. */ - static class UserSwitcherViewMode implements ViewMode { + static class UserSwitcherViewMode extends SidedSecurityMode { private ViewGroup mView; private ViewGroup mUserSwitcherViewGroup; private KeyguardSecurityViewFlipper mViewFlipper; @@ -814,11 +1025,15 @@ public class KeyguardSecurityContainer extends FrameLayout { private UserSwitcherController.UserSwitchCallback mUserSwitchCallback = this::setupUserSwitcher; + private float mAnimationLastAlpha = 1f; + private boolean mAnimationWaitsToShift = true; + @Override public void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings, @NonNull KeyguardSecurityViewFlipper viewFlipper, @NonNull FalsingManager falsingManager, @NonNull UserSwitcherController userSwitcherController) { + init(v, viewFlipper, globalSettings, /* leftAlignedByDefault= */false); mView = v; mViewFlipper = viewFlipper; mFalsingManager = falsingManager; @@ -832,9 +1047,7 @@ public class KeyguardSecurityContainer extends FrameLayout { true); mUserSwitcherViewGroup = mView.findViewById(R.id.keyguard_bouncer_user_switcher); } - updateSecurityViewLocation(); - mUserSwitcher = mView.findViewById(R.id.user_switcher_header); setupUserSwitcher(); mUserSwitcherController.addUserSwitchCallback(mUserSwitchCallback); @@ -1030,18 +1243,65 @@ public class KeyguardSecurityContainer extends FrameLayout { @Override public void updateSecurityViewLocation() { - int yTrans = mResources.getDimensionPixelSize(R.dimen.bouncer_user_switcher_y_trans); + updateSecurityViewLocation(isLeftAligned(), /* animate= */false); + } + + public void updateSecurityViewLocation(boolean leftAlign, boolean animate) { + setYTranslation(); + setGravity(); + setXTranslation(leftAlign, animate); + } + private void setXTranslation(boolean leftAlign, boolean animate) { + if (mResources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { + mUserSwitcherViewGroup.setTranslationX(0); + mViewFlipper.setTranslationX(0); + } else { + int switcherTargetTranslation = leftAlign + ? mView.getMeasuredWidth() - mViewFlipper.getWidth() : 0; + if (animate) { + mAnimationWaitsToShift = true; + mAnimationLastAlpha = 1f; + translateSecurityViewLocation(leftAlign, animate, securityAlpha -> { + // During the animation security view fades out - alpha goes from 1 to + // (almost) 0 - and then fades in - alpha grows back to 1. + // If new alpha is bigger than previous one it means we're at inflection + // point and alpha is zero or almost zero. That's when we want to do + // translation of user switcher, so that it's not visible to the user. + boolean fullyFadeOut = securityAlpha == 0.0f + || securityAlpha > mAnimationLastAlpha; + if (fullyFadeOut && mAnimationWaitsToShift) { + mUserSwitcherViewGroup.setTranslationX(switcherTargetTranslation); + mAnimationWaitsToShift = false; + } + mUserSwitcherViewGroup.setAlpha(securityAlpha); + mAnimationLastAlpha = securityAlpha; + }); + } else { + translateSecurityViewLocation(leftAlign, animate); + mUserSwitcherViewGroup.setTranslationX(switcherTargetTranslation); + } + } + + } + + private void setGravity() { if (mResources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { - updateViewGravity(mViewFlipper, Gravity.CENTER_HORIZONTAL); updateViewGravity(mUserSwitcherViewGroup, Gravity.CENTER_HORIZONTAL); + updateViewGravity(mViewFlipper, Gravity.CENTER_HORIZONTAL); + } else { + // horizontal gravity is the same because we translate these views anyway + updateViewGravity(mViewFlipper, Gravity.LEFT | Gravity.BOTTOM); + updateViewGravity(mUserSwitcherViewGroup, Gravity.LEFT | Gravity.CENTER_VERTICAL); + } + } + private void setYTranslation() { + int yTrans = mResources.getDimensionPixelSize(R.dimen.bouncer_user_switcher_y_trans); + if (mResources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { mUserSwitcherViewGroup.setTranslationY(yTrans); mViewFlipper.setTranslationY(-yTrans); } else { - updateViewGravity(mViewFlipper, Gravity.RIGHT | Gravity.BOTTOM); - updateViewGravity(mUserSwitcherViewGroup, Gravity.LEFT | Gravity.CENTER_VERTICAL); - // Attempt to reposition a bit higher to make up for this frame being a bit lower // on the device mUserSwitcherViewGroup.setTranslationY(-yTrans); @@ -1060,20 +1320,18 @@ public class KeyguardSecurityContainer extends FrameLayout { * Logic to enabled one-handed bouncer mode. Supports animating the bouncer * between alternate sides of the display. */ - static class OneHandedViewMode implements ViewMode { - @Nullable private ValueAnimator mRunningOneHandedAnimator; + static class OneHandedViewMode extends SidedSecurityMode { private ViewGroup mView; private KeyguardSecurityViewFlipper mViewFlipper; - private GlobalSettings mGlobalSettings; @Override public void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings, @NonNull KeyguardSecurityViewFlipper viewFlipper, @NonNull FalsingManager falsingManager, @NonNull UserSwitcherController userSwitcherController) { + init(v, viewFlipper, globalSettings, /* leftAlignedByDefault= */true); mView = v; mViewFlipper = viewFlipper; - mGlobalSettings = globalSettings; updateSecurityViewGravity(); updateSecurityViewLocation(isLeftAligned(), /* animate= */false); @@ -1107,159 +1365,13 @@ public class KeyguardSecurityContainer extends FrameLayout { updateSecurityViewLocation(isTouchOnLeft, /* animate= */false); } - boolean isLeftAligned() { - return mGlobalSettings.getInt(Settings.Global.ONE_HANDED_KEYGUARD_SIDE, - Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT) - == Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT; - } - - private void updateSideSetting(boolean leftAligned) { - mGlobalSettings.putInt( - Settings.Global.ONE_HANDED_KEYGUARD_SIDE, - leftAligned ? Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT - : Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT); - } - - /** - * Determine if a double tap on this view is on the other side. If so, will animate - * positions and record the preference to always show on this side. - */ - @Override - public void handleDoubleTap(MotionEvent event) { - float x = event.getX(); - boolean currentlyLeftAligned = isLeftAligned(); - // Did the tap hit the "other" side of the bouncer? - if ((currentlyLeftAligned && (x > mView.getWidth() / 2f)) - || (!currentlyLeftAligned && (x < mView.getWidth() / 2f))) { - - boolean willBeLeftAligned = !currentlyLeftAligned; - updateSideSetting(willBeLeftAligned); - - int keyguardState = willBeLeftAligned - ? SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SWITCH_LEFT - : SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SWITCH_RIGHT; - SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, keyguardState); - - updateSecurityViewLocation(willBeLeftAligned, true /* animate */); - } - } - @Override public void updateSecurityViewLocation() { updateSecurityViewLocation(isLeftAligned(), /* animate= */false); } - /** - * Moves the inner security view to the correct location (in one handed mode) with - * animation. This is triggered when the user taps on the side of the screen that is not - * currently occupied by the security view. - */ - private void updateSecurityViewLocation(boolean leftAlign, boolean animate) { - if (mRunningOneHandedAnimator != null) { - mRunningOneHandedAnimator.cancel(); - mRunningOneHandedAnimator = null; - } - - int targetTranslation = leftAlign - ? 0 : (int) (mView.getMeasuredWidth() - mViewFlipper.getWidth()); - - if (animate) { - // This animation is a bit fun to implement. The bouncer needs to move, and fade - // in/out at the same time. The issue is, the bouncer should only move a short - // amount (120dp or so), but obviously needs to go from one side of the screen to - // the other. This needs a pretty custom animation. - // - // This works as follows. It uses a ValueAnimation to simply drive the animation - // progress. This animator is responsible for both the translation of the bouncer, - // and the current fade. It will fade the bouncer out while also moving it along the - // 120dp path. Once the bouncer is fully faded out though, it will "snap" the - // bouncer closer to its destination, then fade it back in again. The effect is that - // the bouncer will move from 0 -> X while fading out, then - // (destination - X) -> destination while fading back in again. - // TODO(b/208250221): Make this animation properly abortable. - Interpolator positionInterpolator = AnimationUtils.loadInterpolator( - mView.getContext(), android.R.interpolator.fast_out_extra_slow_in); - Interpolator fadeOutInterpolator = Interpolators.FAST_OUT_LINEAR_IN; - Interpolator fadeInInterpolator = Interpolators.LINEAR_OUT_SLOW_IN; - - mRunningOneHandedAnimator = ValueAnimator.ofFloat(0.0f, 1.0f); - mRunningOneHandedAnimator.setDuration(BOUNCER_HANDEDNESS_ANIMATION_DURATION_MS); - mRunningOneHandedAnimator.setInterpolator(Interpolators.LINEAR); - - int initialTranslation = (int) mViewFlipper.getTranslationX(); - int totalTranslation = (int) mView.getResources().getDimension( - R.dimen.one_handed_bouncer_move_animation_translation); - - final boolean shouldRestoreLayerType = mViewFlipper.hasOverlappingRendering() - && mViewFlipper.getLayerType() != View.LAYER_TYPE_HARDWARE; - if (shouldRestoreLayerType) { - mViewFlipper.setLayerType(View.LAYER_TYPE_HARDWARE, /* paint= */null); - } - - float initialAlpha = mViewFlipper.getAlpha(); - - mRunningOneHandedAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mRunningOneHandedAnimator = null; - } - }); - mRunningOneHandedAnimator.addUpdateListener(animation -> { - float switchPoint = BOUNCER_HANDEDNESS_ANIMATION_FADE_OUT_PROPORTION; - boolean isFadingOut = animation.getAnimatedFraction() < switchPoint; - - int currentTranslation = (int) (positionInterpolator.getInterpolation( - animation.getAnimatedFraction()) * totalTranslation); - int translationRemaining = totalTranslation - currentTranslation; - - // Flip the sign if we're going from right to left. - if (leftAlign) { - currentTranslation = -currentTranslation; - translationRemaining = -translationRemaining; - } - - if (isFadingOut) { - // The bouncer fades out over the first X%. - float fadeOutFraction = MathUtils.constrainedMap( - /* rangeMin= */1.0f, - /* rangeMax= */0.0f, - /* valueMin= */0.0f, - /* valueMax= */switchPoint, - animation.getAnimatedFraction()); - float opacity = fadeOutInterpolator.getInterpolation(fadeOutFraction); - - // When fading out, the alpha needs to start from the initial opacity of the - // view flipper, otherwise we get a weird bit of jank as it ramps back to - // 100%. - mViewFlipper.setAlpha(opacity * initialAlpha); - - // Animate away from the source. - mViewFlipper.setTranslationX(initialTranslation + currentTranslation); - } else { - // And in again over the remaining (100-X)%. - float fadeInFraction = MathUtils.constrainedMap( - /* rangeMin= */0.0f, - /* rangeMax= */1.0f, - /* valueMin= */switchPoint, - /* valueMax= */1.0f, - animation.getAnimatedFraction()); - - float opacity = fadeInInterpolator.getInterpolation(fadeInFraction); - mViewFlipper.setAlpha(opacity); - - // Fading back in, animate towards the destination. - mViewFlipper.setTranslationX(targetTranslation - translationRemaining); - } - - if (animation.getAnimatedFraction() == 1.0f && shouldRestoreLayerType) { - mViewFlipper.setLayerType(View.LAYER_TYPE_NONE, /* paint= */null); - } - }); - - mRunningOneHandedAnimator.start(); - } else { - mViewFlipper.setTranslationX(targetTranslation); - } + protected void updateSecurityViewLocation(boolean leftAlign, boolean animate) { + translateSecurityViewLocation(leftAlign, animate); } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 19a2d9ed5b7b..61e262440607 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -116,12 +116,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard // If we're in one handed mode, the user can tap on the opposite side of the screen // to move the bouncer across. In that case, inhibit the falsing (otherwise the taps // to move the bouncer to each screen side can end up closing it instead). - if (mView.getMode() == KeyguardSecurityContainer.MODE_ONE_HANDED) { - boolean isLeftAligned = mView.isOneHandedModeLeftAligned(); - if ((isLeftAligned && ev.getX() > mView.getWidth() / 2f) - || (!isLeftAligned && ev.getX() <= mView.getWidth() / 2f)) { - mFalsingCollector.avoidGesture(); - } + if (mView.isTouchOnTheOtherSideOfSecurity(ev)) { + mFalsingCollector.avoidGesture(); } if (mTouchDown != null) { @@ -169,8 +165,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) { int bouncerSide = SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__SIDE__DEFAULT; - if (mView.getMode() == KeyguardSecurityContainer.MODE_ONE_HANDED) { - bouncerSide = mView.isOneHandedModeLeftAligned() + if (mView.isSidedSecurityMode()) { + bouncerSide = mView.isSecurityLeftAligned() ? SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__SIDE__LEFT : SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__SIDE__RIGHT; } @@ -367,8 +363,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard public void onResume(int reason) { if (mCurrentSecurityMode != SecurityMode.None) { int state = SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN; - if (mView.getMode() == KeyguardSecurityContainer.MODE_ONE_HANDED) { - state = mView.isOneHandedModeLeftAligned() + if (mView.isSidedSecurityMode()) { + state = mView.isSecurityLeftAligned() ? SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN_LEFT : SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN_RIGHT; } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java index cb3172dabdb1..83e23bd52f19 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java @@ -17,14 +17,11 @@ package com.android.keyguard; import android.content.Context; -import android.graphics.Color; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import android.widget.GridLayout; -import androidx.core.graphics.ColorUtils; - import com.android.systemui.R; import com.android.systemui.statusbar.CrossFadeHelper; @@ -46,7 +43,6 @@ public class KeyguardStatusView extends GridLayout { private View mMediaHostContainer; private float mDarkAmount = 0; - private int mTextColor; public KeyguardStatusView(Context context) { this(context, null, 0); @@ -71,7 +67,6 @@ public class KeyguardStatusView extends GridLayout { } mKeyguardSlice = findViewById(R.id.keyguard_slice_view); - mTextColor = mClockView.getCurrentTextColor(); mMediaHostContainer = findViewById(R.id.status_view_media_container); @@ -83,15 +78,12 @@ public class KeyguardStatusView extends GridLayout { return; } mDarkAmount = darkAmount; - mClockView.setDarkAmount(darkAmount); CrossFadeHelper.fadeOut(mMediaHostContainer, darkAmount); updateDark(); } void updateDark() { - final int blendedTextColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount); mKeyguardSlice.setDarkAmount(mDarkAmount); - mClockView.setTextColor(blendedTextColor); } /** Sets a translationY value on every child view except for the media view. */ @@ -113,7 +105,6 @@ public class KeyguardStatusView extends GridLayout { public void dump(PrintWriter pw, String[] args) { pw.println("KeyguardStatusView:"); pw.println(" mDarkAmount: " + mDarkAmount); - pw.println(" mTextColor: " + Integer.toHexString(mTextColor)); if (mClockView != null) { mClockView.dump(pw, args); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 014d08288158..c715a4eaef2b 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -30,8 +30,6 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.ViewController; -import java.util.TimeZone; - import javax.inject.Inject; /** @@ -96,13 +94,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV } /** - * The amount we're in doze. - */ - public void setDarkAmount(float darkAmount) { - mView.setDarkAmount(darkAmount); - } - - /** * Set which clock should be displayed on the keyguard. The other one will be automatically * hidden. */ @@ -114,16 +105,10 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV * Performs fold to aod animation of the clocks (changes font weight from bold to thin). * This animation is played when AOD is enabled and foldable device is fully folded, it is * displayed on the outer screen + * @param foldFraction current fraction of fold animation complete */ - public void animateFoldToAod() { - mKeyguardClockSwitchController.animateFoldToAod(); - } - - /** - * If we're presenting a custom clock of just the default one. - */ - public boolean hasCustomClock() { - return mKeyguardClockSwitchController.hasCustomClock(); + public void animateFoldToAod(float foldFraction) { + mKeyguardClockSwitchController.animateFoldToAod(foldFraction); } /** @@ -143,24 +128,11 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV } /** - * Set pivot x. - */ - public void setPivotX(float pivot) { - mView.setPivotX(pivot); - } - - /** - * Set pivot y. - */ - public void setPivotY(float pivot) { - mView.setPivotY(pivot); - } - - /** - * Get the clock text size. + * Update the pivot position based on the parent view */ - public float getClockTextSize() { - return mKeyguardClockSwitchController.getClockTextSize(); + public void updatePivot(float parentWidth, float parentHeight) { + mView.setPivotX(parentWidth / 2f); + mView.setPivotY(mKeyguardClockSwitchController.getClockHeight() / 2f); } /** @@ -240,11 +212,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV } @Override - public void onTimeZoneChanged(TimeZone timeZone) { - mKeyguardClockSwitchController.updateTimeZone(timeZone); - } - - @Override public void onKeyguardVisibilityChanged(boolean showing) { if (showing) { if (DEBUG) Slog.v(TAG, "refresh statusview showing:" + showing); diff --git a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt index 61b1b66338e8..c5955860aebf 100644 --- a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt @@ -27,12 +27,15 @@ import android.graphics.Matrix import android.graphics.Paint import android.graphics.Path import android.graphics.RectF +import android.hardware.biometrics.BiometricSourceType import android.view.View import androidx.core.graphics.ColorUtils import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.settingslib.Utils import com.android.systemui.animation.Interpolators import com.android.systemui.plugins.statusbar.StatusBarStateController +import java.util.concurrent.Executor /** * When the face is enrolled, we use this view to show the face scanning animation and the camera @@ -42,7 +45,8 @@ class FaceScanningOverlay( context: Context, pos: Int, val statusBarStateController: StatusBarStateController, - val keyguardUpdateMonitor: KeyguardUpdateMonitor + val keyguardUpdateMonitor: KeyguardUpdateMonitor, + val mainExecutor: Executor ) : ScreenDecorations.DisplayCutoutView(context, pos) { private var showScanningAnim = false private val rimPaint = Paint() @@ -54,11 +58,26 @@ class FaceScanningOverlay( com.android.systemui.R.attr.wallpaperTextColorAccent) private var cameraProtectionAnimator: ValueAnimator? = null var hideOverlayRunnable: Runnable? = null + var faceAuthSucceeded = false init { visibility = View.INVISIBLE // only show this view when face scanning is happening } + override fun onAttachedToWindow() { + super.onAttachedToWindow() + mainExecutor.execute { + keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) + } + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + mainExecutor.execute { + keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback) + } + } + override fun setColor(color: Int) { cameraProtectionColor = color invalidate() @@ -108,7 +127,6 @@ class FaceScanningOverlay( if (showScanningAnimNow == showScanningAnim) { return } - showScanningAnim = showScanningAnimNow updateProtectionBoundingPath() // Delay the relayout until the end of the animation when hiding, @@ -120,13 +138,20 @@ class FaceScanningOverlay( cameraProtectionAnimator?.cancel() cameraProtectionAnimator = ValueAnimator.ofFloat(cameraProtectionProgress, - if (showScanningAnimNow) 1.0f else HIDDEN_CAMERA_PROTECTION_SCALE).apply { - startDelay = if (showScanningAnim) 0 else PULSE_DISAPPEAR_DURATION - duration = if (showScanningAnim) PULSE_APPEAR_DURATION else - CAMERA_PROTECTION_DISAPPEAR_DURATION - interpolator = if (showScanningAnim) Interpolators.STANDARD else - Interpolators.EMPHASIZED - + if (showScanningAnimNow) SHOW_CAMERA_PROTECTION_SCALE + else HIDDEN_CAMERA_PROTECTION_SCALE).apply { + startDelay = + if (showScanningAnim) 0 + else if (faceAuthSucceeded) PULSE_SUCCESS_DISAPPEAR_DURATION + else PULSE_ERROR_DISAPPEAR_DURATION + duration = + if (showScanningAnim) CAMERA_PROTECTION_APPEAR_DURATION + else if (faceAuthSucceeded) CAMERA_PROTECTION_SUCCESS_DISAPPEAR_DURATION + else CAMERA_PROTECTION_ERROR_DISAPPEAR_DURATION + interpolator = + if (showScanningAnim) Interpolators.STANDARD_ACCELERATE + else if (faceAuthSucceeded) Interpolators.STANDARD + else Interpolators.STANDARD_DECELERATE addUpdateListener(ValueAnimator.AnimatorUpdateListener { animation: ValueAnimator -> cameraProtectionProgress = animation.animatedValue as Float @@ -143,47 +168,73 @@ class FaceScanningOverlay( } } }) - start() } rimAnimator?.cancel() rimAnimator = AnimatorSet().apply { - val rimAppearOrDisappearAnimator = ValueAnimator.ofFloat(rimProgress, - if (showScanningAnim) PULSE_RADIUS_OUT else (PULSE_RADIUS_IN * 1.15f)).apply { - duration = if (showScanningAnim) PULSE_APPEAR_DURATION else PULSE_DISAPPEAR_DURATION - interpolator = Interpolators.STANDARD - addUpdateListener(ValueAnimator.AnimatorUpdateListener { - animation: ValueAnimator -> - rimProgress = animation.animatedValue as Float - invalidate() - }) - } if (showScanningAnim) { - // appear and then pulse in/out - playSequentially(rimAppearOrDisappearAnimator, + val rimAppearAnimator = ValueAnimator.ofFloat(SHOW_CAMERA_PROTECTION_SCALE, + PULSE_RADIUS_OUT).apply { + duration = PULSE_APPEAR_DURATION + interpolator = Interpolators.STANDARD_DECELERATE + addUpdateListener(ValueAnimator.AnimatorUpdateListener { + animation: ValueAnimator -> + rimProgress = animation.animatedValue as Float + invalidate() + }) + } + + // animate in camera protection, rim, and then pulse in/out + playSequentially(cameraProtectionAnimator, rimAppearAnimator, createPulseAnimator(), createPulseAnimator(), createPulseAnimator(), createPulseAnimator(), createPulseAnimator(), createPulseAnimator()) } else { - val opacityAnimator = ValueAnimator.ofInt(255, 0).apply { - duration = PULSE_DISAPPEAR_DURATION - interpolator = Interpolators.LINEAR + val rimDisappearAnimator = ValueAnimator.ofFloat( + rimProgress, + if (faceAuthSucceeded) PULSE_RADIUS_SUCCESS + else SHOW_CAMERA_PROTECTION_SCALE + ).apply { + duration = + if (faceAuthSucceeded) PULSE_SUCCESS_DISAPPEAR_DURATION + else PULSE_ERROR_DISAPPEAR_DURATION + interpolator = + if (faceAuthSucceeded) Interpolators.STANDARD_DECELERATE + else Interpolators.STANDARD addUpdateListener(ValueAnimator.AnimatorUpdateListener { animation: ValueAnimator -> - rimPaint.alpha = animation.animatedValue as Int + rimProgress = animation.animatedValue as Float invalidate() }) + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + rimProgress = HIDDEN_RIM_SCALE + invalidate() + } + }) } - addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - rimProgress = HIDDEN_RIM_SCALE - rimPaint.alpha = 255 - invalidate() + if (faceAuthSucceeded) { + val successOpacityAnimator = ValueAnimator.ofInt(255, 0).apply { + duration = PULSE_SUCCESS_DISAPPEAR_DURATION + interpolator = Interpolators.LINEAR + addUpdateListener(ValueAnimator.AnimatorUpdateListener { + animation: ValueAnimator -> + rimPaint.alpha = animation.animatedValue as Int + invalidate() + }) + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + rimPaint.alpha = 255 + invalidate() + } + }) } - }) - - // disappear - playTogether(rimAppearOrDisappearAnimator, opacityAnimator) + val rimSuccessAnimator = AnimatorSet() + rimSuccessAnimator.playTogether(rimDisappearAnimator, successOpacityAnimator) + playTogether(rimSuccessAnimator, cameraProtectionAnimator) + } else { + playTogether(rimDisappearAnimator, cameraProtectionAnimator) + } } addListener(object : AnimatorListenerAdapter() { @@ -253,15 +304,72 @@ class FaceScanningOverlay( } } + private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() { + override fun onBiometricAuthenticated( + userId: Int, + biometricSourceType: BiometricSourceType?, + isStrongBiometric: Boolean + ) { + if (biometricSourceType == BiometricSourceType.FACE) { + post { + faceAuthSucceeded = true + enableShowProtection(true) + } + } + } + + override fun onBiometricAcquired( + biometricSourceType: BiometricSourceType?, + acquireInfo: Int + ) { + if (biometricSourceType == BiometricSourceType.FACE) { + post { + faceAuthSucceeded = false // reset + } + } + } + + override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType?) { + if (biometricSourceType == BiometricSourceType.FACE) { + post { + faceAuthSucceeded = false + enableShowProtection(false) + } + } + } + + override fun onBiometricError( + msgId: Int, + errString: String?, + biometricSourceType: BiometricSourceType? + ) { + if (biometricSourceType == BiometricSourceType.FACE) { + post { + faceAuthSucceeded = false + enableShowProtection(false) + } + } + } + } + companion object { private const val HIDDEN_RIM_SCALE = HIDDEN_CAMERA_PROTECTION_SCALE + private const val SHOW_CAMERA_PROTECTION_SCALE = 1f + + private const val PULSE_RADIUS_IN = 1.1f + private const val PULSE_RADIUS_OUT = 1.125f + private const val PULSE_RADIUS_SUCCESS = 1.25f + + private const val CAMERA_PROTECTION_APPEAR_DURATION = 250L + private const val PULSE_APPEAR_DURATION = 250L // without start delay - private const val PULSE_APPEAR_DURATION = 350L private const val PULSE_DURATION_INWARDS = 500L private const val PULSE_DURATION_OUTWARDS = 500L - private const val PULSE_DISAPPEAR_DURATION = 850L - private const val CAMERA_PROTECTION_DISAPPEAR_DURATION = 700L // excluding start delay - private const val PULSE_RADIUS_IN = 1.15f - private const val PULSE_RADIUS_OUT = 1.25f + + private const val PULSE_SUCCESS_DISAPPEAR_DURATION = 400L + private const val CAMERA_PROTECTION_SUCCESS_DISAPPEAR_DURATION = 500L // without start delay + + private const val PULSE_ERROR_DISAPPEAR_DURATION = 200L + private const val CAMERA_PROTECTION_ERROR_DISAPPEAR_DURATION = 300L // without start delay } } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java index 061a05dcfb72..8de8e020a359 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java @@ -61,6 +61,7 @@ import android.net.ConnectivityManager; import android.net.NetworkScoreManager; import android.net.wifi.WifiManager; import android.os.BatteryStats; +import android.os.PowerExemptionManager; import android.os.PowerManager; import android.os.ServiceManager; import android.os.UserManager; @@ -379,6 +380,13 @@ public class FrameworkServicesModule { /** */ @Provides + @Singleton + static PowerExemptionManager providePowerExemptionManager(Context context) { + return context.getSystemService(PowerExemptionManager.class); + } + + /** */ + @Provides @Main public SharedPreferences provideSharePreferences(Context context) { return Prefs.get(context); diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt index adc0096eed37..81d3d6caebd7 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt @@ -32,10 +32,12 @@ import android.widget.FrameLayout import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.FaceScanningOverlay import com.android.systemui.biometrics.AuthController +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.plugins.statusbar.StatusBarStateController +import java.util.concurrent.Executor import javax.inject.Inject @SysUISingleton @@ -44,6 +46,7 @@ class FaceScanningProviderFactory @Inject constructor( private val context: Context, private val statusBarStateController: StatusBarStateController, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + @Main private val mainExecutor: Executor, private val featureFlags: FeatureFlags ) : DecorProviderFactory() { private val display = context.display @@ -82,7 +85,9 @@ class FaceScanningProviderFactory @Inject constructor( bound.baseOnRotation0(displayInfo.rotation), authController, statusBarStateController, - keyguardUpdateMonitor) + keyguardUpdateMonitor, + mainExecutor + ) ) } } @@ -102,7 +107,8 @@ class FaceScanningOverlayProviderImpl( override val alignedBound: Int, private val authController: AuthController, private val statusBarStateController: StatusBarStateController, - private val keyguardUpdateMonitor: KeyguardUpdateMonitor + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + private val mainExecutor: Executor ) : BoundDecorProvider() { override val viewId: Int = com.android.systemui.R.id.face_scanning_anim @@ -127,7 +133,9 @@ class FaceScanningOverlayProviderImpl( context, alignedBound, statusBarStateController, - keyguardUpdateMonitor) + keyguardUpdateMonitor, + mainExecutor + ) view.id = viewId FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT).let { diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java index fc71e2fb2329..69e41ba9b284 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java @@ -101,6 +101,9 @@ public class DreamOverlayStateController implements public void addComplication(Complication complication) { mExecutor.execute(() -> { if (mComplications.add(complication)) { + if (DEBUG) { + Log.d(TAG, "addComplication: added " + complication); + } mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged()); } }); @@ -112,6 +115,9 @@ public class DreamOverlayStateController implements public void removeComplication(Complication complication) { mExecutor.execute(() -> { if (mComplications.remove(complication)) { + if (DEBUG) { + Log.d(TAG, "removeComplication: removed " + complication); + } mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged()); } }); diff --git a/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java index 486fc893732f..be94e5031917 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java @@ -82,6 +82,7 @@ public class SmartSpaceComplication implements Complication { mSmartSpaceController.addListener(mSmartspaceListener); } else { mSmartSpaceController.removeListener(mSmartspaceListener); + mDreamOverlayStateController.removeComplication(mComplication); } } }); diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java index c1173aea8b15..fd6cfc0700ad 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java @@ -21,6 +21,7 @@ import static com.android.systemui.dreams.complication.dagger.ComplicationModule import android.graphics.Rect; import android.graphics.Region; +import android.os.Debug; import android.util.Log; import android.view.View; @@ -44,7 +45,8 @@ import javax.inject.Named; * a {@link ComplicationLayoutEngine}. */ public class ComplicationHostViewController extends ViewController<ConstraintLayout> { - public static final String TAG = "ComplicationHostVwCtrl"; + private static final String TAG = "ComplicationHostVwCtrl"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final ComplicationLayoutEngine mLayoutEngine; private final LifecycleOwner mLifecycleOwner; @@ -90,6 +92,11 @@ public class ComplicationHostViewController extends ViewController<ConstraintLay } private void updateComplications(Collection<ComplicationViewModel> complications) { + if (DEBUG) { + Log.d(TAG, "updateComplications called. Callers = " + Debug.getCallers(25)); + Log.d(TAG, " mComplications = " + mComplications.toString()); + Log.d(TAG, " complications = " + complications.toString()); + } final Collection<ComplicationId> ids = complications.stream() .map(complicationViewModel -> complicationViewModel.getId()) .collect(Collectors.toSet()); diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java index ded61a8b80d9..9cd149b9bfee 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java @@ -54,7 +54,7 @@ import javax.inject.Named; */ @DreamOverlayComponent.DreamOverlayScope public class ComplicationLayoutEngine implements Complication.VisibilityController { - public static final String TAG = "ComplicationLayoutEngine"; + public static final String TAG = "ComplicationLayoutEng"; /** * {@link ViewEntry} is an internal container, capturing information necessary for working with @@ -529,7 +529,7 @@ public class ComplicationLayoutEngine implements Complication.VisibilityControll */ public void addComplication(ComplicationId id, View view, ComplicationLayoutParams lp, @Complication.Category int category) { - Log.d(TAG, "engine: " + this + " addComplication"); + Log.d(TAG, "@" + Integer.toHexString(this.hashCode()) + " addComplication: " + id); // If the complication is present, remove. if (mEntries.containsKey(id)) { diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModel.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModel.java index f0239371ee63..00cf58c0c665 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModel.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModel.java @@ -64,4 +64,9 @@ public class ComplicationViewModel extends ViewModel { public void exitDream() { mHost.requestExitDream(); } + + @Override + public String toString() { + return mId + "=" + mComplication.toString(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt index 9789cef949d3..63f63a5093d2 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt @@ -145,9 +145,6 @@ class DreamSmartspaceController @Inject constructor( if (view !is View) { return null } - - view.setIsDreaming(true) - return view } else { null diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index 66b6a5078462..56bb53b567be 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -60,6 +60,9 @@ public class Flags { public static final ResourceBooleanFlag NOTIFICATION_DRAG_TO_CONTENTS = new ResourceBooleanFlag(108, R.bool.config_notificationToContents); + public static final BooleanFlag REMOVE_UNRANKED_NOTIFICATIONS = + new BooleanFlag(109, false); + /***************************************/ // 200 - keyguard/lockscreen @@ -81,12 +84,15 @@ public class Flags { public static final ResourceBooleanFlag FACE_SCANNING_ANIM = new ResourceBooleanFlag(205, R.bool.config_enableFaceScanningAnimation); + /** * Whether the KeyguardBottomArea(View|Controller) should use the modern architecture or the old * one. */ public static final BooleanFlag MODERN_BOTTOM_AREA = new BooleanFlag(206, false); + public static final BooleanFlag LOCKSCREEN_CUSTOM_CLOCKS = new BooleanFlag(207, false); + /***************************************/ // 300 - power menu public static final BooleanFlag POWER_MENU_LITE = @@ -157,7 +163,7 @@ public class Flags { /***************************************/ // 900 - media - public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, true); + public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, false); public static final BooleanFlag MEDIA_SESSION_ACTIONS = new BooleanFlag(901, false); public static final BooleanFlag MEDIA_NEARBY_DEVICES = new BooleanFlag(903, true); public static final BooleanFlag MEDIA_MUTE_AWAIT = new BooleanFlag(904, true); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index b96eee717260..95b3b3f6452f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -55,6 +55,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.SystemProperties; import android.os.Trace; +import android.util.ArrayMap; import android.util.Log; import android.util.Slog; import android.view.IRemoteAnimationFinishedCallback; @@ -157,7 +158,7 @@ public class KeyguardService extends Service { Rect localBounds = new Rect(change.getEndAbsBounds()); localBounds.offsetTo(change.getEndRelOffset().x, change.getEndRelOffset().y); - out.add(new RemoteAnimationTarget( + final RemoteAnimationTarget target = new RemoteAnimationTarget( taskId, newModeToLegacyMode(change.getMode()), change.getLeash(), @@ -168,7 +169,15 @@ public class KeyguardService extends Service { info.getChanges().size() - i, new Point(), localBounds, new Rect(change.getEndAbsBounds()), windowConfiguration, isNotInRecents, null /* startLeash */, - change.getStartAbsBounds(), taskInfo, false /* allowEnterPip */)); + change.getStartAbsBounds(), taskInfo, false /* allowEnterPip */); + // Use hasAnimatingParent to mark the anything below root task + if (taskId != -1 && change.getParent() != null) { + final TransitionInfo.Change parentChange = info.getChange(change.getParent()); + if (parentChange != null && parentChange.getTaskInfo() != null) { + target.hasAnimatingParent = true; + } + } + out.add(target); } return out.toArray(new RemoteAnimationTarget[out.size()]); } @@ -189,8 +198,12 @@ public class KeyguardService extends Service { } } + // Wrap Keyguard going away animation private static IRemoteTransition wrap(IRemoteAnimationRunner runner) { return new IRemoteTransition.Stub() { + final ArrayMap<IBinder, IRemoteTransitionFinishedCallback> mFinishCallbacks = + new ArrayMap<>(); + @Override public void startAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, IRemoteTransitionFinishedCallback finishCallback) @@ -200,16 +213,37 @@ public class KeyguardService extends Service { final RemoteAnimationTarget[] wallpapers = wrap(info, true /* wallpapers */); final RemoteAnimationTarget[] nonApps = new RemoteAnimationTarget[0]; - // TODO: Remove this, and update alpha value in the IAnimationRunner. - for (TransitionInfo.Change change : info.getChanges()) { - t.setAlpha(change.getLeash(), 1.0f); + // Sets the alpha to 0 for the opening root task for fade in animation. And since + // the fade in animation can only apply on the first opening app, so set alpha to 1 + // for anything else. + boolean foundOpening = false; + for (RemoteAnimationTarget target : apps) { + if (target.taskId != -1 + && target.mode == RemoteAnimationTarget.MODE_OPENING + && !target.hasAnimatingParent) { + if (foundOpening) { + Log.w(TAG, "More than one opening target"); + t.setAlpha(target.leash, 1.0f); + continue; + } + t.setAlpha(target.leash, 0.0f); + foundOpening = true; + } else { + t.setAlpha(target.leash, 1.0f); + } } t.apply(); + synchronized (mFinishCallbacks) { + mFinishCallbacks.put(transition, finishCallback); + } runner.onAnimationStart(getTransitionOldType(info.getType(), info.getFlags(), apps), apps, wallpapers, nonApps, new IRemoteAnimationFinishedCallback.Stub() { @Override public void onAnimationFinished() throws RemoteException { + synchronized (mFinishCallbacks) { + if (mFinishCallbacks.remove(transition) == null) return; + } Slog.d(TAG, "Finish IRemoteAnimationRunner."); finishCallback.onTransitionFinished(null /* wct */, null /* t */); } @@ -220,7 +254,20 @@ public class KeyguardService extends Service { public void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, IBinder mergeTarget, IRemoteTransitionFinishedCallback finishCallback) { - + try { + final IRemoteTransitionFinishedCallback origFinishCB; + synchronized (mFinishCallbacks) { + origFinishCB = mFinishCallbacks.remove(transition); + } + if (origFinishCB == null) { + // already finished (or not started yet), so do nothing. + return; + } + runner.onAnimationCancelled(false /* isKeyguardOccluded */); + origFinishCB.onTransitionFinished(null /* wct */, null /* t */); + } catch (RemoteException e) { + // nothing, we'll just let it finish on its own I guess. + } } }; } @@ -349,7 +396,7 @@ public class KeyguardService extends Service { } @Override // Binder interface - public void onAnimationCancelled() { + public void onAnimationCancelled(boolean isKeyguardOccluded) { mKeyguardViewMediator.cancelKeyguardExitAnimation(); } }; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index 99b57200a397..382323f3aed9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -26,6 +26,7 @@ import android.os.Handler import android.os.RemoteException import android.util.Log import android.view.RemoteAnimationTarget +import android.view.SurfaceControl import android.view.SyncRtSurfaceTransactionApplier import android.view.View import androidx.annotation.VisibleForTesting @@ -293,6 +294,8 @@ class KeyguardUnlockAnimationController @Inject constructor( private val handler = Handler() + private val tmpFloat = FloatArray(9) + init { with(surfaceBehindAlphaAnimator) { duration = SURFACE_BEHIND_SWIPE_FADE_DURATION_MS @@ -723,13 +726,27 @@ class KeyguardUnlockAnimationController @Inject constructor( if (keyguardStateController.isSnappingKeyguardBackAfterSwipe) amount else surfaceBehindAlpha - applyParamsToSurface( - SyncRtSurfaceTransactionApplier.SurfaceParams.Builder( - surfaceBehindRemoteAnimationTarget!!.leash) - .withMatrix(surfaceBehindMatrix) - .withCornerRadius(roundedCornerRadius) - .withAlpha(animationAlpha) - .build()) + // SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is unable + // to draw + val sc: SurfaceControl? = surfaceBehindRemoteAnimationTarget?.leash + if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE && + sc?.isValid == true) { + with(SurfaceControl.Transaction()) { + setMatrix(sc, surfaceBehindMatrix, tmpFloat) + setCornerRadius(sc, roundedCornerRadius) + setAlpha(sc, animationAlpha) + apply() + } + } else { + applyParamsToSurface( + SyncRtSurfaceTransactionApplier.SurfaceParams.Builder( + surfaceBehindRemoteAnimationTarget!!.leash) + .withMatrix(surfaceBehindMatrix) + .withCornerRadius(roundedCornerRadius) + .withAlpha(animationAlpha) + .build() + ) + } } /** @@ -744,8 +761,11 @@ class KeyguardUnlockAnimationController @Inject constructor( handler.removeCallbacksAndMessages(null) // Make sure we made the surface behind fully visible, just in case. It should already be - // fully visible. If the launcher is doing its own animation, let it continue without - // forcing it to 1f. + // fully visible. The exit animation is finished, and we should not hold the leash anymore, + // so forcing it to 1f. + surfaceBehindAlphaAnimator.cancel() + surfaceBehindEntryAnimator.cancel() + surfaceBehindAlpha = 1f setSurfaceBehindAppearAmount(1f) launcherUnlockController?.setUnlockAmount(1f, false /* forceIfAnimating */) @@ -910,4 +930,4 @@ class KeyguardUnlockAnimationController @Inject constructor( return context.resources.getIntArray(R.array.config_foldedDeviceStates).isNotEmpty() } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 72dd36fec349..4ee89854987c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -911,12 +911,12 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, private final Matrix mUnoccludeMatrix = new Matrix(); @Override - public void onAnimationCancelled() { + public void onAnimationCancelled(boolean isKeyguardOccluded) { if (mUnoccludeAnimator != null) { mUnoccludeAnimator.cancel(); } - setOccluded(false /* isOccluded */, false /* animate */); + setOccluded(isKeyguardOccluded, false /* animate */); Log.d(TAG, "Unocclude animation cancelled. Occluded state is now: " + mOccluded); } @@ -2497,10 +2497,18 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, mInteractionJankMonitor.begin( createInteractionJankMonitorConf("DismissPanel")); + // Apply the opening animation on root task if exists + RemoteAnimationTarget aniTarget = apps[0]; + for (RemoteAnimationTarget tmpTarget : apps) { + if (tmpTarget.taskId != -1 && !tmpTarget.hasAnimatingParent) { + aniTarget = tmpTarget; + break; + } + } // Pass the surface and metadata to the unlock animation controller. mKeyguardUnlockAnimationControllerLazy.get() .notifyStartSurfaceBehindRemoteAnimation( - apps[0], startTime, mSurfaceBehindRemoteAnimationRequested); + aniTarget, startTime, mSurfaceBehindRemoteAnimationRequested); } else { mInteractionJankMonitor.begin( createInteractionJankMonitorConf("RemoteAnimationDisabled")); @@ -3161,9 +3169,9 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, } @Override - public void onAnimationCancelled() throws RemoteException { + public void onAnimationCancelled(boolean isKeyguardOccluded) throws RemoteException { if (mRunner != null) { - mRunner.onAnimationCancelled(); + mRunner.onAnimationCancelled(isKeyguardOccluded); } } @@ -3204,8 +3212,8 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, } @Override - public void onAnimationCancelled() throws RemoteException { - super.onAnimationCancelled(); + public void onAnimationCancelled(boolean isKeyguardOccluded) throws RemoteException { + super.onAnimationCancelled(isKeyguardOccluded); Log.d(TAG, "Occlude launch animation cancelled. Occluded state is now: " + mOccluded); } } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index 30ba476abce2..88a1b17e37fe 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -1096,7 +1096,7 @@ class MediaDataManager( fun onNotificationRemoved(key: String) { Assert.isMainThread() val removed = mediaEntries.remove(key) - if (useMediaResumption && removed?.resumeAction != null && removed?.isLocalSession()) { + if (useMediaResumption && removed?.resumeAction != null && removed.isLocalSession()) { Log.d(TAG, "Not removing $key because resumable") // Move to resume key (aka package name) if that key doesn't already exist. val resumeAction = getResumeMediaAction(removed.resumeAction!!) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt index ed5c1933af25..2f732de50ea1 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt @@ -23,6 +23,7 @@ import android.annotation.IntDef import android.content.Context import android.content.res.Configuration import android.graphics.Rect +import android.util.Log import android.util.MathUtils import android.view.View import android.view.ViewGroup @@ -48,6 +49,8 @@ import com.android.systemui.util.animation.UniqueObjectHostView import com.android.systemui.util.traceSection import javax.inject.Inject +private val TAG: String = MediaHierarchyManager::class.java.simpleName + /** * Similarly to isShown but also excludes views that have 0 alpha */ @@ -964,6 +967,14 @@ class MediaHierarchyManager @Inject constructor( top, left + currentBounds.width(), top + currentBounds.height()) + + if (mediaFrame.childCount > 0) { + val child = mediaFrame.getChildAt(0) + if (mediaFrame.height < child.height) { + Log.wtf(TAG, "mediaFrame height is too small for child: " + + "${mediaFrame.height} vs ${child.height}") + } + } } if (isCrossFadeAnimatorRunning) { // When cross-fading with an animation, we only notify the media carousel of the diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java index f0ce30d2dc66..f2f275323d58 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java @@ -285,6 +285,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { Log.d(TAG, "This device is already connected! : " + device.getName()); return; } + mController.setTemporaryAllowListExceptionIfNeeded(device); mCurrentActivePosition = -1; mController.connectDevice(device); device.setState(MediaDeviceState.STATE_CONNECTING); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java index 2c5d1b95654e..e6116c16ec4a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java @@ -69,11 +69,13 @@ public abstract class MediaOutputBaseAdapter extends View mHolderView; boolean mIsDragging; int mCurrentActivePosition; + private boolean mIsInitVolumeFirstTime; public MediaOutputBaseAdapter(MediaOutputController controller) { mController = controller; mIsDragging = false; mCurrentActivePosition = -1; + mIsInitVolumeFirstTime = true; } @Override @@ -275,7 +277,7 @@ public abstract class MediaOutputBaseAdapter extends mSeekBar.setMaxVolume(device.getMaxVolume()); final int currentVolume = device.getCurrentVolume(); if (mSeekBar.getVolume() != currentVolume) { - if (isCurrentSeekbarInvisible) { + if (isCurrentSeekbarInvisible && !mIsInitVolumeFirstTime) { animateCornerAndVolume(mSeekBar.getProgress(), MediaOutputSeekbar.scaleVolumeToProgress(currentVolume)); } else { @@ -284,6 +286,9 @@ public abstract class MediaOutputBaseAdapter extends } } } + if (mIsInitVolumeFirstTime) { + mIsInitVolumeFirstTime = false; + } mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt index 38005db28cf6..0fa326573c9c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt @@ -19,6 +19,7 @@ package com.android.systemui.media.dialog import android.content.Context import android.media.AudioManager import android.media.session.MediaSessionManager +import android.os.PowerExemptionManager import android.view.View import com.android.internal.logging.UiEventLogger import com.android.settingslib.bluetooth.LocalBluetoothManager @@ -43,7 +44,8 @@ class MediaOutputBroadcastDialogFactory @Inject constructor( private val uiEventLogger: UiEventLogger, private val dialogLaunchAnimator: DialogLaunchAnimator, private val nearbyMediaDevicesManagerOptional: Optional<NearbyMediaDevicesManager>, - private val audioManager: AudioManager + private val audioManager: AudioManager, + private val powerExemptionManager: PowerExemptionManager ) { var mediaOutputBroadcastDialog: MediaOutputBroadcastDialog? = null @@ -54,7 +56,8 @@ class MediaOutputBroadcastDialogFactory @Inject constructor( val controller = MediaOutputController(context, packageName, mediaSessionManager, lbm, starter, notifCollection, - dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager) + dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager, + powerExemptionManager) val dialog = MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller) mediaOutputBroadcastDialog = dialog diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java index 0b4b03619a13..247ffa7c2ef9 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -44,6 +44,7 @@ import android.media.session.MediaController; import android.media.session.MediaSessionManager; import android.media.session.PlaybackState; import android.os.IBinder; +import android.os.PowerExemptionManager; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; @@ -101,6 +102,9 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final String PAGE_CONNECTED_DEVICES_KEY = "top_level_connected_devices"; + private static final long ALLOWLIST_DURATION_MS = 20000; + private static final String ALLOWLIST_REASON = "mediaoutput:remote_transfer"; + private final String mPackageName; private final Context mContext; private final MediaSessionManager mMediaSessionManager; @@ -114,6 +118,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>(); final List<MediaDevice> mCachedMediaDevices = new CopyOnWriteArrayList<>(); private final AudioManager mAudioManager; + private final PowerExemptionManager mPowerExemptionManager; private final NearbyMediaDevicesManager mNearbyMediaDevicesManager; private final Map<String, Integer> mNearbyDeviceInfoMap = new ConcurrentHashMap<>(); @@ -147,7 +152,8 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, CommonNotifCollection notifCollection, DialogLaunchAnimator dialogLaunchAnimator, Optional<NearbyMediaDevicesManager> nearbyMediaDevicesManagerOptional, - AudioManager audioManager) { + AudioManager audioManager, + PowerExemptionManager powerExemptionManager) { mContext = context; mPackageName = packageName; mMediaSessionManager = mediaSessionManager; @@ -155,6 +161,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, mActivityStarter = starter; mNotifCollection = notifCollection; mAudioManager = audioManager; + mPowerExemptionManager = powerExemptionManager; InfoMediaManager imm = new InfoMediaManager(mContext, packageName, null, lbm); mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName); mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName); @@ -776,7 +783,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, MediaOutputController controller = new MediaOutputController(mContext, mPackageName, mMediaSessionManager, mLocalBluetoothManager, mActivityStarter, mNotifCollection, mDialogLaunchAnimator, Optional.of(mNearbyMediaDevicesManager), - mAudioManager); + mAudioManager, mPowerExemptionManager); MediaOutputBroadcastDialog dialog = new MediaOutputBroadcastDialog(mContext, true, broadcastSender, controller); mDialogLaunchAnimator.showFromView(dialog, mediaOutputDialog); @@ -822,6 +829,17 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, broadcast.setBroadcastCode(broadcastCode.getBytes(StandardCharsets.UTF_8)); } + void setTemporaryAllowListExceptionIfNeeded(MediaDevice targetDevice) { + if (mPowerExemptionManager == null || mPackageName == null) { + Log.w(TAG, "powerExemptionManager or package name is null"); + return; + } + mPowerExemptionManager.addToTemporaryAllowList(mPackageName, + PowerExemptionManager.REASON_MEDIA_NOTIFICATION_TRANSFER, + ALLOWLIST_REASON, + ALLOWLIST_DURATION_MS); + } + String getBroadcastMetadata() { LocalBluetoothLeBroadcast broadcast = mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile(); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt index 8701d4aba720..8249a7c0779c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt @@ -19,6 +19,7 @@ package com.android.systemui.media.dialog import android.content.Context import android.media.AudioManager import android.media.session.MediaSessionManager +import android.os.PowerExemptionManager import android.view.View import com.android.internal.jank.InteractionJankMonitor import com.android.internal.logging.UiEventLogger @@ -45,7 +46,8 @@ class MediaOutputDialogFactory @Inject constructor( private val uiEventLogger: UiEventLogger, private val dialogLaunchAnimator: DialogLaunchAnimator, private val nearbyMediaDevicesManagerOptional: Optional<NearbyMediaDevicesManager>, - private val audioManager: AudioManager + private val audioManager: AudioManager, + private val powerExemptionManager: PowerExemptionManager ) { companion object { private const val INTERACTION_JANK_TAG = "media_output" @@ -60,8 +62,8 @@ class MediaOutputDialogFactory @Inject constructor( val controller = MediaOutputController( context, packageName, mediaSessionManager, lbm, starter, notifCollection, - dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager - ) + dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager, + powerExemptionManager) val dialog = MediaOutputDialog(context, aboveStatusBar, broadcastSender, controller, uiEventLogger) mediaOutputDialog = dialog diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamComplication.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamComplication.java index 7c0481049098..2c35db337cda 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamComplication.java +++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamComplication.java @@ -37,11 +37,6 @@ public class MediaDreamComplication implements Complication { } @Override - public int getRequiredTypeAvailability() { - return COMPLICATION_TYPE_CAST_INFO; - } - - @Override public ViewHolder createView(ComplicationViewModel model) { return mComponentFactory.create().getViewHolder(); } diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/ChipInfoCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/ChipInfoCommon.kt index 3cc99a8ef77e..e95976f555f8 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/ChipInfoCommon.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/ChipInfoCommon.kt @@ -27,4 +27,4 @@ interface ChipInfoCommon { fun getTimeoutMs(): Long } -const val DEFAULT_TIMEOUT_MILLIS = 3000L +const val DEFAULT_TIMEOUT_MILLIS = 4000L diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt index 7cc52e428218..fe1ac80e24df 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt @@ -31,6 +31,10 @@ import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.view.WindowManager +import android.view.accessibility.AccessibilityManager +import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS +import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS +import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT import android.widget.LinearLayout import com.android.internal.widget.CachingIconView import com.android.settingslib.Utils @@ -56,6 +60,7 @@ abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>( private val windowManager: WindowManager, private val viewUtil: ViewUtil, @Main private val mainExecutor: DelayableExecutor, + private val accessibilityManager: AccessibilityManager, private val tapGestureDetector: TapGestureDetector, private val powerManager: PowerManager, @LayoutRes private val chipLayoutRes: Int @@ -110,10 +115,16 @@ abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>( } // Cancel and re-set the chip timeout each time we get a new state. + val timeout = accessibilityManager.getRecommendedTimeoutMillis( + chipInfo.getTimeoutMs().toInt(), + // Not all chips have controls so FLAG_CONTENT_CONTROLS might be superfluous, but + // include it just to be safe. + FLAG_CONTENT_ICONS or FLAG_CONTENT_TEXT or FLAG_CONTENT_CONTROLS + ) cancelChipViewTimeout?.run() cancelChipViewTimeout = mainExecutor.executeDelayed( { removeChip(MediaTttRemovalReason.REASON_TIMEOUT) }, - chipInfo.getTimeoutMs() + timeout.toLong() ) } diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt index 072263fcf38c..a5d763c5327b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt @@ -26,6 +26,7 @@ import android.os.PowerManager import android.util.Log import android.view.ViewGroup import android.view.WindowManager +import android.view.accessibility.AccessibilityManager import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main @@ -52,6 +53,7 @@ class MediaTttChipControllerReceiver @Inject constructor( windowManager: WindowManager, viewUtil: ViewUtil, mainExecutor: DelayableExecutor, + accessibilityManager: AccessibilityManager, tapGestureDetector: TapGestureDetector, powerManager: PowerManager, @Main private val mainHandler: Handler, @@ -62,6 +64,7 @@ class MediaTttChipControllerReceiver @Inject constructor( windowManager, viewUtil, mainExecutor, + accessibilityManager, tapGestureDetector, powerManager, R.layout.media_ttt_chip_receiver diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt index 54b4380e2443..943604cff887 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt @@ -24,6 +24,7 @@ import android.util.Log import android.view.View import android.view.ViewGroup import android.view.WindowManager +import android.view.accessibility.AccessibilityManager import android.widget.TextView import com.android.internal.statusbar.IUndoMediaTransferCallback import com.android.systemui.R @@ -53,6 +54,7 @@ class MediaTttChipControllerSender @Inject constructor( windowManager: WindowManager, viewUtil: ViewUtil, @Main mainExecutor: DelayableExecutor, + accessibilityManager: AccessibilityManager, tapGestureDetector: TapGestureDetector, powerManager: PowerManager, private val uiEventLogger: MediaTttSenderUiEventLogger @@ -62,6 +64,7 @@ class MediaTttChipControllerSender @Inject constructor( windowManager, viewUtil, mainExecutor, + accessibilityManager, tapGestureDetector, powerManager, R.layout.media_ttt_chip diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt index f1dd5ffd2077..8179d1763ea1 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt @@ -291,7 +291,8 @@ class BackPanelController private constructor( override fun onMotionEvent(event: MotionEvent) { backAnimation?.onBackMotion( - event, + event.x, + event.y, event.actionMasked, if (mView.isLeftPanel) BackEvent.EDGE_LEFT else BackEvent.EDGE_RIGHT ) diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index 19b9757bb772..bd6a5fc8661b 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -83,13 +83,16 @@ import com.android.systemui.tracing.ProtoTracer; import com.android.systemui.tracing.nano.EdgeBackGestureHandlerProto; import com.android.systemui.tracing.nano.SystemUiTraceProto; import com.android.wm.shell.back.BackAnimation; +import com.android.wm.shell.pip.Pip; import java.io.PrintWriter; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.Executor; +import java.util.function.Consumer; import javax.inject.Inject; import javax.inject.Provider; @@ -147,16 +150,6 @@ public class EdgeBackGestureHandler extends CurrentUserTracker mPackageName = "_UNKNOWN"; } } - - @Override - public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { - mIsInPipMode = true; - } - - @Override - public void onActivityUnpinned() { - mIsInPipMode = false; - } }; private DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener = @@ -188,6 +181,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker private final ViewConfiguration mViewConfiguration; private final WindowManager mWindowManager; private final IWindowManager mWindowManagerService; + private final Optional<Pip> mPipOptional; private final FalsingManager mFalsingManager; // Activities which should not trigger Back gesture. private final List<ComponentName> mGestureBlockingActivities = new ArrayList<>(); @@ -220,6 +214,8 @@ public class EdgeBackGestureHandler extends CurrentUserTracker // We temporarily disable back gesture when user is quickswitching // between apps of different orientations private boolean mDisabledForQuickstep; + // This gets updated when the value of PipTransitionState#isInPip changes. + private boolean mIsInPip; private final PointF mDownPoint = new PointF(); private final PointF mEndPoint = new PointF(); @@ -235,7 +231,6 @@ public class EdgeBackGestureHandler extends CurrentUserTracker private boolean mIsNavBarShownTransiently; private boolean mIsBackGestureAllowed; private boolean mGestureBlockingActivityRunning; - private boolean mIsInPipMode; private boolean mIsNewBackAffordanceEnabled; private InputMonitor mInputMonitor; @@ -304,6 +299,8 @@ public class EdgeBackGestureHandler extends CurrentUserTracker } }; + private final Consumer<Boolean> mOnIsInPipStateChangedListener = + (isInPip) -> mIsInPip = isInPip; EdgeBackGestureHandler( Context context, @@ -318,6 +315,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker ViewConfiguration viewConfiguration, WindowManager windowManager, IWindowManager windowManagerService, + Optional<Pip> pipOptional, FalsingManager falsingManager, LatencyTracker latencyTracker, Provider<BackGestureTfClassifierProvider> backGestureTfClassifierProviderProvider, @@ -335,6 +333,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker mViewConfiguration = viewConfiguration; mWindowManager = windowManager; mWindowManagerService = windowManagerService; + mPipOptional = pipOptional; mFalsingManager = falsingManager; mLatencyTracker = latencyTracker; mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider; @@ -495,6 +494,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker mPluginManager.removePluginListener(this); TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener); DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener); + mPipOptional.ifPresent(pip -> pip.setOnIsInPipStateChangedListener(null)); try { mWindowManagerService.unregisterSystemGestureExclusionListener( @@ -512,6 +512,8 @@ public class EdgeBackGestureHandler extends CurrentUserTracker TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener); DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, mMainExecutor::execute, mOnPropertiesChangedListener); + mPipOptional.ifPresent( + pip -> pip.setOnIsInPipStateChangedListener(mOnIsInPipStateChangedListener)); try { mWindowManagerService.registerSystemGestureExclusionListener( @@ -681,7 +683,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker private boolean isWithinTouchRegion(int x, int y) { // If the point is inside the PiP or Nav bar overlay excluded bounds, then ignore the back // gesture - final boolean isInsidePip = mIsInPipMode && mPipExcludedBounds.contains(x, y); + final boolean isInsidePip = mIsInPip && mPipExcludedBounds.contains(x, y); if (isInsidePip || mNavBarOverlayExcludedBounds.contains(x, y)) { return false; } @@ -934,7 +936,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker pw.println(" mInRejectedExclusion=" + mInRejectedExclusion); pw.println(" mExcludeRegion=" + mExcludeRegion); pw.println(" mUnrestrictedExcludeRegion=" + mUnrestrictedExcludeRegion); - pw.println(" mIsInPipMode=" + mIsInPipMode); + pw.println(" mIsInPip=" + mIsInPip); pw.println(" mPipExcludedBounds=" + mPipExcludedBounds); pw.println(" mNavBarOverlayExcludedBounds=" + mNavBarOverlayExcludedBounds); pw.println(" mEdgeWidthLeft=" + mEdgeWidthLeft); @@ -1003,6 +1005,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker private final ViewConfiguration mViewConfiguration; private final WindowManager mWindowManager; private final IWindowManager mWindowManagerService; + private final Optional<Pip> mPipOptional; private final FalsingManager mFalsingManager; private final LatencyTracker mLatencyTracker; private final Provider<BackGestureTfClassifierProvider> @@ -1021,6 +1024,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker ViewConfiguration viewConfiguration, WindowManager windowManager, IWindowManager windowManagerService, + Optional<Pip> pipOptional, FalsingManager falsingManager, LatencyTracker latencyTracker, Provider<BackGestureTfClassifierProvider> @@ -1037,6 +1041,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker mViewConfiguration = viewConfiguration; mWindowManager = windowManager; mWindowManagerService = windowManagerService; + mPipOptional = pipOptional; mFalsingManager = falsingManager; mLatencyTracker = latencyTracker; mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider; @@ -1058,6 +1063,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker mViewConfiguration, mWindowManager, mWindowManagerService, + mPipOptional, mFalsingManager, mLatencyTracker, mBackGestureTfClassifierProviderProvider, diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java index a74c59618c95..eba9d3fdcab8 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java @@ -486,7 +486,7 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl public void onMotionEvent(MotionEvent event) { if (mBackAnimation != null) { mBackAnimation.onBackMotion( - event, + event.getX(), event.getY(), event.getActionMasked(), mIsLeftPanel ? BackEvent.EDGE_LEFT : BackEvent.EDGE_RIGHT); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt index 892c28306f46..0288c9fce64a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt @@ -342,7 +342,8 @@ class FgsManagerController @Inject constructor( } val addedPackages = runningServiceTokens.keys.filter { - it.uiControl != UIControl.HIDE_ENTRY && runningApps[it]?.stopped != true + currentProfileIds.contains(it.userId) && + it.uiControl != UIControl.HIDE_ENTRY && runningApps[it]?.stopped != true } val removedPackages = runningApps.keys.filter { !runningServiceTokens.containsKey(it) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index 5d2060d8043e..7b1ddd62ec6e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -139,12 +139,11 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { void updateResources(QSPanelController qsPanelController, QuickStatusBarHeaderController quickStatusBarHeaderController) { - int bottomPadding = getResources().getDimensionPixelSize(R.dimen.qs_panel_padding_bottom); mQSPanelContainer.setPaddingRelative( mQSPanelContainer.getPaddingStart(), QSUtils.getQsHeaderSystemIconsAreaHeight(mContext), mQSPanelContainer.getPaddingEnd(), - bottomPadding); + mQSPanelContainer.getPaddingBottom()); int horizontalMargins = getResources().getDimensionPixelSize(R.dimen.qs_horizontal_margin); int horizontalPadding = getResources().getDimensionPixelSize( diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 41724ef62683..324c01959084 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -362,11 +362,11 @@ public class QSPanel extends LinearLayout implements Tunable { protected void updatePadding() { final Resources res = mContext.getResources(); int paddingTop = res.getDimensionPixelSize(R.dimen.qs_panel_padding_top); - // Bottom padding only when there's a new footer with its height. + int paddingBottom = res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom); setPaddingRelative(getPaddingStart(), paddingTop, getPaddingEnd(), - getPaddingBottom()); + paddingBottom); } void addOnConfigurationChangedListener(OnConfigurationChangedListener listener) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 19eb168fddc0..89a15f65e98f 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -142,7 +142,7 @@ public class ScreenshotController { } @Override - public void onAnimationCancelled() { + public void onAnimationCancelled(boolean isKeyguardOccluded) { } }; diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt index d3ae198e8e35..236ba1f92d1f 100644 --- a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt @@ -56,6 +56,8 @@ interface SmartspaceViewComponent { ): BcSmartspaceDataPlugin.SmartspaceView { val ssView = plugin.getView(parent) + // Currently, this is only used to provide SmartspaceView on Dream surface. + ssView.setIsDreaming(true) ssView.registerDataProvider(plugin) ssView.setIntentStarter(object : BcSmartspaceDataPlugin.IntentStarter { @@ -81,4 +83,4 @@ interface SmartspaceViewComponent { return ssView } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java index 1ffa6f41d8a3..decc02c98b61 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java @@ -65,7 +65,7 @@ public abstract class AlertingNotificationManager implements NotificationLifetim * @param entry entry to show */ public void showNotification(@NonNull NotificationEntry entry) { - mLogger.logShowNotification(entry.getKey()); + mLogger.logShowNotification(entry); addAlertEntry(entry); updateNotification(entry.getKey(), true /* alert */); entry.setInterruption(); @@ -319,7 +319,7 @@ public abstract class AlertingNotificationManager implements NotificationLifetim * @param updatePostTime whether or not to refresh the post time */ public void updateEntry(boolean updatePostTime) { - mLogger.logUpdateEntry(mEntry.getKey(), updatePostTime); + mLogger.logUpdateEntry(mEntry, updatePostTime); long currentTime = mClock.currentTimeMillis(); mEarliestRemovaltime = currentTime + mMinimumDisplayTime; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 86f9fa155179..9e029095ea6b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar; import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED; import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_MANAGEMENT_DISCLOSURE; +import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE; import static android.view.View.GONE; import static android.view.View.VISIBLE; @@ -407,7 +408,7 @@ public class KeyguardIndicationController { organizationName); } else { return mDevicePolicyManager.getResources().getString( - KEYGUARD_MANAGEMENT_DISCLOSURE, + KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE, () -> packageResources.getString( R.string.do_disclosure_with_name, organizationName), organizationName); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java index 9d8667a3ccb0..f2014e9deb72 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java @@ -110,7 +110,7 @@ public class MobileSignalController extends SignalController<MobileState, Mobile @Override public void onMobileStatusChanged(boolean updateTelephony, MobileStatus mobileStatus) { - if (Log.isLoggable(mTag, Log.DEBUG)) { + if (DEBUG) { Log.d(mTag, "onMobileStatusChanged=" + " updateTelephony=" + updateTelephony + " mobileStatus=" + mobileStatus.toString()); @@ -717,7 +717,7 @@ public class MobileSignalController extends SignalController<MobileState, Mobile * This will call listeners if necessary. */ private void updateTelephony() { - if (Log.isLoggable(mTag, Log.DEBUG)) { + if (DEBUG) { Log.d(mTag, "updateTelephonySignalStrength: hasService=" + mCurrentState.isInService() + " ss=" + mCurrentState.signalStrength diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt index 478f7aa8ce09..c4947ca2dd90 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt @@ -56,4 +56,7 @@ class NotifPipelineFlags @Inject constructor( fun isSmartspaceDedupingEnabled(): Boolean = featureFlags.isEnabled(Flags.SMARTSPACE) && featureFlags.isEnabled(Flags.SMARTSPACE_DEDUPING) -}
\ No newline at end of file + + fun removeUnrankedNotifs(): Boolean = + featureFlags.isEnabled(Flags.REMOVE_UNRANKED_NOTIFICATIONS) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java index 792ff8d20b97..f6a572ec6ce6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.collection; +import static com.android.systemui.statusbar.notification.NotificationUtils.logKey; import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_NOT_CANCELED; import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.NOT_DISMISSED; @@ -52,7 +53,7 @@ public class ListDumper { sb, true, includeRecordKeeping, - interactionTracker.hasUserInteractedWith(entry.getKey())); + interactionTracker.hasUserInteractedWith(logKey(entry))); if (entry instanceof GroupEntry) { GroupEntry ge = (GroupEntry) entry; NotificationEntry summary = ge.getSummary(); @@ -63,7 +64,7 @@ public class ListDumper { sb, true, includeRecordKeeping, - interactionTracker.hasUserInteractedWith(summary.getKey())); + interactionTracker.hasUserInteractedWith(logKey(summary))); } List<NotificationEntry> children = ge.getChildren(); for (int childIndex = 0; childIndex < children.size(); childIndex++) { @@ -74,7 +75,7 @@ public class ListDumper { sb, true, includeRecordKeeping, - interactionTracker.hasUserInteractedWith(child.getKey())); + interactionTracker.hasUserInteractedWith(logKey(child))); } } } @@ -116,11 +117,11 @@ public class ListDumper { sb.append(indent) .append("[").append(index).append("] ") .append(index.length() == 1 ? " " : "") - .append(entry.getKey()); + .append(logKey(entry)); if (includeParent) { sb.append(" (parent=") - .append(entry.getParent() != null ? entry.getParent().getKey() : null) + .append(logKey(entry.getParent())) .append(")"); NotificationEntry notifEntry = entry.getRepresentativeEntry(); @@ -185,8 +186,8 @@ public class ListDumper { if (notifEntry.getAttachState().getSuppressedChanges().getParent() != null) { rksb.append("suppressedParent=") - .append(notifEntry.getAttachState().getSuppressedChanges() - .getParent().getKey()) + .append(logKey(notifEntry.getAttachState().getSuppressedChanges() + .getParent())) .append(" "); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index 72aa89aa382d..6a01df61705d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -89,6 +89,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.In import com.android.systemui.statusbar.notification.collection.notifcollection.InternalNotifUpdater; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger; +import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLoggerKt; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifEvent; import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; @@ -110,6 +111,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Queue; +import java.util.Set; import java.util.concurrent.TimeUnit; import javax.inject.Inject; @@ -157,6 +159,8 @@ public class NotifCollection implements Dumpable, PipelineDumpable { private final List<NotifLifetimeExtender> mLifetimeExtenders = new ArrayList<>(); private final List<NotifDismissInterceptor> mDismissInterceptors = new ArrayList<>(); + private Set<String> mNotificationsWithoutRankings = Collections.emptySet(); + private Queue<NotifEvent> mEventQueue = new ArrayDeque<>(); private boolean mAttached = false; @@ -267,13 +271,14 @@ public class NotifCollection implements Dumpable, PipelineDumpable { requireNonNull(stats); NotificationEntry storedEntry = mNotificationSet.get(entry.getKey()); if (storedEntry == null) { - mLogger.logNonExistentNotifDismissed(entry.getKey()); + mLogger.logNonExistentNotifDismissed(entry); continue; } if (entry != storedEntry) { throw mEulogizer.record( new IllegalStateException("Invalid entry: " - + "different stored and dismissed entries for " + entry.getKey())); + + "different stored and dismissed entries for " + logKey(entry) + + " stored=@" + Integer.toHexString(storedEntry.hashCode()))); } if (entry.getDismissState() == DISMISSED) { @@ -282,7 +287,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable { updateDismissInterceptors(entry); if (isDismissIntercepted(entry)) { - mLogger.logNotifDismissedIntercepted(entry.getKey()); + mLogger.logNotifDismissedIntercepted(entry); continue; } @@ -299,7 +304,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable { stats.notificationVisibility); } catch (RemoteException e) { // system process is dead if we're here. - mLogger.logRemoteExceptionOnNotificationClear(entry.getKey(), e); + mLogger.logRemoteExceptionOnNotificationClear(entry, e); } } } @@ -342,7 +347,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable { // interceptors the chance to filter the notification updateDismissInterceptors(entry); if (isDismissIntercepted(entry)) { - mLogger.logNotifClearAllDismissalIntercepted(entry.getKey()); + mLogger.logNotifClearAllDismissalIntercepted(entry); } entries.remove(i); } @@ -363,7 +368,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable { NotificationEntry entry = entries.get(i); entry.setDismissState(DISMISSED); - mLogger.logNotifDismissed(entry.getKey()); + mLogger.logNotifDismissed(entry); if (isCanceled(entry)) { canceledEntries.add(entry); @@ -416,12 +421,12 @@ public class NotifCollection implements Dumpable, PipelineDumpable { int reason) { Assert.isMainThread(); - mLogger.logNotifRemoved(sbn.getKey(), reason); + mLogger.logNotifRemoved(sbn, reason); final NotificationEntry entry = mNotificationSet.get(sbn.getKey()); if (entry == null) { // TODO (b/160008901): Throw an exception here - mLogger.logNoNotificationToRemoveWithKey(sbn.getKey(), reason); + mLogger.logNoNotificationToRemoveWithKey(sbn, reason); return; } @@ -464,7 +469,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable { mEventQueue.add(new BindEntryEvent(entry, sbn)); mNotificationSet.put(sbn.getKey(), entry); - mLogger.logNotifPosted(sbn.getKey()); + mLogger.logNotifPosted(entry); mEventQueue.add(new EntryAddedEvent(entry)); } else { @@ -483,7 +488,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable { entry.setSbn(sbn); mEventQueue.add(new BindEntryEvent(entry, sbn)); - mLogger.logNotifUpdated(sbn.getKey()); + mLogger.logNotifUpdated(entry); mEventQueue.add(new EntryUpdatedEvent(entry, true /* fromSystem */)); } } @@ -498,12 +503,12 @@ public class NotifCollection implements Dumpable, PipelineDumpable { if (mNotificationSet.get(entry.getKey()) != entry) { throw mEulogizer.record( new IllegalStateException("No notification to remove with key " - + entry.getKey())); + + logKey(entry))); } if (!isCanceled(entry)) { throw mEulogizer.record( - new IllegalStateException("Cannot remove notification " + entry.getKey() + new IllegalStateException("Cannot remove notification " + logKey(entry) + ": has not been marked for removal")); } @@ -514,7 +519,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable { } if (!isLifetimeExtended(entry)) { - mLogger.logNotifReleased(entry.getKey()); + mLogger.logNotifReleased(entry); mNotificationSet.remove(entry.getKey()); cancelDismissInterception(entry); mEventQueue.add(new EntryRemovedEvent(entry, entry.mCancellationReason)); @@ -559,6 +564,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable { } private void applyRanking(@NonNull RankingMap rankingMap) { + ArrayMap<String, NotificationEntry> currentEntriesWithoutRankings = null; for (NotificationEntry entry : mNotificationSet.values()) { if (!isCanceled(entry)) { @@ -580,10 +586,27 @@ public class NotifCollection implements Dumpable, PipelineDumpable { } } } else { - mLogger.logRankingMissing(entry.getKey(), rankingMap); + if (currentEntriesWithoutRankings == null) { + currentEntriesWithoutRankings = new ArrayMap<>(); + } + currentEntriesWithoutRankings.put(entry.getKey(), entry); } } } + NotifCollectionLoggerKt.maybeLogInconsistentRankings( + mLogger, + mNotificationsWithoutRankings, + currentEntriesWithoutRankings, + rankingMap + ); + mNotificationsWithoutRankings = currentEntriesWithoutRankings == null + ? Collections.emptySet() : currentEntriesWithoutRankings.keySet(); + if (currentEntriesWithoutRankings != null && mNotifPipelineFlags.removeUnrankedNotifs()) { + for (NotificationEntry entry : currentEntriesWithoutRankings.values()) { + entry.mCancellationReason = REASON_UNKNOWN; + tryRemoveNotification(entry); + } + } mEventQueue.add(new RankingAppliedEvent()); } @@ -627,10 +650,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable { extender.getName(), logKey, collectionEntryIs))); } - mLogger.logLifetimeExtensionEnded( - entry.getKey(), - extender, - entry.mLifetimeExtenders.size()); + mLogger.logLifetimeExtensionEnded(entry, extender, entry.mLifetimeExtenders.size()); if (!isLifetimeExtended(entry)) { if (tryRemoveNotification(entry)) { @@ -657,7 +677,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable { mAmDispatchingToOtherCode = true; for (NotifLifetimeExtender extender : mLifetimeExtenders) { if (extender.maybeExtendLifetime(entry, entry.mCancellationReason)) { - mLogger.logLifetimeExtended(entry.getKey(), extender); + mLogger.logLifetimeExtended(entry, extender); entry.mLifetimeExtenders.add(extender); } } @@ -838,6 +858,11 @@ public class NotifCollection implements Dumpable, PipelineDumpable { entries, true, "\t\t")); + + pw.println("\n\tmNotificationsWithoutRankings: " + mNotificationsWithoutRankings.size()); + for (String key : mNotificationsWithoutRankings) { + pw.println("\t * : " + key); + } } @Override @@ -924,17 +949,17 @@ public class NotifCollection implements Dumpable, PipelineDumpable { // Make sure we have the notification to update NotificationEntry entry = mNotificationSet.get(sbn.getKey()); if (entry == null) { - mLogger.logNotifInternalUpdateFailed(sbn.getKey(), name, reason); + mLogger.logNotifInternalUpdateFailed(sbn, name, reason); return; } - mLogger.logNotifInternalUpdate(sbn.getKey(), name, reason); + mLogger.logNotifInternalUpdate(entry, name, reason); // First do the pieces of postNotification which are not about assuming the notification // was sent by the app entry.setSbn(sbn); mEventQueue.add(new BindEntryEvent(entry, sbn)); - mLogger.logNotifUpdated(sbn.getKey()); + mLogger.logNotifUpdated(entry); mEventQueue.add(new EntryUpdatedEvent(entry, false /* fromSystem */)); // Skip the applyRanking step and go straight to dispatching the events diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java index 083759c1d757..26d2ee349f50 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java @@ -579,11 +579,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { if (existingSummary == null) { group.setSummary(entry); } else { - mLogger.logDuplicateSummary( - mIterationCount, - group.getKey(), - existingSummary.getKey(), - entry.getKey()); + mLogger.logDuplicateSummary(mIterationCount, group, existingSummary, entry); // Use whichever one was posted most recently if (entry.getSbn().getPostTime() @@ -1084,7 +1080,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable { if (!Objects.equals(curr, prev)) { mLogger.logEntryAttachStateChanged( mIterationCount, - entry.getKey(), + entry, prev.getParent(), curr.getParent()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java index 8f37bafa45e6..023c4ef2b8b7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java @@ -360,13 +360,13 @@ public class PreparationCoordinator implements Coordinator { } private void abortInflation(NotificationEntry entry, String reason) { - mLogger.logInflationAborted(entry.getKey(), reason); + mLogger.logInflationAborted(entry, reason); mNotifInflater.abortInflation(entry); mInflatingNotifs.remove(entry); } private void onInflationFinished(NotificationEntry entry, NotifViewController controller) { - mLogger.logNotifInflated(entry.getKey()); + mLogger.logNotifInflated(entry); mInflatingNotifs.remove(entry); mViewBarn.registerViewForEntry(entry, controller); mInflationStates.put(entry, STATE_INFLATED); @@ -398,20 +398,20 @@ public class PreparationCoordinator implements Coordinator { return false; } if (isBeyondGroupInitializationWindow(group, now)) { - mLogger.logGroupInflationTookTooLong(group.getKey()); + mLogger.logGroupInflationTookTooLong(group); return false; } if (mInflatingNotifs.contains(group.getSummary())) { - mLogger.logDelayingGroupRelease(group.getKey(), group.getSummary().getKey()); + mLogger.logDelayingGroupRelease(group, group.getSummary()); return true; } for (NotificationEntry child : group.getChildren()) { if (mInflatingNotifs.contains(child) && !child.wasAttachedInPreviousPass()) { - mLogger.logDelayingGroupRelease(group.getKey(), child.getKey()); + mLogger.logDelayingGroupRelease(group, child); return true; } } - mLogger.logDoneWaitingForGroupInflation(group.getKey()); + mLogger.logDoneWaitingForGroupInflation(group); return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt index f8352500923e..30f13152126c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt @@ -19,48 +19,51 @@ package com.android.systemui.statusbar.notification.collection.coordinator import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogLevel import com.android.systemui.log.dagger.NotificationLog +import com.android.systemui.statusbar.notification.collection.GroupEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.logKey import javax.inject.Inject class PreparationCoordinatorLogger @Inject constructor( @NotificationLog private val buffer: LogBuffer ) { - fun logNotifInflated(key: String) { + fun logNotifInflated(entry: NotificationEntry) { buffer.log(TAG, LogLevel.DEBUG, { - str1 = key + str1 = entry.logKey }, { "NOTIF INFLATED $str1" }) } - fun logInflationAborted(key: String, reason: String) { + fun logInflationAborted(entry: NotificationEntry, reason: String) { buffer.log(TAG, LogLevel.DEBUG, { - str1 = key + str1 = entry.logKey str2 = reason }, { "NOTIF INFLATION ABORTED $str1 reason=$str2" }) } - fun logDoneWaitingForGroupInflation(groupKey: String) { + fun logDoneWaitingForGroupInflation(group: GroupEntry) { buffer.log(TAG, LogLevel.DEBUG, { - str1 = groupKey + str1 = group.logKey }, { "Finished inflating all members of group $str1, releasing group" }) } - fun logGroupInflationTookTooLong(groupKey: String) { + fun logGroupInflationTookTooLong(group: GroupEntry) { buffer.log(TAG, LogLevel.WARNING, { - str1 = groupKey + str1 = group.logKey }, { "Group inflation took too long for $str1, releasing children early" }) } - fun logDelayingGroupRelease(groupKey: String, childKey: String) { + fun logDelayingGroupRelease(group: GroupEntry, child: NotificationEntry) { buffer.log(TAG, LogLevel.DEBUG, { - str1 = groupKey - str2 = childKey + str1 = group.logKey + str2 = child.logKey }, { "Delaying release of group $str1 because child $str2 is still inflating" }) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt index 9e8b35af1bce..1494574b26f0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt @@ -68,4 +68,4 @@ class RowAppearanceCoordinator @Inject internal constructor( // Show the "alerted" bell icon controller.setLastAudiblyAlertedMs(entry.lastAudiblyAlertedMs) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt index f8bf85f92eae..8d1759b8f475 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt @@ -23,8 +23,10 @@ import com.android.systemui.log.LogLevel.WARNING import com.android.systemui.log.dagger.NotificationLog import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.ListEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter +import com.android.systemui.statusbar.notification.logKey import javax.inject.Inject class ShadeListBuilderLogger @Inject constructor( @@ -110,12 +112,17 @@ class ShadeListBuilderLogger @Inject constructor( }) } - fun logDuplicateSummary(buildId: Int, groupKey: String, existingKey: String, newKey: String) { + fun logDuplicateSummary( + buildId: Int, + group: GroupEntry, + existingSummary: NotificationEntry, + newSummary: NotificationEntry + ) { buffer.log(TAG, WARNING, { long1 = buildId.toLong() - str1 = groupKey - str2 = existingKey - str3 = newKey + str1 = group.logKey + str2 = existingSummary.logKey + str3 = newSummary.logKey }, { """(Build $long1) Duplicate summary for group "$str1": "$str2" vs. "$str3"""" }) @@ -124,7 +131,7 @@ class ShadeListBuilderLogger @Inject constructor( fun logDuplicateTopLevelKey(buildId: Int, topLevelKey: String) { buffer.log(TAG, WARNING, { long1 = buildId.toLong() - str1 = topLevelKey + str1 = logKey(topLevelKey) }, { "(Build $long1) Duplicate top-level key: $str1" }) @@ -132,15 +139,15 @@ class ShadeListBuilderLogger @Inject constructor( fun logEntryAttachStateChanged( buildId: Int, - key: String, + entry: ListEntry, prevParent: GroupEntry?, newParent: GroupEntry? ) { buffer.log(TAG, INFO, { long1 = buildId.toLong() - str1 = key - str2 = prevParent?.key - str3 = newParent?.key + str1 = entry.logKey + str2 = prevParent?.logKey + str3 = newParent?.logKey }, { val action = if (str2 == null && str3 != null) { @@ -160,8 +167,8 @@ class ShadeListBuilderLogger @Inject constructor( fun logParentChanged(buildId: Int, prevParent: GroupEntry?, newParent: GroupEntry?) { buffer.log(TAG, INFO, { long1 = buildId.toLong() - str1 = prevParent?.key - str2 = newParent?.key + str1 = prevParent?.logKey + str2 = newParent?.logKey }, { if (str1 == null && str2 != null) { "(Build $long1) Parent is {$str2}" @@ -180,8 +187,8 @@ class ShadeListBuilderLogger @Inject constructor( ) { buffer.log(TAG, INFO, { long1 = buildId.toLong() - str1 = suppressedParent?.key - str2 = keepingParent?.key + str1 = suppressedParent?.logKey + str2 = keepingParent?.logKey }, { "(Build $long1) Change of parent to '$str1' suppressed; keeping parent '$str2'" }) @@ -193,7 +200,7 @@ class ShadeListBuilderLogger @Inject constructor( ) { buffer.log(TAG, INFO, { long1 = buildId.toLong() - str1 = keepingParent?.key + str1 = keepingParent?.logKey }, { "(Build $long1) Group pruning suppressed; keeping parent '$str1'" }) @@ -281,7 +288,7 @@ class ShadeListBuilderLogger @Inject constructor( val entry = entries[i] buffer.log(TAG, DEBUG, { int1 = i - str1 = entry.key + str1 = entry.logKey }, { "[$int1] $str1" }) @@ -289,7 +296,7 @@ class ShadeListBuilderLogger @Inject constructor( if (entry is GroupEntry) { entry.summary?.let { buffer.log(TAG, DEBUG, { - str1 = it.key + str1 = it.logKey }, { " [*] $str1 (summary)" }) @@ -298,7 +305,7 @@ class ShadeListBuilderLogger @Inject constructor( val child = entry.children[j] buffer.log(TAG, DEBUG, { int1 = j - str1 = child.key + str1 = child.logKey }, { " [$int1] $str1" }) @@ -308,7 +315,7 @@ class ShadeListBuilderLogger @Inject constructor( } fun logPipelineRunSuppressed() = - buffer.log(TAG, INFO, {}) { "Suppressing pipeline run during animation." } + buffer.log(TAG, INFO, {}) { "Suppressing pipeline run during animation." } } private const val TAG = "ShadeListBuilder"
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt index ac0b1ee6c442..ebcac6b6afb3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection.notifcollection import android.os.RemoteException import android.service.notification.NotificationListenerService import android.service.notification.NotificationListenerService.RankingMap +import android.service.notification.StatusBarNotification import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogLevel.DEBUG import com.android.systemui.log.LogLevel.ERROR @@ -65,9 +66,9 @@ fun cancellationReasonDebugString(@CancellationReason reason: Int) = class NotifCollectionLogger @Inject constructor( @NotificationLog private val buffer: LogBuffer ) { - fun logNotifPosted(key: String) { + fun logNotifPosted(entry: NotificationEntry) { buffer.log(TAG, INFO, { - str1 = key + str1 = entry.logKey }, { "POSTED $str1" }) @@ -75,49 +76,49 @@ class NotifCollectionLogger @Inject constructor( fun logNotifGroupPosted(groupKey: String, batchSize: Int) { buffer.log(TAG, INFO, { - str1 = groupKey + str1 = logKey(groupKey) int1 = batchSize }, { "POSTED GROUP $str1 ($int1 events)" }) } - fun logNotifUpdated(key: String) { + fun logNotifUpdated(entry: NotificationEntry) { buffer.log(TAG, INFO, { - str1 = key + str1 = entry.logKey }, { "UPDATED $str1" }) } - fun logNotifRemoved(key: String, @CancellationReason reason: Int) { + fun logNotifRemoved(sbn: StatusBarNotification, @CancellationReason reason: Int) { buffer.log(TAG, INFO, { - str1 = key + str1 = sbn.logKey int1 = reason }, { "REMOVED $str1 reason=${cancellationReasonDebugString(int1)}" }) } - fun logNotifReleased(key: String) { + fun logNotifReleased(entry: NotificationEntry) { buffer.log(TAG, INFO, { - str1 = key + str1 = entry.logKey }, { "RELEASED $str1" }) } - fun logNotifDismissed(key: String) { + fun logNotifDismissed(entry: NotificationEntry) { buffer.log(TAG, INFO, { - str1 = key + str1 = entry.logKey }, { "DISMISSED $str1" }) } - fun logNonExistentNotifDismissed(key: String) { + fun logNonExistentNotifDismissed(entry: NotificationEntry) { buffer.log(TAG, INFO, { - str1 = key + str1 = entry.logKey }, { "DISMISSED Non Existent $str1" }) @@ -125,7 +126,7 @@ class NotifCollectionLogger @Inject constructor( fun logChildDismissed(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { - str1 = entry.key + str1 = entry.logKey }, { "CHILD DISMISSED (inferred): $str1" }) @@ -141,31 +142,31 @@ class NotifCollectionLogger @Inject constructor( fun logDismissOnAlreadyCanceledEntry(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { - str1 = entry.key + str1 = entry.logKey }, { "Dismiss on $str1, which was already canceled. Trying to remove..." }) } - fun logNotifDismissedIntercepted(key: String) { + fun logNotifDismissedIntercepted(entry: NotificationEntry) { buffer.log(TAG, INFO, { - str1 = key + str1 = entry.logKey }, { "DISMISS INTERCEPTED $str1" }) } - fun logNotifClearAllDismissalIntercepted(key: String) { + fun logNotifClearAllDismissalIntercepted(entry: NotificationEntry) { buffer.log(TAG, INFO, { - str1 = key + str1 = entry.logKey }, { "CLEAR ALL DISMISSAL INTERCEPTED $str1" }) } - fun logNotifInternalUpdate(key: String, name: String, reason: String) { + fun logNotifInternalUpdate(entry: NotificationEntry, name: String, reason: String) { buffer.log(TAG, INFO, { - str1 = key + str1 = entry.logKey str2 = name str3 = reason }, { @@ -173,9 +174,9 @@ class NotifCollectionLogger @Inject constructor( }) } - fun logNotifInternalUpdateFailed(key: String, name: String, reason: String) { + fun logNotifInternalUpdateFailed(sbn: StatusBarNotification, name: String, reason: String) { buffer.log(TAG, INFO, { - str1 = key + str1 = sbn.logKey str2 = name str3 = reason }, { @@ -183,26 +184,49 @@ class NotifCollectionLogger @Inject constructor( }) } - fun logNoNotificationToRemoveWithKey(key: String, @CancellationReason reason: Int) { + fun logNoNotificationToRemoveWithKey( + sbn: StatusBarNotification, + @CancellationReason reason: Int + ) { buffer.log(TAG, ERROR, { - str1 = key + str1 = sbn.logKey int1 = reason }, { "No notification to remove with key $str1 reason=${cancellationReasonDebugString(int1)}" }) } - fun logRankingMissing(key: String, rankingMap: RankingMap) { - buffer.log(TAG, WARNING, { str1 = key }, { "Ranking update is missing ranking for $str1" }) - buffer.log(TAG, DEBUG, {}, { "Ranking map contents:" }) - for (entry in rankingMap.orderedKeys) { - buffer.log(TAG, DEBUG, { str1 = entry }, { " $str1" }) - } + fun logMissingRankings( + newlyInconsistentEntries: List<NotificationEntry>, + totalInconsistent: Int, + rankingMap: RankingMap + ) { + buffer.log(TAG, WARNING, { + int1 = totalInconsistent + int2 = newlyInconsistentEntries.size + str1 = newlyInconsistentEntries.joinToString { it.logKey ?: "null" } + }, { + "Ranking update is missing ranking for $int1 entries ($int2 new): $str1" + }) + buffer.log(TAG, DEBUG, { + str1 = rankingMap.orderedKeys.map { logKey(it) ?: "null" }.toString() + }, { + "Ranking map contents: $str1" + }) + } + + fun logRecoveredRankings(newlyConsistentKeys: List<String>) { + buffer.log(TAG, INFO, { + int1 = newlyConsistentKeys.size + str1 = newlyConsistentKeys.joinToString { logKey(it) ?: "null" } + }, { + "Ranking update now contains rankings for $int1 previously inconsistent entries: $str1" + }) } - fun logRemoteExceptionOnNotificationClear(key: String, e: RemoteException) { + fun logRemoteExceptionOnNotificationClear(entry: NotificationEntry, e: RemoteException) { buffer.log(TAG, WTF, { - str1 = key + str1 = entry.logKey str2 = e.toString() }, { "RemoteException while attempting to clear $str1:\n$str2" @@ -217,9 +241,9 @@ class NotifCollectionLogger @Inject constructor( }) } - fun logLifetimeExtended(key: String, extender: NotifLifetimeExtender) { + fun logLifetimeExtended(entry: NotificationEntry, extender: NotifLifetimeExtender) { buffer.log(TAG, INFO, { - str1 = key + str1 = entry.logKey str2 = extender.name }, { "LIFETIME EXTENDED: $str1 by $str2" @@ -227,12 +251,12 @@ class NotifCollectionLogger @Inject constructor( } fun logLifetimeExtensionEnded( - key: String, + entry: NotificationEntry, extender: NotifLifetimeExtender, totalExtenders: Int ) { buffer.log(TAG, INFO, { - str1 = key + str1 = entry.logKey str2 = extender.name int1 = totalExtenders }, { @@ -338,4 +362,29 @@ class NotifCollectionLogger @Inject constructor( } } +fun maybeLogInconsistentRankings( + logger: NotifCollectionLogger, + oldKeysWithoutRankings: Set<String>, + newEntriesWithoutRankings: Map<String, NotificationEntry>?, + rankingMap: RankingMap +) { + if (oldKeysWithoutRankings.isEmpty() && newEntriesWithoutRankings == null) return + val newlyConsistent: List<String> = oldKeysWithoutRankings + .mapNotNull { key -> + key.takeIf { key !in (newEntriesWithoutRankings ?: emptyMap()) } + .takeIf { key in rankingMap.orderedKeys } + }.sorted() + if (newlyConsistent.isNotEmpty()) { + logger.logRecoveredRankings(newlyConsistent) + } + val newlyInconsistent: List<NotificationEntry> = newEntriesWithoutRankings + ?.mapNotNull { (key, entry) -> + entry.takeIf { key !in oldKeysWithoutRankings } + }?.sortedBy { it.key } ?: emptyList() + if (newlyInconsistent.isNotEmpty()) { + val totalInconsistent: Int = newEntriesWithoutRankings?.size ?: 0 + logger.logMissingRankings(newlyInconsistent, totalInconsistent, rankingMap) + } +} + private const val TAG = "NotifCollection" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt index 2148d3bb336a..82c7aae08f6a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt @@ -17,8 +17,8 @@ package com.android.systemui.statusbar.notification.collection.provider import android.content.Context -import com.android.systemui.dagger.SysUISingleton import com.android.systemui.R +import com.android.systemui.dagger.SysUISingleton import javax.inject.Inject /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt index 50e7d1ce4ba0..7b9483022fd8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt @@ -43,4 +43,4 @@ class SectionStyleProvider @Inject constructor() { fun isMinimizedSection(section: NotifSection): Boolean { return lowPrioritySections.contains(section.sectioner) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt index 34d25cf9c3be..d234e54e6725 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt @@ -107,4 +107,4 @@ class NodeSpecBuilder( .apply { entry.children.forEach { children.add(buildNotifNode(this, it)) } } else -> throw RuntimeException("Unexpected entry: $entry") } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java index 19cf9dc135b1..5ef2b9e55d9e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java @@ -83,7 +83,7 @@ public class HeadsUpViewBinder { params.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp); params.requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP); CancellationSignal signal = mStage.requestRebind(entry, en -> { - mLogger.entryBoundSuccessfully(entry.getKey()); + mLogger.entryBoundSuccessfully(entry); en.getRow().setUsesIncreasedHeadsUpHeight(params.useIncreasedHeadsUpHeight()); // requestRebing promises that if we called cancel before this callback would be // invoked, then we will not enter this callback, and because we always cancel before @@ -94,7 +94,7 @@ public class HeadsUpViewBinder { } }); abortBindCallback(entry); - mLogger.startBindingHun(entry.getKey()); + mLogger.startBindingHun(entry); mOngoingBindCallbacks.put(entry, signal); } @@ -105,7 +105,7 @@ public class HeadsUpViewBinder { public void abortBindCallback(NotificationEntry entry) { CancellationSignal ongoingBindCallback = mOngoingBindCallbacks.remove(entry); if (ongoingBindCallback != null) { - mLogger.currentOngoingBindingAborted(entry.getKey()); + mLogger.currentOngoingBindingAborted(entry); ongoingBindCallback.cancel(); } } @@ -116,7 +116,7 @@ public class HeadsUpViewBinder { public void unbindHeadsUpView(NotificationEntry entry) { abortBindCallback(entry); mStage.getStageParams(entry).markContentViewsFreeable(FLAG_CONTENT_VIEW_HEADS_UP); - mLogger.entryContentViewMarkedFreeable(entry.getKey()); - mStage.requestRebind(entry, e -> mLogger.entryUnbound(e.getKey())); + mLogger.entryContentViewMarkedFreeable(entry); + mStage.requestRebind(entry, e -> mLogger.entryUnbound(e)); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt index 50a6207efe0b..d1feaa05c653 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt @@ -3,44 +3,46 @@ package com.android.systemui.statusbar.notification.interruption import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogLevel.INFO import com.android.systemui.log.dagger.NotificationHeadsUpLog +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.logKey import javax.inject.Inject class HeadsUpViewBinderLogger @Inject constructor(@NotificationHeadsUpLog val buffer: LogBuffer) { - fun startBindingHun(key: String) { + fun startBindingHun(entry: NotificationEntry) { buffer.log(TAG, INFO, { - str1 = key + str1 = entry.logKey }, { "start binding heads up entry $str1 " }) } - fun currentOngoingBindingAborted(key: String) { + fun currentOngoingBindingAborted(entry: NotificationEntry) { buffer.log(TAG, INFO, { - str1 = key + str1 = entry.logKey }, { "aborted potential ongoing heads up entry binding $str1 " }) } - fun entryBoundSuccessfully(key: String) { + fun entryBoundSuccessfully(entry: NotificationEntry) { buffer.log(TAG, INFO, { - str1 = key + str1 = entry.logKey }, { "heads up entry bound successfully $str1 " }) } - fun entryUnbound(key: String) { + fun entryUnbound(entry: NotificationEntry) { buffer.log(TAG, INFO, { - str1 = key + str1 = entry.logKey }, { "heads up entry unbound successfully $str1 " }) } - fun entryContentViewMarkedFreeable(key: String) { + fun entryContentViewMarkedFreeable(entry: NotificationEntry) { buffer.log(TAG, INFO, { - str1 = key + str1 = entry.logKey }, { "start unbinding heads up entry $str1 " }) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt index 1d18ca3dfade..016b388ff60a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt @@ -16,11 +16,12 @@ package com.android.systemui.statusbar.notification.interruption -import android.service.notification.StatusBarNotification import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogLevel.DEBUG import com.android.systemui.log.LogLevel.INFO import com.android.systemui.log.dagger.NotificationInterruptLog +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.logKey import javax.inject.Inject class NotificationInterruptLogger @Inject constructor( @@ -41,17 +42,17 @@ class NotificationInterruptLogger @Inject constructor( }) } - fun logNoBubbleNotAllowed(sbn: StatusBarNotification) { + fun logNoBubbleNotAllowed(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { - str1 = sbn.key + str1 = entry.logKey }, { "No bubble up: not allowed to bubble: $str1" }) } - fun logNoBubbleNoMetadata(sbn: StatusBarNotification) { + fun logNoBubbleNoMetadata(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { - str1 = sbn.key + str1 = entry.logKey }, { "No bubble up: notification: $str1 doesn't have valid metadata" }) @@ -64,89 +65,89 @@ class NotificationInterruptLogger @Inject constructor( }) } - fun logNoHeadsUpPackageSnoozed(sbn: StatusBarNotification) { + fun logNoHeadsUpPackageSnoozed(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { - str1 = sbn.key + str1 = entry.logKey }, { "No alerting: snoozed package: $str1" }) } - fun logNoHeadsUpAlreadyBubbled(sbn: StatusBarNotification) { + fun logNoHeadsUpAlreadyBubbled(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { - str1 = sbn.key + str1 = entry.logKey }, { "No heads up: in unlocked shade where notification is shown as a bubble: $str1" }) } - fun logNoHeadsUpSuppressedByDnd(sbn: StatusBarNotification) { + fun logNoHeadsUpSuppressedByDnd(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { - str1 = sbn.key + str1 = entry.logKey }, { "No heads up: suppressed by DND: $str1" }) } - fun logNoHeadsUpNotImportant(sbn: StatusBarNotification) { + fun logNoHeadsUpNotImportant(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { - str1 = sbn.key + str1 = entry.logKey }, { "No heads up: unimportant notification: $str1" }) } - fun logNoHeadsUpNotInUse(sbn: StatusBarNotification) { + fun logNoHeadsUpNotInUse(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { - str1 = sbn.key + str1 = entry.logKey }, { "No heads up: not in use: $str1" }) } fun logNoHeadsUpSuppressedBy( - sbn: StatusBarNotification, + entry: NotificationEntry, suppressor: NotificationInterruptSuppressor ) { buffer.log(TAG, DEBUG, { - str1 = sbn.key + str1 = entry.logKey str2 = suppressor.name }, { "No heads up: aborted by suppressor: $str2 sbnKey=$str1" }) } - fun logHeadsUp(sbn: StatusBarNotification) { + fun logHeadsUp(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { - str1 = sbn.key + str1 = entry.logKey }, { "Heads up: $str1" }) } - fun logNoAlertingFilteredOut(sbn: StatusBarNotification) { + fun logNoAlertingFilteredOut(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { - str1 = sbn.key + str1 = entry.logKey }, { "No alerting: filtered notification: $str1" }) } - fun logNoAlertingGroupAlertBehavior(sbn: StatusBarNotification) { + fun logNoAlertingGroupAlertBehavior(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { - str1 = sbn.key + str1 = entry.logKey }, { "No alerting: suppressed due to group alert behavior: $str1" }) } fun logNoAlertingSuppressedBy( - sbn: StatusBarNotification, + entry: NotificationEntry, suppressor: NotificationInterruptSuppressor, awake: Boolean ) { buffer.log(TAG, DEBUG, { - str1 = sbn.key + str1 = entry.logKey str2 = suppressor.name bool1 = awake }, { @@ -154,65 +155,65 @@ class NotificationInterruptLogger @Inject constructor( }) } - fun logNoAlertingRecentFullscreen(sbn: StatusBarNotification) { + fun logNoAlertingRecentFullscreen(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { - str1 = sbn.key + str1 = entry.logKey }, { "No alerting: recent fullscreen: $str1" }) } - fun logNoPulsingSettingDisabled(sbn: StatusBarNotification) { + fun logNoPulsingSettingDisabled(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { - str1 = sbn.key + str1 = entry.logKey }, { "No pulsing: disabled by setting: $str1" }) } - fun logNoPulsingBatteryDisabled(sbn: StatusBarNotification) { + fun logNoPulsingBatteryDisabled(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { - str1 = sbn.key + str1 = entry.logKey }, { "No pulsing: disabled by battery saver: $str1" }) } - fun logNoPulsingNoAlert(sbn: StatusBarNotification) { + fun logNoPulsingNoAlert(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { - str1 = sbn.key + str1 = entry.logKey }, { "No pulsing: notification shouldn't alert: $str1" }) } - fun logNoPulsingNoAmbientEffect(sbn: StatusBarNotification) { + fun logNoPulsingNoAmbientEffect(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { - str1 = sbn.key + str1 = entry.logKey }, { "No pulsing: ambient effect suppressed: $str1" }) } - fun logNoPulsingNotImportant(sbn: StatusBarNotification) { + fun logNoPulsingNotImportant(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { - str1 = sbn.key + str1 = entry.logKey }, { "No pulsing: not important enough: $str1" }) } - fun logPulsing(sbn: StatusBarNotification) { + fun logPulsing(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { - str1 = sbn.key + str1 = entry.logKey }, { "Pulsing: $str1" }) } - fun keyguardHideNotification(key: String) { + fun keyguardHideNotification(entry: NotificationEntry) { buffer.log(TAG, DEBUG, { - str1 = key + str1 = entry.logKey }, { "Keyguard Hide Notification: $str1" }) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java index a063dbd99626..8378b69bee9a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java @@ -147,14 +147,14 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter } if (!entry.canBubble()) { - mLogger.logNoBubbleNotAllowed(sbn); + mLogger.logNoBubbleNotAllowed(entry); return false; } if (entry.getBubbleMetadata() == null || (entry.getBubbleMetadata().getShortcutId() == null && entry.getBubbleMetadata().getIntent() == null)) { - mLogger.logNoBubbleNoMetadata(sbn); + mLogger.logNoBubbleNoMetadata(entry); return false; } @@ -203,23 +203,23 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter } if (isSnoozedPackage(sbn)) { - mLogger.logNoHeadsUpPackageSnoozed(sbn); + mLogger.logNoHeadsUpPackageSnoozed(entry); return false; } boolean inShade = mStatusBarStateController.getState() == SHADE; if (entry.isBubble() && inShade) { - mLogger.logNoHeadsUpAlreadyBubbled(sbn); + mLogger.logNoHeadsUpAlreadyBubbled(entry); return false; } if (entry.shouldSuppressPeek()) { - mLogger.logNoHeadsUpSuppressedByDnd(sbn); + mLogger.logNoHeadsUpSuppressedByDnd(entry); return false; } if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) { - mLogger.logNoHeadsUpNotImportant(sbn); + mLogger.logNoHeadsUpNotImportant(entry); return false; } @@ -232,17 +232,17 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter boolean inUse = mPowerManager.isScreenOn() && !isDreaming; if (!inUse) { - mLogger.logNoHeadsUpNotInUse(sbn); + mLogger.logNoHeadsUpNotInUse(entry); return false; } for (int i = 0; i < mSuppressors.size(); i++) { if (mSuppressors.get(i).suppressAwakeHeadsUp(entry)) { - mLogger.logNoHeadsUpSuppressedBy(sbn, mSuppressors.get(i)); + mLogger.logNoHeadsUpSuppressedBy(entry, mSuppressors.get(i)); return false; } } - mLogger.logHeadsUp(sbn); + mLogger.logHeadsUp(entry); return true; } @@ -254,38 +254,36 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter * @return true if the entry should ambient pulse, false otherwise */ private boolean shouldHeadsUpWhenDozing(NotificationEntry entry) { - StatusBarNotification sbn = entry.getSbn(); - if (!mAmbientDisplayConfiguration.pulseOnNotificationEnabled(UserHandle.USER_CURRENT)) { - mLogger.logNoPulsingSettingDisabled(sbn); + mLogger.logNoPulsingSettingDisabled(entry); return false; } if (mBatteryController.isAodPowerSave()) { - mLogger.logNoPulsingBatteryDisabled(sbn); + mLogger.logNoPulsingBatteryDisabled(entry); return false; } if (!canAlertCommon(entry)) { - mLogger.logNoPulsingNoAlert(sbn); + mLogger.logNoPulsingNoAlert(entry); return false; } if (!canAlertHeadsUpCommon(entry)) { - mLogger.logNoPulsingNoAlert(sbn); + mLogger.logNoPulsingNoAlert(entry); return false; } if (entry.shouldSuppressAmbient()) { - mLogger.logNoPulsingNoAmbientEffect(sbn); + mLogger.logNoPulsingNoAmbientEffect(entry); return false; } if (entry.getImportance() < NotificationManager.IMPORTANCE_DEFAULT) { - mLogger.logNoPulsingNotImportant(sbn); + mLogger.logNoPulsingNotImportant(entry); return false; } - mLogger.logPulsing(sbn); + mLogger.logPulsing(entry); return true; } @@ -296,22 +294,20 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter * @return true if these checks pass, false if the notification should not alert */ private boolean canAlertCommon(NotificationEntry entry) { - StatusBarNotification sbn = entry.getSbn(); - if (!mFlags.isNewPipelineEnabled() && mNotificationFilter.shouldFilterOut(entry)) { - mLogger.logNoAlertingFilteredOut(sbn); + mLogger.logNoAlertingFilteredOut(entry); return false; } for (int i = 0; i < mSuppressors.size(); i++) { if (mSuppressors.get(i).suppressInterruptions(entry)) { - mLogger.logNoAlertingSuppressedBy(sbn, mSuppressors.get(i), /* awake */ false); + mLogger.logNoAlertingSuppressedBy(entry, mSuppressors.get(i), /* awake */ false); return false; } } if (mKeyguardNotificationVisibilityProvider.shouldHideNotification(entry)) { - mLogger.keyguardHideNotification(entry.getKey()); + mLogger.keyguardHideNotification(entry); return false; } @@ -329,12 +325,12 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter // Don't alert notifications that are suppressed due to group alert behavior if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) { - mLogger.logNoAlertingGroupAlertBehavior(sbn); + mLogger.logNoAlertingGroupAlertBehavior(entry); return false; } if (entry.hasJustLaunchedFullScreenIntent()) { - mLogger.logNoAlertingRecentFullscreen(sbn); + mLogger.logNoAlertingRecentFullscreen(entry); return false; } @@ -352,7 +348,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter for (int i = 0; i < mSuppressors.size(); i++) { if (mSuppressors.get(i).suppressAwakeInterruptions(entry)) { - mLogger.logNoAlertingSuppressedBy(sbn, mSuppressors.get(i), /* awake */ true); + mLogger.logNoAlertingSuppressedBy(entry, mSuppressors.get(i), /* awake */ true); return false; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java index 599039d46556..a493a676e3d8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.row; import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME; import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; +import static com.android.systemui.statusbar.notification.NotificationUtils.logKey; import android.util.Log; import android.view.View; @@ -247,7 +248,7 @@ public class ExpandableNotificationRowController implements NotifViewController @Override @NonNull public String getNodeLabel() { - return mView.getEntry().getKey(); + return logKey(mView.getEntry()); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java index c66140822d92..99a24cb3e9ca 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java @@ -67,6 +67,7 @@ public class HybridConversationNotificationView extends HybridNotificationView { mConversationIconView = requireViewById(com.android.internal.R.id.conversation_icon); mConversationFacePile = requireViewById(com.android.internal.R.id.conversation_face_pile); mConversationSenderName = requireViewById(R.id.conversation_notification_sender); + applyTextColor(mConversationSenderName, mSecondaryTextColor); mFacePileSize = getResources() .getDimensionPixelSize(R.dimen.conversation_single_line_face_pile_size); mFacePileAvatarSize = getResources() @@ -75,6 +76,9 @@ public class HybridConversationNotificationView extends HybridNotificationView { .getDimensionPixelSize(R.dimen.conversation_single_line_avatar_size); mFacePileProtectionWidth = getResources().getDimensionPixelSize( R.dimen.conversation_single_line_face_pile_protection_width); + mTransformationHelper.setCustomTransformation( + new FadeOutAndDownWithTitleTransformation(mConversationSenderName), + mConversationSenderName.getId()); mTransformationHelper.addViewTransformingToSimilar(mConversationIconView); mTransformationHelper.addTransformedView(mConversationSenderName); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java index 40a44ffd7fe3..77fd05186090 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java @@ -24,7 +24,6 @@ import android.content.Context; import android.content.res.Resources; import android.service.notification.StatusBarNotification; import android.util.TypedValue; -import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -57,10 +56,8 @@ public class HybridGroupManager { mOverflowNumberPadding = res.getDimensionPixelSize(R.dimen.group_overflow_number_padding); } - private HybridNotificationView inflateHybridViewWithStyle(int style, - View contentView, ViewGroup parent) { - LayoutInflater inflater = new ContextThemeWrapper(mContext, style) - .getSystemService(LayoutInflater.class); + private HybridNotificationView inflateHybridView(View contentView, ViewGroup parent) { + LayoutInflater inflater = LayoutInflater.from(mContext); int layout = contentView instanceof ConversationLayout ? R.layout.hybrid_conversation_notification : R.layout.hybrid_notification; @@ -93,16 +90,8 @@ public class HybridGroupManager { public HybridNotificationView bindFromNotification(HybridNotificationView reusableView, View contentView, StatusBarNotification notification, ViewGroup parent) { - return bindFromNotificationWithStyle(reusableView, contentView, notification, - R.style.HybridNotification, parent); - } - - private HybridNotificationView bindFromNotificationWithStyle( - HybridNotificationView reusableView, View contentView, - StatusBarNotification notification, - int style, ViewGroup parent) { if (reusableView == null) { - reusableView = inflateHybridViewWithStyle(style, contentView, parent); + reusableView = inflateHybridView(contentView, parent); } CharSequence titleText = resolveTitle(notification.getNotification()); CharSequence contentText = resolveText(notification.getNotification()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java index c0d85a6a16ef..fc9d9e8b736c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java @@ -16,13 +16,18 @@ package com.android.systemui.statusbar.notification.row; +import static android.app.Notification.COLOR_INVALID; + import android.annotation.Nullable; import android.content.Context; +import android.content.res.TypedArray; import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; import android.widget.TextView; +import androidx.annotation.ColorInt; + import com.android.keyguard.AlphaOptimizedLinearLayout; import com.android.systemui.R; import com.android.systemui.statusbar.CrossFadeHelper; @@ -40,6 +45,8 @@ public class HybridNotificationView extends AlphaOptimizedLinearLayout protected final ViewTransformationHelper mTransformationHelper = new ViewTransformationHelper(); protected TextView mTitleView; protected TextView mTextView; + protected int mPrimaryTextColor = COLOR_INVALID; + protected int mSecondaryTextColor = COLOR_INVALID; public HybridNotificationView(Context context) { this(context, null); @@ -69,42 +76,37 @@ public class HybridNotificationView extends AlphaOptimizedLinearLayout @Override protected void onFinishInflate() { super.onFinishInflate(); + resolveThemeTextColors(); mTitleView = findViewById(R.id.notification_title); mTextView = findViewById(R.id.notification_text); + applyTextColor(mTitleView, mPrimaryTextColor); + applyTextColor(mTextView, mSecondaryTextColor); mTransformationHelper.setCustomTransformation( - new ViewTransformationHelper.CustomTransformation() { - @Override - public boolean transformTo(TransformState ownState, TransformableView notification, - float transformationAmount) { - // We want to transform to the same y location as the title - TransformState otherState = notification.getCurrentState( - TRANSFORMING_VIEW_TITLE); - CrossFadeHelper.fadeOut(mTextView, transformationAmount); - if (otherState != null) { - ownState.transformViewVerticalTo(otherState, transformationAmount); - otherState.recycle(); - } - return true; - } - - @Override - public boolean transformFrom(TransformState ownState, - TransformableView notification, float transformationAmount) { - // We want to transform from the same y location as the title - TransformState otherState = notification.getCurrentState( - TRANSFORMING_VIEW_TITLE); - CrossFadeHelper.fadeIn(mTextView, transformationAmount, true /* remap */); - if (otherState != null) { - ownState.transformViewVerticalFrom(otherState, transformationAmount); - otherState.recycle(); - } - return true; - } - }, TRANSFORMING_VIEW_TEXT); + new FadeOutAndDownWithTitleTransformation(mTextView), + TRANSFORMING_VIEW_TEXT); mTransformationHelper.addTransformedView(TRANSFORMING_VIEW_TITLE, mTitleView); mTransformationHelper.addTransformedView(TRANSFORMING_VIEW_TEXT, mTextView); } + protected void applyTextColor(TextView textView, @ColorInt int textColor) { + if (textColor != COLOR_INVALID) { + textView.setTextColor(textColor); + } + } + + private void resolveThemeTextColors() { + try (TypedArray ta = mContext.getTheme().obtainStyledAttributes( + android.R.style.Theme_DeviceDefault_DayNight, new int[]{ + android.R.attr.textColorPrimary, + android.R.attr.textColorSecondary + })) { + if (ta != null) { + mPrimaryTextColor = ta.getColor(0, mPrimaryTextColor); + mSecondaryTextColor = ta.getColor(1, mSecondaryTextColor); + } + } + } + public void bind(@Nullable CharSequence title, @Nullable CharSequence text, @Nullable View contentView) { mTitleView.setText(title); @@ -152,4 +154,40 @@ public class HybridNotificationView extends AlphaOptimizedLinearLayout @Override public void setNotificationFaded(boolean faded) {} + + protected static class FadeOutAndDownWithTitleTransformation extends + ViewTransformationHelper.CustomTransformation { + + private final View mView; + + public FadeOutAndDownWithTitleTransformation(View view) { + mView = view; + } + + @Override + public boolean transformTo(TransformState ownState, TransformableView notification, + float transformationAmount) { + // We want to transform to the same y location as the title + TransformState otherState = notification.getCurrentState(TRANSFORMING_VIEW_TITLE); + CrossFadeHelper.fadeOut(mView, transformationAmount); + if (otherState != null) { + ownState.transformViewVerticalTo(otherState, transformationAmount); + otherState.recycle(); + } + return true; + } + + @Override + public boolean transformFrom(TransformState ownState, + TransformableView notification, float transformationAmount) { + // We want to transform from the same y location as the title + TransformState otherState = notification.getCurrentState(TRANSFORMING_VIEW_TITLE); + CrossFadeHelper.fadeIn(mView, transformationAmount, true /* remap */); + if (otherState != null) { + ownState.transformViewVerticalFrom(otherState, transformationAmount); + otherState.recycle(); + } + return true; + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java index f693ebbb7830..ea564ddb9193 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java @@ -112,7 +112,8 @@ public final class NotifBindPipeline { public void manageRow( @NonNull NotificationEntry entry, @NonNull ExpandableNotificationRow row) { - mLogger.logManagedRow(entry.getKey()); + mLogger.logManagedRow(entry); + mLogger.logManagedRow(entry); final BindEntry bindEntry = getBindEntry(entry); if (bindEntry == null) { @@ -154,12 +155,12 @@ public final class NotifBindPipeline { * the real work once rather than repeatedly start and cancel it. */ private void requestPipelineRun(NotificationEntry entry) { - mLogger.logRequestPipelineRun(entry.getKey()); + mLogger.logRequestPipelineRun(entry); final BindEntry bindEntry = getBindEntry(entry); if (bindEntry.row == null) { // Row is not managed yet but may be soon. Stop for now. - mLogger.logRequestPipelineRowNotSet(entry.getKey()); + mLogger.logRequestPipelineRowNotSet(entry); return; } @@ -177,7 +178,7 @@ public final class NotifBindPipeline { * callbacks when the run finishes. If a run is already in progress, it is restarted. */ private void startPipeline(NotificationEntry entry) { - mLogger.logStartPipeline(entry.getKey()); + mLogger.logStartPipeline(entry); if (mStage == null) { throw new IllegalStateException("No stage was ever set on the pipeline"); @@ -193,7 +194,7 @@ public final class NotifBindPipeline { final BindEntry bindEntry = getBindEntry(entry); final Set<BindCallback> callbacks = bindEntry.callbacks; - mLogger.logFinishedPipeline(entry.getKey(), callbacks.size()); + mLogger.logFinishedPipeline(entry, callbacks.size()); bindEntry.invalidated = false; // Move all callbacks to separate list as callbacks may themselves add/remove callbacks. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt index ec406f0524ff..ab91926d466a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt @@ -19,6 +19,8 @@ package com.android.systemui.statusbar.notification.row import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogLevel.INFO import com.android.systemui.log.dagger.NotificationLog +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.logKey import javax.inject.Inject class NotifBindPipelineLogger @Inject constructor( @@ -32,41 +34,41 @@ class NotifBindPipelineLogger @Inject constructor( }) } - fun logManagedRow(notifKey: String) { + fun logManagedRow(entry: NotificationEntry) { buffer.log(TAG, INFO, { - str1 = notifKey + str1 = entry.logKey }, { "Row set for notif: $str1" }) } - fun logRequestPipelineRun(notifKey: String) { + fun logRequestPipelineRun(entry: NotificationEntry) { buffer.log(TAG, INFO, { - str1 = notifKey + str1 = entry.logKey }, { "Request pipeline run for notif: $str1" }) } - fun logRequestPipelineRowNotSet(notifKey: String) { + fun logRequestPipelineRowNotSet(entry: NotificationEntry) { buffer.log(TAG, INFO, { - str1 = notifKey + str1 = entry.logKey }, { "Row is not set so pipeline will not run. notif = $str1" }) } - fun logStartPipeline(notifKey: String) { + fun logStartPipeline(entry: NotificationEntry) { buffer.log(TAG, INFO, { - str1 = notifKey + str1 = entry.logKey }, { "Start pipeline for notif: $str1" }) } - fun logFinishedPipeline(notifKey: String, numCallbacks: Int) { + fun logFinishedPipeline(entry: NotificationEntry, numCallbacks: Int) { buffer.log(TAG, INFO, { - str1 = notifKey + str1 = entry.logKey int1 = numCallbacks }, { "Finished pipeline for notif $str1 with $int1 callbacks" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java index 3616f8faee1e..81cf14646465 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java @@ -57,7 +57,7 @@ public class RowContentBindStage extends BindStage<RowContentBindParams> { @NonNull StageCallback callback) { RowContentBindParams params = getStageParams(entry); - mLogger.logStageParams(entry.getKey(), params.toString()); + mLogger.logStageParams(entry, params); // Resolve content to bind/unbind. @InflationFlag int inflationFlags = params.getContentViews(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt index 29cce3375c8a..f9923b2254d7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt @@ -19,17 +19,19 @@ package com.android.systemui.statusbar.notification.row import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogLevel.INFO import com.android.systemui.log.dagger.NotificationLog +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.logKey import javax.inject.Inject class RowContentBindStageLogger @Inject constructor( @NotificationLog private val buffer: LogBuffer ) { - fun logStageParams(notifKey: String, stageParams: String) { + fun logStageParams(entry: NotificationEntry, stageParams: RowContentBindParams) { buffer.log(TAG, INFO, { - str1 = notifKey - str2 = stageParams + str1 = entry.logKey + str2 = stageParams.toString() }, { - "Invalidated notif $str1 with params: \n$str2" + "Invalidated notif $str1 with params: $str2" }) } } 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 213f00b8e03d..2fd02d9f1cd9 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 @@ -748,19 +748,20 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - private void logHunSkippedForUnexpectedState(String key, boolean expected, boolean actual) { + private void logHunSkippedForUnexpectedState(ExpandableNotificationRow enr, + boolean expected, boolean actual) { if (mLogger == null) return; - mLogger.hunSkippedForUnexpectedState(key, expected, actual); + mLogger.hunSkippedForUnexpectedState(enr.getEntry(), expected, actual); } - private void logHunAnimationSkipped(String key, String reason) { + private void logHunAnimationSkipped(ExpandableNotificationRow enr, String reason) { if (mLogger == null) return; - mLogger.hunAnimationSkipped(key, reason); + mLogger.hunAnimationSkipped(enr.getEntry(), reason); } - private void logHunAnimationEventAdded(String key, int type) { + private void logHunAnimationEventAdded(ExpandableNotificationRow enr, int type) { if (mLogger == null) return; - mLogger.hunAnimationEventAdded(key, type); + mLogger.hunAnimationEventAdded(enr.getEntry(), type); } private void onDrawDebug(Canvas canvas) { @@ -3174,7 +3175,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (isHeadsUp != row.isHeadsUp()) { // For cases where we have a heads up showing and appearing again we shouldn't // do the animations at all. - logHunSkippedForUnexpectedState(key, isHeadsUp, row.isHeadsUp()); + logHunSkippedForUnexpectedState(row, isHeadsUp, row.isHeadsUp()); continue; } int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER; @@ -3192,7 +3193,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (row.isChildInGroup()) { // We can otherwise get stuck in there if it was just isolated row.setHeadsUpAnimatingAway(false); - logHunAnimationSkipped(key, "row is child in group"); + logHunAnimationSkipped(row, "row is child in group"); continue; } } else { @@ -3200,7 +3201,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (viewState == null) { // A view state was never generated for this view, so we don't need to animate // this. This may happen with notification children. - logHunAnimationSkipped(key, "row has no viewState"); + logHunAnimationSkipped(row, "row has no viewState"); continue; } if (isHeadsUp && (mAddedHeadsUpChildren.contains(row) || pinnedAndClosed)) { @@ -3224,7 +3225,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable + " onBottom=" + onBottom + " row=" + row.getEntry().getKey()); } - logHunAnimationEventAdded(key, type); + logHunAnimationEventAdded(row, type); } mHeadsUpChangeAnimations.clear(); mAddedHeadsUpChildren.clear(); @@ -4360,8 +4361,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable /** * Update colors of "dismiss" and "empty shade" views. - * - * @param lightTheme True if light theme should be used. */ @ShadeViewRefactor(RefactorComponent.DECORATOR) void updateDecorViews() { @@ -4777,8 +4776,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (SPEW) { Log.v(TAG, "generateHeadsUpAnimation: previous hun appear animation cancelled"); } - logHunAnimationSkipped(row.getEntry().getKey(), - "previous hun appear animation cancelled"); + logHunAnimationSkipped(row, "previous hun appear animation cancelled"); return; } mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp)); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt index 04bf62104f66..5f79c0e3913a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt @@ -3,21 +3,27 @@ package com.android.systemui.statusbar.notification.stack import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogLevel.INFO import com.android.systemui.log.dagger.NotificationHeadsUpLog -import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.* +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.logKey +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER import javax.inject.Inject class NotificationStackScrollLogger @Inject constructor( @NotificationHeadsUpLog private val buffer: LogBuffer ) { - fun hunAnimationSkipped(key: String, reason: String) { + fun hunAnimationSkipped(entry: NotificationEntry, reason: String) { buffer.log(TAG, INFO, { - str1 = key + str1 = entry.logKey str2 = reason }, { "heads up animation skipped: key: $str1 reason: $str2" }) } - fun hunAnimationEventAdded(key: String, type: Int) { + fun hunAnimationEventAdded(entry: NotificationEntry, type: Int) { val reason: String reason = if (type == ANIMATION_TYPE_HEADS_UP_DISAPPEAR) { "HEADS_UP_DISAPPEAR" @@ -33,16 +39,16 @@ class NotificationStackScrollLogger @Inject constructor( type.toString() } buffer.log(TAG, INFO, { - str1 = key + str1 = entry.logKey str2 = reason }, { "heads up animation added: $str1 with type $str2" }) } - fun hunSkippedForUnexpectedState(key: String, expected: Boolean, actual: Boolean) { + fun hunSkippedForUnexpectedState(entry: NotificationEntry, expected: Boolean, actual: Boolean) { buffer.log(TAG, INFO, { - str1 = key + str1 = entry.logKey bool1 = expected bool2 = actual }, { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt index 77377af9ddfb..cb4a0884fea4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt @@ -3,6 +3,7 @@ package com.android.systemui.statusbar.notification.stack import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogLevel import com.android.systemui.log.dagger.NotificationHeadsUpLog +import com.android.systemui.statusbar.notification.logKey import javax.inject.Inject class StackStateLogger @Inject constructor( @@ -10,7 +11,7 @@ class StackStateLogger @Inject constructor( ) { fun logHUNViewDisappearing(key: String) { buffer.log(TAG, LogLevel.INFO, { - str1 = key + str1 = logKey(key) }, { "Heads up view disappearing $str1 " }) @@ -18,7 +19,7 @@ class StackStateLogger @Inject constructor( fun logHUNViewAppearing(key: String) { buffer.log(TAG, LogLevel.INFO, { - str1 = key + str1 = logKey(key) }, { "Heads up notification view appearing $str1 " }) @@ -26,7 +27,7 @@ class StackStateLogger @Inject constructor( fun logHUNViewDisappearingWithRemoveEvent(key: String) { buffer.log(TAG, LogLevel.ERROR, { - str1 = key + str1 = logKey(key) }, { "Heads up view disappearing $str1 for ANIMATION_TYPE_REMOVE" }) @@ -34,7 +35,7 @@ class StackStateLogger @Inject constructor( fun logHUNViewAppearingWithAddEvent(key: String) { buffer.log(TAG, LogLevel.ERROR, { - str1 = key + str1 = logKey(key) }, { "Heads up view disappearing $str1 for ANIMATION_TYPE_ADD" }) @@ -42,7 +43,7 @@ class StackStateLogger @Inject constructor( fun disappearAnimationEnded(key: String) { buffer.log(TAG, LogLevel.INFO, { - str1 = key + str1 = logKey(key) }, { "Heads up notification disappear animation ended $str1 " }) @@ -50,7 +51,7 @@ class StackStateLogger @Inject constructor( fun appearAnimationEnded(key: String) { buffer.log(TAG, LogLevel.INFO, { - str1 = key + str1 = logKey(key) }, { "Heads up notification appear animation ended $str1 " }) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index 414bc84fcd85..b43e9df79241 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -28,17 +28,13 @@ import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_RIGHT_UNL import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE; import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE; -import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.ActivityTaskManager; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.ServiceConnection; -import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.ColorStateList; @@ -46,13 +42,8 @@ import android.content.res.Configuration; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.Bundle; -import android.os.IBinder; -import android.os.Message; -import android.os.Messenger; import android.os.RemoteException; import android.os.UserHandle; -import android.provider.MediaStore; -import android.service.media.CameraPrewarmService; import android.service.quickaccesswallet.GetWalletCardsError; import android.service.quickaccesswallet.GetWalletCardsResponse; import android.service.quickaccesswallet.QuickAccessWalletClient; @@ -172,20 +163,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private KeyguardAffordanceHelper mAffordanceHelper; private FalsingManager mFalsingManager; private boolean mUserSetupComplete; - private boolean mPrewarmBound; - private Messenger mPrewarmMessenger; - private final ServiceConnection mPrewarmConnection = new ServiceConnection() { - - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - mPrewarmMessenger = new Messenger(service); - } - - @Override - public void onServiceDisconnected(ComponentName name) { - mPrewarmMessenger = null; - } - }; private boolean mLeftIsVoiceAssist; private Drawable mLeftAssistIcon; @@ -602,46 +579,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } } - public void bindCameraPrewarmService() { - Intent intent = getCameraIntent(); - ActivityInfo targetInfo = mActivityIntentHelper.getTargetActivityInfo(intent, - KeyguardUpdateMonitor.getCurrentUser(), true /* onlyDirectBootAware */); - if (targetInfo != null && targetInfo.metaData != null) { - String clazz = targetInfo.metaData.getString( - MediaStore.META_DATA_STILL_IMAGE_CAMERA_PREWARM_SERVICE); - if (clazz != null) { - Intent serviceIntent = new Intent(); - serviceIntent.setClassName(targetInfo.packageName, clazz); - serviceIntent.setAction(CameraPrewarmService.ACTION_PREWARM); - try { - if (getContext().bindServiceAsUser(serviceIntent, mPrewarmConnection, - Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, - new UserHandle(UserHandle.USER_CURRENT))) { - mPrewarmBound = true; - } - } catch (SecurityException e) { - Log.w(TAG, "Unable to bind to prewarm service package=" + targetInfo.packageName - + " class=" + clazz, e); - } - } - } - } - - public void unbindCameraPrewarmService(boolean launched) { - if (mPrewarmBound) { - if (mPrewarmMessenger != null && launched) { - try { - mPrewarmMessenger.send(Message.obtain(null /* handler */, - CameraPrewarmService.MSG_CAMERA_FIRED)); - } catch (RemoteException e) { - Log.w(TAG, "Error sending camera fired message", e); - } - } - mContext.unbindService(mPrewarmConnection); - mPrewarmBound = false; - } - } - public void launchCamera(String source) { final Intent intent = getCameraIntent(); intent.putExtra(EXTRA_CAMERA_LAUNCH_SOURCE, source); @@ -651,8 +588,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL AsyncTask.execute(new Runnable() { @Override public void run() { - int result = ActivityManager.START_CANCELED; - // Normally an activity will set it's requested rotation // animation on its window. However when launching an activity // causes the orientation to change this is too late. In these cases @@ -666,7 +601,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL o.setRotationAnimationHint( WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS); try { - result = ActivityTaskManager.getService().startActivityAsUser( + ActivityTaskManager.getService().startActivityAsUser( null, getContext().getBasePackageName(), getContext().getAttributionTag(), intent, intent.resolveTypeIfNeeded(getContext().getContentResolver()), @@ -675,25 +610,12 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } catch (RemoteException e) { Log.w(TAG, "Unable to start camera activity", e); } - final boolean launched = isSuccessfulLaunch(result); - post(new Runnable() { - @Override - public void run() { - unbindCameraPrewarmService(launched); - } - }); } }); } else { // We need to delay starting the activity because ResolverActivity finishes itself if // launched behind lockscreen. - mActivityStarter.startActivity(intent, false /* dismissShade */, - new ActivityStarter.Callback() { - @Override - public void onActivityStarted(int resultCode) { - unbindCameraPrewarmService(isSuccessfulLaunch(resultCode)); - } - }); + mActivityStarter.startActivity(intent, false /* dismissShade */); } } @@ -705,12 +627,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL dozeTimeTick(); } - private static boolean isSuccessfulLaunch(int result) { - return result == ActivityManager.START_SUCCESS - || result == ActivityManager.START_DELIVERED_TO_TOP - || result == ActivityManager.START_TASK_TO_FRONT; - } - public void launchLeftAffordance() { if (mLeftIsVoiceAssist) { launchVoiceAssist(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 967b807f6f29..b435055d1b16 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -276,11 +276,6 @@ public class NotificationPanelViewController extends PanelViewController { private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; private final NotificationIconAreaController mNotificationIconAreaController; - // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is - // changed. - private static final int CAP_HEIGHT = 1456; - private static final int FONT_HEIGHT = 2163; - /** * Maximum time before which we will expand the panel even for slow motions when getting a * touch passed over from launcher. @@ -1123,13 +1118,6 @@ public class NotificationPanelViewController extends PanelViewController { } } - /** - * Returns if there's a custom clock being presented. - */ - public boolean hasCustomClock() { - return mKeyguardStatusViewController.hasCustomClock(); - } - private void setCentralSurfaces(CentralSurfaces centralSurfaces) { // TODO: this can be injected. mCentralSurfaces = centralSurfaces; @@ -1673,7 +1661,6 @@ public class NotificationPanelViewController extends PanelViewController { setQsExpansionEnabled(); } - @Override public void resetViews(boolean animate) { mIsLaunchTransitionFinished = false; mBlockTouches = false; @@ -4009,9 +3996,10 @@ public class NotificationPanelViewController extends PanelViewController { endAction.run(); } }) + .setUpdateListener(anim -> { + mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction()); + }) .start(); - - mKeyguardStatusViewController.animateFoldToAod(); } /** @@ -4593,13 +4581,6 @@ public class NotificationPanelViewController extends PanelViewController { @Override public void onSwipingStarted(boolean rightIcon) { mFalsingCollector.onAffordanceSwipingStarted(rightIcon); - boolean - camera = - mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? !rightIcon - : rightIcon; - if (camera) { - mKeyguardBottomArea.bindCameraPrewarmService(); - } mView.requestDisallowInterceptTouchEvent(true); mOnlyAffordanceInThisMotion = true; mQsTracking = false; @@ -4608,7 +4589,6 @@ public class NotificationPanelViewController extends PanelViewController { @Override public void onSwipingAborted() { mFalsingCollector.onAffordanceSwipingAborted(); - mKeyguardBottomArea.unbindCameraPrewarmService(false /* launched */); } @Override @@ -4873,7 +4853,6 @@ public class NotificationPanelViewController extends PanelViewController { public void onDozeAmountChanged(float linearAmount, float amount) { mInterpolatedDarkAmount = amount; mLinearDarkAmount = linearAmount; - mKeyguardStatusViewController.setDarkAmount(mInterpolatedDarkAmount); mKeyguardBottomArea.setDarkAmount(mInterpolatedDarkAmount); positionClockAndNotifications(); } @@ -4982,11 +4961,8 @@ public class NotificationPanelViewController extends PanelViewController { updateMaxDisplayedNotifications(!shouldAvoidChangingNotificationsCount()); setIsFullWidth(mNotificationStackScrollLayoutController.getWidth() == mView.getWidth()); - // Update Clock Pivot - mKeyguardStatusViewController.setPivotX(mView.getWidth() / 2); - mKeyguardStatusViewController.setPivotY( - (FONT_HEIGHT - CAP_HEIGHT) / 2048f - * mKeyguardStatusViewController.getClockTextSize()); + // Update Clock Pivot (used by anti-burnin transformations) + mKeyguardStatusViewController.updatePivot(mView.getWidth(), mView.getHeight()); // Calculate quick setting heights. int oldMaxHeight = mQsMaxExpansionHeight; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java index 9f0ecb9d4096..ed12b00cc644 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -1163,8 +1163,6 @@ public abstract class PanelViewController { mTouchDisabled ? "T" : "f")); } - public abstract void resetViews(boolean animate); - public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) { mHeadsUpManager = headsUpManager; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java index 6e331bc13294..fa701e704c21 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java @@ -89,6 +89,17 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh } public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock) { + // TODO(b/219008720): Remove those calls to Dependency.get by introducing a + // SystemUIDialogFactory and make all other dialogs create a SystemUIDialog to which we set + // the content and attach listeners. + this(context, theme, dismissOnDeviceLock, Dependency.get(SystemUIDialogManager.class), + Dependency.get(SysUiState.class), Dependency.get(BroadcastDispatcher.class), + Dependency.get(DialogLaunchAnimator.class)); + } + + public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock, + SystemUIDialogManager dialogManager, SysUiState sysUiState, + BroadcastDispatcher broadcastDispatcher, DialogLaunchAnimator dialogLaunchAnimator) { super(context, theme); mContext = context; @@ -97,13 +108,10 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh attrs.setTitle(getClass().getSimpleName()); getWindow().setAttributes(attrs); - mDismissReceiver = dismissOnDeviceLock ? new DismissReceiver(this) : null; - - // TODO(b/219008720): Remove those calls to Dependency.get by introducing a - // SystemUIDialogFactory and make all other dialogs create a SystemUIDialog to which we set - // the content and attach listeners. - mDialogManager = Dependency.get(SystemUIDialogManager.class); - mSysUiState = Dependency.get(SysUiState.class); + mDismissReceiver = dismissOnDeviceLock ? new DismissReceiver(this, broadcastDispatcher, + dialogLaunchAnimator) : null; + mDialogManager = dialogManager; + mSysUiState = sysUiState; } @Override @@ -322,7 +330,10 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh * @param dismissAction An action to run when the dialog is dismissed. */ public static void registerDismissListener(Dialog dialog, @Nullable Runnable dismissAction) { - DismissReceiver dismissReceiver = new DismissReceiver(dialog); + // TODO(b/219008720): Remove those calls to Dependency.get. + DismissReceiver dismissReceiver = new DismissReceiver(dialog, + Dependency.get(BroadcastDispatcher.class), + Dependency.get(DialogLaunchAnimator.class)); dialog.setOnDismissListener(d -> { dismissReceiver.unregister(); if (dismissAction != null) dismissAction.run(); @@ -404,11 +415,11 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh private final BroadcastDispatcher mBroadcastDispatcher; private final DialogLaunchAnimator mDialogLaunchAnimator; - DismissReceiver(Dialog dialog) { + DismissReceiver(Dialog dialog, BroadcastDispatcher broadcastDispatcher, + DialogLaunchAnimator dialogLaunchAnimator) { mDialog = dialog; - // TODO(b/219008720): Remove those calls to Dependency.get. - mBroadcastDispatcher = Dependency.get(BroadcastDispatcher.class); - mDialogLaunchAnimator = Dependency.get(DialogLaunchAnimator.class); + mBroadcastDispatcher = broadcastDispatcher; + mDialogLaunchAnimator = dialogLaunchAnimator; } void register() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index 670ba96ca1ff..25892d9db080 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -24,7 +24,6 @@ import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedul import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.SHOWING_PERSISTENT_DOT; import android.animation.Animator; -import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.Fragment; @@ -395,14 +394,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue state |= DISABLE_CLOCK; } - - // The shelf will be hidden when dozing with a custom clock, we must show notification - // icons in this occasion. - if (mStatusBarStateController.isDozing() - && mNotificationPanelViewController.hasCustomClock()) { - state |= DISABLE_CLOCK | DISABLE_SYSTEM_INFO; - } - if (mOngoingCallController.hasOngoingCall()) { state &= ~DISABLE_ONGOING_CALL_CHIP; } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionController.kt index 1740bcbbefc8..16f28e7d1a21 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionController.kt @@ -2,10 +2,13 @@ package com.android.systemui.statusbar.phone.shade.transition import android.content.res.Configuration import android.content.res.Resources +import android.util.MathUtils.constrain import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager +import com.android.systemui.statusbar.StatusBarState +import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.phone.ScrimController import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent import com.android.systemui.statusbar.policy.ConfigurationController @@ -21,7 +24,8 @@ constructor( configurationController: ConfigurationController, dumpManager: DumpManager, private val scrimController: ScrimController, - @Main private val resources: Resources + @Main private val resources: Resources, + private val statusBarStateController: SysuiStatusBarStateController, ) { private var inSplitShade = false @@ -55,19 +59,23 @@ constructor( } private fun calculateScrimExpansionFraction(expansionEvent: PanelExpansionChangeEvent): Float { - return if (inSplitShade) { - expansionEvent.dragDownPxAmount / splitShadeScrimTransitionDistance + return if (inSplitShade && isScreenUnlocked()) { + constrain(expansionEvent.dragDownPxAmount / splitShadeScrimTransitionDistance, 0f, 1f) } else { expansionEvent.fraction } } + private fun isScreenUnlocked() = + statusBarStateController.currentOrUpcomingState == StatusBarState.SHADE + private fun dump(printWriter: PrintWriter, args: Array<String>) { printWriter.println( """ ScrimShadeTransitionController: Resources: inSplitShade: $inSplitShade + isScreenUnlocked: ${isScreenUnlocked()} splitShadeScrimTransitionDistance: $splitShadeScrimTransitionDistance State: lastExpansionFraction: $lastExpansionFraction diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java index e5d5ed48cc9f..699414c27059 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java @@ -145,7 +145,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { protected void setEntryPinned( @NonNull HeadsUpManager.HeadsUpEntry headsUpEntry, boolean isPinned) { - mLogger.logSetEntryPinned(headsUpEntry.mEntry.getKey(), isPinned); + mLogger.logSetEntryPinned(headsUpEntry.mEntry, isPinned); NotificationEntry entry = headsUpEntry.mEntry; if (entry.isRowPinned() != isPinned) { entry.setRowPinned(isPinned); @@ -186,7 +186,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { entry.setHeadsUp(false); setEntryPinned((HeadsUpEntry) alertEntry, false /* isPinned */); EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 0 /* visible */); - mLogger.logNotificationActuallyRemoved(entry.getKey()); + mLogger.logNotificationActuallyRemoved(entry); for (OnHeadsUpChangedListener listener : mListeners) { listener.onHeadsUpStateChanged(entry, false); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt index 6a74ba957b4b..d7c81af53d8b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt @@ -20,6 +20,8 @@ import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogLevel.INFO import com.android.systemui.log.LogLevel.VERBOSE import com.android.systemui.log.dagger.NotificationHeadsUpLog +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.logKey import javax.inject.Inject /** Logger for [HeadsUpManager]. */ @@ -56,9 +58,9 @@ class HeadsUpManagerLogger @Inject constructor( }) } - fun logShowNotification(key: String) { + fun logShowNotification(entry: NotificationEntry) { buffer.log(TAG, INFO, { - str1 = key + str1 = entry.logKey }, { "show notification $str1" }) @@ -66,16 +68,16 @@ class HeadsUpManagerLogger @Inject constructor( fun logRemoveNotification(key: String, releaseImmediately: Boolean) { buffer.log(TAG, INFO, { - str1 = key + str1 = logKey(key) bool1 = releaseImmediately }, { "remove notification $str1 releaseImmediately: $bool1" }) } - fun logNotificationActuallyRemoved(key: String) { + fun logNotificationActuallyRemoved(entry: NotificationEntry) { buffer.log(TAG, INFO, { - str1 = key + str1 = entry.logKey }, { "notification removed $str1 " }) @@ -83,7 +85,7 @@ class HeadsUpManagerLogger @Inject constructor( fun logUpdateNotification(key: String, alert: Boolean, hasEntry: Boolean) { buffer.log(TAG, INFO, { - str1 = key + str1 = logKey(key) bool1 = alert bool2 = hasEntry }, { @@ -91,12 +93,12 @@ class HeadsUpManagerLogger @Inject constructor( }) } - fun logUpdateEntry(key: String, updatePostTime: Boolean) { + fun logUpdateEntry(entry: NotificationEntry, updatePostTime: Boolean) { buffer.log(TAG, INFO, { - str1 = key + str1 = entry.logKey bool1 = updatePostTime }, { - "update entry $key updatePostTime: $bool1" + "update entry $str1 updatePostTime: $bool1" }) } @@ -108,9 +110,9 @@ class HeadsUpManagerLogger @Inject constructor( }) } - fun logSetEntryPinned(key: String, isPinned: Boolean) { + fun logSetEntryPinned(entry: NotificationEntry, isPinned: Boolean) { buffer.log(TAG, VERBOSE, { - str1 = key + str1 = entry.logKey bool1 = isPinned }, { "set entry pinned $str1 pinned: $bool1" diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt new file mode 100644 index 000000000000..4f3995252b54 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ +package com.android.keyguard + +import android.content.BroadcastReceiver +import android.testing.AndroidTestingRunner +import android.widget.TextView +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.plugins.Clock +import com.android.systemui.plugins.ClockAnimations +import com.android.systemui.plugins.ClockEvents +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.statusbar.policy.BatteryController +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.capture +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import java.util.TimeZone +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyFloat +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever +import org.mockito.junit.MockitoJUnit + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class ClockEventControllerTest : SysuiTestCase() { + + @JvmField @Rule val mockito = MockitoJUnit.rule() + @Mock private lateinit var statusBarStateController: StatusBarStateController + @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher + @Mock private lateinit var batteryController: BatteryController + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock private lateinit var configurationController: ConfigurationController + @Mock private lateinit var animations: ClockAnimations + @Mock private lateinit var events: ClockEvents + @Mock private lateinit var clock: Clock + + private lateinit var clockEventController: ClockEventController + + @Before + fun setUp() { + whenever(clock.smallClock).thenReturn(TextView(context)) + whenever(clock.largeClock).thenReturn(TextView(context)) + whenever(clock.events).thenReturn(events) + whenever(clock.animations).thenReturn(animations) + + clockEventController = ClockEventController( + statusBarStateController, + broadcastDispatcher, + batteryController, + keyguardUpdateMonitor, + configurationController, + context.resources, + context + ) + } + + @Test + fun clockSet_validateInitialization() { + clockEventController.clock = clock + + verify(clock).initialize(any(), anyFloat(), anyFloat()) + } + + @Test + fun clockUnset_validateState() { + clockEventController.clock = clock + clockEventController.clock = null + + assertEquals(clockEventController.clock, null) + } + + @Test + fun themeChanged_verifyClockPaletteUpdated() { + clockEventController.clock = clock + clockEventController.registerListeners() + + val captor = argumentCaptor<ConfigurationController.ConfigurationListener>() + verify(configurationController).addCallback(capture(captor)) + captor.value.onThemeChanged() + + verify(events).onColorPaletteChanged(any()) + } + + @Test + fun batteryCallback_keyguardShowingCharging_verifyChargeAnimation() { + clockEventController.clock = clock + clockEventController.registerListeners() + + val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>() + verify(batteryController).addCallback(capture(batteryCaptor)) + val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>() + verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor)) + keyguardCaptor.value.onKeyguardVisibilityChanged(true) + batteryCaptor.value.onBatteryLevelChanged(10, false, true) + + verify(animations).charge() + } + + @Test + fun batteryCallback_keyguardShowingCharging_Duplicate_verifyChargeAnimation() { + clockEventController.clock = clock + clockEventController.registerListeners() + + val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>() + verify(batteryController).addCallback(capture(batteryCaptor)) + val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>() + verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor)) + keyguardCaptor.value.onKeyguardVisibilityChanged(true) + batteryCaptor.value.onBatteryLevelChanged(10, false, true) + batteryCaptor.value.onBatteryLevelChanged(10, false, true) + + verify(animations, times(1)).charge() + } + + @Test + fun batteryCallback_keyguardHiddenCharging_verifyChargeAnimation() { + clockEventController.clock = clock + clockEventController.registerListeners() + + val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>() + verify(batteryController).addCallback(capture(batteryCaptor)) + val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>() + verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor)) + keyguardCaptor.value.onKeyguardVisibilityChanged(false) + batteryCaptor.value.onBatteryLevelChanged(10, false, true) + + verify(animations, never()).charge() + } + + @Test + fun batteryCallback_keyguardShowingNotCharging_verifyChargeAnimation() { + clockEventController.clock = clock + clockEventController.registerListeners() + + val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>() + verify(batteryController).addCallback(capture(batteryCaptor)) + val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>() + verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor)) + keyguardCaptor.value.onKeyguardVisibilityChanged(true) + batteryCaptor.value.onBatteryLevelChanged(10, false, false) + + verify(animations, never()).charge() + } + + @Test + fun localeCallback_verifyClockNotified() { + clockEventController.clock = clock + clockEventController.registerListeners() + + val captor = argumentCaptor<BroadcastReceiver>() + verify(broadcastDispatcher).registerReceiver( + capture(captor), any(), eq(null), eq(null), anyInt(), eq(null) + ) + captor.value.onReceive(context, mock()) + + verify(events).onLocaleChanged(any()) + } + + @Test + fun keyguardCallback_visibilityChanged_clockDozeCalled() { + clockEventController.clock = clock + clockEventController.registerListeners() + + val captor = argumentCaptor<KeyguardUpdateMonitorCallback>() + verify(keyguardUpdateMonitor).registerCallback(capture(captor)) + + captor.value.onKeyguardVisibilityChanged(true) + verify(animations, never()).doze(0f) + + captor.value.onKeyguardVisibilityChanged(false) + verify(animations, times(1)).doze(0f) + } + + @Test + fun keyguardCallback_timeFormat_clockNotified() { + clockEventController.clock = clock + clockEventController.registerListeners() + + val captor = argumentCaptor<KeyguardUpdateMonitorCallback>() + verify(keyguardUpdateMonitor).registerCallback(capture(captor)) + captor.value.onTimeFormatChanged("12h") + + verify(events).onTimeFormatChanged(false) + } + + @Test + fun keyguardCallback_timezoneChanged_clockNotified() { + val mockTimeZone = mock<TimeZone>() + clockEventController.clock = clock + clockEventController.registerListeners() + + val captor = argumentCaptor<KeyguardUpdateMonitorCallback>() + verify(keyguardUpdateMonitor).registerCallback(capture(captor)) + captor.value.onTimeZoneChanged(mockTimeZone) + + verify(events).onTimeZoneChanged(mockTimeZone) + } + + @Test + fun keyguardCallback_userSwitched_clockNotified() { + clockEventController.clock = clock + clockEventController.registerListeners() + + val captor = argumentCaptor<KeyguardUpdateMonitorCallback>() + verify(keyguardUpdateMonitor).registerCallback(capture(captor)) + captor.value.onUserSwitchComplete(10) + + verify(events).onTimeFormatChanged(false) + } + + @Test + fun keyguardCallback_verifyKeyguardChanged() { + clockEventController.clock = clock + clockEventController.registerListeners() + + val captor = argumentCaptor<StatusBarStateController.StateListener>() + verify(statusBarStateController).addCallback(capture(captor)) + captor.value.onDozeAmountChanged(0.4f, 0.6f) + + verify(animations).doze(0.4f) + } + + @Test + fun unregisterListeners_validate() { + clockEventController.unregisterListeners() + verify(broadcastDispatcher).unregisterReceiver(any()) + verify(configurationController).removeCallback(any()) + verify(batteryController).removeCallback(any()) + verify(keyguardUpdateMonitor).removeCallback(any()) + verify(statusBarStateController).removeCallback(any()) + } +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java index 8b9a1e022d26..635ee9ea1a2f 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java @@ -19,7 +19,6 @@ package com.android.keyguard; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -41,23 +40,19 @@ import android.widget.RelativeLayout; import androidx.test.filters.SmallTest; -import com.android.internal.colorextraction.ColorExtractor; -import com.android.keyguard.clock.ClockManager; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; -import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; -import com.android.systemui.plugins.ClockPlugin; +import com.android.systemui.plugins.Clock; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shared.clocks.AnimatableClockView; +import com.android.systemui.shared.clocks.ClockRegistry; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController; -import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.NotificationIconContainer; -import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.time.FakeSystemClock; @@ -79,22 +74,12 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { @Mock private StatusBarStateController mStatusBarStateController; @Mock - private SysuiColorExtractor mColorExtractor; - @Mock - private ClockManager mClockManager; + private ClockRegistry mClockRegistry; @Mock KeyguardSliceViewController mKeyguardSliceViewController; @Mock NotificationIconAreaController mNotificationIconAreaController; @Mock - BroadcastDispatcher mBroadcastDispatcher; - @Mock - BatteryController mBatteryController; - @Mock - KeyguardUpdateMonitor mKeyguardUpdateMonitor; - @Mock - KeyguardBypassController mBypassController; - @Mock LockscreenSmartspaceController mSmartspaceController; @Mock @@ -102,11 +87,11 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { @Mock KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; @Mock - private ClockPlugin mClockPlugin; - @Mock - ColorExtractor.GradientColors mGradientColors; + private Clock mClock; @Mock DumpManager mDumpManager; + @Mock + ClockEventController mClockEventController; @Mock private NotificationIconContainer mNotificationIcons; @@ -118,6 +103,8 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { private FrameLayout mLargeClockFrame; @Mock private SecureSettings mSecureSettings; + @Mock + private FeatureFlags mFeatureFlags; private final View mFakeSmartspaceView = new View(mContext); @@ -136,8 +123,6 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { when(mView.getContext()).thenReturn(getContext()); when(mView.getResources()).thenReturn(mResources); - when(mView.findViewById(R.id.animatable_clock_view)).thenReturn(mClockView); - when(mView.findViewById(R.id.animatable_clock_view_large)).thenReturn(mLargeClockView); when(mView.findViewById(R.id.lockscreen_clock_view_large)).thenReturn(mLargeClockFrame); when(mClockView.getContext()).thenReturn(getContext()); when(mLargeClockView.getContext()).thenReturn(getContext()); @@ -148,23 +133,20 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { mController = new KeyguardClockSwitchController( mView, mStatusBarStateController, - mColorExtractor, - mClockManager, + mClockRegistry, mKeyguardSliceViewController, mNotificationIconAreaController, - mBroadcastDispatcher, - mBatteryController, - mKeyguardUpdateMonitor, mSmartspaceController, mKeyguardUnlockAnimationController, mSecureSettings, mExecutor, - mResources, - mDumpManager + mDumpManager, + mClockEventController, + mFeatureFlags ); when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE); - when(mColorExtractor.getColors(anyInt())).thenReturn(mGradientColors); + when(mClockRegistry.createCurrentClock()).thenReturn(mClock); mSliceView = new View(getContext()); when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView); @@ -211,20 +193,20 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { verifyAttachment(times(1)); listenerArgumentCaptor.getValue().onViewDetachedFromWindow(mView); - verify(mColorExtractor).removeOnColorsChangedListener( - any(ColorExtractor.OnColorsChangedListener.class)); + verify(mClockEventController).unregisterListeners(); } @Test public void testPluginPassesStatusBarState() { - ArgumentCaptor<ClockManager.ClockChangedListener> listenerArgumentCaptor = - ArgumentCaptor.forClass(ClockManager.ClockChangedListener.class); + ArgumentCaptor<ClockRegistry.ClockChangeListener> listenerArgumentCaptor = + ArgumentCaptor.forClass(ClockRegistry.ClockChangeListener.class); mController.init(); - verify(mClockManager).addOnClockChangedListener(listenerArgumentCaptor.capture()); + verify(mClockRegistry).registerClockChangeListener(listenerArgumentCaptor.capture()); - listenerArgumentCaptor.getValue().onClockChanged(mClockPlugin); - verify(mView).setClockPlugin(mClockPlugin, StatusBarState.SHADE); + listenerArgumentCaptor.getValue().onClockChanged(); + verify(mView, times(2)).setClock(mClock, StatusBarState.SHADE); + verify(mClockEventController, times(2)).setClock(mClock); } @Test @@ -281,10 +263,8 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { } private void verifyAttachment(VerificationMode times) { - verify(mClockManager, times).addOnClockChangedListener( - any(ClockManager.ClockChangedListener.class)); - verify(mColorExtractor, times).addOnColorsChangedListener( - any(ColorExtractor.OnColorsChangedListener.class)); - verify(mView, times).updateColors(mGradientColors); + verify(mClockRegistry, times).registerClockChangeListener( + any(ClockRegistry.ClockChangeListener.class)); + verify(mClockEventController, times).registerListeners(); } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java index 6c6f0acd7085..a0295d09826f 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java @@ -16,7 +16,6 @@ package com.android.keyguard; -import static android.view.View.GONE; import static android.view.View.VISIBLE; import static com.android.keyguard.KeyguardClockSwitch.LARGE; @@ -24,56 +23,61 @@ import static com.android.keyguard.KeyguardClockSwitch.SMALL; import static com.google.common.truth.Truth.assertThat; +import static junit.framework.TestCase.assertEquals; + import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; -import android.graphics.Color; -import android.graphics.Paint.Style; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import android.widget.FrameLayout; -import android.widget.TextClock; +import android.widget.TextView; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; -import com.android.systemui.plugins.ClockPlugin; -import com.android.systemui.shared.clocks.AnimatableClockView; +import com.android.systemui.plugins.Clock; import com.android.systemui.statusbar.StatusBarState; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidTestingRunner.class) // Need to run on the main thread because KeyguardSliceView$Row init checks for // the main thread before acquiring a wake lock. This class is constructed when -// the keyguard_clcok_switch layout is inflated. +// the keyguard_clock_switch layout is inflated. @RunWithLooper(setAsMainLooper = true) public class KeyguardClockSwitchTest extends SysuiTestCase { - private FrameLayout mClockFrame; + @Mock + ViewGroup mMockKeyguardSliceView; + + @Mock + Clock mClock; + + private FrameLayout mSmallClockFrame; private FrameLayout mLargeClockFrame; - private TextClock mBigClock; - private AnimatableClockView mClockView; - private AnimatableClockView mLargeClockView; - View mMockKeyguardSliceView; KeyguardClockSwitch mKeyguardClockSwitch; @Before public void setUp() { - mMockKeyguardSliceView = mock(KeyguardSliceView.class); + MockitoAnnotations.initMocks(this); when(mMockKeyguardSliceView.getContext()).thenReturn(mContext); when(mMockKeyguardSliceView.findViewById(R.id.keyguard_status_area)) .thenReturn(mMockKeyguardSliceView); + when(mClock.getSmallClock()).thenReturn(new TextView(getContext())); + when(mClock.getLargeClock()).thenReturn(new TextView(getContext())); + LayoutInflater layoutInflater = LayoutInflater.from(getContext()); layoutInflater.setPrivateFactory(new LayoutInflater.Factory2() { @@ -93,164 +97,68 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { }); mKeyguardClockSwitch = (KeyguardClockSwitch) layoutInflater.inflate(R.layout.keyguard_clock_switch, null); - mClockFrame = mKeyguardClockSwitch.findViewById(R.id.lockscreen_clock_view); - mClockView = mKeyguardClockSwitch.findViewById(R.id.animatable_clock_view); + mSmallClockFrame = mKeyguardClockSwitch.findViewById(R.id.lockscreen_clock_view); mLargeClockFrame = mKeyguardClockSwitch.findViewById(R.id.lockscreen_clock_view_large); - mLargeClockView = mKeyguardClockSwitch.findViewById(R.id.animatable_clock_view_large); - mBigClock = new TextClock(getContext()); mKeyguardClockSwitch.mChildrenAreLaidOut = true; - MockitoAnnotations.initMocks(this); - } - - @Test - public void onPluginConnected_showPluginClock() { - ClockPlugin plugin = mock(ClockPlugin.class); - TextClock pluginView = new TextClock(getContext()); - when(plugin.getView()).thenReturn(pluginView); - - mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD); - - assertThat(mClockView.getVisibility()).isEqualTo(GONE); - assertThat(plugin.getView().getParent()).isEqualTo(mClockFrame); } @Test - public void onPluginConnected_showPluginBigClock() { - // GIVEN the plugin returns a view for the big clock - ClockPlugin plugin = mock(ClockPlugin.class); - when(plugin.getBigClockView()).thenReturn(mBigClock); - // WHEN the plugin is connected - mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD); - // THEN the big clock container is visible and it is the parent of the - // big clock view. - assertThat(mLargeClockView.getVisibility()).isEqualTo(View.GONE); - assertThat(mBigClock.getParent()).isEqualTo(mLargeClockFrame); + public void noPluginConnected_showNothing() { + mKeyguardClockSwitch.setClock(null, StatusBarState.KEYGUARD); + assertEquals(mLargeClockFrame.getChildCount(), 0); + assertEquals(mSmallClockFrame.getChildCount(), 0); } @Test - public void onPluginConnected_nullView() { - ClockPlugin plugin = mock(ClockPlugin.class); - mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD); - assertThat(mClockView.getVisibility()).isEqualTo(VISIBLE); - } - - @Test - public void onPluginConnected_showSecondPluginClock() { - // GIVEN a plugin has already connected - ClockPlugin plugin1 = mock(ClockPlugin.class); - when(plugin1.getView()).thenReturn(new TextClock(getContext())); - mKeyguardClockSwitch.setClockPlugin(plugin1, StatusBarState.KEYGUARD); - // WHEN a second plugin is connected - ClockPlugin plugin2 = mock(ClockPlugin.class); - when(plugin2.getView()).thenReturn(new TextClock(getContext())); - mKeyguardClockSwitch.setClockPlugin(plugin2, StatusBarState.KEYGUARD); - // THEN only the view from the second plugin should be a child of KeyguardClockSwitch. - assertThat(plugin2.getView().getParent()).isEqualTo(mClockFrame); - assertThat(plugin1.getView().getParent()).isNull(); - } + public void pluginConnectedThenDisconnected_showNothing() { + mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD); + assertEquals(mLargeClockFrame.getChildCount(), 1); + assertEquals(mSmallClockFrame.getChildCount(), 1); - @Test - public void onPluginConnected_darkAmountInitialized() { - // GIVEN that the dark amount has already been set - mKeyguardClockSwitch.setDarkAmount(0.5f); - // WHEN a plugin is connected - ClockPlugin plugin = mock(ClockPlugin.class); - mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD); - // THEN dark amount should be initalized on the plugin. - verify(plugin).setDarkAmount(0.5f); + mKeyguardClockSwitch.setClock(null, StatusBarState.KEYGUARD); + assertEquals(mLargeClockFrame.getChildCount(), 0); + assertEquals(mSmallClockFrame.getChildCount(), 0); } @Test - public void onPluginDisconnected_showDefaultClock() { - ClockPlugin plugin = mock(ClockPlugin.class); - TextClock pluginView = new TextClock(getContext()); - when(plugin.getView()).thenReturn(pluginView); - - mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD); - assertThat(mClockView.getVisibility()).isEqualTo(GONE); + public void onPluginConnected_showClock() { + mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD); - mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD); - assertThat(mClockView.getVisibility()).isEqualTo(VISIBLE); - - assertThat(plugin.getView().getParent()).isNull(); + assertEquals(mClock.getSmallClock().getParent(), mSmallClockFrame); + assertEquals(mClock.getLargeClock().getParent(), mLargeClockFrame); } @Test - public void onPluginDisconnected_hidePluginBigClock() { - // GIVEN the plugin returns a view for the big clock - ClockPlugin plugin = mock(ClockPlugin.class); - TextClock pluginView = new TextClock(getContext()); - when(plugin.getBigClockView()).thenReturn(pluginView); - // WHEN the plugin is connected and then disconnected - mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD); - mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD); - // THEN the big lock container is GONE and the big clock view doesn't have - // a parent. - assertThat(mLargeClockView.getVisibility()).isEqualTo(VISIBLE); - assertThat(pluginView.getParent()).isNull(); - } + public void onPluginConnected_showSecondPluginClock() { + // GIVEN a plugin has already connected + Clock otherClock = mock(Clock.class); + when(otherClock.getSmallClock()).thenReturn(new TextView(getContext())); + when(otherClock.getLargeClock()).thenReturn(new TextView(getContext())); + mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD); + mKeyguardClockSwitch.setClock(otherClock, StatusBarState.KEYGUARD); - @Test - public void onPluginDisconnected_nullView() { - ClockPlugin plugin = mock(ClockPlugin.class); - mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD); - mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD); - assertThat(mClockView.getVisibility()).isEqualTo(VISIBLE); + // THEN only the view from the second plugin should be a child of KeyguardClockSwitch. + assertThat(otherClock.getSmallClock().getParent()).isEqualTo(mSmallClockFrame); + assertThat(otherClock.getLargeClock().getParent()).isEqualTo(mLargeClockFrame); + assertThat(mClock.getSmallClock().getParent()).isNull(); + assertThat(mClock.getLargeClock().getParent()).isNull(); } @Test public void onPluginDisconnected_secondOfTwoDisconnected() { // GIVEN two plugins are connected - ClockPlugin plugin1 = mock(ClockPlugin.class); - when(plugin1.getView()).thenReturn(new TextClock(getContext())); - mKeyguardClockSwitch.setClockPlugin(plugin1, StatusBarState.KEYGUARD); - ClockPlugin plugin2 = mock(ClockPlugin.class); - when(plugin2.getView()).thenReturn(new TextClock(getContext())); - mKeyguardClockSwitch.setClockPlugin(plugin2, StatusBarState.KEYGUARD); + Clock otherClock = mock(Clock.class); + when(otherClock.getSmallClock()).thenReturn(new TextView(getContext())); + when(otherClock.getLargeClock()).thenReturn(new TextView(getContext())); + mKeyguardClockSwitch.setClock(otherClock, StatusBarState.KEYGUARD); + mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD); // WHEN the second plugin is disconnected - mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD); - // THEN the default clock should be shown. - assertThat(mClockView.getVisibility()).isEqualTo(VISIBLE); - assertThat(plugin1.getView().getParent()).isNull(); - assertThat(plugin2.getView().getParent()).isNull(); - } - - @Test - public void onPluginDisconnected_onDestroyView() { - // GIVEN a plugin is connected - ClockPlugin clockPlugin = mock(ClockPlugin.class); - when(clockPlugin.getView()).thenReturn(new TextClock(getContext())); - mKeyguardClockSwitch.setClockPlugin(clockPlugin, StatusBarState.KEYGUARD); - // WHEN the plugin is disconnected - mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD); - // THEN onDestroyView is called on the plugin - verify(clockPlugin).onDestroyView(); - } - - @Test - public void setTextColor_pluginClockSetTextColor() { - ClockPlugin plugin = mock(ClockPlugin.class); - TextClock pluginView = new TextClock(getContext()); - when(plugin.getView()).thenReturn(pluginView); - mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD); - - mKeyguardClockSwitch.setTextColor(Color.WHITE); - - verify(plugin).setTextColor(Color.WHITE); - } - - - @Test - public void setStyle_pluginClockSetStyle() { - ClockPlugin plugin = mock(ClockPlugin.class); - TextClock pluginView = new TextClock(getContext()); - when(plugin.getView()).thenReturn(pluginView); - Style style = mock(Style.class); - mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD); - - mKeyguardClockSwitch.setStyle(style); - - verify(plugin).setStyle(style); + mKeyguardClockSwitch.setClock(null, StatusBarState.KEYGUARD); + // THEN nothing should be shown + assertThat(otherClock.getSmallClock().getParent()).isNull(); + assertThat(otherClock.getLargeClock().getParent()).isNull(); + assertThat(mClock.getSmallClock().getParent()).isNull(); + assertThat(mClock.getLargeClock().getParent()).isNull(); } @Test @@ -262,7 +170,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1); assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE); - assertThat(mClockFrame.getAlpha()).isEqualTo(0); + assertThat(mSmallClockFrame.getAlpha()).isEqualTo(0); } @Test @@ -271,7 +179,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1); assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE); - assertThat(mClockFrame.getAlpha()).isEqualTo(0); + assertThat(mSmallClockFrame.getAlpha()).isEqualTo(0); } @Test @@ -281,8 +189,8 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { mKeyguardClockSwitch.mClockInAnim.end(); mKeyguardClockSwitch.mClockOutAnim.end(); - assertThat(mClockFrame.getAlpha()).isEqualTo(1); - assertThat(mClockFrame.getVisibility()).isEqualTo(VISIBLE); + assertThat(mSmallClockFrame.getAlpha()).isEqualTo(1); + assertThat(mSmallClockFrame.getVisibility()).isEqualTo(VISIBLE); // only big clock is removed at switch assertThat(mLargeClockFrame.getParent()).isNull(); assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0); @@ -292,8 +200,8 @@ public class KeyguardClockSwitchTest extends SysuiTestCase { public void switchingToSmallClockNoAnimation_makesBigClockDisappear() { mKeyguardClockSwitch.switchToClock(SMALL, false); - assertThat(mClockFrame.getAlpha()).isEqualTo(1); - assertThat(mClockFrame.getVisibility()).isEqualTo(VISIBLE); + assertThat(mSmallClockFrame.getAlpha()).isEqualTo(1); + assertThat(mSmallClockFrame.getVisibility()).isEqualTo(VISIBLE); // only big clock is removed at switch assertThat(mLargeClockFrame.getParent()).isNull(); assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java index 7de4586e1901..bc351427310d 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java @@ -27,7 +27,6 @@ import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -68,8 +67,6 @@ import org.mockito.junit.MockitoRule; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper() public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { - private static final int VIEW_WIDTH = 1600; - @Rule public MockitoRule mRule = MockitoJUnit.rule(); @@ -141,7 +138,6 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { when(mResources.getConfiguration()).thenReturn(mConfiguration); when(mView.getContext()).thenReturn(mContext); when(mView.getResources()).thenReturn(mResources); - when(mView.getWidth()).thenReturn(VIEW_WIDTH); when(mAdminSecondaryLockScreenControllerFactory.create(any(KeyguardSecurityCallback.class))) .thenReturn(mAdminSecondaryLockScreenController); when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController); @@ -212,49 +208,26 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { mUserSwitcherController); } - private void touchDownLeftSide() { + private void touchDown() { mKeyguardSecurityContainerController.mGlobalTouchListener.onTouchEvent( MotionEvent.obtain( /* downTime= */0, /* eventTime= */0, MotionEvent.ACTION_DOWN, - /* x= */VIEW_WIDTH / 3f, - /* y= */0, - /* metaState= */0)); - } - - private void touchDownRightSide() { - mKeyguardSecurityContainerController.mGlobalTouchListener.onTouchEvent( - MotionEvent.obtain( - /* downTime= */0, - /* eventTime= */0, - MotionEvent.ACTION_DOWN, - /* x= */(VIEW_WIDTH / 3f) * 2, + /* x= */0, /* y= */0, /* metaState= */0)); } @Test - public void onInterceptTap_inhibitsFalsingInOneHandedMode() { - when(mView.getMode()).thenReturn(MODE_ONE_HANDED); - when(mView.isOneHandedModeLeftAligned()).thenReturn(true); - - touchDownLeftSide(); - verify(mFalsingCollector, never()).avoidGesture(); - - // Now on the right. - touchDownRightSide(); - verify(mFalsingCollector).avoidGesture(); - - // Move and re-test - reset(mFalsingCollector); - when(mView.isOneHandedModeLeftAligned()).thenReturn(false); + public void onInterceptTap_inhibitsFalsingInSidedSecurityMode() { - // On the right... - touchDownRightSide(); + when(mView.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(false); + touchDown(); verify(mFalsingCollector, never()).avoidGesture(); - touchDownLeftSide(); + when(mView.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(true); + touchDown(); verify(mFalsingCollector).avoidGesture(); } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java index d49f4d8172dc..f2ac0c7a7736 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java @@ -16,6 +16,11 @@ package com.android.keyguard; +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.provider.Settings.Global.ONE_HANDED_KEYGUARD_SIDE; +import static android.provider.Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT; +import static android.provider.Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.systemBars; @@ -25,7 +30,9 @@ import static com.android.keyguard.KeyguardSecurityContainer.MODE_ONE_HANDED; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; @@ -34,14 +41,13 @@ import static org.mockito.Mockito.when; import android.content.pm.UserInfo; import android.content.res.Configuration; import android.graphics.Insets; -import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.Gravity; +import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; -import android.view.WindowInsetsController; import android.widget.FrameLayout; import androidx.test.filters.SmallTest; @@ -70,6 +76,9 @@ import java.util.ArrayList; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper() public class KeyguardSecurityContainerTest extends SysuiTestCase { + + private static final int VIEW_WIDTH = 1600; + private int mScreenWidth; private int mFakeMeasureSpec; @@ -77,8 +86,6 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { public MockitoRule mRule = MockitoJUnit.rule(); @Mock - private WindowInsetsController mWindowInsetsController; - @Mock private KeyguardSecurityViewFlipper mSecurityViewFlipper; @Mock private GlobalSettings mGlobalSettings; @@ -102,7 +109,6 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { mSecurityViewFlipperLayoutParams = new FrameLayout.LayoutParams( MATCH_PARENT, MATCH_PARENT); - when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController); when(mSecurityViewFlipper.getLayoutParams()).thenReturn(mSecurityViewFlipperLayoutParams); mKeyguardSecurityContainer = new KeyguardSecurityContainer(getContext()); mKeyguardSecurityContainer.mSecurityViewFlipper = mSecurityViewFlipper; @@ -212,14 +218,12 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { mKeyguardSecurityContainer.updatePositionByTouchX( mKeyguardSecurityContainer.getWidth() - 1f); - verify(mGlobalSettings).putInt(Settings.Global.ONE_HANDED_KEYGUARD_SIDE, - Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT); - verify(mSecurityViewFlipper).setTranslationX( + verify(mGlobalSettings).putInt(ONE_HANDED_KEYGUARD_SIDE, ONE_HANDED_KEYGUARD_SIDE_RIGHT); + assertSecurityTranslationX( mKeyguardSecurityContainer.getWidth() - mSecurityViewFlipper.getWidth()); mKeyguardSecurityContainer.updatePositionByTouchX(1f); - verify(mGlobalSettings).putInt(Settings.Global.ONE_HANDED_KEYGUARD_SIDE, - Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT); + verify(mGlobalSettings).putInt(ONE_HANDED_KEYGUARD_SIDE, ONE_HANDED_KEYGUARD_SIDE_LEFT); verify(mSecurityViewFlipper).setTranslationX(0.0f); } @@ -237,49 +241,43 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { } @Test - public void testUserSwitcherModeViewGravityLandscape() { + public void testUserSwitcherModeViewPositionLandscape() { // GIVEN one user has been setup and in landscape when(mUserSwitcherController.getUsers()).thenReturn(buildUserRecords(1)); - Configuration config = new Configuration(); - config.orientation = Configuration.ORIENTATION_LANDSCAPE; - when(getContext().getResources().getConfiguration()).thenReturn(config); + Configuration landscapeConfig = configuration(ORIENTATION_LANDSCAPE); + when(getContext().getResources().getConfiguration()).thenReturn(landscapeConfig); // WHEN UserSwitcherViewMode is initialized and config has changed setupUserSwitcher(); - reset(mSecurityViewFlipper); - when(mSecurityViewFlipper.getLayoutParams()).thenReturn(mSecurityViewFlipperLayoutParams); - mKeyguardSecurityContainer.onConfigurationChanged(config); + mKeyguardSecurityContainer.onConfigurationChanged(landscapeConfig); // THEN views are oriented side by side - verify(mSecurityViewFlipper).setLayoutParams(mLayoutCaptor.capture()); - assertThat(mLayoutCaptor.getValue().gravity).isEqualTo(Gravity.RIGHT | Gravity.BOTTOM); - ViewGroup userSwitcher = mKeyguardSecurityContainer.findViewById( - R.id.keyguard_bouncer_user_switcher); - assertThat(((FrameLayout.LayoutParams) userSwitcher.getLayoutParams()).gravity) - .isEqualTo(Gravity.LEFT | Gravity.CENTER_VERTICAL); + assertSecurityGravity(Gravity.LEFT | Gravity.BOTTOM); + assertUserSwitcherGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL); + assertSecurityTranslationX( + mKeyguardSecurityContainer.getWidth() - mSecurityViewFlipper.getWidth()); + assertUserSwitcherTranslationX(0f); + } @Test public void testUserSwitcherModeViewGravityPortrait() { // GIVEN one user has been setup and in landscape when(mUserSwitcherController.getUsers()).thenReturn(buildUserRecords(1)); - Configuration config = new Configuration(); - config.orientation = Configuration.ORIENTATION_PORTRAIT; - when(getContext().getResources().getConfiguration()).thenReturn(config); + Configuration portraitConfig = configuration(ORIENTATION_PORTRAIT); + when(getContext().getResources().getConfiguration()).thenReturn(portraitConfig); // WHEN UserSwitcherViewMode is initialized and config has changed setupUserSwitcher(); reset(mSecurityViewFlipper); when(mSecurityViewFlipper.getLayoutParams()).thenReturn(mSecurityViewFlipperLayoutParams); - mKeyguardSecurityContainer.onConfigurationChanged(config); + mKeyguardSecurityContainer.onConfigurationChanged(portraitConfig); // THEN views are both centered horizontally - verify(mSecurityViewFlipper).setLayoutParams(mLayoutCaptor.capture()); - assertThat(mLayoutCaptor.getValue().gravity).isEqualTo(Gravity.CENTER_HORIZONTAL); - ViewGroup userSwitcher = mKeyguardSecurityContainer.findViewById( - R.id.keyguard_bouncer_user_switcher); - assertThat(((FrameLayout.LayoutParams) userSwitcher.getLayoutParams()).gravity) - .isEqualTo(Gravity.CENTER_HORIZONTAL); + assertSecurityGravity(Gravity.CENTER_HORIZONTAL); + assertUserSwitcherGravity(Gravity.CENTER_HORIZONTAL); + assertSecurityTranslationX(0); + assertUserSwitcherTranslationX(0); } @Test @@ -310,9 +308,102 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { assertThat(anchor.isClickable()).isTrue(); } + @Test + public void testTouchesAreRecognizedAsBeingOnTheOtherSideOfSecurity() { + setupUserSwitcher(); + setViewWidth(VIEW_WIDTH); + + // security is on the right side by default + assertThat(mKeyguardSecurityContainer.isTouchOnTheOtherSideOfSecurity( + touchEventLeftSide())).isTrue(); + assertThat(mKeyguardSecurityContainer.isTouchOnTheOtherSideOfSecurity( + touchEventRightSide())).isFalse(); + + // move security to the left side + when(mGlobalSettings.getInt(any(), anyInt())).thenReturn(ONE_HANDED_KEYGUARD_SIDE_LEFT); + mKeyguardSecurityContainer.onConfigurationChanged(new Configuration()); + + assertThat(mKeyguardSecurityContainer.isTouchOnTheOtherSideOfSecurity( + touchEventLeftSide())).isFalse(); + assertThat(mKeyguardSecurityContainer.isTouchOnTheOtherSideOfSecurity( + touchEventRightSide())).isTrue(); + } + + @Test + public void testSecuritySwitchesSidesInLandscapeUserSwitcherMode() { + when(getContext().getResources().getConfiguration()) + .thenReturn(configuration(ORIENTATION_LANDSCAPE)); + setupUserSwitcher(); + + // switch sides + when(mGlobalSettings.getInt(any(), anyInt())).thenReturn(ONE_HANDED_KEYGUARD_SIDE_LEFT); + mKeyguardSecurityContainer.onConfigurationChanged(new Configuration()); + + assertSecurityTranslationX(0); + assertUserSwitcherTranslationX( + mKeyguardSecurityContainer.getWidth() - mSecurityViewFlipper.getWidth()); + } + + private Configuration configuration(@Configuration.Orientation int orientation) { + Configuration config = new Configuration(); + config.orientation = orientation; + return config; + } + + private void assertSecurityTranslationX(float translation) { + verify(mSecurityViewFlipper).setTranslationX(translation); + } + + private void assertUserSwitcherTranslationX(float translation) { + ViewGroup userSwitcher = mKeyguardSecurityContainer.findViewById( + R.id.keyguard_bouncer_user_switcher); + assertThat(userSwitcher.getTranslationX()).isEqualTo(translation); + } + + private void assertUserSwitcherGravity(@Gravity.GravityFlags int gravity) { + ViewGroup userSwitcher = mKeyguardSecurityContainer.findViewById( + R.id.keyguard_bouncer_user_switcher); + assertThat(((FrameLayout.LayoutParams) userSwitcher.getLayoutParams()).gravity) + .isEqualTo(gravity); + } + + private void assertSecurityGravity(@Gravity.GravityFlags int gravity) { + verify(mSecurityViewFlipper, atLeastOnce()).setLayoutParams(mLayoutCaptor.capture()); + assertThat(mLayoutCaptor.getValue().gravity).isEqualTo(gravity); + } + + private void setViewWidth(int width) { + mKeyguardSecurityContainer.setRight(width); + mKeyguardSecurityContainer.setLeft(0); + } + + private MotionEvent touchEventLeftSide() { + return MotionEvent.obtain( + /* downTime= */0, + /* eventTime= */0, + MotionEvent.ACTION_DOWN, + /* x= */VIEW_WIDTH / 3f, + /* y= */0, + /* metaState= */0); + } + + private MotionEvent touchEventRightSide() { + return MotionEvent.obtain( + /* downTime= */0, + /* eventTime= */0, + MotionEvent.ACTION_DOWN, + /* x= */(VIEW_WIDTH / 3f) * 2, + /* y= */0, + /* metaState= */0); + } + private void setupUserSwitcher() { + when(mGlobalSettings.getInt(any(), anyInt())).thenReturn(ONE_HANDED_KEYGUARD_SIDE_RIGHT); mKeyguardSecurityContainer.initMode(KeyguardSecurityContainer.MODE_USER_SWITCHER, mGlobalSettings, mFalsingManager, mUserSwitcherController); + // reset mSecurityViewFlipper so setup doesn't influence test verifications + reset(mSecurityViewFlipper); + when(mSecurityViewFlipper.getLayoutParams()).thenReturn(mSecurityViewFlipperLayoutParams); } private ArrayList<UserRecord> buildUserRecords(int count) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java index a35efa995ddd..90609fa2772f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -210,7 +210,8 @@ public class ScreenDecorationsTest extends SysuiTestCase { BOUNDS_POSITION_TOP, mAuthController, mStatusBarStateController, - mKeyguardUpdateMonitor)); + mKeyguardUpdateMonitor, + mExecutor)); mScreenDecorations = spy(new ScreenDecorations(mContext, mExecutor, mSecureSettings, mBroadcastDispatcher, mTunerService, mUserTracker, mDotViewController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt index d5df9fe0c2e8..c48cbb19b40a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt @@ -159,7 +159,7 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() { @Test fun doesNotStartIfAnimationIsCancelled() { val runner = activityLaunchAnimator.createRunner(controller) - runner.onAnimationCancelled() + runner.onAnimationCancelled(false /* isKeyguardOccluded */) runner.onAnimationStart(0, emptyArray(), emptyArray(), emptyArray(), iCallback) waitForIdleSync() diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java index fb64c7b58aac..2adf2857a385 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java @@ -207,4 +207,15 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase { assertThat(complications.contains(weatherComplication)).isFalse(); } } + + @Test + public void testComplicationWithNoTypeNotFiltered() { + final Complication complication = Mockito.mock(Complication.class); + final DreamOverlayStateController stateController = + new DreamOverlayStateController(mExecutor); + stateController.addComplication(complication); + mExecutor.runAllReady(); + assertThat(stateController.getComplications(true).contains(complication)) + .isTrue(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java index dc1ae0e93757..964e6d79f0bf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java @@ -72,19 +72,67 @@ public class SmartSpaceComplicationTest extends SysuiTestCase { } /** - * Ensures {@link SmartSpaceComplication} is only registered when it is available. + * Ensures {@link SmartSpaceComplication} isn't registered right away on start. */ @Test - public void testAvailability() { + public void testRegistrantStart_doesNotAddComplication() { + final SmartSpaceComplication.Registrant registrant = getRegistrant(); + registrant.start(); + verify(mDreamOverlayStateController, never()).addComplication(eq(mComplication)); + } - final SmartSpaceComplication.Registrant registrant = new SmartSpaceComplication.Registrant( + private SmartSpaceComplication.Registrant getRegistrant() { + return new SmartSpaceComplication.Registrant( mContext, mDreamOverlayStateController, mComplication, mSmartspaceController); + } + + @Test + public void testOverlayActive_addsTargetListener() { + final SmartSpaceComplication.Registrant registrant = getRegistrant(); registrant.start(); - verify(mDreamOverlayStateController, never()).addComplication(eq(mComplication)); + final ArgumentCaptor<DreamOverlayStateController.Callback> dreamCallbackCaptor = + ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class); + verify(mDreamOverlayStateController).addCallback(dreamCallbackCaptor.capture()); + + when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true); + dreamCallbackCaptor.getValue().onStateChanged(); + + // Test + final ArgumentCaptor<BcSmartspaceDataPlugin.SmartspaceTargetListener> listenerCaptor = + ArgumentCaptor.forClass(BcSmartspaceDataPlugin.SmartspaceTargetListener.class); + verify(mSmartspaceController).addListener(listenerCaptor.capture()); + } + + @Test + public void testOverlayActive_targetsNonEmpty_addsComplication() { + final SmartSpaceComplication.Registrant registrant = getRegistrant(); + registrant.start(); + + final ArgumentCaptor<DreamOverlayStateController.Callback> dreamCallbackCaptor = + ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class); + verify(mDreamOverlayStateController).addCallback(dreamCallbackCaptor.capture()); + + when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true); + dreamCallbackCaptor.getValue().onStateChanged(); + + final ArgumentCaptor<BcSmartspaceDataPlugin.SmartspaceTargetListener> listenerCaptor = + ArgumentCaptor.forClass(BcSmartspaceDataPlugin.SmartspaceTargetListener.class); + verify(mSmartspaceController).addListener(listenerCaptor.capture()); + + // Test + final SmartspaceTarget target = Mockito.mock(SmartspaceTarget.class); + listenerCaptor.getValue().onSmartspaceTargetsUpdated(Arrays.asList(target)); + verify(mDreamOverlayStateController).addComplication(eq(mComplication)); + } + + @Test + public void testOverlayActive_targetsEmpty_removesComplication() { + final SmartSpaceComplication.Registrant registrant = getRegistrant(); + registrant.start(); final ArgumentCaptor<DreamOverlayStateController.Callback> dreamCallbackCaptor = ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class); @@ -100,10 +148,41 @@ public class SmartSpaceComplicationTest extends SysuiTestCase { final SmartspaceTarget target = Mockito.mock(SmartspaceTarget.class); listenerCaptor.getValue().onSmartspaceTargetsUpdated(Arrays.asList(target)); verify(mDreamOverlayStateController).addComplication(eq(mComplication)); + + // Test + listenerCaptor.getValue().onSmartspaceTargetsUpdated(Arrays.asList()); + verify(mDreamOverlayStateController).removeComplication(eq(mComplication)); + } + + @Test + public void testOverlayInActive_removesTargetListener_removesComplication() { + final SmartSpaceComplication.Registrant registrant = getRegistrant(); + registrant.start(); + + final ArgumentCaptor<DreamOverlayStateController.Callback> dreamCallbackCaptor = + ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class); + verify(mDreamOverlayStateController).addCallback(dreamCallbackCaptor.capture()); + + when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true); + dreamCallbackCaptor.getValue().onStateChanged(); + + final ArgumentCaptor<BcSmartspaceDataPlugin.SmartspaceTargetListener> listenerCaptor = + ArgumentCaptor.forClass(BcSmartspaceDataPlugin.SmartspaceTargetListener.class); + verify(mSmartspaceController).addListener(listenerCaptor.capture()); + + final SmartspaceTarget target = Mockito.mock(SmartspaceTarget.class); + listenerCaptor.getValue().onSmartspaceTargetsUpdated(Arrays.asList(target)); + verify(mDreamOverlayStateController).addComplication(eq(mComplication)); + + // Test + when(mDreamOverlayStateController.isOverlayActive()).thenReturn(false); + dreamCallbackCaptor.getValue().onStateChanged(); + verify(mSmartspaceController).removeListener(listenerCaptor.getValue()); + verify(mDreamOverlayStateController).removeComplication(eq(mComplication)); } @Test - public void testGetViewReusesSameView() { + public void testGetView_reusesSameView() { final SmartSpaceComplication complication = new SmartSpaceComplication(getContext(), mSmartspaceController); final Complication.ViewHolder viewHolder = complication.createView(mComplicationViewModel); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java index 11326e76b25e..59475cf0cb90 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java @@ -19,6 +19,7 @@ package com.android.systemui.media.dialog; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -51,6 +52,8 @@ public class MediaOutputAdapterTest extends SysuiTestCase { private static final String TEST_DEVICE_ID_1 = "test_device_id_1"; private static final String TEST_DEVICE_ID_2 = "test_device_id_2"; private static final String TEST_SESSION_NAME = "test_session_name"; + private static final int TEST_MAX_VOLUME = 20; + private static final int TEST_CURRENT_VOLUME = 10; // Mock private MediaOutputController mMediaOutputController = mock(MediaOutputController.class); @@ -64,12 +67,14 @@ public class MediaOutputAdapterTest extends SysuiTestCase { private MediaOutputAdapter mMediaOutputAdapter; private MediaOutputAdapter.MediaDeviceViewHolder mViewHolder; private List<MediaDevice> mMediaDevices = new ArrayList<>(); + MediaOutputSeekbar mSpyMediaOutputSeekbar; @Before public void setUp() { mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController, mMediaOutputDialog); mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter .onCreateViewHolder(new LinearLayout(mContext), 0); + mSpyMediaOutputSeekbar = spy(mViewHolder.mSeekBar); when(mMediaOutputController.getMediaDevices()).thenReturn(mMediaDevices); when(mMediaOutputController.hasAdjustVolumeUserRestriction()).thenReturn(false); @@ -169,6 +174,16 @@ public class MediaOutputAdapterTest extends SysuiTestCase { } @Test + public void onBindViewHolder_initSeekbar_setsVolume() { + when(mMediaDevice1.getMaxVolume()).thenReturn(TEST_MAX_VOLUME); + when(mMediaDevice1.getCurrentVolume()).thenReturn(TEST_CURRENT_VOLUME); + mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); + + assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mSeekBar.getVolume()).isEqualTo(TEST_CURRENT_VOLUME); + } + + @Test public void onBindViewHolder_bindNonActiveConnectedDevice_verifyView() { mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java index e3b5059131fa..9eaa20c2afed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java @@ -32,6 +32,7 @@ import android.media.session.MediaController; import android.media.session.MediaSessionManager; import android.media.session.PlaybackState; import android.os.Bundle; +import android.os.PowerExemptionManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; @@ -86,6 +87,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { NearbyMediaDevicesManager.class); private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class); private final AudioManager mAudioManager = mock(AudioManager.class); + private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class); private List<MediaController> mMediaControllers = new ArrayList<>(); private MediaOutputBaseDialogImpl mMediaOutputBaseDialogImpl; @@ -110,7 +112,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, mMediaSessionManager, mLocalBluetoothManager, mStarter, mNotificationEntryManager, mDialogLaunchAnimator, - Optional.of(mNearbyMediaDevicesManager), mAudioManager); + Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager); mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender, mMediaOutputController); mMediaOutputBaseDialogImpl.onCreate(new Bundle()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java index feed3347f3e2..2bf5f0fcbfcb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java @@ -19,6 +19,9 @@ package com.android.systemui.media.dialog; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -34,10 +37,12 @@ import android.graphics.drawable.Icon; import android.media.AudioManager; import android.media.MediaDescription; import android.media.MediaMetadata; +import android.media.MediaRoute2Info; import android.media.NearbyDevice; import android.media.RoutingSessionInfo; import android.media.session.MediaController; import android.media.session.MediaSessionManager; +import android.os.PowerExemptionManager; import android.os.RemoteException; import android.service.notification.StatusBarNotification; import android.testing.AndroidTestingRunner; @@ -97,6 +102,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { private RoutingSessionInfo mRemoteSessionInfo = mock(RoutingSessionInfo.class); private ActivityStarter mStarter = mock(ActivityStarter.class); private AudioManager mAudioManager = mock(AudioManager.class); + private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class); private CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class); private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class); private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock( @@ -125,7 +131,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mMediaOutputController = new MediaOutputController(mSpyContext, TEST_PACKAGE_NAME, mMediaSessionManager, mLocalBluetoothManager, mStarter, mNotifCollection, mDialogLaunchAnimator, - Optional.of(mNearbyMediaDevicesManager), mAudioManager); + Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager); mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager); mMediaOutputController.mLocalMediaManager = mLocalMediaManager; MediaDescription.Builder builder = new MediaDescription.Builder(); @@ -177,7 +183,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mMediaOutputController = new MediaOutputController(mSpyContext, null, mMediaSessionManager, mLocalBluetoothManager, mStarter, mNotifCollection, mDialogLaunchAnimator, - Optional.of(mNearbyMediaDevicesManager), mAudioManager); + Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager); mMediaOutputController.start(mCb); @@ -206,7 +212,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mMediaOutputController = new MediaOutputController(mSpyContext, null, mMediaSessionManager, mLocalBluetoothManager, mStarter, mNotifCollection, mDialogLaunchAnimator, - Optional.of(mNearbyMediaDevicesManager), mAudioManager); + Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager); mMediaOutputController.start(mCb); @@ -511,7 +517,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mMediaOutputController = new MediaOutputController(mSpyContext, null, mMediaSessionManager, mLocalBluetoothManager, mStarter, mNotifCollection, mDialogLaunchAnimator, - Optional.of(mNearbyMediaDevicesManager), mAudioManager); + Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager); assertThat(mMediaOutputController.getNotificationIcon()).isNull(); } @@ -591,4 +597,20 @@ public class MediaOutputControllerTest extends SysuiTestCase { assertThat(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).isTrue(); } + + @Test + public void setTemporaryAllowListExceptionIfNeeded_fromRemoteToBluetooth_addsAllowList() { + when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1); + when(mMediaDevice1.getDeviceType()).thenReturn( + MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE); + when(mMediaDevice1.getFeatures()).thenReturn( + ImmutableList.of(MediaRoute2Info.FEATURE_REMOTE_AUDIO_PLAYBACK)); + when(mMediaDevice2.getDeviceType()).thenReturn( + MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE); + + mMediaOutputController.setTemporaryAllowListExceptionIfNeeded(mMediaDevice2); + + verify(mPowerExemptionManager).addToTemporaryAllowList(anyString(), anyInt(), anyString(), + anyLong()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java index b16c928adcd4..c45db05bacee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java @@ -29,6 +29,7 @@ import android.media.MediaRoute2Info; import android.media.session.MediaController; import android.media.session.MediaSessionManager; import android.media.session.PlaybackState; +import android.os.PowerExemptionManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; @@ -84,6 +85,7 @@ public class MediaOutputDialogTest extends SysuiTestCase { private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock( NearbyMediaDevicesManager.class); private final AudioManager mAudioManager = mock(AudioManager.class); + private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class); private List<MediaController> mMediaControllers = new ArrayList<>(); private MediaOutputDialog mMediaOutputDialog; @@ -103,7 +105,7 @@ public class MediaOutputDialogTest extends SysuiTestCase { mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, mMediaSessionManager, mLocalBluetoothManager, mStarter, mNotificationEntryManager, mDialogLaunchAnimator, - Optional.of(mNearbyMediaDevicesManager), mAudioManager); + Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager); mMediaOutputController.mLocalMediaManager = mLocalMediaManager; mMediaOutputDialog = new MediaOutputDialog(mContext, false, mBroadcastSender, mMediaOutputController, mUiEventLogger); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java index 379bb4fd6336..4534ae6448ec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.when; import android.media.AudioManager; import android.media.session.MediaSessionManager; +import android.os.PowerExemptionManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; @@ -70,6 +71,7 @@ public class MediaOutputGroupDialogTest extends SysuiTestCase { private NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock( NearbyMediaDevicesManager.class); private final AudioManager mAudioManager = mock(AudioManager.class); + private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class); private MediaOutputGroupDialog mMediaOutputGroupDialog; private MediaOutputController mMediaOutputController; @@ -80,7 +82,7 @@ public class MediaOutputGroupDialogTest extends SysuiTestCase { mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, mMediaSessionManager, mLocalBluetoothManager, mStarter, mNotificationEntryManager, mDialogLaunchAnimator, - Optional.of(mNearbyMediaDevicesManager), mAudioManager); + Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager); mMediaOutputController.mLocalMediaManager = mLocalMediaManager; mMediaOutputGroupDialog = new MediaOutputGroupDialog(mContext, false, mBroadcastSender, mMediaOutputController); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt index b9a69bb8641a..1527f0d0d71f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt @@ -25,6 +25,7 @@ import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.view.WindowManager +import android.view.accessibility.AccessibilityManager import android.widget.ImageView import androidx.test.filters.SmallTest import com.android.systemui.R @@ -65,6 +66,8 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() { @Mock private lateinit var logger: MediaTttLogger @Mock + private lateinit var accessibilityManager: AccessibilityManager + @Mock private lateinit var windowManager: WindowManager @Mock private lateinit var viewUtil: ViewUtil @@ -88,11 +91,21 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() { )).thenReturn(applicationInfo) context.setMockPackageManager(packageManager) + whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())) + .thenReturn(TIMEOUT_MS.toInt()) + fakeClock = FakeSystemClock() fakeExecutor = FakeExecutor(fakeClock) controllerCommon = TestControllerCommon( - context, logger, windowManager, viewUtil, fakeExecutor, tapGestureDetector, powerManager + context, + logger, + windowManager, + viewUtil, + fakeExecutor, + accessibilityManager, + tapGestureDetector, + powerManager ) } @@ -344,6 +357,7 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() { windowManager: WindowManager, viewUtil: ViewUtil, @Main mainExecutor: DelayableExecutor, + accessibilityManager: AccessibilityManager, tapGestureDetector: TapGestureDetector, powerManager: PowerManager ) : MediaTttChipControllerCommon<ChipInfo>( @@ -352,6 +366,7 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() { windowManager, viewUtil, mainExecutor, + accessibilityManager, tapGestureDetector, powerManager, R.layout.media_ttt_chip @@ -364,7 +379,7 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() { } inner class ChipInfo : ChipInfoCommon { - override fun getTimeoutMs() = TIMEOUT_MS + override fun getTimeoutMs() = 1L } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt index 9edc4f4c71c3..bbc564193080 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt @@ -28,6 +28,7 @@ import android.testing.TestableLooper import android.view.View import android.view.ViewGroup import android.view.WindowManager +import android.view.accessibility.AccessibilityManager import android.widget.ImageView import androidx.test.filters.SmallTest import com.android.internal.logging.testing.UiEventLoggerFake @@ -65,6 +66,8 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { @Mock private lateinit var logger: MediaTttLogger @Mock + private lateinit var accessibilityManager: AccessibilityManager + @Mock private lateinit var powerManager: PowerManager @Mock private lateinit var windowManager: WindowManager @@ -99,6 +102,7 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { windowManager, viewUtil, FakeExecutor(FakeSystemClock()), + accessibilityManager, TapGestureDetector(context), powerManager, Handler.getMain(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt index a8c72ddfd5d7..7ca0cd34ab26 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt @@ -27,6 +27,7 @@ import android.testing.TestableLooper import android.view.View import android.view.ViewGroup import android.view.WindowManager +import android.view.accessibility.AccessibilityManager import android.widget.ImageView import android.widget.TextView import androidx.test.filters.SmallTest @@ -67,6 +68,8 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { @Mock private lateinit var logger: MediaTttLogger @Mock + private lateinit var accessibilityManager: AccessibilityManager + @Mock private lateinit var powerManager: PowerManager @Mock private lateinit var windowManager: WindowManager @@ -95,9 +98,12 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { fakeClock = FakeSystemClock() fakeExecutor = FakeExecutor(fakeClock) + uiEventLoggerFake = UiEventLoggerFake() senderUiEventLogger = MediaTttSenderUiEventLogger(uiEventLoggerFake) + whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenReturn(TIMEOUT) + controllerSender = MediaTttChipControllerSender( commandQueue, context, @@ -105,6 +111,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { windowManager, viewUtil, fakeExecutor, + accessibilityManager, TapGestureDetector(context), powerManager, senderUiEventLogger @@ -592,7 +599,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { fakeClock.advanceTime(1000L) controllerSender.removeChip("fakeRemovalReason") - fakeClock.advanceTime(state.state.timeout + 1) + fakeClock.advanceTime(TIMEOUT + 1L) verify(windowManager).removeView(any()) } @@ -615,7 +622,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { fakeClock.advanceTime(1000L) controllerSender.removeChip("fakeRemovalReason") - fakeClock.advanceTime(state.state.timeout + 1) + fakeClock.advanceTime(TIMEOUT + 1L) verify(windowManager).removeView(any()) } @@ -674,6 +681,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { private const val APP_NAME = "Fake app name" private const val OTHER_DEVICE_NAME = "My Tablet" private const val PACKAGE_NAME = "com.android.systemui" +private const val TIMEOUT = 10000 private val routeInfo = MediaRoute2Info.Builder("id", OTHER_DEVICE_NAME) .addFeature("feature") diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt index 489c8c86028e..bf237abba8fa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt @@ -57,6 +57,7 @@ class QSContainerImplTest : SysuiTestCase() { @Test fun testContainerBottomPadding() { + val originalPadding = qsPanelContainer.paddingBottom qsContainer.updateResources( qsPanelController, quickStatusBarHeaderController @@ -66,7 +67,7 @@ class QSContainerImplTest : SysuiTestCase() { anyInt(), anyInt(), anyInt(), - eq(mContext.resources.getDimensionPixelSize(R.dimen.footer_actions_height)) + eq(originalPadding) ) } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt index 60cfd7249919..b98be75a51c7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt @@ -150,6 +150,14 @@ class QSPanelTest : SysuiTestCase() { assertThat(footer.isVisibleToUser).isTrue() } + @Test + fun testBottomPadding() { + val padding = 10 + context.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_bottom, padding) + qsPanel.updatePadding() + assertThat(qsPanel.paddingBottom).isEqualTo(padding) + } + private infix fun View.isLeftOf(other: View): Boolean { val rect = Rect() getBoundsOnScreen(rect) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt index d61989fc3128..131eac668af3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt @@ -110,6 +110,7 @@ class ClockRegistryTest : SysuiTestCase() { get() = settingValue set(value) { settingValue = value } } + registry.isEnabled = true verify(mockPluginManager) .addPluginListener(captor.capture(), eq(ClockProviderPlugin::class.java)) @@ -129,13 +130,16 @@ class ClockRegistryTest : SysuiTestCase() { pluginListener.onPluginConnected(plugin1, mockContext) pluginListener.onPluginConnected(plugin2, mockContext) val list = registry.getClocks() - assertEquals(list, listOf( - ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME), - ClockMetadata("clock_1", "clock 1"), - ClockMetadata("clock_2", "clock 2"), - ClockMetadata("clock_3", "clock 3"), - ClockMetadata("clock_4", "clock 4") - )) + assertEquals( + list, + listOf( + ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME), + ClockMetadata("clock_1", "clock 1"), + ClockMetadata("clock_2", "clock 2"), + ClockMetadata("clock_3", "clock 3"), + ClockMetadata("clock_4", "clock 4") + ) + ) } @Test @@ -157,11 +161,14 @@ class ClockRegistryTest : SysuiTestCase() { pluginListener.onPluginConnected(plugin1, mockContext) pluginListener.onPluginConnected(plugin2, mockContext) val list = registry.getClocks() - assertEquals(list, listOf( - ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME), - ClockMetadata("clock_1", "clock 1"), - ClockMetadata("clock_2", "clock 2") - )) + assertEquals( + list, + listOf( + ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME), + ClockMetadata("clock_1", "clock 1"), + ClockMetadata("clock_2", "clock 2") + ) + ) assertEquals(registry.createExampleClock("clock_1"), mockClock) assertEquals(registry.createExampleClock("clock_2"), mockClock) @@ -221,7 +228,7 @@ class ClockRegistryTest : SysuiTestCase() { pluginListener.onPluginConnected(plugin2, mockContext) var changeCallCount = 0 - registry.registerClockChangeListener({ changeCallCount++ }) + registry.registerClockChangeListener { changeCallCount++ } pluginListener.onPluginDisconnected(plugin1) assertEquals(0, changeCallCount) diff --git a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt index 2f0f0a0a1b8f..37f96c8d7023 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt @@ -36,17 +36,17 @@ import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.withArgCaptor import com.google.common.truth.Truth.assertThat +import java.util.Optional +import java.util.concurrent.Executor import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Spy import org.mockito.Mockito -import org.mockito.Mockito.`when` import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations -import java.util.Optional -import java.util.concurrent.Executor +import org.mockito.Spy @SmallTest @RunWith(AndroidTestingRunner::class) @@ -87,6 +87,34 @@ class DreamSmartspaceControllerTest : SysuiTestCase() { private lateinit var controller: DreamSmartspaceController + /** + * A class which implements SmartspaceView and extends View. This is mocked to provide the right + * object inheritance and interface implementation used in DreamSmartspaceController + */ + private class TestView(context: Context?) : View(context), SmartspaceView { + override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {} + + override fun setPrimaryTextColor(color: Int) {} + + override fun setIsDreaming(isDreaming: Boolean) {} + + override fun setDozeAmount(amount: Float) {} + + override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) {} + + override fun setFalsingManager(falsingManager: FalsingManager?) {} + + override fun setDnd(image: Drawable?, description: String?) {} + + override fun setNextAlarm(image: Drawable?, description: String?) {} + + override fun setMediaTarget(target: SmartspaceTarget?) {} + + override fun getSelectedPage(): Int { return 0; } + + override fun getCurrentCardTopPadding(): Int { return 0; } + } + @Before fun setup() { MockitoAnnotations.initMocks(this) @@ -130,34 +158,6 @@ class DreamSmartspaceControllerTest : SysuiTestCase() { } /** - * A class which implements SmartspaceView and extends View. This is mocked to provide the right - * object inheritance and interface implementation used in DreamSmartspaceController - */ - private class TestView(context: Context?) : View(context), SmartspaceView { - override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {} - - override fun setPrimaryTextColor(color: Int) {} - - override fun setIsDreaming(isDreaming: Boolean) {} - - override fun setDozeAmount(amount: Float) {} - - override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) {} - - override fun setFalsingManager(falsingManager: FalsingManager?) {} - - override fun setDnd(image: Drawable?, description: String?) {} - - override fun setNextAlarm(image: Drawable?, description: String?) {} - - override fun setMediaTarget(target: SmartspaceTarget?) {} - - override fun getSelectedPage(): Int { return 0; } - - override fun getCurrentCardTopPadding(): Int { return 0; } - } - - /** * Ensures session begins when a view is attached. */ @Test @@ -180,16 +180,4 @@ class DreamSmartspaceControllerTest : SysuiTestCase() { verify(session).close() } - - /** - * Ensures setIsDreaming(true) is called when the view is built. - */ - @Test - fun testSetIsDreamingTrueOnViewCreate() { - `when`(precondition.conditionsMet()).thenReturn(true) - - controller.buildAndConnectView(Mockito.mock(ViewGroup::class.java)) - - verify(smartspaceView).setIsDreaming(true) - } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NoManSimulator.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NoManSimulator.java index 4507366e3073..ee7d55864931 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NoManSimulator.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NoManSimulator.java @@ -85,6 +85,11 @@ public class NoManSimulator { mRankings.put(key, ranking); } + /** This is for testing error cases: b/216384850 */ + public Ranking removeRankingWithoutEvent(String key) { + return mRankings.remove(key); + } + private RankingMap buildRankingMap() { return new RankingMap(mRankings.values().toArray(new Ranking[0])); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java index 958d54230f1c..f286349971d2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java @@ -1492,6 +1492,80 @@ public class NotifCollectionTest extends SysuiTestCase { } @Test + public void testMissingRankingWhenRemovalFeatureIsDisabled() { + // GIVEN a pipeline with one two notifications + when(mNotifPipelineFlags.removeUnrankedNotifs()).thenReturn(false); + String key1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 1, "myTag")).key; + String key2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 2, "myTag")).key; + NotificationEntry entry1 = mCollectionListener.getEntry(key1); + NotificationEntry entry2 = mCollectionListener.getEntry(key2); + clearInvocations(mCollectionListener); + + // GIVEN the message for removing key1 gets does not reach NotifCollection + Ranking ranking1 = mNoMan.removeRankingWithoutEvent(key1); + // WHEN the message for removing key2 arrives + mNoMan.retractNotif(entry2.getSbn(), REASON_APP_CANCEL); + + // THEN only entry2 gets removed + verify(mCollectionListener).onEntryRemoved(eq(entry2), eq(REASON_APP_CANCEL)); + verify(mCollectionListener).onEntryCleanUp(eq(entry2)); + verify(mCollectionListener).onRankingApplied(); + verifyNoMoreInteractions(mCollectionListener); + verify(mLogger).logMissingRankings(eq(List.of(entry1)), eq(1), any()); + verify(mLogger, never()).logRecoveredRankings(any()); + clearInvocations(mCollectionListener, mLogger); + + // WHEN a ranking update includes key1 again + mNoMan.setRanking(key1, ranking1); + mNoMan.issueRankingUpdate(); + + // VERIFY that we do nothing but log the 'recovery' + verify(mCollectionListener).onRankingUpdate(any()); + verify(mCollectionListener).onRankingApplied(); + verifyNoMoreInteractions(mCollectionListener); + verify(mLogger, never()).logMissingRankings(any(), anyInt(), any()); + verify(mLogger).logRecoveredRankings(eq(List.of(key1))); + } + + @Test + public void testMissingRankingWhenRemovalFeatureIsEnabled() { + // GIVEN a pipeline with one two notifications + when(mNotifPipelineFlags.removeUnrankedNotifs()).thenReturn(true); + String key1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 1, "myTag")).key; + String key2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 2, "myTag")).key; + NotificationEntry entry1 = mCollectionListener.getEntry(key1); + NotificationEntry entry2 = mCollectionListener.getEntry(key2); + clearInvocations(mCollectionListener); + + // GIVEN the message for removing key1 gets does not reach NotifCollection + Ranking ranking1 = mNoMan.removeRankingWithoutEvent(key1); + // WHEN the message for removing key2 arrives + mNoMan.retractNotif(entry2.getSbn(), REASON_APP_CANCEL); + + // THEN both entry1 and entry2 get removed + verify(mCollectionListener).onEntryRemoved(eq(entry2), eq(REASON_APP_CANCEL)); + verify(mCollectionListener).onEntryRemoved(eq(entry1), eq(REASON_UNKNOWN)); + verify(mCollectionListener).onEntryCleanUp(eq(entry2)); + verify(mCollectionListener).onEntryCleanUp(eq(entry1)); + verify(mCollectionListener).onRankingApplied(); + verifyNoMoreInteractions(mCollectionListener); + verify(mLogger).logMissingRankings(eq(List.of(entry1)), eq(1), any()); + verify(mLogger, never()).logRecoveredRankings(any()); + clearInvocations(mCollectionListener, mLogger); + + // WHEN a ranking update includes key1 again + mNoMan.setRanking(key1, ranking1); + mNoMan.issueRankingUpdate(); + + // VERIFY that we do nothing but log the 'recovery' + verify(mCollectionListener).onRankingUpdate(any()); + verify(mCollectionListener).onRankingApplied(); + verifyNoMoreInteractions(mCollectionListener); + verify(mLogger, never()).logMissingRankings(any(), anyInt(), any()); + verify(mLogger).logRecoveredRankings(eq(List.of(key1))); + } + + @Test public void testRegisterFutureDismissal() throws RemoteException { // GIVEN a pipeline with one notification NotifEvent notifEvent = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java deleted file mode 100644 index 6171e2f760d3..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.notification.collection.coordinator; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import android.os.Handler; -import android.os.UserHandle; -import android.testing.AndroidTestingRunner; - -import androidx.test.filters.SmallTest; - -import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.statusbar.notification.collection.NotifPipeline; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; -import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; -import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; -import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider; -import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider; -import com.android.systemui.statusbar.policy.KeyguardStateController; - -import org.junit.Before; -import org.junit.Ignore; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -/** - * TODO(b/224771204) Create test cases - */ -@SmallTest -@RunWith(AndroidTestingRunner.class) -@Ignore -public class KeyguardCoordinatorTest extends SysuiTestCase { - private static final int NOTIF_USER_ID = 0; - private static final int CURR_USER_ID = 1; - - @Mock private Handler mMainHandler; - @Mock private KeyguardStateController mKeyguardStateController; - @Mock private BroadcastDispatcher mBroadcastDispatcher; - @Mock private StatusBarStateController mStatusBarStateController; - @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; - @Mock private HighPriorityProvider mHighPriorityProvider; - @Mock private SectionHeaderVisibilityProvider mSectionHeaderVisibilityProvider; - @Mock private NotifPipeline mNotifPipeline; - @Mock private KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider; - - private NotificationEntry mEntry; - private NotifFilter mKeyguardFilter; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - KeyguardCoordinator keyguardCoordinator = new KeyguardCoordinator( - mKeyguardNotificationVisibilityProvider, - mSectionHeaderVisibilityProvider, - mock(SharedCoordinatorLogger.class), - mStatusBarStateController); - - mEntry = new NotificationEntryBuilder() - .setUser(new UserHandle(NOTIF_USER_ID)) - .build(); - - ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class); - keyguardCoordinator.attach(mNotifPipeline); - verify(mNotifPipeline, times(1)).addFinalizeFilter(filterCaptor.capture()); - mKeyguardFilter = filterCaptor.getValue(); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt new file mode 100644 index 000000000000..8c506a6d16ae --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.notification.collection.coordinator + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.statusbar.StatusBarState +import com.android.systemui.statusbar.notification.collection.NotifPipeline +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter +import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider +import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.withArgCaptor +import java.util.function.Consumer +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class KeyguardCoordinatorTest : SysuiTestCase() { + private val notifPipeline: NotifPipeline = mock() + private val keyguardNotifVisibilityProvider: KeyguardNotificationVisibilityProvider = mock() + private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider = mock() + private val sharedCoordinatorLogger: SharedCoordinatorLogger = mock() + private val statusBarStateController: StatusBarStateController = mock() + + private lateinit var onStateChangeListener: Consumer<String> + private lateinit var keyguardFilter: NotifFilter + + @Before + fun setup() { + val keyguardCoordinator = KeyguardCoordinator( + keyguardNotifVisibilityProvider, + sectionHeaderVisibilityProvider, + sharedCoordinatorLogger, + statusBarStateController + ) + keyguardCoordinator.attach(notifPipeline) + onStateChangeListener = withArgCaptor { + verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture()) + } + keyguardFilter = withArgCaptor { + verify(notifPipeline).addFinalizeFilter(capture()) + } + } + + @Test + fun testSetSectionHeadersVisibleInShade() { + clearInvocations(sectionHeaderVisibilityProvider) + whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE) + onStateChangeListener.accept("state change") + verify(sectionHeaderVisibilityProvider).sectionHeadersVisible = eq(true) + } + + @Test + fun testSetSectionHeadersNotVisibleOnKeyguard() { + clearInvocations(sectionHeaderVisibilityProvider) + whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) + onStateChangeListener.accept("state change") + verify(sectionHeaderVisibilityProvider).sectionHeadersVisible = eq(false) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt index 40859d0e6304..3f3de009fb04 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt @@ -37,8 +37,8 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations.initMocks import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations.initMocks @SmallTest @RunWith(AndroidTestingRunner::class) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLoggerTest.kt new file mode 100644 index 000000000000..6c07174bce65 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLoggerTest.kt @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.notification.collection.notifcollection + +import android.service.notification.NotificationListenerService.RankingMap +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class NotifCollectionLoggerTest : SysuiTestCase() { + private val logger: NotifCollectionLogger = mock() + private val entry1: NotificationEntry = NotificationEntryBuilder().setId(1).build() + private val entry2: NotificationEntry = NotificationEntryBuilder().setId(2).build() + + private fun mapOfEntries(vararg entries: NotificationEntry): Map<String, NotificationEntry> = + entries.associateBy { it.key } + + private fun rankingMapOf(vararg entries: NotificationEntry): RankingMap = + RankingMap(entries.map { it.ranking }.toTypedArray()) + + @Test + fun testMaybeLogInconsistentRankings_logsNewlyInconsistentRanking() { + val rankingMap = rankingMapOf(entry1) + maybeLogInconsistentRankings( + logger = logger, + oldKeysWithoutRankings = emptySet(), + newEntriesWithoutRankings = mapOfEntries(entry2), + rankingMap = rankingMap + ) + verify(logger).logMissingRankings( + newlyInconsistentEntries = eq(listOf(entry2)), + totalInconsistent = eq(1), + rankingMap = eq(rankingMap), + ) + verifyNoMoreInteractions(logger) + } + + @Test + fun testMaybeLogInconsistentRankings_doesNotLogAlreadyInconsistentRanking() { + maybeLogInconsistentRankings( + logger = logger, + oldKeysWithoutRankings = setOf(entry2.key), + newEntriesWithoutRankings = mapOfEntries(entry2), + rankingMap = rankingMapOf(entry1) + ) + verifyNoMoreInteractions(logger) + } + + @Test + fun testMaybeLogInconsistentRankings_logsWhenRankingIsAdded() { + maybeLogInconsistentRankings( + logger = logger, + oldKeysWithoutRankings = setOf(entry2.key), + newEntriesWithoutRankings = mapOfEntries(), + rankingMap = rankingMapOf(entry1, entry2) + ) + verify(logger).logRecoveredRankings( + newlyConsistentKeys = eq(listOf(entry2.key)), + ) + verifyNoMoreInteractions(logger) + } + + @Test + fun testMaybeLogInconsistentRankings_doesNotLogsWhenEntryIsRemoved() { + maybeLogInconsistentRankings( + logger = logger, + oldKeysWithoutRankings = setOf(entry2.key), + newEntriesWithoutRankings = mapOfEntries(), + rankingMap = rankingMapOf(entry1) + ) + verifyNoMoreInteractions(logger) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt index ff601938d544..ac254abe60b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt @@ -413,4 +413,4 @@ private fun buildSection( return nodeController } }, index) -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java index 0d5a5fe086a3..3f641df376ed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java @@ -72,32 +72,32 @@ public class HeadsUpViewBinderTest extends SysuiTestCase { }); mViewBinder.bindHeadsUpView(mEntry, null); - verify(mLogger).startBindingHun(eq("key")); + verify(mLogger).startBindingHun(eq(mEntry)); verifyNoMoreInteractions(mLogger); clearInvocations(mLogger); callback.get().onBindFinished(mEntry); - verify(mLogger).entryBoundSuccessfully(eq("key")); + verify(mLogger).entryBoundSuccessfully(eq(mEntry)); verifyNoMoreInteractions(mLogger); clearInvocations(mLogger); mViewBinder.bindHeadsUpView(mEntry, null); - verify(mLogger).startBindingHun(eq("key")); + verify(mLogger).startBindingHun(eq(mEntry)); verifyNoMoreInteractions(mLogger); clearInvocations(mLogger); callback.get().onBindFinished(mEntry); - verify(mLogger).entryBoundSuccessfully(eq("key")); + verify(mLogger).entryBoundSuccessfully(eq(mEntry)); verifyNoMoreInteractions(mLogger); clearInvocations(mLogger); mViewBinder.unbindHeadsUpView(mEntry); - verify(mLogger).entryContentViewMarkedFreeable(eq("key")); + verify(mLogger).entryContentViewMarkedFreeable(eq(mEntry)); verifyNoMoreInteractions(mLogger); clearInvocations(mLogger); callback.get().onBindFinished(mEntry); - verify(mLogger).entryUnbound(eq("key")); + verify(mLogger).entryUnbound(eq(mEntry)); verifyNoMoreInteractions(mLogger); clearInvocations(mLogger); } @@ -111,12 +111,12 @@ public class HeadsUpViewBinderTest extends SysuiTestCase { }); mViewBinder.bindHeadsUpView(mEntry, null); - verify(mLogger).startBindingHun(eq("key")); + verify(mLogger).startBindingHun(eq(mEntry)); verifyNoMoreInteractions(mLogger); clearInvocations(mLogger); mViewBinder.abortBindCallback(mEntry); - verify(mLogger).currentOngoingBindingAborted(eq("key")); + verify(mLogger).currentOngoingBindingAborted(eq(mEntry)); verifyNoMoreInteractions(mLogger); clearInvocations(mLogger); @@ -135,18 +135,18 @@ public class HeadsUpViewBinderTest extends SysuiTestCase { }); mViewBinder.bindHeadsUpView(mEntry, null); - verify(mLogger).startBindingHun(eq("key")); + verify(mLogger).startBindingHun(eq(mEntry)); verifyNoMoreInteractions(mLogger); clearInvocations(mLogger); mViewBinder.unbindHeadsUpView(mEntry); - verify(mLogger).currentOngoingBindingAborted(eq("key")); - verify(mLogger).entryContentViewMarkedFreeable(eq("key")); + verify(mLogger).currentOngoingBindingAborted(eq(mEntry)); + verify(mLogger).entryContentViewMarkedFreeable(eq(mEntry)); verifyNoMoreInteractions(mLogger); clearInvocations(mLogger); callback.get().onBindFinished(mEntry); - verify(mLogger).entryUnbound(eq("key")); + verify(mLogger).entryUnbound(eq(mEntry)); verifyNoMoreInteractions(mLogger); clearInvocations(mLogger); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index b94aac21acad..3d03c4750869 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -223,10 +223,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - public void disable_isDozingButNoCustomClock_clockAndSystemInfoVisible() { + public void disable_isDozing_clockAndSystemInfoVisible() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mStatusBarStateController.isDozing()).thenReturn(true); - when(mNotificationPanelViewController.hasCustomClock()).thenReturn(false); fragment.disable(DEFAULT_DISPLAY, 0, 0, false); @@ -235,10 +234,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - public void disable_customClockButNotDozing_clockAndSystemInfoVisible() { + public void disable_NotDozing_clockAndSystemInfoVisible() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mStatusBarStateController.isDozing()).thenReturn(false); - when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true); fragment.disable(DEFAULT_DISPLAY, 0, 0, false); @@ -247,40 +245,6 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - public void disable_dozingAndCustomClock_clockAndSystemInfoHidden() { - CollapsedStatusBarFragment fragment = resumeAndGetFragment(); - when(mStatusBarStateController.isDozing()).thenReturn(true); - when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true); - - // Make sure they start out as visible - assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility()); - assertEquals(View.VISIBLE, getClockView().getVisibility()); - - fragment.disable(DEFAULT_DISPLAY, 0, 0, false); - - assertEquals(View.INVISIBLE, getSystemIconAreaView().getVisibility()); - assertEquals(View.GONE, getClockView().getVisibility()); - } - - @Test - public void onDozingChanged_clockAndSystemInfoVisibilitiesUpdated() { - CollapsedStatusBarFragment fragment = resumeAndGetFragment(); - when(mStatusBarStateController.isDozing()).thenReturn(true); - when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true); - - // Make sure they start out as visible - assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility()); - assertEquals(View.VISIBLE, getClockView().getVisibility()); - - fragment.onDozingChanged(true); - - // When this callback is triggered, we want to make sure the clock and system info - // visibilities are recalculated. Since dozing=true, they shouldn't be visible. - assertEquals(View.INVISIBLE, getSystemIconAreaView().getVisibility()); - assertEquals(View.GONE, getClockView().getVisibility()); - } - - @Test public void disable_headsUpShouldBeVisibleTrue_clockDisabled() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionControllerTest.kt index b24b348ac316..cafe113e7872 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionControllerTest.kt @@ -5,6 +5,8 @@ import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager +import com.android.systemui.statusbar.StatusBarState +import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.phone.ScrimController import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent import com.android.systemui.statusbar.policy.FakeConfigurationController @@ -13,6 +15,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @@ -21,6 +24,7 @@ class ScrimShadeTransitionControllerTest : SysuiTestCase() { @Mock private lateinit var scrimController: ScrimController @Mock private lateinit var dumpManager: DumpManager + @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController private val configurationController = FakeConfigurationController() private lateinit var controller: ScrimShadeTransitionController @@ -31,9 +35,14 @@ class ScrimShadeTransitionControllerTest : SysuiTestCase() { context.ensureTestableResources() controller = ScrimShadeTransitionController( - configurationController, dumpManager, scrimController, context.resources + configurationController, + dumpManager, + scrimController, + context.resources, + statusBarStateController ) } + @Test fun onPanelExpansionChanged_inSingleShade_setsFractionEqualToEventFraction() { setSplitShadeEnabled(false) @@ -44,7 +53,9 @@ class ScrimShadeTransitionControllerTest : SysuiTestCase() { } @Test - fun onPanelExpansionChanged_inSplitShade_setsFractionBasedOnDragDownAmount() { + fun onPanelExpansionChanged_inSplitShade_unlockedShade_setsFractionBasedOnDragDownAmount() { + whenever(statusBarStateController.currentOrUpcomingState) + .thenReturn(StatusBarState.SHADE) val scrimShadeTransitionDistance = context.resources.getDimensionPixelSize(R.dimen.split_shade_scrim_transition_distance) setSplitShadeEnabled(true) @@ -55,6 +66,54 @@ class ScrimShadeTransitionControllerTest : SysuiTestCase() { verify(scrimController).setRawPanelExpansionFraction(expectedFraction) } + @Test + fun onPanelExpansionChanged_inSplitShade_largeDragDownAmount_fractionIsNotGreaterThan1() { + whenever(statusBarStateController.currentOrUpcomingState) + .thenReturn(StatusBarState.SHADE) + val scrimShadeTransitionDistance = + context.resources.getDimensionPixelSize(R.dimen.split_shade_scrim_transition_distance) + setSplitShadeEnabled(true) + + controller.onPanelExpansionChanged( + EXPANSION_EVENT.copy(dragDownPxAmount = 100f * scrimShadeTransitionDistance) + ) + + verify(scrimController).setRawPanelExpansionFraction(1f) + } + + @Test + fun onPanelExpansionChanged_inSplitShade_negativeDragDownAmount_fractionIsNotLessThan0() { + whenever(statusBarStateController.currentOrUpcomingState) + .thenReturn(StatusBarState.SHADE) + setSplitShadeEnabled(true) + + controller.onPanelExpansionChanged(EXPANSION_EVENT.copy(dragDownPxAmount = -100f)) + + verify(scrimController).setRawPanelExpansionFraction(0f) + } + + @Test + fun onPanelExpansionChanged_inSplitShade_onLockedShade_setsFractionEqualToEventFraction() { + whenever(statusBarStateController.currentOrUpcomingState) + .thenReturn(StatusBarState.SHADE_LOCKED) + setSplitShadeEnabled(true) + + controller.onPanelExpansionChanged(EXPANSION_EVENT) + + verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction) + } + + @Test + fun onPanelExpansionChanged_inSplitShade_onKeyguard_setsFractionEqualToEventFraction() { + whenever(statusBarStateController.currentOrUpcomingState) + .thenReturn(StatusBarState.KEYGUARD) + setSplitShadeEnabled(true) + + controller.onPanelExpansionChanged(EXPANSION_EVENT) + + verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction) + } + private fun setSplitShadeEnabled(enabled: Boolean) { overrideResource(R.bool.config_use_split_notification_shade, enabled) configurationController.notifyConfigurationChanged() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java index f39d6875cffc..e2b9a9ee7826 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java @@ -106,7 +106,7 @@ public class HeadsUpManagerTest extends AlertingNotificationManagerTest { public void testHunRemovedLogging() { mAlertEntry.mEntry = mEntry; mHeadsUpManager.onAlertEntryRemoved(mAlertEntry); - verify(mLogger, times(1)).logNotificationActuallyRemoved(eq(mEntry.getKey())); + verify(mLogger, times(1)).logNotificationActuallyRemoved(eq(mEntry)); } @Test diff --git a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java index 229799a8457d..d5991d3930a8 100644 --- a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java +++ b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java @@ -73,6 +73,9 @@ class AssociationStoreImpl implements AssociationStore { private final Set<OnChangeListener> mListeners = new LinkedHashSet<>(); void addAssociation(@NonNull AssociationInfo association) { + // Validity check first. + checkNotRevoked(association); + final int id = association.getId(); if (DEBUG) { @@ -99,6 +102,9 @@ class AssociationStoreImpl implements AssociationStore { } void updateAssociation(@NonNull AssociationInfo updated) { + // Validity check first. + checkNotRevoked(updated); + final int id = updated.getId(); if (DEBUG) { @@ -292,6 +298,9 @@ class AssociationStoreImpl implements AssociationStore { } void setAssociations(Collection<AssociationInfo> allAssociations) { + // Validity check first. + allAssociations.forEach(AssociationStoreImpl::checkNotRevoked); + if (DEBUG) { Log.i(TAG, "setAssociations() n=" + allAssociations.size()); final StringJoiner stringJoiner = new StringJoiner(", "); @@ -324,4 +333,11 @@ class AssociationStoreImpl implements AssociationStore { mAddressMap.clear(); mCachedPerUser.clear(); } + + private static void checkNotRevoked(@NonNull AssociationInfo association) { + if (association.isRevoked()) { + throw new IllegalArgumentException( + "Revoked (removed) associations MUST NOT appear in the AssociationStore"); + } + } } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 04468ed47640..ab9966f218c1 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -19,6 +19,7 @@ package com.android.server.companion; import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES; import static android.Manifest.permission.MANAGE_COMPANION_DEVICES; +import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE; import static android.content.pm.PackageManager.CERT_INPUT_SHA256; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Process.SYSTEM_UID; @@ -49,6 +50,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.UserIdInt; +import android.app.ActivityManager; +import android.app.ActivityManager.RunningAppProcessInfo; import android.app.ActivityManagerInternal; import android.app.AppOpsManager; import android.app.NotificationManager; @@ -93,6 +96,7 @@ import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IAppOpsService; import com.android.internal.content.PackageMonitor; +import com.android.internal.infra.PerUser; import com.android.internal.notification.NotificationAccessConfirmationActivityContract; import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; @@ -110,6 +114,7 @@ import com.android.server.pm.UserManagerInternal; import java.io.File; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -133,6 +138,9 @@ public class CompanionDeviceManagerService extends SystemService { private static final long ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT = DAYS.toMillis(90); + private final ActivityManager mActivityManager; + private final OnPackageVisibilityChangeListener mOnPackageVisibilityChangeListener; + private PersistentDataStore mPersistentStore; private final PersistUserStateHandler mUserPersistenceHandler; @@ -160,12 +168,40 @@ public class CompanionDeviceManagerService extends SystemService { @GuardedBy("mPreviouslyUsedIds") private final SparseArray<Map<String, Set<Integer>>> mPreviouslyUsedIds = new SparseArray<>(); + /** + * A structure that consists of a set of revoked associations that pending for role holder + * removal per each user. + * + * @see #maybeRemoveRoleHolderForAssociation(AssociationInfo) + * @see #addToPendingRoleHolderRemoval(AssociationInfo) + * @see #removeFromPendingRoleHolderRemoval(AssociationInfo) + * @see #getPendingRoleHolderRemovalAssociationsForUser(int) + */ + @GuardedBy("mRevokedAssociationsPendingRoleHolderRemoval") + private final PerUserAssociationSet mRevokedAssociationsPendingRoleHolderRemoval = + new PerUserAssociationSet(); + /** + * Contains uid-s of packages pending to be removed from the role holder list (after + * revocation of an association), which will happen one the package is no longer visible to the + * user. + * For quicker uid -> (userId, packageName) look-up this is not a {@code Set<Integer>} but + * a {@code Map<Integer, String>} which maps uid-s to packageName-s (userId-s can be derived + * from uid-s using {@link UserHandle#getUserId(int)}). + * + * @see #maybeRemoveRoleHolderForAssociation(AssociationInfo) + * @see #addToPendingRoleHolderRemoval(AssociationInfo) + * @see #removeFromPendingRoleHolderRemoval(AssociationInfo) + */ + @GuardedBy("mRevokedAssociationsPendingRoleHolderRemoval") + private final Map<Integer, String> mUidsPendingRoleHolderRemoval = new HashMap<>(); + private final RemoteCallbackList<IOnAssociationsChangedListener> mListeners = new RemoteCallbackList<>(); public CompanionDeviceManagerService(Context context) { super(context); + mActivityManager = context.getSystemService(ActivityManager.class); mPowerWhitelistManager = context.getSystemService(PowerWhitelistManager.class); mAppOpsManager = IAppOpsService.Stub.asInterface( ServiceManager.getService(Context.APP_OPS_SERVICE)); @@ -176,6 +212,9 @@ public class CompanionDeviceManagerService extends SystemService { mUserPersistenceHandler = new PersistUserStateHandler(); mAssociationStore = new AssociationStoreImpl(); mSystemDataTransferRequestStore = new SystemDataTransferRequestStore(); + + mOnPackageVisibilityChangeListener = + new OnPackageVisibilityChangeListener(mActivityManager); } @Override @@ -217,7 +256,33 @@ public class CompanionDeviceManagerService extends SystemService { mUserManager.getAliveUsers(), allAssociations, mPreviouslyUsedIds); } - mAssociationStore.setAssociations(allAssociations); + final Set<AssociationInfo> activeAssociations = + new ArraySet<>(/* capacity */ allAssociations.size()); + // A set contains the userIds that need to persist state after remove the app + // from the list of role holders. + final Set<Integer> usersToPersistStateFor = new ArraySet<>(); + + for (AssociationInfo association : allAssociations) { + if (!association.isRevoked()) { + activeAssociations.add(association); + } else if (maybeRemoveRoleHolderForAssociation(association)) { + // Nothing more to do here, but we'll need to persist all the associations to the + // disk afterwards. + usersToPersistStateFor.add(association.getUserId()); + } else { + addToPendingRoleHolderRemoval(association); + } + } + + mAssociationStore.setAssociations(activeAssociations); + + // IMPORTANT: only do this AFTER mAssociationStore.setAssociations(), because + // persistStateForUser() queries AssociationStore. + // (If persistStateForUser() is invoked before mAssociationStore.setAssociations() it + // would effectively just clear-out all the persisted associations). + for (int userId : usersToPersistStateFor) { + persistStateForUser(userId); + } } @Override @@ -367,10 +432,18 @@ public class CompanionDeviceManagerService extends SystemService { } private void persistStateForUser(@UserIdInt int userId) { - final List<AssociationInfo> updatedAssociations = - mAssociationStore.getAssociationsForUser(userId); + // We want to store both active associations and the revoked (removed) association that we + // are keeping around for the final clean-up (delayed role holder removal). + final List<AssociationInfo> allAssociations; + // Start with the active associations - these we can get from the AssociationStore. + allAssociations = new ArrayList<>( + mAssociationStore.getAssociationsForUser(userId)); + // ... and add the revoked (removed) association, that are yet to be permanently removed. + allAssociations.addAll(getPendingRoleHolderRemovalAssociationsForUser(userId)); + final Map<String, Set<Integer>> usedIdsForUser = getPreviouslyUsedIdsForUser(userId); - mPersistentStore.persistStateForUser(userId, updatedAssociations, usedIdsForUser); + + mPersistentStore.persistStateForUser(userId, allAssociations, usedIdsForUser); } private void notifyListeners( @@ -438,13 +511,17 @@ public class CompanionDeviceManagerService extends SystemService { removalWindow = ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT; } - for (AssociationInfo ai : mAssociationStore.getAssociations()) { - if (!ai.isSelfManaged()) continue; - final boolean isInactive = currentTime - ai.getLastTimeConnectedMs() >= removalWindow; - if (isInactive) { - Slog.i(TAG, "Removing inactive self-managed association: " + ai.getId()); - disassociateInternal(ai.getId()); - } + for (AssociationInfo association : mAssociationStore.getAssociations()) { + if (!association.isSelfManaged()) continue; + + final boolean isInactive = + currentTime - association.getLastTimeConnectedMs() >= removalWindow; + if (!isInactive) continue; + + final int id = association.getId(); + + Slog.i(TAG, "Removing inactive self-managed association id=" + id); + disassociateInternal(id); } } @@ -712,7 +789,7 @@ public class CompanionDeviceManagerService extends SystemService { enforceCallerIsSystemOr(userId, packageName); AssociationInfo association = mAssociationStore.getAssociationsForPackageWithAddress( - userId, packageName, deviceAddress); + userId, packageName, deviceAddress); if (association == null) { throw new RemoteException(new DeviceNotAssociatedException("App " + packageName @@ -772,7 +849,7 @@ public class CompanionDeviceManagerService extends SystemService { enforceUsesCompanionDeviceFeature(getContext(), userId, callingPackage); checkState(!ArrayUtils.isEmpty( - mAssociationStore.getAssociationsForPackage(userId, callingPackage)), + mAssociationStore.getAssociationsForPackage(userId, callingPackage)), "App must have an association before calling this API"); } @@ -832,8 +909,8 @@ public class CompanionDeviceManagerService extends SystemService { final long timestamp = System.currentTimeMillis(); final AssociationInfo association = new AssociationInfo(id, userId, packageName, - macAddress, displayName, deviceProfile, selfManaged, false, timestamp, - Long.MAX_VALUE); + macAddress, displayName, deviceProfile, selfManaged, + /* notifyOnDeviceNearby */ false, /* revoked */ false, timestamp, Long.MAX_VALUE); Slog.i(TAG, "New CDM association created=" + association); mAssociationStore.addAssociation(association); @@ -845,6 +922,11 @@ public class CompanionDeviceManagerService extends SystemService { updateSpecialAccessPermissionForAssociatedPackage(association); logCreateAssociation(deviceProfile); + + // Don't need to update the mRevokedAssociationsPendingRoleHolderRemoval since + // maybeRemoveRoleHolderForAssociation in PackageInactivityListener will handle the case + // that there are other devices with the same profile, so the role holder won't be removed. + return association; } @@ -925,39 +1007,187 @@ public class CompanionDeviceManagerService extends SystemService { final String packageName = association.getPackageName(); final String deviceProfile = association.getDeviceProfile(); + if (!maybeRemoveRoleHolderForAssociation(association)) { + // Need to remove the app from list of the role holders, but will have to do it later + // (the app is in foreground at the moment). + addToPendingRoleHolderRemoval(association); + } + + // Need to check if device still present now because CompanionDevicePresenceMonitor will + // remove current connected device after mAssociationStore.removeAssociation final boolean wasPresent = mDevicePresenceMonitor.isDevicePresent(associationId); // Removing the association. mAssociationStore.removeAssociation(associationId); + // Do not need to persistUserState since CompanionDeviceManagerService will get callback + // from #onAssociationChanged, and it will handle the persistUserState which including + // active and revoked association. logRemoveAssociation(deviceProfile); // Remove all the system data transfer requests for the association. mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, associationId); - final List<AssociationInfo> otherAssociations = - mAssociationStore.getAssociationsForPackage(userId, packageName); - - // Check if the package is associated with other devices with the same profile. - // If not: take away the role. - if (deviceProfile != null) { - final boolean shouldKeepTheRole = any(otherAssociations, - it -> deviceProfile.equals(it.getDeviceProfile())); - if (!shouldKeepTheRole) { - Binder.withCleanCallingIdentity(() -> - removeRoleHolderForAssociation(getContext(), association)); - } - } - if (!wasPresent || !association.isNotifyOnDeviceNearby()) return; // The device was connected and the app was notified: check if we need to unbind the app // now. - final boolean shouldStayBound = any(otherAssociations, + final boolean shouldStayBound = any( + mAssociationStore.getAssociationsForPackage(userId, packageName), it -> it.isNotifyOnDeviceNearby() && mDevicePresenceMonitor.isDevicePresent(it.getId())); if (shouldStayBound) return; mCompanionAppController.unbindCompanionApplication(userId, packageName); } + /** + * First, checks if the companion application should be removed from the list role holders when + * upon association's removal, i.e.: association's profile (matches the role) is not null, + * the application does not have other associations with the same profile, etc. + * + * <p> + * Then, if establishes that the application indeed has to be removed from the list of the role + * holders, checks if it could be done right now - + * {@link android.app.role.RoleManager#removeRoleHolderAsUser(String, String, int, UserHandle, java.util.concurrent.Executor, java.util.function.Consumer) RoleManager#removeRoleHolderAsUser()} + * will kill the application's process, which leads poor user experience if the application was + * in foreground when this happened, to avoid this CDMS delays invoking + * {@code RoleManager.removeRoleHolderAsUser()} until the app is no longer in foreground. + * + * @return {@code true} if the application does NOT need be removed from the list of the role + * holders OR if the application was successfully removed from the list of role holders. + * I.e.: from the role-management perspective the association is done with. + * {@code false} if the application needs to be removed from the list of role the role + * holders, BUT it CDMS would prefer to do it later. + * I.e.: application is in the foreground at the moment, but invoking + * {@code RoleManager.removeRoleHolderAsUser()} will kill the application's process, + * which would lead to the poor UX, hence need to try later. + */ + + private boolean maybeRemoveRoleHolderForAssociation(@NonNull AssociationInfo association) { + if (DEBUG) Log.d(TAG, "maybeRemoveRoleHolderForAssociation() association=" + association); + + final String deviceProfile = association.getDeviceProfile(); + if (deviceProfile == null) { + // No role was granted to for this association, there is nothing else we need to here. + return true; + } + + // Check if the applications is associated with another devices with the profile. If so, + // it should remain the role holder. + final int id = association.getId(); + final int userId = association.getUserId(); + final String packageName = association.getPackageName(); + final boolean roleStillInUse = any( + mAssociationStore.getAssociationsForPackage(userId, packageName), + it -> deviceProfile.equals(it.getDeviceProfile()) && id != it.getId()); + if (roleStillInUse) { + // Application should remain a role holder, there is nothing else we need to here. + return true; + } + + final int packageProcessImportance = getPackageProcessImportance(userId, packageName); + if (packageProcessImportance <= IMPORTANCE_VISIBLE) { + // Need to remove the app from the list of role holders, but the process is visible to + // the user at the moment, so we'll need to it later: log and return false. + Slog.i(TAG, "Cannot remove role holder for the removed association id=" + id + + " now - process is visible."); + return false; + } + + removeRoleHolderForAssociation(getContext(), association); + return true; + } + + private int getPackageProcessImportance(@UserIdInt int userId, @NonNull String packageName) { + return Binder.withCleanCallingIdentity(() -> { + final int uid = + mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId); + return mActivityManager.getUidImportance(uid); + }); + } + + /** + * Set revoked flag for active association and add the revoked association and the uid into + * the caches. + * + * @see #mRevokedAssociationsPendingRoleHolderRemoval + * @see #mUidsPendingRoleHolderRemoval + * @see OnPackageVisibilityChangeListener + */ + private void addToPendingRoleHolderRemoval(@NonNull AssociationInfo association) { + // First: set revoked flag. + association = AssociationInfo.builder(association) + .setRevoked(true) + .build(); + + final String packageName = association.getPackageName(); + final int userId = association.getUserId(); + final int uid = mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId); + + // Second: add to the set. + synchronized (mRevokedAssociationsPendingRoleHolderRemoval) { + mRevokedAssociationsPendingRoleHolderRemoval.forUser(association.getUserId()) + .add(association); + if (!mUidsPendingRoleHolderRemoval.containsKey(uid)) { + mUidsPendingRoleHolderRemoval.put(uid, packageName); + + if (mUidsPendingRoleHolderRemoval.size() == 1) { + // Just added first uid: start the listener + mOnPackageVisibilityChangeListener.startListening(); + } + } + } + } + + /** + * Remove the revoked association form the cache and also remove the uid form the map if + * there are other associations with the same package still pending for role holder removal. + * + * @see #mRevokedAssociationsPendingRoleHolderRemoval + * @see #mUidsPendingRoleHolderRemoval + * @see OnPackageVisibilityChangeListener + */ + private void removeFromPendingRoleHolderRemoval(@NonNull AssociationInfo association) { + final String packageName = association.getPackageName(); + final int userId = association.getUserId(); + final int uid = mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId); + + synchronized (mRevokedAssociationsPendingRoleHolderRemoval) { + mRevokedAssociationsPendingRoleHolderRemoval.forUser(userId) + .remove(association); + + final boolean shouldKeepUidForRemoval = any( + getPendingRoleHolderRemovalAssociationsForUser(userId), + ai -> packageName.equals(ai.getPackageName())); + // Do not remove the uid form the map since other associations with + // the same packageName still pending for role holder removal. + if (!shouldKeepUidForRemoval) { + mUidsPendingRoleHolderRemoval.remove(uid); + } + + if (mUidsPendingRoleHolderRemoval.isEmpty()) { + // The set is empty now - can "turn off" the listener. + mOnPackageVisibilityChangeListener.stopListening(); + } + } + } + + /** + * @return a copy of the revoked associations set (safeguarding against + * {@code ConcurrentModificationException}-s). + */ + private @NonNull Set<AssociationInfo> getPendingRoleHolderRemovalAssociationsForUser( + @UserIdInt int userId) { + synchronized (mRevokedAssociationsPendingRoleHolderRemoval) { + // Return a copy. + return new ArraySet<>(mRevokedAssociationsPendingRoleHolderRemoval.forUser(userId)); + } + } + + private String getPackageNameByUid(int uid) { + synchronized (mRevokedAssociationsPendingRoleHolderRemoval) { + return mUidsPendingRoleHolderRemoval.get(uid); + } + } + private void updateSpecialAccessPermissionForAssociatedPackage(AssociationInfo association) { final PackageInfo packageInfo = getPackageInfo(getContext(), association.getUserId(), association.getPackageName()); @@ -1175,4 +1405,80 @@ public class CompanionDeviceManagerService extends SystemService { persistStateForUser(userId); } } + + /** + * An OnUidImportanceListener class which watches the importance of the packages. + * In this class, we ONLY interested in the importance of the running process is greater than + * {@link RunningAppProcessInfo.IMPORTANCE_VISIBLE} for the uids have been added into the + * {@link mUidsPendingRoleHolderRemoval}. Lastly remove the role holder for the revoked + * associations for the same packages. + * + * @see #maybeRemoveRoleHolderForAssociation(AssociationInfo) + * @see #removeFromPendingRoleHolderRemoval(AssociationInfo) + * @see #getPendingRoleHolderRemovalAssociationsForUser(int) + */ + private class OnPackageVisibilityChangeListener implements + ActivityManager.OnUidImportanceListener { + final @NonNull ActivityManager mAm; + + OnPackageVisibilityChangeListener(@NonNull ActivityManager am) { + this.mAm = am; + } + + void startListening() { + Binder.withCleanCallingIdentity( + () -> mAm.addOnUidImportanceListener( + /* listener */ OnPackageVisibilityChangeListener.this, + RunningAppProcessInfo.IMPORTANCE_VISIBLE)); + } + + void stopListening() { + Binder.withCleanCallingIdentity( + () -> mAm.removeOnUidImportanceListener( + /* listener */ OnPackageVisibilityChangeListener.this)); + } + + @Override + public void onUidImportance(int uid, int importance) { + if (importance <= RunningAppProcessInfo.IMPORTANCE_VISIBLE) { + // The lower the importance value the more "important" the process is. + // We are only interested when the process ceases to be visible. + return; + } + + final String packageName = getPackageNameByUid(uid); + if (packageName == null) { + // Not interested in this uid. + return; + } + + final int userId = UserHandle.getUserId(uid); + + boolean needToPersistStateForUser = false; + + for (AssociationInfo association : + getPendingRoleHolderRemovalAssociationsForUser(userId)) { + if (!packageName.equals(association.getPackageName())) continue; + + if (!maybeRemoveRoleHolderForAssociation(association)) { + // Did not remove the role holder, will have to try again later. + continue; + } + + removeFromPendingRoleHolderRemoval(association); + needToPersistStateForUser = true; + } + + if (needToPersistStateForUser) { + mUserPersistenceHandler.postPersistUserState(userId); + } + } + } + + private static class PerUserAssociationSet extends PerUser<Set<AssociationInfo>> { + @Override + protected @NonNull Set<AssociationInfo> create(int userId) { + return new ArraySet<>(); + } + } } diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/PersistentDataStore.java index 4d42838fff50..4b56c1b28036 100644 --- a/services/companion/java/com/android/server/companion/PersistentDataStore.java +++ b/services/companion/java/com/android/server/companion/PersistentDataStore.java @@ -103,7 +103,7 @@ import java.util.concurrent.ConcurrentMap; * Since Android T the data is stored to "companion_device_manager.xml" file in * {@link Environment#getDataSystemDeDirectory(int) /data/system_de/}. * - * See {@link #getStorageFileForUser(int)} + * See {@link DataStoreUtils#getBaseStorageFileForUser(int, String)} * * <p> * Since Android T the data is stored using the v1 schema. @@ -120,7 +120,7 @@ import java.util.concurrent.ConcurrentMap; * <li> {@link #readPreviouslyUsedIdsV1(TypedXmlPullParser, Map) readPreviouslyUsedIdsV1()} * </ul> * - * The following snippet is a sample of a file that is using v0 schema. + * The following snippet is a sample of a file that is using v1 schema. * <pre>{@code * <state persistence-version="1"> * <associations> @@ -130,6 +130,8 @@ import java.util.concurrent.ConcurrentMap; * mac_address="AA:BB:CC:DD:EE:00" * self_managed="false" * notify_device_nearby="false" + * revoked="false" + * last_time_connected="1634641160229" * time_approved="1634389553216"/> * * <association @@ -139,6 +141,8 @@ import java.util.concurrent.ConcurrentMap; * display_name="Jhon's Chromebook" * self_managed="true" * notify_device_nearby="false" + * revoked="false" + * last_time_connected="1634641160229" * time_approved="1634641160229"/> * </associations> * @@ -178,6 +182,7 @@ final class PersistentDataStore { private static final String XML_ATTR_PROFILE = "profile"; private static final String XML_ATTR_SELF_MANAGED = "self_managed"; private static final String XML_ATTR_NOTIFY_DEVICE_NEARBY = "notify_device_nearby"; + private static final String XML_ATTR_REVOKED = "revoked"; private static final String XML_ATTR_TIME_APPROVED = "time_approved"; private static final String XML_ATTR_LAST_TIME_CONNECTED = "last_time_connected"; @@ -415,7 +420,8 @@ final class PersistentDataStore { out.add(new AssociationInfo(associationId, userId, appPackage, MacAddress.fromString(deviceAddress), null, profile, - /* managedByCompanionApp */false, notify, timeApproved, Long.MAX_VALUE)); + /* managedByCompanionApp */ false, notify, /* revoked */ false, timeApproved, + Long.MAX_VALUE)); } private static void readAssociationsV1(@NonNull TypedXmlPullParser parser, @@ -444,13 +450,14 @@ final class PersistentDataStore { final String displayName = readStringAttribute(parser, XML_ATTR_DISPLAY_NAME); final boolean selfManaged = readBooleanAttribute(parser, XML_ATTR_SELF_MANAGED); final boolean notify = readBooleanAttribute(parser, XML_ATTR_NOTIFY_DEVICE_NEARBY); + final boolean revoked = readBooleanAttribute(parser, XML_ATTR_REVOKED, false); final long timeApproved = readLongAttribute(parser, XML_ATTR_TIME_APPROVED, 0L); final long lastTimeConnected = readLongAttribute( parser, XML_ATTR_LAST_TIME_CONNECTED, Long.MAX_VALUE); final AssociationInfo associationInfo = createAssociationInfoNoThrow(associationId, userId, - appPackage, macAddress, displayName, profile, selfManaged, notify, timeApproved, - lastTimeConnected); + appPackage, macAddress, displayName, profile, selfManaged, notify, revoked, + timeApproved, lastTimeConnected); if (associationInfo != null) { out.add(associationInfo); } @@ -503,6 +510,8 @@ final class PersistentDataStore { writeBooleanAttribute(serializer, XML_ATTR_SELF_MANAGED, a.isSelfManaged()); writeBooleanAttribute( serializer, XML_ATTR_NOTIFY_DEVICE_NEARBY, a.isNotifyOnDeviceNearby()); + writeBooleanAttribute( + serializer, XML_ATTR_REVOKED, a.isRevoked()); writeLongAttribute(serializer, XML_ATTR_TIME_APPROVED, a.getTimeApprovedMs()); writeLongAttribute( serializer, XML_ATTR_LAST_TIME_CONNECTED, a.getLastTimeConnectedMs()); @@ -544,11 +553,12 @@ final class PersistentDataStore { private static AssociationInfo createAssociationInfoNoThrow(int associationId, @UserIdInt int userId, @NonNull String appPackage, @Nullable MacAddress macAddress, @Nullable CharSequence displayName, @Nullable String profile, boolean selfManaged, - boolean notify, long timeApproved, long lastTimeConnected) { + boolean notify, boolean revoked, long timeApproved, long lastTimeConnected) { AssociationInfo associationInfo = null; try { associationInfo = new AssociationInfo(associationId, userId, appPackage, macAddress, - displayName, profile, selfManaged, notify, timeApproved, lastTimeConnected); + displayName, profile, selfManaged, notify, revoked, timeApproved, + lastTimeConnected); } catch (Exception e) { if (DEBUG) Log.w(TAG, "Could not create AssociationInfo", e); } diff --git a/services/companion/java/com/android/server/companion/RolesUtils.java b/services/companion/java/com/android/server/companion/RolesUtils.java index 35488a80b78b..0fff3f488562 100644 --- a/services/companion/java/com/android/server/companion/RolesUtils.java +++ b/services/companion/java/com/android/server/companion/RolesUtils.java @@ -85,6 +85,8 @@ final class RolesUtils { final int userId = associationInfo.getUserId(); final UserHandle userHandle = UserHandle.of(userId); + Slog.i(TAG, "Removing CDM role holder, role=" + deviceProfile + + ", package=u" + userId + "\\" + packageName); roleManager.removeRoleHolderAsUser(deviceProfile, packageName, MANAGE_HOLDERS_FLAG_DONT_KILL_APP, userHandle, context.getMainExecutor(), success -> { diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java index 395cf1805b63..70745ba0b368 100644 --- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java +++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java @@ -24,8 +24,6 @@ import static android.content.ComponentName.createRelative; import static com.android.server.companion.Utils.prepareForIpc; -import static java.nio.charset.StandardCharsets.UTF_8; - import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.PendingIntent; @@ -41,23 +39,17 @@ import android.os.Bundle; import android.os.Handler; import android.os.ResultReceiver; import android.os.UserHandle; +import android.permission.PermissionControllerManager; import android.util.Slog; -import android.util.Xml; import com.android.server.companion.AssociationStore; import com.android.server.companion.CompanionDeviceManagerService; import com.android.server.companion.PermissionsUtils; -import com.android.server.companion.datatransfer.permbackup.BackupHelper; import com.android.server.companion.proto.CompanionMessage; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlSerializer; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; /** * This processor builds user consent intent for a given SystemDataTransferRequest and processes the @@ -83,6 +75,8 @@ public class SystemDataTransferProcessor { private final AssociationStore mAssociationStore; private final SystemDataTransferRequestStore mSystemDataTransferRequestStore; private final CompanionMessageProcessor mCompanionMessageProcessor; + private final PermissionControllerManager mPermissionControllerManager; + private final ExecutorService mExecutor; public SystemDataTransferProcessor(CompanionDeviceManagerService service, AssociationStore associationStore, @@ -93,6 +87,8 @@ public class SystemDataTransferProcessor { mSystemDataTransferRequestStore = systemDataTransferRequestStore; mCompanionMessageProcessor = companionMessageProcessor; mCompanionMessageProcessor.setListener(this::onCompleteMessageReceived); + mPermissionControllerManager = mContext.getSystemService(PermissionControllerManager.class); + mExecutor = Executors.newSingleThreadExecutor(); } /** @@ -180,23 +176,13 @@ public class SystemDataTransferProcessor { // TODO: Establish a secure channel - final long callingIdentityToken = Binder.clearCallingIdentity(); - // Start permission sync + final long callingIdentityToken = Binder.clearCallingIdentity(); try { - BackupHelper backupHelper = new BackupHelper(mContext, UserHandle.of(userId)); - XmlSerializer serializer = Xml.newSerializer(); - ByteArrayOutputStream backup = new ByteArrayOutputStream(); - serializer.setOutput(backup, UTF_8.name()); - - backupHelper.writeState(serializer); - - serializer.flush(); - - mCompanionMessageProcessor.paginateAndDispatchMessagesToApp(backup.toByteArray(), - CompanionMessage.PERMISSION_SYNC, packageName, userId, associationId); - } catch (IOException ioe) { - Slog.e(LOG_TAG, "Error while writing permission state."); + mPermissionControllerManager.getRuntimePermissionBackup(UserHandle.of(userId), + mExecutor, + backup -> mCompanionMessageProcessor.paginateAndDispatchMessagesToApp(backup, + CompanionMessage.PERMISSION_SYNC, packageName, userId, associationId)); } finally { Binder.restoreCallingIdentity(callingIdentityToken); } @@ -219,21 +205,11 @@ public class SystemDataTransferProcessor { private void processPermissionSyncMessage(CompanionMessageInfo messageInfo) { Slog.i(LOG_TAG, "Applying permissions."); // Start applying permissions + UserHandle user = mContext.getUser(); final long callingIdentityToken = Binder.clearCallingIdentity(); try { - BackupHelper backupHelper = new BackupHelper(mContext, mContext.getUser()); - XmlPullParser parser = Xml.newPullParser(); - ByteArrayInputStream stream = new ByteArrayInputStream( - messageInfo.getData()); - parser.setInput(stream, UTF_8.name()); - - backupHelper.restoreState(parser); - } catch (IOException e) { - Slog.e(LOG_TAG, "IOException reading message: " - + new String(messageInfo.getData())); - } catch (XmlPullParserException e) { - Slog.e(LOG_TAG, "Error parsing message: " - + new String(messageInfo.getData())); + mPermissionControllerManager.stageAndApplyRuntimePermissionsBackup( + messageInfo.getData(), user); } finally { Slog.i(LOG_TAG, "Permissions applied."); Binder.restoreCallingIdentity(callingIdentityToken); diff --git a/services/companion/java/com/android/server/companion/datatransfer/permbackup/BackupHelper.java b/services/companion/java/com/android/server/companion/datatransfer/permbackup/BackupHelper.java deleted file mode 100644 index 5e3c4c7834fe..000000000000 --- a/services/companion/java/com/android/server/companion/datatransfer/permbackup/BackupHelper.java +++ /dev/null @@ -1,782 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.companion.datatransfer.permbackup; - -import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED; -import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED; -import static android.content.pm.PackageManager.GET_PERMISSIONS; -import static android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES; - -import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; -import static org.xmlpull.v1.XmlPullParser.END_TAG; -import static org.xmlpull.v1.XmlPullParser.START_TAG; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.Signature; -import android.content.pm.SigningInfo; -import android.os.Build; -import android.os.UserHandle; -import android.permission.PermissionManager; -import android.permission.PermissionManager.SplitPermissionInfo; -import android.util.ArraySet; -import android.util.Log; -import android.util.Slog; - -import com.android.server.companion.datatransfer.permbackup.model.AppPermissionGroup; -import com.android.server.companion.datatransfer.permbackup.model.AppPermissions; -import com.android.server.companion.datatransfer.permbackup.model.Permission; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlSerializer; - -import java.io.IOException; -import java.security.cert.CertificateException; -import java.util.ArrayList; -import java.util.List; - -/** - * Helper for creating and restoring permission backups. - */ -public class BackupHelper { - private static final String LOG_TAG = BackupHelper.class.getSimpleName(); - - private static final String TAG_PERMISSION_BACKUP = "perm-grant-backup"; - private static final String ATTR_PLATFORM_VERSION = "version"; - - private static final String TAG_ALL_GRANTS = "rt-grants"; - - private static final String TAG_GRANT = "grant"; - private static final String ATTR_PACKAGE_NAME = "pkg"; - private static final String ATTR_HAS_MULTIPLE_SIGNERS = "multi-signers"; - - private static final String TAG_SIGNATURE = "sig"; - private static final String ATTR_SIGNATURE_VALUE = "v"; - - private static final String TAG_PERMISSION = "perm"; - private static final String ATTR_PERMISSION_NAME = "name"; - private static final String ATTR_IS_GRANTED = "g"; - private static final String ATTR_USER_SET = "set"; - private static final String ATTR_USER_FIXED = "fixed"; - private static final String ATTR_WAS_REVIEWED = "was-reviewed"; - - /** Flags of permissions to <u>not</u> back up */ - private static final int SYSTEM_RUNTIME_GRANT_MASK = FLAG_PERMISSION_POLICY_FIXED - | FLAG_PERMISSION_SYSTEM_FIXED; - - /** Make sure only one user can change the delayed permissions at a time */ - private static final Object sLock = new Object(); - - private final Context mContext; - - /** - * Create a new backup utils for a user. - * - * @param context A context to use - * @param user The user that is backed up / restored - */ - public BackupHelper(@NonNull Context context, @NonNull UserHandle user) { - try { - mContext = context.createPackageContextAsUser(context.getPackageName(), 0, user); - } catch (PackageManager.NameNotFoundException doesNotHappen) { - throw new IllegalStateException(); - } - } - - /** - * Forward parser and skip everything up to the end of the current tag. - * - * @param parser The parser to forward - */ - private static void skipToEndOfTag(@NonNull XmlPullParser parser) - throws IOException, XmlPullParserException { - int numOpenTags = 1; - while (numOpenTags > 0) { - switch (parser.next()) { - case START_TAG: - numOpenTags++; - break; - case END_TAG: - numOpenTags--; - break; - default: - // ignore - } - } - } - - /** - * Forward parser to a given direct sub-tag. - * - * @param parser The parser to forward - * @param tag The tag to search for - */ - private void skipToTag(@NonNull XmlPullParser parser, @NonNull String tag) - throws IOException, XmlPullParserException { - int type; - do { - type = parser.next(); - - switch (type) { - case START_TAG: - if (!parser.getName().equals(tag)) { - skipToEndOfTag(parser); - } - - return; - } - } while (type != END_DOCUMENT); - } - - /** - * Read a XML file and return the packages stored in it. - * - * @param parser The file to read - * - * @return The packages in this file - */ - private @NonNull ArrayList<BackupPackageState> parseFromXml(@NonNull XmlPullParser parser) - throws IOException, XmlPullParserException { - ArrayList<BackupPackageState> pkgStates = new ArrayList<>(); - - skipToTag(parser, TAG_PERMISSION_BACKUP); - - int backupPlatformVersion; - try { - backupPlatformVersion = Integer.parseInt( - parser.getAttributeValue(null, ATTR_PLATFORM_VERSION)); - } catch (NumberFormatException ignored) { - // Platforms P and before did not store the platform version - backupPlatformVersion = Build.VERSION_CODES.P; - } - - skipToTag(parser, TAG_ALL_GRANTS); - - if (parser.getEventType() != START_TAG && !parser.getName().equals(TAG_ALL_GRANTS)) { - throw new XmlPullParserException("Could not find " + TAG_PERMISSION_BACKUP + " > " - + TAG_ALL_GRANTS); - } - - // Read packages to restore from xml - int type; - do { - type = parser.next(); - - switch (type) { - case START_TAG: - switch (parser.getName()) { - case TAG_GRANT: - try { - pkgStates.add(BackupPackageState.parseFromXml(parser, mContext, - backupPlatformVersion)); - } catch (XmlPullParserException e) { - Log.e(LOG_TAG, "Could not parse permissions ", e); - skipToEndOfTag(parser); - } - break; - default: - // ignore tag - Log.w(LOG_TAG, "Found unexpected tag " + parser.getName() - + " during restore"); - skipToEndOfTag(parser); - } - } - } while (type != END_DOCUMENT); - - return pkgStates; - } - - /** - * Try to restore the permission state from XML. - * - * @param parser The xml to read - */ - public void restoreState(@NonNull XmlPullParser parser) throws IOException, - XmlPullParserException { - ArrayList<BackupPackageState> pkgStates = parseFromXml(parser); - - ArrayList<BackupPackageState> packagesToRestoreLater = new ArrayList<>(); - int numPkgStates = pkgStates.size(); - if (numPkgStates > 0) { - // Try to restore packages - for (int i = 0; i < numPkgStates; i++) { - BackupPackageState pkgState = pkgStates.get(i); - - PackageInfo pkgInfo; - try { - pkgInfo = mContext.getPackageManager().getPackageInfo(pkgState.mPackageName, - GET_PERMISSIONS | GET_SIGNING_CERTIFICATES); - } catch (PackageManager.NameNotFoundException ignored) { - packagesToRestoreLater.add(pkgState); - continue; - } - - pkgState.restore(mContext, pkgInfo); - } - } - -// synchronized (sLock) { -// writeDelayedStorePkgsLocked(packagesToRestoreLater); -// } - } - - /** - * Write a xml file for the given packages. - * - * @param serializer The file to write to - * @param pkgs The packages to write - */ - private static void writePkgsAsXml(@NonNull XmlSerializer serializer, - @NonNull ArrayList<BackupPackageState> pkgs) throws IOException { - serializer.startDocument(null, true); - - serializer.startTag(null, TAG_PERMISSION_BACKUP); - -// if (SDK_INT >= Build.VERSION_CODES.Q) { - // STOPSHIP: Remove compatibility code once Q SDK level is declared - serializer.attribute(null, ATTR_PLATFORM_VERSION, - Integer.valueOf(Build.VERSION_CODES.Q).toString()); -// } else { -// serializer.attribute(null, ATTR_PLATFORM_VERSION, -// Integer.valueOf(SDK_INT).toString()); -// } - - serializer.startTag(null, TAG_ALL_GRANTS); - - int numPkgs = pkgs.size(); - for (int i = 0; i < numPkgs; i++) { - BackupPackageState packageState = pkgs.get(i); - - if (packageState != null) { - packageState.writeAsXml(serializer); - } - } - - serializer.endTag(null, TAG_ALL_GRANTS); - serializer.endTag(null, TAG_PERMISSION_BACKUP); - - serializer.endDocument(); - } - - /** - * Write the state of all packages as XML. - * - * @param serializer The xml to write to - */ - public void writeState(@NonNull XmlSerializer serializer) throws IOException { - List<PackageInfo> pkgs = mContext.getPackageManager().getInstalledPackages( - GET_PERMISSIONS | GET_SIGNING_CERTIFICATES); - ArrayList<BackupPackageState> backupPkgs = new ArrayList<>(); - - int numPkgs = pkgs.size(); - for (int i = 0; i < numPkgs; i++) { - BackupPackageState packageState = BackupPackageState.fromAppPermissions(mContext, - pkgs.get(i)); - - if (packageState != null) { - backupPkgs.add(packageState); - } - } - - writePkgsAsXml(serializer, backupPkgs); - } - - /** - * State that needs to be backed up for a permission. - */ - private static class BackupPermissionState { - private final @NonNull String mPermissionName; - private final boolean mIsGranted; - private final boolean mIsUserSet; - private final boolean mIsUserFixed; - private final boolean mWasReviewed; - - private BackupPermissionState(@NonNull String permissionName, boolean isGranted, - boolean isUserSet, boolean isUserFixed, boolean wasReviewed) { - mPermissionName = permissionName; - mIsGranted = isGranted; - mIsUserSet = isUserSet; - mIsUserFixed = isUserFixed; - mWasReviewed = wasReviewed; - } - - /** - * Parse a package state from XML. - * - * @param parser The data to read - * @param context a context to use - * @param backupPlatformVersion The platform version the backup was created on - * - * @return The state - */ - static @NonNull List<BackupPermissionState> parseFromXml(@NonNull XmlPullParser parser, - @NonNull Context context, int backupPlatformVersion) - throws XmlPullParserException { - String permName = parser.getAttributeValue(null, ATTR_PERMISSION_NAME); - if (permName == null) { - throw new XmlPullParserException("Found " + TAG_PERMISSION + " without " - + ATTR_PERMISSION_NAME); - } - - ArrayList<String> expandedPermissions = new ArrayList<>(); - expandedPermissions.add(permName); - - List<SplitPermissionInfo> splitPerms = context.getSystemService( - PermissionManager.class).getSplitPermissions(); - - // Expand the properties to permissions that were split between the platform version the - // backup was taken and the current version. - int numSplitPerms = splitPerms.size(); - for (int i = 0; i < numSplitPerms; i++) { - SplitPermissionInfo splitPerm = splitPerms.get(i); - if (backupPlatformVersion < splitPerm.getTargetSdk() - && permName.equals(splitPerm.getSplitPermission())) { - expandedPermissions.addAll(splitPerm.getNewPermissions()); - } - } - - ArrayList<BackupPermissionState> parsedPermissions = new ArrayList<>( - expandedPermissions.size()); - int numExpandedPerms = expandedPermissions.size(); - for (int i = 0; i < numExpandedPerms; i++) { - parsedPermissions.add(new BackupPermissionState(expandedPermissions.get(i), - "true".equals(parser.getAttributeValue(null, ATTR_IS_GRANTED)), - "true".equals(parser.getAttributeValue(null, ATTR_USER_SET)), - "true".equals(parser.getAttributeValue(null, ATTR_USER_FIXED)), - "true".equals(parser.getAttributeValue(null, ATTR_WAS_REVIEWED)))); - } - - return parsedPermissions; - } - - /** - * Is the permission granted, also considering the app-op. - * - * <p>This does not consider the review-required state of the permission. - * - * @param perm The permission that might be granted - * - * @return {@code true} iff the permission and app-op is granted - */ - private static boolean isPermGrantedIncludingAppOp(@NonNull Permission perm) { - return perm.isGranted() && (!perm.affectsAppOp() || perm.isAppOpAllowed()); - } - - /** - * Get the state of a permission to back up. - * - * @param perm The permission to back up - * @param appSupportsRuntimePermissions If the app supports runtimePermissions - * - * @return The state to back up or {@code null} if the permission does not need to be - * backed up. - */ - private static @Nullable BackupPermissionState fromPermission(@NonNull Permission perm, - boolean appSupportsRuntimePermissions) { - int grantFlags = perm.getFlags(); - - if ((grantFlags & SYSTEM_RUNTIME_GRANT_MASK) != 0) { - return null; - } - - if (!perm.isUserSet() && perm.isGrantedByDefault()) { - return null; - } - - boolean permissionWasReviewed; - boolean isNotInDefaultGrantState; - if (appSupportsRuntimePermissions) { - isNotInDefaultGrantState = isPermGrantedIncludingAppOp(perm); - permissionWasReviewed = false; - } else { - isNotInDefaultGrantState = !isPermGrantedIncludingAppOp(perm); - permissionWasReviewed = !perm.isReviewRequired(); - } - -// if (isNotInDefaultGrantState || perm.isUserSet() || perm.isUserFixed() -// || permissionWasReviewed) { -// return new BackupPermissionState(perm.getName(), -// isPermGrantedIncludingAppOp(perm), -// perm.isUserSet(), perm.isUserFixed(), permissionWasReviewed); -// } else { -// return null; -// } - if (perm.isUserSet() && isPermGrantedIncludingAppOp(perm)) { - return new BackupPermissionState(perm.getName(), /* isGranted */ true, - /* isUserSet */ true, perm.isUserFixed(), permissionWasReviewed); - } else { - return null; - } - } - - /** - * Get the states of all permissions of a group to back up. - * - * @param group The group of the permissions to back up - * - * @return The state to back up. Empty list if no permissions in the group need to be backed - * up - */ - static @NonNull ArrayList<BackupPermissionState> fromPermissionGroup( - @NonNull AppPermissionGroup group) { - ArrayList<BackupPermissionState> permissionsToRestore = new ArrayList<>(); - List<Permission> perms = group.getPermissions(); - - boolean appSupportsRuntimePermissions = - group.getApp().applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M; - - int numPerms = perms.size(); - for (int i = 0; i < numPerms; i++) { - BackupPermissionState permState = fromPermission(perms.get(i), - appSupportsRuntimePermissions); - if (permState != null) { - permissionsToRestore.add(permState); - } - } - - return permissionsToRestore; - } - - /** - * Write this state as XML. - * - * @param serializer The file to write to - */ - void writeAsXml(@NonNull XmlSerializer serializer) throws IOException { - serializer.startTag(null, TAG_PERMISSION); - - serializer.attribute(null, ATTR_PERMISSION_NAME, mPermissionName); - - if (mIsGranted) { - serializer.attribute(null, ATTR_IS_GRANTED, "true"); - } - - if (mIsUserSet) { - serializer.attribute(null, ATTR_USER_SET, "true"); - } - - if (mIsUserFixed) { - serializer.attribute(null, ATTR_USER_FIXED, "true"); - } - - if (mWasReviewed) { - serializer.attribute(null, ATTR_WAS_REVIEWED, "true"); - } - - serializer.endTag(null, TAG_PERMISSION); - } - - /** - * Restore this permission state. - * - * @param appPerms The {@link AppPermissions} to restore the state to - * @param restoreBackgroundPerms if {@code true} only restore background permissions, - * if {@code false} do not restore background permissions - */ - void restore(@NonNull AppPermissions appPerms, boolean restoreBackgroundPerms) { - AppPermissionGroup group = appPerms.getGroupForPermission(mPermissionName); - if (group == null) { - Log.w(LOG_TAG, "Could not find group for " + mPermissionName + " in " - + appPerms.getPackageInfo().packageName); - return; - } - - if (restoreBackgroundPerms != group.isBackgroundGroup()) { - return; - } - - Permission perm = group.getPermission(mPermissionName); - if (mWasReviewed) { - perm.unsetReviewRequired(); - } - - // Don't grant or revoke fixed permission groups - if (group.isSystemFixed() || group.isPolicyFixed()) { - return; - } - - if (!perm.isUserSet()) { - if (mIsGranted) { - group.grantRuntimePermissions(false, mIsUserFixed, - new String[]{mPermissionName}); - } else { - group.revokeRuntimePermissions(mIsUserFixed, - new String[]{mPermissionName}); - } - - perm.setUserSet(mIsUserSet); - } - } - } - - /** - * State that needs to be backed up for a package. - */ - private static class BackupPackageState { - final @NonNull String mPackageName; - final boolean mHasMultipleSigners; - @NonNull Signature[] mSignatures; - private final @NonNull ArrayList<BackupPermissionState> mPermissionsToRestore; - - private BackupPackageState(@NonNull String packageName, boolean hasMultipleSigners, - @NonNull Signature[] signatures, - @NonNull ArrayList<BackupPermissionState> permissionsToRestore) { - mPackageName = packageName; - mHasMultipleSigners = hasMultipleSigners; - mSignatures = signatures; - mPermissionsToRestore = permissionsToRestore; - } - - /** - * Parse a package state from XML. - * - * @param parser The data to read - * @param context a context to use - * @param backupPlatformVersion The platform version the backup was created on - * - * @return The state - */ - static @NonNull BackupPackageState parseFromXml(@NonNull XmlPullParser parser, - @NonNull Context context, int backupPlatformVersion) - throws IOException, XmlPullParserException { - String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME); - if (packageName == null) { - throw new XmlPullParserException("Found " + TAG_GRANT + " without " - + ATTR_PACKAGE_NAME); - } - - boolean hasMultipleSigners = Boolean.parseBoolean( - parser.getAttributeValue(null, ATTR_HAS_MULTIPLE_SIGNERS)); - ArrayList<Signature> signatureList = new ArrayList<>(); - - ArrayList<BackupPermissionState> permissionsToRestore = new ArrayList<>(); - - while (true) { - switch (parser.next()) { - case START_TAG: - switch (parser.getName()) { - case TAG_PERMISSION: - try { - permissionsToRestore.addAll( - BackupPermissionState.parseFromXml(parser, context, - backupPlatformVersion)); - } catch (XmlPullParserException e) { - Log.e(LOG_TAG, "Could not parse permission for " - + packageName, e); - } - - skipToEndOfTag(parser); - break; - case TAG_SIGNATURE: - signatureList.add(new Signature( - parser.getAttributeValue(null, ATTR_SIGNATURE_VALUE))); - skipToEndOfTag(parser); - break; - default: - // ignore tag - Log.w(LOG_TAG, "Found unexpected tag " + parser.getName() - + " while restoring " + packageName); - skipToEndOfTag(parser); - } - - break; - case END_TAG: - Signature[] signatures = new Signature[signatureList.size()]; - for (int i = 0; i < signatureList.size(); i++) { - signatures[i] = signatureList.get(i); - } - return new BackupPackageState(packageName, hasMultipleSigners, signatures, - permissionsToRestore); - case END_DOCUMENT: - throw new XmlPullParserException("Could not parse state for " - + packageName); - } - } - } - - /** - * Get the state of a package to back up. - * - * @param context A context to use - * @param pkgInfo The package to back up. - * - * @return The state to back up or {@code null} if no permission of the package need to be - * backed up. - */ - static @Nullable BackupPackageState fromAppPermissions(@NonNull Context context, - @NonNull PackageInfo pkgInfo) { - AppPermissions appPerms = new AppPermissions(context, pkgInfo, false, null); - - ArrayList<BackupPermissionState> permissionsToRestore = new ArrayList<>(); - List<AppPermissionGroup> groups = appPerms.getPermissionGroups(); - - // Check if the package has signatures - SigningInfo signingInfo = pkgInfo.signingInfo; - Signature[] signatures; - boolean hasMultipleSigners; - if (signingInfo.hasMultipleSigners()) { - hasMultipleSigners = true; - signatures = signingInfo.getApkContentsSigners(); - } else { - hasMultipleSigners = false; - signatures = signingInfo.getSigningCertificateHistory(); - } - if (signatures == null) { - Slog.d(LOG_TAG, "Skipping " + pkgInfo.packageName + ", it's unsigned."); - return null; - } - - int numGroups = groups.size(); - for (int groupNum = 0; groupNum < numGroups; groupNum++) { - AppPermissionGroup group = groups.get(groupNum); - - permissionsToRestore.addAll(BackupPermissionState.fromPermissionGroup(group)); - - // Background permissions are in a subgroup that is not part of - // {@link AppPermission#getPermissionGroups}. Hence add it explicitly here. - if (group.getBackgroundPermissions() != null) { - permissionsToRestore.addAll(BackupPermissionState.fromPermissionGroup( - group.getBackgroundPermissions())); - } - } - - if (permissionsToRestore.size() == 0) { - return null; - } - - return new BackupPackageState(pkgInfo.packageName, hasMultipleSigners, signatures, - permissionsToRestore); - } - - /** - * Write this state as XML. - * - * @param serializer The file to write to - */ - void writeAsXml(@NonNull XmlSerializer serializer) throws IOException { - if (mPermissionsToRestore.size() == 0) { - return; - } - - serializer.startTag(null, TAG_GRANT); - serializer.attribute(null, ATTR_PACKAGE_NAME, mPackageName); - - // Add signing info - serializer.attribute(null, ATTR_HAS_MULTIPLE_SIGNERS, - String.valueOf(mHasMultipleSigners)); - for (Signature signature : mSignatures) { - serializer.startTag(null, TAG_SIGNATURE); - serializer.attribute(null, ATTR_SIGNATURE_VALUE, signature.toCharsString()); - serializer.endTag(null, TAG_SIGNATURE); - } - - int numPerms = mPermissionsToRestore.size(); - for (int i = 0; i < numPerms; i++) { - mPermissionsToRestore.get(i).writeAsXml(serializer); - } - - serializer.endTag(null, TAG_GRANT); - } - - /** - * Restore this package state. - * - * @param context A context to use - * @param pkgInfo The package to restore. - */ - void restore(@NonNull Context context, @NonNull PackageInfo pkgInfo) { - Slog.e(LOG_TAG, "Restoring permissions for package [" + mPackageName + "]"); - - // Verify signature info - try { - if (mHasMultipleSigners && pkgInfo.signingInfo.hasMultipleSigners()) { - // If both packages are signed by multi signers, check if two signature sets are - // effectively matched. - if (!Signature.areEffectiveMatch(mSignatures, - pkgInfo.signingInfo.getApkContentsSigners())) { - Slog.e(LOG_TAG, "Multi-signers signatures don't match for package [" - + mPackageName + "], skipped."); - return; - } - } else if (!mHasMultipleSigners && !pkgInfo.signingInfo.hasMultipleSigners()) { - // If both packages are not signed by multi signers, check if two signature sets - // have overlaps. - Signature[] signatures = pkgInfo.signingInfo.getSigningCertificateHistory(); - if (signatures == null) { - Slog.e(LOG_TAG, "The dest package is unsigned."); - return; - } - boolean isMatched = false; - for (int i = 0; i < mSignatures.length; i++) { - for (int j = 0; j < signatures.length; j++) { - isMatched = Signature.areEffectiveMatch(mSignatures[i], signatures[j]); - } - } - if (!isMatched) { - Slog.e(LOG_TAG, "Single signer signatures don't match for package [" - + mPackageName + "], skipped."); - return; - } - } else { - Slog.e(LOG_TAG, "Number of signers don't match."); - return; - } - } catch (CertificateException ce) { - Slog.e(LOG_TAG, "Either the source or the dest package's bounced cert length " - + "looks fishy, skipped package [" + pkgInfo.packageName + "]"); - } - - AppPermissions appPerms = new AppPermissions(context, pkgInfo, false, true, null); - - ArraySet<String> affectedPermissions = new ArraySet<>(); - // Restore background permissions after foreground permissions as for pre-M apps bg - // granted and fg revoked cannot be expressed. - int numPerms = mPermissionsToRestore.size(); - for (int i = 0; i < numPerms; i++) { - mPermissionsToRestore.get(i).restore(appPerms, false); - affectedPermissions.add(mPermissionsToRestore.get(i).mPermissionName); - } - for (int i = 0; i < numPerms; i++) { - mPermissionsToRestore.get(i).restore(appPerms, true); - } - - int numGroups = appPerms.getPermissionGroups().size(); - for (int i = 0; i < numGroups; i++) { - AppPermissionGroup group = appPerms.getPermissionGroups().get(i); - - // Only denied groups can be user fixed - if (group.areRuntimePermissionsGranted()) { - group.setUserFixed(false); - } - - AppPermissionGroup bgGroup = group.getBackgroundPermissions(); - if (bgGroup != null) { - // Only denied groups can be user fixed - if (bgGroup.areRuntimePermissionsGranted()) { - bgGroup.setUserFixed(false); - } - } - } - - appPerms.persistChanges(true, affectedPermissions); - } - } -} diff --git a/services/companion/java/com/android/server/companion/datatransfer/permbackup/model/AppPermissionGroup.java b/services/companion/java/com/android/server/companion/datatransfer/permbackup/model/AppPermissionGroup.java deleted file mode 100644 index cf146ac7a48e..000000000000 --- a/services/companion/java/com/android/server/companion/datatransfer/permbackup/model/AppPermissionGroup.java +++ /dev/null @@ -1,1574 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.companion.datatransfer.permbackup.model; - -import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION; -import static android.Manifest.permission.ACCESS_FINE_LOCATION; -import static android.app.AppOpsManager.MODE_ALLOWED; -import static android.app.AppOpsManager.MODE_FOREGROUND; -import static android.app.AppOpsManager.MODE_IGNORED; -import static android.app.AppOpsManager.OPSTR_LEGACY_STORAGE; -import static android.content.pm.PackageManager.PERMISSION_GRANTED; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.StringRes; -import android.app.ActivityManager; -import android.app.AppOpsManager; -import android.app.Application; -import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageItemInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.PermissionGroupInfo; -import android.content.pm.PermissionInfo; -import android.os.Build; -import android.os.UserHandle; -import android.permission.PermissionManager; -import android.text.TextUtils; -import android.util.ArrayMap; -import android.util.Log; - -import com.android.server.companion.datatransfer.permbackup.utils.ArrayUtils; -import com.android.server.companion.datatransfer.permbackup.utils.LocationUtils; -import com.android.server.companion.datatransfer.permbackup.utils.SoftRestrictedPermissionPolicy; -import com.android.server.companion.datatransfer.permbackup.utils.Utils; - -import java.text.Collator; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Set; - -/** - * All permissions of a permission group that are requested by an app. - * - * <p>Some permissions only grant access to the protected resource while the app is running in the - * foreground. These permissions are considered "split" into this foreground and a matching - * "background" permission. - * - * <p>All background permissions of the group are not in the main group and will not be affected - * by operations on the group. The background permissions can be found in the {@link - * #getBackgroundPermissions() background permissions group}. - */ -public final class AppPermissionGroup implements Comparable<AppPermissionGroup> { - private static final String LOG_TAG = AppPermissionGroup.class.getSimpleName(); - private static final String PLATFORM_PACKAGE_NAME = "android"; - - private static final String KILL_REASON_APP_OP_CHANGE = "Permission related app op changed"; - - /** - * Importance level to define the threshold for whether a package is in a state which resets the - * timer on its one-time permission session - */ - private static final int ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_RESET_TIMER = - ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; - - /** - * Importance level to define the threshold for whether a package is in a state which keeps its - * one-time permission session alive after the timer ends - */ - private static final int ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_KEEP_SESSION_ALIVE = - ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE; - - private final Context mContext; - private final UserHandle mUserHandle; - private final PackageManager mPackageManager; - private final AppOpsManager mAppOps; - private final ActivityManager mActivityManager; - private final Collator mCollator; - - private final PackageInfo mPackageInfo; - private final String mName; - private final String mDeclaringPackage; - private final CharSequence mLabel; - private final CharSequence mFullLabel; - private final @StringRes int mRequest; - private final @StringRes int mRequestDetail; - private final @StringRes int mBackgroundRequest; - private final @StringRes int mBackgroundRequestDetail; - private final @StringRes int mUpgradeRequest; - private final @StringRes int mUpgradeRequestDetail; - private final CharSequence mDescription; - private final ArrayMap<String, Permission> mPermissions = new ArrayMap<>(); - private final String mIconPkg; - private final int mIconResId; - - /** Delay changes until {@link #persistChanges} is called */ - private final boolean mDelayChanges; - - /** - * Some permissions are split into foreground and background permission. All non-split and - * foreground permissions are in {@link #mPermissions}, all background permissions are in - * this field. - */ - private AppPermissionGroup mBackgroundPermissions; - - private final boolean mAppSupportsRuntimePermissions; - private final boolean mIsEphemeralApp; - private final boolean mIsNonIsolatedStorage; - private boolean mContainsEphemeralPermission; - private boolean mContainsPreRuntimePermission; - - /** - * Does this group contain at least one permission that is split into a foreground and - * background permission? This does not necessarily mean that the app also requested the - * background permission. - */ - private boolean mHasPermissionWithBackgroundMode; - - private boolean mTriggerLocationAccessCheckOnPersist; - - private boolean mIsSelfRevoked; - - /** - * Create the app permission group. - * - * @param context the {@code Context} to retrieve system services. - * @param packageInfo package information about the app. - * @param permissionName the name of the permission this object represents. - * @param delayChanges whether to delay changes until {@link #persistChanges} is called. - * - * @return the AppPermissionGroup. - */ - public static AppPermissionGroup create(Context context, PackageInfo packageInfo, - String permissionName, boolean delayChanges) { - PermissionInfo permissionInfo; - try { - permissionInfo = context.getPackageManager().getPermissionInfo(permissionName, 0); - } catch (PackageManager.NameNotFoundException e) { - return null; - } - - if ((permissionInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) - != PermissionInfo.PROTECTION_DANGEROUS - || (permissionInfo.flags & PermissionInfo.FLAG_INSTALLED) == 0 - || (permissionInfo.flags & PermissionInfo.FLAG_REMOVED) != 0) { - return null; - } - - String group = Utils.getGroupOfPermission(permissionInfo); - PackageItemInfo groupInfo = permissionInfo; - if (group != null) { - try { - groupInfo = context.getPackageManager().getPermissionGroupInfo(group, 0); - } catch (PackageManager.NameNotFoundException e) { - /* ignore */ - } - } - - List<PermissionInfo> permissionInfos = null; - if (groupInfo instanceof PermissionGroupInfo) { - try { - permissionInfos = Utils.getPermissionInfosForGroup(context.getPackageManager(), - groupInfo.name); - } catch (PackageManager.NameNotFoundException e) { - /* ignore */ - } - } - - return create(context, packageInfo, groupInfo, permissionInfos, delayChanges); - } - - /** - * Create the app permission group. - * - * @param app the current application - * @param packageName the name of the package - * @param permissionGroupName the name of the permission group - * @param user the user of the package - * @param delayChanges whether to delay changes until {@link #persistChanges} is called. - * - * @return the AppPermissionGroup. - */ - public static AppPermissionGroup create(Application app, String packageName, - String permissionGroupName, UserHandle user, boolean delayChanges) { - try { - PackageInfo packageInfo = Utils.getUserContext(app, user).getPackageManager() - .getPackageInfo(packageName, PackageManager.GET_PERMISSIONS); - PackageItemInfo groupInfo = Utils.getGroupInfo(permissionGroupName, app); - if (groupInfo == null) { - return null; - } - - List<PermissionInfo> permissionInfos = null; - if (groupInfo instanceof PermissionGroupInfo) { - permissionInfos = Utils.getPermissionInfosForGroup(app.getPackageManager(), - groupInfo.name); - } - return create(app, packageInfo, groupInfo, permissionInfos, delayChanges); - } catch (PackageManager.NameNotFoundException e) { - return null; - } - } - - /** - * Create the app permission group. - * - * @param context the {@code Context} to retrieve system services. - * @param packageInfo package information about the app. - * @param groupInfo the information about the group created. - * @param permissionInfos the information about the permissions belonging to the group. - * @param delayChanges whether to delay changes until {@link #persistChanges} is called. - * - * @return the AppPermissionGroup. - */ - public static AppPermissionGroup create(Context context, PackageInfo packageInfo, - PackageItemInfo groupInfo, List<PermissionInfo> permissionInfos, boolean delayChanges) { - PackageManager packageManager = context.getPackageManager(); - CharSequence groupLabel = groupInfo.loadLabel(packageManager); - CharSequence fullGroupLabel = groupInfo.loadSafeLabel(packageManager, 0, - TextUtils.SAFE_STRING_FLAG_TRIM | TextUtils.SAFE_STRING_FLAG_FIRST_LINE); - return create(context, packageInfo, groupInfo, permissionInfos, groupLabel, - fullGroupLabel, delayChanges); - } - - /** - * Create the app permission group. - * - * @param context the {@code Context} to retrieve system services. - * @param packageInfo package information about the app. - * @param groupInfo the information about the group created. - * @param permissionInfos the information about the permissions belonging to the group. - * @param groupLabel the label of the group. - * @param fullGroupLabel the untruncated label of the group. - * @param delayChanges whether to delay changes until {@link #persistChanges} is called. - * - * @return the AppPermissionGroup. - */ - public static AppPermissionGroup create(Context context, PackageInfo packageInfo, - PackageItemInfo groupInfo, List<PermissionInfo> permissionInfos, - CharSequence groupLabel, CharSequence fullGroupLabel, boolean delayChanges) { - PackageManager packageManager = context.getPackageManager(); - UserHandle userHandle = UserHandle.getUserHandleForUid(packageInfo.applicationInfo.uid); - - if (groupInfo instanceof PermissionInfo) { - permissionInfos = new ArrayList<>(); - permissionInfos.add((PermissionInfo) groupInfo); - } - - if (permissionInfos == null || permissionInfos.isEmpty()) { - return null; - } - - AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); - - AppPermissionGroup group = new AppPermissionGroup(context, packageInfo, groupInfo.name, - groupInfo.packageName, groupLabel, fullGroupLabel, - /* description */ null, /* request */ 0, - /* requestDetail */ 0, /* backgroundRequest */ 0, - /* backgroundRequestDetail */ 0, /* upgradeRequest */0, - /* upgradeRequestDetail */ 0, groupInfo.packageName, groupInfo.icon, - userHandle, delayChanges, appOpsManager); - - final Set<String> exemptedRestrictedPermissions = context.getPackageManager() - .getWhitelistedRestrictedPermissions(packageInfo.packageName, - Utils.FLAGS_PERMISSION_WHITELIST_ALL); - - // Parse and create permissions requested by the app - ArrayMap<String, Permission> allPermissions = new ArrayMap<>(); - final int permissionCount = packageInfo.requestedPermissions == null ? 0 - : packageInfo.requestedPermissions.length; - String packageName = packageInfo.packageName; - for (int i = 0; i < permissionCount; i++) { - String requestedPermission = packageInfo.requestedPermissions[i]; - - PermissionInfo requestedPermissionInfo = null; - - for (PermissionInfo permissionInfo : permissionInfos) { - if (requestedPermission.equals(permissionInfo.name)) { - requestedPermissionInfo = permissionInfo; - break; - } - } - - if (requestedPermissionInfo == null) { - continue; - } - - // Collect only runtime permissions. - if ((requestedPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) - != PermissionInfo.PROTECTION_DANGEROUS) { - continue; - } - - // Don't allow toggling non-platform permission groups for legacy apps via app ops. - if (packageInfo.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1 - && !PLATFORM_PACKAGE_NAME.equals(groupInfo.packageName)) { - continue; - } - - final boolean granted = (packageInfo.requestedPermissionsFlags[i] - & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0; - - final String appOp = PLATFORM_PACKAGE_NAME.equals(requestedPermissionInfo.packageName) - ? AppOpsManager.permissionToOp(requestedPermissionInfo.name) : null; - - final boolean appOpAllowed; - if (appOp == null) { - appOpAllowed = false; - } else { - int appOpsMode = appOpsManager.unsafeCheckOpRaw(appOp, - packageInfo.applicationInfo.uid, packageName); - appOpAllowed = appOpsMode == MODE_ALLOWED || appOpsMode == MODE_FOREGROUND; - } - - final int flags = packageManager.getPermissionFlags( - requestedPermission, packageName, userHandle); - - Permission permission = new Permission(requestedPermission, requestedPermissionInfo, - granted, appOp, appOpAllowed, flags); - - if (requestedPermissionInfo.backgroundPermission != null) { - group.mHasPermissionWithBackgroundMode = true; - } - - allPermissions.put(requestedPermission, permission); - } - - int numPermissions = allPermissions.size(); - if (numPermissions == 0) { - return null; - } - - // Link up foreground and background permissions - for (int i = 0; i < allPermissions.size(); i++) { - Permission permission = allPermissions.valueAt(i); - - if (permission.getBackgroundPermissionName() != null) { - Permission backgroundPermission = allPermissions.get( - permission.getBackgroundPermissionName()); - - if (backgroundPermission != null) { - backgroundPermission.addForegroundPermissions(permission); - permission.setBackgroundPermission(backgroundPermission); - - // The background permissions isAppOpAllowed refers to the background state of - // the foregound permission's appOp. Hence we can only set it once we know the - // matching foreground permission. - // @see #allowAppOp - if (context.getSystemService(AppOpsManager.class).unsafeCheckOpRaw( - permission.getAppOp(), packageInfo.applicationInfo.uid, - packageInfo.packageName) == MODE_ALLOWED) { - backgroundPermission.setAppOpAllowed(true); - } - } - } - } - - // Add permissions found to this group - for (int i = 0; i < numPermissions; i++) { - Permission permission = allPermissions.valueAt(i); - - if ((!permission.isHardRestricted() - || exemptedRestrictedPermissions.contains(permission.getName())) - && (!permission.isSoftRestricted() - || SoftRestrictedPermissionPolicy.shouldShow(packageInfo, permission))) { - if (permission.isBackgroundPermission()) { - if (group.getBackgroundPermissions() == null) { - group.mBackgroundPermissions = new AppPermissionGroup(group.mContext, - group.getApp(), group.getName(), group.getDeclaringPackage(), - group.getLabel(), group.getFullLabel(), group.getDescription(), - group.getRequest(), group.getRequestDetail(), - group.getBackgroundRequest(), group.getBackgroundRequestDetail(), - group.getUpgradeRequest(), group.getUpgradeRequestDetail(), - group.getIconPkg(), group.getIconResId(), group.getUser(), - delayChanges, appOpsManager); - } - - group.getBackgroundPermissions().addPermission(permission); - } else { - group.addPermission(permission); - } - } - } - - if (group.getPermissions().isEmpty()) { - return null; - } - - return group; - } - - private AppPermissionGroup(Context context, PackageInfo packageInfo, String name, - String declaringPackage, CharSequence label, CharSequence fullLabel, - CharSequence description, @StringRes int request, @StringRes int requestDetail, - @StringRes int backgroundRequest, @StringRes int backgroundRequestDetail, - @StringRes int upgradeRequest, @StringRes int upgradeRequestDetail, - String iconPkg, int iconResId, UserHandle userHandle, boolean delayChanges, - @NonNull AppOpsManager appOpsManager) { - int targetSDK = packageInfo.applicationInfo.targetSdkVersion; - - mContext = context; - mUserHandle = userHandle; - mPackageManager = mContext.getPackageManager(); - mPackageInfo = packageInfo; - mAppSupportsRuntimePermissions = targetSDK > Build.VERSION_CODES.LOLLIPOP_MR1; - mIsEphemeralApp = packageInfo.applicationInfo.isInstantApp(); - mAppOps = appOpsManager; - mActivityManager = context.getSystemService(ActivityManager.class); - mDeclaringPackage = declaringPackage; - mName = name; - mLabel = label; - mFullLabel = fullLabel; - mDescription = description; - mCollator = Collator.getInstance( - context.getResources().getConfiguration().getLocales().get(0)); - mRequest = request; - mRequestDetail = requestDetail; - mBackgroundRequest = backgroundRequest; - mBackgroundRequestDetail = backgroundRequestDetail; - mUpgradeRequest = upgradeRequest; - mUpgradeRequestDetail = upgradeRequestDetail; - mDelayChanges = delayChanges; - if (iconResId != 0) { - mIconPkg = iconPkg; - mIconResId = iconResId; - } else { - mIconPkg = context.getPackageName(); - mIconResId = 0; // doesn't matter to CDM - } - - mIsNonIsolatedStorage = targetSDK < Build.VERSION_CODES.P - || (targetSDK < Build.VERSION_CODES.R - && mAppOps.unsafeCheckOpNoThrow(OPSTR_LEGACY_STORAGE, - packageInfo.applicationInfo.uid, packageInfo.packageName) == MODE_ALLOWED); - } - - boolean doesSupportRuntimePermissions() { - return mAppSupportsRuntimePermissions; - } - - boolean isGrantingAllowed() { - return (!mIsEphemeralApp || mContainsEphemeralPermission) - && (mAppSupportsRuntimePermissions || mContainsPreRuntimePermission); - } - - boolean isReviewRequired() { - if (mAppSupportsRuntimePermissions) { - return false; - } - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - Permission permission = mPermissions.valueAt(i); - if (permission.isReviewRequired()) { - return true; - } - } - return false; - } - - /** - * Are any of the permissions in this group user sensitive. - * - * @return {@code true} if any of the permissions in the group is user sensitive. - */ - public boolean isUserSensitive() { - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - Permission permission = mPermissions.valueAt(i); - if (permission.isUserSensitive()) { - return true; - } - } - return false; - } - - void unsetReviewRequired() { - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - Permission permission = mPermissions.valueAt(i); - if (permission.isReviewRequired()) { - permission.unsetReviewRequired(); - } - } - - if (!mDelayChanges) { - persistChanges(false); - } - } - - boolean hasGrantedByDefaultPermission() { - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - Permission permission = mPermissions.valueAt(i); - if (permission.isGrantedByDefault()) { - return true; - } - } - return false; - } - - public PackageInfo getApp() { - return mPackageInfo; - } - - String getName() { - return mName; - } - - String getDeclaringPackage() { - return mDeclaringPackage; - } - - String getIconPkg() { - return mIconPkg; - } - - int getIconResId() { - return mIconResId; - } - - CharSequence getLabel() { - return mLabel; - } - - /** - * Get the full un-ellipsized label of the permission group. - * - * @return the full label of the group. - */ - public CharSequence getFullLabel() { - return mFullLabel; - } - - /** - * @hide - * @return The resource Id of the request string. - */ - public @StringRes int getRequest() { - return mRequest; - } - - /** - * Get the (subtitle) message explaining to the user that the permission is only granted to - * the apps running in the foreground. - * - * @return the message or 0 if unset - */ - public @StringRes int getRequestDetail() { - return mRequestDetail; - } - - /** - * Get the title of the dialog explaining to the user that the permission is granted while - * the app is in background and in foreground. - * - * @return the message or 0 if unset - */ - public @StringRes int getBackgroundRequest() { - return mBackgroundRequest; - } - - /** - * Get the (subtitle) message explaining to the user that the she/he is about to allow the - * app to have background access. - * - * @return the message or 0 if unset - */ - public @StringRes int getBackgroundRequestDetail() { - return mBackgroundRequestDetail; - } - - /** - * Get the title of the dialog explaining to the user that the permission, which was - * previously only granted for foreground, is granted while the app is in background and in - * foreground. - * - * @return the message or 0 if unset - */ - public @StringRes int getUpgradeRequest() { - return mUpgradeRequest; - } - - /** - * Get the (subtitle) message explaining to the user that the she/he is about to allow the - * app to have background access while currently having foreground only. - * - * @return the message or 0 if unset - */ - public @StringRes int getUpgradeRequestDetail() { - return mUpgradeRequestDetail; - } - - public CharSequence getDescription() { - return mDescription; - } - - public UserHandle getUser() { - return mUserHandle; - } - - /** - * Check if the group contains the permission. - */ - public boolean hasPermission(String permission) { - return mPermissions.get(permission) != null; - } - - /** - * Return a permission if in this group. - * - * @param permissionName The name of the permission - * - * @return The permission - */ - public @Nullable Permission getPermission(@NonNull String permissionName) { - return mPermissions.get(permissionName); - } - - /** - * Check if at least one of the permissions in the entire permission group should be considered - * granted. - */ - public boolean areRuntimePermissionsGranted() { - return areRuntimePermissionsGranted(null); - } - - /** - * Check if at least one of the permissions in the filterPermissions should be considered - * granted. - */ - public boolean areRuntimePermissionsGranted(String[] filterPermissions) { - return areRuntimePermissionsGranted(filterPermissions, false); - } - - /** - * @param filterPermissions the permissions to check for, null for all in this group - * @param asOneTime add the requirement that at least one of the granted permissions must have - * the ONE_TIME flag to return true - */ - public boolean areRuntimePermissionsGranted(String[] filterPermissions, boolean asOneTime) { - return areRuntimePermissionsGranted(filterPermissions, asOneTime, true); - } - - /** - * Returns true if at least one of the permissions in filterPermissions (or the entire - * permission group if null) should be considered granted and satisfy the requirements - * described by asOneTime and includingAppOp. - * - * @param filterPermissions the permissions to check for, null for all in this group - * @param asOneTime add the requirement that the granted permission must have the ONE_TIME flag - * @param includingAppOp add the requirement that if the granted permissions has a - * corresponding AppOp, it must be allowed. - */ - public boolean areRuntimePermissionsGranted(String[] filterPermissions, boolean asOneTime, - boolean includingAppOp) { - if (LocationUtils.isLocationGroupAndProvider(mContext, mName, mPackageInfo.packageName)) { - return LocationUtils.isLocationEnabled(mContext) && !asOneTime; - } - // The permission of the extra location controller package is determined by the status of - // the controller package itself. - if (LocationUtils.isLocationGroupAndControllerExtraPackage( - mContext, mName, mPackageInfo.packageName)) { - return LocationUtils.isExtraLocationControllerPackageEnabled(mContext) && !asOneTime; - } - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - Permission permission = mPermissions.valueAt(i); - if (filterPermissions != null - && !ArrayUtils.contains(filterPermissions, permission.getName())) { - continue; - } - boolean isGranted = includingAppOp ? permission.isGrantedIncludingAppOp() - : permission.isGranted(); - if (isGranted && (!asOneTime || permission.isOneTime())) { - return true; - } - } - if (mBackgroundPermissions != null) { - // If asOneTime is true and none of the foreground permissions are one-time, but some - // background permissions are, then we still want to return true. - return mBackgroundPermissions.areRuntimePermissionsGranted(filterPermissions, - asOneTime, includingAppOp); - } - return false; - } - - boolean grantRuntimePermissions(boolean setByTheUser, boolean fixedByTheUser) { - return grantRuntimePermissions(setByTheUser, fixedByTheUser, null); - } - - /** - * Set mode of an app-op if needed. - * - * @param op The op to set - * @param uid The uid the app-op belongs to - * @param mode The new mode - * - * @return {@code true} iff app-op was changed - */ - private boolean setAppOpMode(@NonNull String op, int uid, int mode) { - int currentMode = mAppOps.unsafeCheckOpRaw(op, uid, mPackageInfo.packageName); - if (currentMode == mode) { - return false; - } - - mAppOps.setUidMode(op, uid, mode); - return true; - } - - /** - * Allow the app op for a permission/uid. - * - * <p>There are three cases: - * <dl> - * <dt>The permission is not split into foreground/background</dt> - * <dd>The app op matching the permission will be set to {@link AppOpsManager#MODE_ALLOWED}</dd> - * <dt>The permission is a foreground permission:</dt> - * <dd><dl><dt>The background permission permission is granted</dt> - * <dd>The app op matching the permission will be set to {@link AppOpsManager#MODE_ALLOWED}</dd> - * <dt>The background permission permission is <u>not</u> granted</dt> - * <dd>The app op matching the permission will be set to - * {@link AppOpsManager#MODE_FOREGROUND}</dd> - * </dl></dd> - * <dt>The permission is a background permission:</dt> - * <dd>All granted foreground permissions for this background permission will be set to - * {@link AppOpsManager#MODE_ALLOWED}</dd> - * </dl> - * - * @param permission The permission which has an appOps that should be allowed - * @param uid The uid of the process the app op is for - * - * @return {@code true} iff app-op was changed - */ - private boolean allowAppOp(Permission permission, int uid) { - boolean wasChanged = false; - - if (permission.isBackgroundPermission()) { - ArrayList<Permission> foregroundPermissions = permission.getForegroundPermissions(); - - int numForegroundPermissions = foregroundPermissions.size(); - for (int i = 0; i < numForegroundPermissions; i++) { - Permission foregroundPermission = foregroundPermissions.get(i); - if (foregroundPermission.isAppOpAllowed()) { - wasChanged |= setAppOpMode(foregroundPermission.getAppOp(), uid, MODE_ALLOWED); - } - } - } else { - if (permission.hasBackgroundPermission()) { - Permission backgroundPermission = permission.getBackgroundPermission(); - - if (backgroundPermission == null) { - // The app requested a permission that has a background permission but it did - // not request the background permission, hence it can never get background - // access - wasChanged = setAppOpMode(permission.getAppOp(), uid, MODE_FOREGROUND); - } else { - if (backgroundPermission.isAppOpAllowed()) { - wasChanged = setAppOpMode(permission.getAppOp(), uid, MODE_ALLOWED); - } else { - wasChanged = setAppOpMode(permission.getAppOp(), uid, MODE_FOREGROUND); - } - } - } else { - wasChanged = setAppOpMode(permission.getAppOp(), uid, MODE_ALLOWED); - } - } - - return wasChanged; - } - - /** - * Kills the app the permissions belong to (and all apps sharing the same uid) - * - * @param reason The reason why the apps are killed - */ - private void killApp(String reason) { - mActivityManager.killUid(mPackageInfo.applicationInfo.uid, reason); - } - - /** - * Grant permissions of the group. - * - * <p>This also automatically grants all app ops for permissions that have app ops. - * <p>This does <u>only</u> grant permissions in {@link #mPermissions}, i.e. usually not - * the background permissions. - * - * @param setByTheUser If the user has made the decision. This does not unset the flag - * @param fixedByTheUser If the user requested that she/he does not want to be asked again - * @param filterPermissions If {@code null} all permissions of the group will be granted. - * Otherwise only permissions in {@code filterPermissions} will be - * granted. - * - * @return {@code true} iff all permissions of this group could be granted. - */ - public boolean grantRuntimePermissions(boolean setByTheUser, boolean fixedByTheUser, - String[] filterPermissions) { - boolean killApp = false; - boolean wasAllGranted = true; - - // We toggle permissions only to apps that support runtime - // permissions, otherwise we toggle the app op corresponding - // to the permission if the permission is granted to the app. - for (Permission permission : mPermissions.values()) { - if (filterPermissions != null - && !ArrayUtils.contains(filterPermissions, permission.getName())) { - continue; - } - - if (!permission.isGrantingAllowed(mIsEphemeralApp, mAppSupportsRuntimePermissions)) { - // Skip unallowed permissions. - continue; - } - - boolean wasGranted = permission.isGrantedIncludingAppOp(); - - if (mAppSupportsRuntimePermissions) { - // Do not touch permissions fixed by the system. - if (permission.isSystemFixed()) { - wasAllGranted = false; - break; - } - - // Ensure the permission app op is enabled before the permission grant. - if (permission.affectsAppOp() && !permission.isAppOpAllowed()) { - permission.setAppOpAllowed(true); - } - - // Grant the permission if needed. - if (!permission.isGranted()) { - permission.setGranted(true); - } - - // Update the permission flags. - if (!fixedByTheUser) { - if (permission.isUserFixed()) { - permission.setUserFixed(false); - } - if (setByTheUser) { - if (!permission.isUserSet()) { - permission.setUserSet(true); - } - } - } else { - if (!permission.isUserFixed()) { - permission.setUserFixed(true); - } - if (permission.isUserSet()) { - permission.setUserSet(false); - } - } - if (permission.isReviewRequired()) { - permission.unsetReviewRequired(); - } - } else { - // Legacy apps cannot have a not granted permission but just in case. - if (!permission.isGranted()) { - continue; - } - - // If the permissions has no corresponding app op, then it is a - // third-party one and we do not offer toggling of such permissions. - if (permission.affectsAppOp()) { - if (!permission.isAppOpAllowed()) { - permission.setAppOpAllowed(true); - - // Legacy apps do not know that they have to retry access to a - // resource due to changes in runtime permissions (app ops in this - // case). Therefore, we restart them on app op change, so they - // can pick up the change. - killApp = true; - } - - // Mark that the permission is not kept granted only for compatibility. - if (permission.isRevokedCompat()) { - permission.setRevokedCompat(false); - } - } - - // Granting a permission explicitly means the user already - // reviewed it so clear the review flag on every grant. - if (permission.isReviewRequired()) { - permission.unsetReviewRequired(); - } - } - - // If we newly grant background access to the fine location, double-guess the user some - // time later if this was really the right choice. - if (!wasGranted && permission.isGrantedIncludingAppOp()) { - if (permission.getName().equals(ACCESS_FINE_LOCATION)) { - Permission bgPerm = permission.getBackgroundPermission(); - if (bgPerm != null) { - if (bgPerm.isGrantedIncludingAppOp()) { - mTriggerLocationAccessCheckOnPersist = true; - } - } - } else if (permission.getName().equals(ACCESS_BACKGROUND_LOCATION)) { - ArrayList<Permission> fgPerms = permission.getForegroundPermissions(); - if (fgPerms != null) { - int numFgPerms = fgPerms.size(); - for (int fgPermNum = 0; fgPermNum < numFgPerms; fgPermNum++) { - Permission fgPerm = fgPerms.get(fgPermNum); - - if (fgPerm.getName().equals(ACCESS_FINE_LOCATION)) { - if (fgPerm.isGrantedIncludingAppOp()) { - mTriggerLocationAccessCheckOnPersist = true; - } - - break; - } - } - } - } - } - } - - if (!mDelayChanges) { - persistChanges(false); - - if (killApp) { - killApp(KILL_REASON_APP_OP_CHANGE); - } - } - - return wasAllGranted; - } - - boolean revokeRuntimePermissions(boolean fixedByTheUser) { - return revokeRuntimePermissions(fixedByTheUser, null); - } - - /** - * Disallow the app op for a permission/uid. - * - * <p>There are three cases: - * <dl> - * <dt>The permission is not split into foreground/background</dt> - * <dd>The app op matching the permission will be set to {@link AppOpsManager#MODE_IGNORED}</dd> - * <dt>The permission is a foreground permission:</dt> - * <dd>The app op matching the permission will be set to {@link AppOpsManager#MODE_IGNORED}</dd> - * <dt>The permission is a background permission:</dt> - * <dd>All granted foreground permissions for this background permission will be set to - * {@link AppOpsManager#MODE_FOREGROUND}</dd> - * </dl> - * - * @param permission The permission which has an appOps that should be disallowed - * @param uid The uid of the process the app op if for - * - * @return {@code true} iff app-op was changed - */ - private boolean disallowAppOp(Permission permission, int uid) { - boolean wasChanged = false; - - if (permission.isBackgroundPermission()) { - ArrayList<Permission> foregroundPermissions = permission.getForegroundPermissions(); - - int numForegroundPermissions = foregroundPermissions.size(); - for (int i = 0; i < numForegroundPermissions; i++) { - Permission foregroundPermission = foregroundPermissions.get(i); - if (foregroundPermission.isAppOpAllowed()) { - wasChanged |= setAppOpMode(foregroundPermission.getAppOp(), uid, - MODE_FOREGROUND); - } - } - } else { - wasChanged = setAppOpMode(permission.getAppOp(), uid, MODE_IGNORED); - } - - return wasChanged; - } - - /** - * Revoke permissions of the group. - * - * <p>This also disallows all app ops for permissions that have app ops. - * <p>This does <u>only</u> revoke permissions in {@link #mPermissions}, i.e. usually not - * the background permissions. - * - * @param fixedByTheUser If the user requested that she/he does not want to be asked again - * @param filterPermissions If {@code null} all permissions of the group will be revoked. - * Otherwise only permissions in {@code filterPermissions} will be - * revoked. - * - * @return {@code true} iff all permissions of this group could be revoked. - */ - public boolean revokeRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) { - boolean killApp = false; - boolean wasAllRevoked = true; - - // We toggle permissions only to apps that support runtime - // permissions, otherwise we toggle the app op corresponding - // to the permission if the permission is granted to the app. - for (Permission permission : mPermissions.values()) { - if (filterPermissions != null - && !ArrayUtils.contains(filterPermissions, permission.getName())) { - continue; - } - - // Do not touch permissions fixed by the system. - if (permission.isSystemFixed()) { - wasAllRevoked = false; - break; - } - - if (mAppSupportsRuntimePermissions) { - // Revoke the permission if needed. - if (permission.isGranted()) { - permission.setGranted(false); - } - - // Update the permission flags. - if (fixedByTheUser) { - // Take a note that the user fixed the permission. - if (permission.isUserSet() || !permission.isUserFixed()) { - permission.setUserSet(false); - permission.setUserFixed(true); - } - } else { - if (!permission.isUserSet() || permission.isUserFixed()) { - permission.setUserSet(true); - permission.setUserFixed(false); - } - } - - if (permission.affectsAppOp()) { - permission.setAppOpAllowed(false); - } - } else { - // Legacy apps cannot have a non-granted permission but just in case. - if (!permission.isGranted()) { - continue; - } - - // If the permission has no corresponding app op, then it is a - // third-party one and we do not offer toggling of such permissions. - if (permission.affectsAppOp()) { - if (permission.isAppOpAllowed()) { - permission.setAppOpAllowed(false); - - // Disabling an app op may put the app in a situation in which it - // has a handle to state it shouldn't have, so we have to kill the - // app. This matches the revoke runtime permission behavior. - killApp = true; - } - - // Mark that the permission is kept granted only for compatibility. - if (!permission.isRevokedCompat()) { - permission.setRevokedCompat(true); - } - } - } - } - - if (!mDelayChanges) { - persistChanges(false); - - if (killApp) { - killApp(KILL_REASON_APP_OP_CHANGE); - } - } - - return wasAllRevoked; - } - - /** - * Mark permissions in this group as policy fixed. - * - * @param filterPermissions The permissions to mark - */ - public void setPolicyFixed(@NonNull String[] filterPermissions) { - for (String permissionName : filterPermissions) { - Permission permission = mPermissions.get(permissionName); - - if (permission != null) { - permission.setPolicyFixed(true); - } - } - - if (!mDelayChanges) { - persistChanges(false); - } - } - - /** - * Set the user-fixed flag for all permissions in this group. - * - * @param isUsedFixed if the flag should be set or not - */ - public void setUserFixed(boolean isUsedFixed) { - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - Permission permission = mPermissions.valueAt(i); - permission.setUserFixed(isUsedFixed); - } - - if (!mDelayChanges) { - persistChanges(false); - } - } - - /** - * Mark this group as having been self-revoked. - */ - public void setSelfRevoked() { - mIsSelfRevoked = true; - } - - /** - * Set the one-time flag for all permissions in this group. - * - * @param isOneTime if the flag should be set or not - */ - public void setOneTime(boolean isOneTime) { - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - Permission permission = mPermissions.valueAt(i); - permission.setOneTime(isOneTime); - } - - if (!mDelayChanges) { - persistChanges(false); - } - } - - /** - * Set the user-set flag for all permissions in this group. - * - * @param isUserSet if the flag should be set or not - */ - public void setUserSet(boolean isUserSet) { - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - Permission permission = mPermissions.valueAt(i); - permission.setUserSet(isUserSet); - } - - if (!mDelayChanges) { - persistChanges(false); - } - } - - /** - * Get all permissions in the group. - */ - public ArrayList<Permission> getPermissions() { - return new ArrayList<>(mPermissions.values()); - } - - /** - * @return An {@link AppPermissionGroup}-object that contains all background permissions for - * this group. - */ - public AppPermissionGroup getBackgroundPermissions() { - return mBackgroundPermissions; - } - - /** - * @return {@code true} iff the app request at least one permission in this group that has a - * background permission. It is possible that the app does not request the matching background - * permission and hence will only ever get foreground access, never background access. - */ - public boolean hasPermissionWithBackgroundMode() { - return mHasPermissionWithBackgroundMode; - } - - /** - * Is the group a storage permission group that is referring to an app that does not have - * isolated storage - * - * @return {@code true} iff this is a storage group on an app that does not have isolated - * storage - */ - public boolean isNonIsolatedStorage() { - return mIsNonIsolatedStorage; - } - - /** - * Whether this is group that contains all the background permission for regular permission - * group. - * - * @return {@code true} iff this is a background permission group. - * - * @see #getBackgroundPermissions() - */ - public boolean isBackgroundGroup() { - return mPermissions.valueAt(0).isBackgroundPermission(); - } - - /** - * Whether this group supports one-time permissions - * @return {@code true} iff this group supports one-time permissions - */ - public boolean supportsOneTimeGrant() { - return Utils.supportsOneTimeGrant(getName()); - } - - int getFlags() { - int flags = 0; - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - Permission permission = mPermissions.valueAt(i); - flags |= permission.getFlags(); - } - return flags; - } - - boolean isUserFixed() { - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - Permission permission = mPermissions.valueAt(i); - if (permission.isUserFixed()) { - return true; - } - } - return false; - } - - /** - * Check if there's a permission in the group is policy fixed. - */ - public boolean isPolicyFixed() { - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - Permission permission = mPermissions.valueAt(i); - if (permission.isPolicyFixed()) { - return true; - } - } - return false; - } - - boolean isUserSet() { - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - Permission permission = mPermissions.valueAt(i); - if (permission.isUserSet()) { - return true; - } - } - return false; - } - - /** - * Check if there's a permission in the group is system fixed. - */ - public boolean isSystemFixed() { - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - Permission permission = mPermissions.valueAt(i); - if (permission.isSystemFixed()) { - return true; - } - } - return false; - } - - /** - * @return Whether any of the permissions in this group is one-time - */ - public boolean isOneTime() { - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - Permission permission = mPermissions.valueAt(i); - if (permission.isOneTime()) { - return true; - } - } - return false; - } - - /** - * @return Whether at least one permission is granted and every granted permission is one-time - */ - public boolean isStrictlyOneTime() { - boolean oneTimePermissionFound = false; - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - Permission permission = mPermissions.valueAt(i); - if (permission.isGranted()) { - if (!permission.isOneTime()) { - return false; - } - oneTimePermissionFound = true; - } - } - return oneTimePermissionFound; - } - - @Override - public int compareTo(AppPermissionGroup another) { - final int result = mCollator.compare(mLabel.toString(), another.mLabel.toString()); - if (result == 0) { - // Unbadged before badged. - return mPackageInfo.applicationInfo.uid - - another.mPackageInfo.applicationInfo.uid; - } - return result; - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof AppPermissionGroup)) { - return false; - } - - AppPermissionGroup other = (AppPermissionGroup) o; - - boolean equal = mName.equals(other.mName) - && mPackageInfo.packageName.equals(other.mPackageInfo.packageName) - && mUserHandle.equals(other.mUserHandle) - && mPermissions.equals(other.mPermissions); - if (!equal) { - return false; - } - - if (mBackgroundPermissions != null && other.getBackgroundPermissions() != null) { - return mBackgroundPermissions.getPermissions().equals( - other.getBackgroundPermissions().getPermissions()); - } - return mBackgroundPermissions == other.getBackgroundPermissions(); - } - - @Override - public int hashCode() { - ArrayList<Permission> backgroundPermissions = new ArrayList<>(); - if (mBackgroundPermissions != null) { - backgroundPermissions = mBackgroundPermissions.getPermissions(); - } - return Objects.hash(mName, mPackageInfo.packageName, mUserHandle, mPermissions, - backgroundPermissions); - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append(getClass().getSimpleName()); - builder.append("{name=").append(mName); - if (mBackgroundPermissions != null) { - builder.append(", <has background permissions>}"); - } - if (!mPermissions.isEmpty()) { - builder.append(", <has permissions>}"); - } else { - builder.append('}'); - } - return builder.toString(); - } - - private void addPermission(Permission permission) { - mPermissions.put(permission.getName(), permission); - if (permission.isEphemeral()) { - mContainsEphemeralPermission = true; - } - if (!permission.isRuntimeOnly()) { - mContainsPreRuntimePermission = true; - } - } - - /** - * If the changes to this group were delayed, persist them to the platform. - * - * @param mayKillBecauseOfAppOpsChange If the app these permissions belong to may be killed if - * app ops change. If this is set to {@code false} the - * caller has to make sure to kill the app if needed. - */ - public void persistChanges(boolean mayKillBecauseOfAppOpsChange) { - persistChanges(mayKillBecauseOfAppOpsChange, null, null); - } - - /** - * If the changes to this group were delayed, persist them to the platform. - * - * @param mayKillBecauseOfAppOpsChange If the app these permissions belong to may be killed if - * app ops change. If this is set to {@code false} the - * caller has to make sure to kill the app if needed. - * @param revokeReason If any permissions are getting revoked, the reason for revoking them. - */ - public void persistChanges(boolean mayKillBecauseOfAppOpsChange, String revokeReason) { - persistChanges(mayKillBecauseOfAppOpsChange, revokeReason, null); - } - - /** - * If the changes to this group were delayed, persist them to the platform. - * - * @param mayKillBecauseOfAppOpsChange If the app these permissions belong to may be killed if - * app ops change. If this is set to {@code false} the - * caller has to make sure to kill the app if needed. - * @param revokeReason If any permissions are getting revoked, the reason for revoking them. - * @param filterPermissions If provided, only persist state for the given permissions - */ - public void persistChanges(boolean mayKillBecauseOfAppOpsChange, String revokeReason, - Set<String> filterPermissions) { - int uid = mPackageInfo.applicationInfo.uid; - - int numPermissions = mPermissions.size(); - boolean shouldKillApp = false; - - for (int i = 0; i < numPermissions; i++) { - Permission permission = mPermissions.valueAt(i); - - if (filterPermissions != null && !filterPermissions.contains(permission.getName())) { - continue; - } - - if (!permission.isSystemFixed()) { - if (permission.isGranted()) { - mPackageManager.grantRuntimePermission(mPackageInfo.packageName, - permission.getName(), mUserHandle); - } else { - boolean isCurrentlyGranted = mContext.checkPermission(permission.getName(), -1, - uid) == PERMISSION_GRANTED; - - if (isCurrentlyGranted) { - if (revokeReason == null) { - mPackageManager.revokeRuntimePermission(mPackageInfo.packageName, - permission.getName(), mUserHandle); - } else { - mPackageManager.revokeRuntimePermission(mPackageInfo.packageName, - permission.getName(), mUserHandle, revokeReason); - } - } - } - } - -// int flags = (permission.isUserSet() ? PackageManager.FLAG_PERMISSION_USER_SET : 0) -// | (permission.isUserFixed() ? PackageManager.FLAG_PERMISSION_USER_FIXED : 0) -// | (permission.isRevokedCompat() -// ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0) -// | (permission.isPolicyFixed() ? -// PackageManager.FLAG_PERMISSION_POLICY_FIXED : 0) -// | (permission.isReviewRequired() -// ? PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED : 0) -// | (permission.isOneTime() ? PackageManager.FLAG_PERMISSION_ONE_TIME : 0) -// | (permission.isSelectedLocationAccuracy() -// ? PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY : 0); - -// mPackageManager.updatePermissionFlags(permission.getName(), -// mPackageInfo.packageName, -// PackageManager.FLAG_PERMISSION_USER_SET -// | PackageManager.FLAG_PERMISSION_USER_FIXED -// | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT -// | PackageManager.FLAG_PERMISSION_POLICY_FIXED -// | (permission.isReviewRequired() -// ? 0 : PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) -// | PackageManager.FLAG_PERMISSION_ONE_TIME -// | PackageManager.FLAG_PERMISSION_AUTO_REVOKED // clear auto revoke -// | PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY, -// flags, mUserHandle); - - if (permission.affectsAppOp()) { - if (!permission.isSystemFixed()) { - // Enabling/Disabling an app op may put the app in a situation in which it has - // a handle to state it shouldn't have, so we have to kill the app. This matches - // the revoke runtime permission behavior. - if (permission.isAppOpAllowed()) { - boolean wasChanged = allowAppOp(permission, uid); - shouldKillApp |= wasChanged && !mAppSupportsRuntimePermissions; - } else { - shouldKillApp |= disallowAppOp(permission, uid); - } - } - } - } - - if (mayKillBecauseOfAppOpsChange && shouldKillApp) { - killApp(KILL_REASON_APP_OP_CHANGE); - } - -// if (mTriggerLocationAccessCheckOnPersist) { -// new LocationAccessCheck(mContext, null).checkLocationAccessSoon(); -// mTriggerLocationAccessCheckOnPersist = false; -// } - -// String packageName = mPackageInfo.packageName; -// if (areRuntimePermissionsGranted(null, true, false)) { -// // Required to read device config in Utils.getOneTimePermissions*(). -// final long token = Binder.clearCallingIdentity(); -// try { -// if (SDK_INT >= Build.VERSION_CODES.TIRAMISU) { -// mContext.getSystemService(PermissionManager.class) -// .startOneTimePermissionSession(packageName, -// Utils.getOneTimePermissionsTimeout(), -// Utils.getOneTimePermissionsKilledDelay(mIsSelfRevoked), -// ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_RESET_TIMER, -// ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_KEEP_SESSION_ALIVE); -// } else { -// mContext.getSystemService(PermissionManager.class) -// .startOneTimePermissionSession(packageName, -// Utils.getOneTimePermissionsTimeout(), -// ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_RESET_TIMER, -// ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_KEEP_SESSION_ALIVE); -// } -// } finally { -// Binder.restoreCallingIdentity(token); -// } -// } else { -// mContext.getSystemService(PermissionManager.class) -// .stopOneTimePermissionSession(packageName); -// } - } - - /** - * Check if permission group contains a runtime permission that split from an installed - * permission and the split happened in an Android version higher than app's targetSdk. - * - * @return {@code true} if there is such permission, {@code false} otherwise - */ - public boolean hasInstallToRuntimeSplit() { - PermissionManager permissionManager = - (PermissionManager) mContext.getSystemService(PermissionManager.class); - - int numSplitPerms = permissionManager.getSplitPermissions().size(); - for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) { - PermissionManager.SplitPermissionInfo spi = - permissionManager.getSplitPermissions().get(splitPermNum); - String splitPerm = spi.getSplitPermission(); - - PermissionInfo pi; - try { - pi = mPackageManager.getPermissionInfo(splitPerm, 0); - } catch (NameNotFoundException e) { - Log.w(LOG_TAG, "No such permission: " + splitPerm, e); - continue; - } - - // Skip if split permission is not "install" permission. - if (pi.getProtection() != pi.PROTECTION_NORMAL) { - continue; - } - - List<String> newPerms = spi.getNewPermissions(); - int numNewPerms = newPerms.size(); - for (int newPermNum = 0; newPermNum < numNewPerms; newPermNum++) { - String newPerm = newPerms.get(newPermNum); - - if (!hasPermission(newPerm)) { - continue; - } - - try { - pi = mPackageManager.getPermissionInfo(newPerm, 0); - } catch (NameNotFoundException e) { - Log.w(LOG_TAG, "No such permission: " + newPerm, e); - continue; - } - - // Skip if new permission is not "runtime" permission. - if (pi.getProtection() != pi.PROTECTION_DANGEROUS) { - continue; - } - - if (mPackageInfo.applicationInfo.targetSdkVersion < spi.getTargetSdk()) { - return true; - } - } - } - return false; - } -} diff --git a/services/companion/java/com/android/server/companion/datatransfer/permbackup/model/AppPermissions.java b/services/companion/java/com/android/server/companion/datatransfer/permbackup/model/AppPermissions.java deleted file mode 100644 index 9ef8d532dbfc..000000000000 --- a/services/companion/java/com/android/server/companion/datatransfer/permbackup/model/AppPermissions.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.companion.datatransfer.permbackup.model; - -import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.os.UserHandle; -import android.util.ArrayMap; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; - -/** - * An app that requests permissions. - * - * <p>Allows to query all permission groups of the app and which permission belongs to which group. - */ -public final class AppPermissions { - /** - * All permission groups the app requests. Background permission groups are attached to their - * foreground groups. - */ - private final ArrayList<AppPermissionGroup> mGroups = new ArrayList<>(); - - /** Cache: group name -> group */ - private final ArrayMap<String, AppPermissionGroup> mGroupNameToGroup = new ArrayMap<>(); - - /** Cache: permission name -> group. Might point to background group */ - private final ArrayMap<String, AppPermissionGroup> mPermissionNameToGroup = new ArrayMap<>(); - - private final Context mContext; - - private final CharSequence mAppLabel; - - private final Runnable mOnErrorCallback; - - private final boolean mSortGroups; - - /** Do not actually commit changes to the platform until {@link #persistChanges} is called */ - private final boolean mDelayChanges; - - private PackageInfo mPackageInfo; - - public AppPermissions(Context context, PackageInfo packageInfo, boolean sortGroups, - Runnable onErrorCallback) { - this(context, packageInfo, sortGroups, false, onErrorCallback); - } - - public AppPermissions(Context context, PackageInfo packageInfo, boolean sortGroups, - boolean delayChanges, Runnable onErrorCallback) { - mContext = context; - mPackageInfo = packageInfo; - mAppLabel = null; // doesn't matter for CDM - mSortGroups = sortGroups; - mDelayChanges = delayChanges; - mOnErrorCallback = onErrorCallback; - loadPermissionGroups(); - } - - public PackageInfo getPackageInfo() { - return mPackageInfo; - } - - /** - * Refresh package info and permission groups. - */ - public void refresh() { - loadPackageInfo(); - loadPermissionGroups(); - } - - public CharSequence getAppLabel() { - return mAppLabel; - } - - /** - * Get permission group by name. - */ - public AppPermissionGroup getPermissionGroup(String name) { - return mGroupNameToGroup.get(name); - } - - public List<AppPermissionGroup> getPermissionGroups() { - return mGroups; - } - - /** - * Check if the group is review required. - */ - public boolean isReviewRequired() { - final int groupCount = mGroups.size(); - for (int i = 0; i < groupCount; i++) { - AppPermissionGroup group = mGroups.get(i); - if (group.isReviewRequired()) { - return true; - } - } - return false; - } - - private void loadPackageInfo() { - try { - mPackageInfo = mContext.createPackageContextAsUser(mPackageInfo.packageName, 0, - UserHandle.getUserHandleForUid(mPackageInfo.applicationInfo.uid)) - .getPackageManager().getPackageInfo(mPackageInfo.packageName, - PackageManager.GET_PERMISSIONS); - } catch (PackageManager.NameNotFoundException e) { - if (mOnErrorCallback != null) { - mOnErrorCallback.run(); - } - } - } - - /** - * Add all individual permissions of the {@code group} to the {@link #mPermissionNameToGroup} - * lookup table. - * - * @param group The group of permissions to add - */ - private void addAllPermissions(AppPermissionGroup group) { - ArrayList<Permission> perms = group.getPermissions(); - - int numPerms = perms.size(); - for (int permNum = 0; permNum < numPerms; permNum++) { - mPermissionNameToGroup.put(perms.get(permNum).getName(), group); - } - } - - private void loadPermissionGroups() { - mGroups.clear(); - mGroupNameToGroup.clear(); - mPermissionNameToGroup.clear(); - - if (mPackageInfo.requestedPermissions != null) { - for (String requestedPerm : mPackageInfo.requestedPermissions) { - if (getGroupForPermission(requestedPerm) == null) { - AppPermissionGroup group = AppPermissionGroup.create(mContext, mPackageInfo, - requestedPerm, mDelayChanges); - if (group == null) { - continue; - } - - mGroups.add(group); - mGroupNameToGroup.put(group.getName(), group); - - addAllPermissions(group); - - AppPermissionGroup backgroundGroup = group.getBackgroundPermissions(); - if (backgroundGroup != null) { - addAllPermissions(backgroundGroup); - } - } - } - - if (mSortGroups) { - Collections.sort(mGroups); - } - } - } - - /** - * Find the group a permission belongs to. - * - * <p>The group found might be a background group. - * - * @param permission The name of the permission - * - * @return The group the permission belongs to - */ - public AppPermissionGroup getGroupForPermission(String permission) { - return mPermissionNameToGroup.get(permission); - } - - /** - * If the changes to the permission groups were delayed, persist them now. - * - * @param mayKillBecauseOfAppOpsChange If the app may be killed if app ops change. If this is - * set to {@code false} the caller has to make sure to kill - * the app if needed. - */ - public void persistChanges(boolean mayKillBecauseOfAppOpsChange) { - persistChanges(mayKillBecauseOfAppOpsChange, null); - } - - /** - * If the changes to the permission groups were delayed, persist them now. - * - * @param mayKillBecauseOfAppOpsChange If the app may be killed if app ops change. If this is - * set to {@code false} the caller has to make sure to kill - * the app if needed. - * @param filterPermissions If provided, only persist state for the given permissions - */ - public void persistChanges(boolean mayKillBecauseOfAppOpsChange, - Set<String> filterPermissions) { - if (mDelayChanges) { - int numGroups = mGroups.size(); - - for (int i = 0; i < numGroups; i++) { - AppPermissionGroup group = mGroups.get(i); - group.persistChanges(mayKillBecauseOfAppOpsChange, null, filterPermissions); - - AppPermissionGroup backgroundGroup = group.getBackgroundPermissions(); - if (backgroundGroup != null) { - backgroundGroup.persistChanges(mayKillBecauseOfAppOpsChange, null, - filterPermissions); - } - } - } - } -} diff --git a/services/companion/java/com/android/server/companion/datatransfer/permbackup/model/Permission.java b/services/companion/java/com/android/server/companion/datatransfer/permbackup/model/Permission.java deleted file mode 100644 index 2bec970a1dea..000000000000 --- a/services/companion/java/com/android/server/companion/datatransfer/permbackup/model/Permission.java +++ /dev/null @@ -1,414 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.companion.datatransfer.permbackup.model; - -import android.annotation.NonNull; -import android.content.pm.PackageManager; -import android.content.pm.PermissionInfo; - -import java.util.ArrayList; -import java.util.Objects; - -/** - * A permission and its properties. - * - * @see AppPermissionGroup - */ -public final class Permission { - private final @NonNull PermissionInfo mPermissionInfo; - private final String mName; - private final String mBackgroundPermissionName; - private final String mAppOp; - - private boolean mGranted; - private boolean mAppOpAllowed; - private int mFlags; - private boolean mIsEphemeral; - private boolean mIsRuntimeOnly; - private Permission mBackgroundPermission; - private ArrayList<Permission> mForegroundPermissions; - private boolean mWhitelisted; - - public Permission(String name, @NonNull PermissionInfo permissionInfo, boolean granted, - String appOp, boolean appOpAllowed, int flags) { - mPermissionInfo = permissionInfo; - mName = name; - mBackgroundPermissionName = permissionInfo.backgroundPermission; - mGranted = granted; - mAppOp = appOp; - mAppOpAllowed = appOpAllowed; - mFlags = flags; - mIsEphemeral = - (permissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTANT) != 0; - mIsRuntimeOnly = - (permissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) != 0; - } - - /** - * Mark this permission as background permission for {@code foregroundPermissions}. - * - * @param foregroundPermission The foreground permission - */ - public void addForegroundPermissions(Permission foregroundPermission) { - if (mForegroundPermissions == null) { - mForegroundPermissions = new ArrayList<>(1); - } - mForegroundPermissions.add(foregroundPermission); - } - - /** - * Mark this permission as foreground permission for {@code backgroundPermission}. - * - * @param backgroundPermission The background permission - */ - public void setBackgroundPermission(Permission backgroundPermission) { - mBackgroundPermission = backgroundPermission; - } - - public PermissionInfo getPermissionInfo() { - return mPermissionInfo; - } - - public String getName() { - return mName; - } - - public String getAppOp() { - return mAppOp; - } - - public int getFlags() { - return mFlags; - } - - boolean isHardRestricted() { - return (mPermissionInfo.flags & PermissionInfo.FLAG_HARD_RESTRICTED) != 0; - } - - boolean isSoftRestricted() { - return (mPermissionInfo.flags & PermissionInfo.FLAG_SOFT_RESTRICTED) != 0; - } - - /** - * Does this permission affect app ops. - * - * <p>I.e. does this permission have a matching app op or is this a background permission. All - * background permissions affect the app op of its assigned foreground permission. - * - * @return {@code true} if this permission affects app ops - */ - public boolean affectsAppOp() { - return mAppOp != null || isBackgroundPermission(); - } - - /** - * Check if the permission is granted. - * - * <p>This ignores the state of the app-op. I.e. for apps not handling runtime permissions, this - * always returns {@code true}. - * - * @return If the permission is granted - */ - public boolean isGranted() { - return mGranted; - } - - /** - * Check if the permission is granted, also considering the state of the app-op. - * - * <p>For the UI, check the grant state of the whole group via - * {@link AppPermissionGroup#areRuntimePermissionsGranted}. - * - * @return {@code true} if the permission (and the app-op) is granted. - */ - public boolean isGrantedIncludingAppOp() { - return mGranted && (!affectsAppOp() || isAppOpAllowed()) && !isReviewRequired(); - } - - public boolean isReviewRequired() { - return (mFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0; - } - - /** - * Unset review required flag. - */ - public void unsetReviewRequired() { - mFlags &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; - } - - public void setGranted(boolean mGranted) { - this.mGranted = mGranted; - } - - public boolean isAppOpAllowed() { - return mAppOpAllowed; - } - - /** - * Check if it's user fixed. - */ - public boolean isUserFixed() { - return (mFlags & PackageManager.FLAG_PERMISSION_USER_FIXED) != 0; - } - - /** - * Set user fixed flag. - */ - public void setUserFixed(boolean userFixed) { - if (userFixed) { - mFlags |= PackageManager.FLAG_PERMISSION_USER_FIXED; - } else { - mFlags &= ~PackageManager.FLAG_PERMISSION_USER_FIXED; - } - } - - /** - * Sets the one-time permission flag - * @param oneTime true to set the flag, false to unset it - */ - public void setOneTime(boolean oneTime) { - if (oneTime) { - mFlags |= PackageManager.FLAG_PERMISSION_ONE_TIME; - } else { - mFlags &= ~PackageManager.FLAG_PERMISSION_ONE_TIME; - } - } - - public boolean isSelectedLocationAccuracy() { - return (mFlags & PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY) != 0; - } - - /** - * Sets the selected-location-accuracy permission flag - * @param selectedLocationAccuracy true to set the flag, false to unset it - */ - public void setSelectedLocationAccuracy(boolean selectedLocationAccuracy) { - if (selectedLocationAccuracy) { - mFlags |= PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY; - } else { - mFlags &= ~PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY; - } - } - - public boolean isSystemFixed() { - return (mFlags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0; - } - - public boolean isPolicyFixed() { - return (mFlags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0; - } - - public boolean isUserSet() { - return (mFlags & PackageManager.FLAG_PERMISSION_USER_SET) != 0; - } - - public boolean isGrantedByDefault() { - return (mFlags & PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT) != 0; - } - - /** - * Is the permission user sensitive, i.e. should it always be shown to the user. - * - * <p>Non-sensitive permission are usually hidden behind a setting in an overflow menu or - * some other kind of flag. - * - * @return {@code true} if the permission is user sensitive. - */ - public boolean isUserSensitive() { - if (isGrantedIncludingAppOp()) { - return (mFlags & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) != 0; - } else { - return (mFlags & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED) != 0; - } - } - - /** - * If this permission is split into a foreground and background permission, this is the name - * of the background permission. - * - * @return The name of the background permission or {@code null} if the permission is not split - */ - public String getBackgroundPermissionName() { - return mBackgroundPermissionName; - } - - /** - * @return If this permission is split into a foreground and background permission, - * returns the background permission - */ - public Permission getBackgroundPermission() { - return mBackgroundPermission; - } - - /** - * @return If this permission is split into a foreground and background permission, - * returns the foreground permission - */ - public ArrayList<Permission> getForegroundPermissions() { - return mForegroundPermissions; - } - - /** - * @return {@code true} iff this is the foreground permission of a background-foreground-split - * permission - */ - public boolean hasBackgroundPermission() { - return mBackgroundPermissionName != null; - } - - /** - * @return {@code true} iff this is the background permission of a background-foreground-split - * permission - */ - public boolean isBackgroundPermission() { - return mForegroundPermissions != null; - } - - /** - * @see PackageManager#FLAG_PERMISSION_ONE_TIME - */ - public boolean isOneTime() { - return (mFlags & PackageManager.FLAG_PERMISSION_ONE_TIME) != 0; - } - - /** - * Set userSet flag. - */ - public void setUserSet(boolean userSet) { - if (userSet) { - mFlags |= PackageManager.FLAG_PERMISSION_USER_SET; - } else { - mFlags &= ~PackageManager.FLAG_PERMISSION_USER_SET; - } - } - - /** - * Set policy fixed flag. - */ - public void setPolicyFixed(boolean policyFixed) { - if (policyFixed) { - mFlags |= PackageManager.FLAG_PERMISSION_POLICY_FIXED; - } else { - mFlags &= ~PackageManager.FLAG_PERMISSION_POLICY_FIXED; - } - } - - /** - * Check if the permission is revoke compat. - */ - public boolean isRevokedCompat() { - return (mFlags & PackageManager.FLAG_PERMISSION_REVOKED_COMPAT) != 0; - } - - /** - * Set revoke compat flag. - */ - public void setRevokedCompat(boolean revokedCompat) { - if (revokedCompat) { - mFlags |= PackageManager.FLAG_PERMISSION_REVOKED_COMPAT; - } else { - mFlags &= ~PackageManager.FLAG_PERMISSION_REVOKED_COMPAT; - } - } - - /** - * Set app op allowed flag. - */ - public void setAppOpAllowed(boolean mAppOpAllowed) { - this.mAppOpAllowed = mAppOpAllowed; - } - - /** - * Check if it's ephemeral. - */ - public boolean isEphemeral() { - return mIsEphemeral; - } - - /** - * Check if it's runtime only. - */ - public boolean isRuntimeOnly() { - return mIsRuntimeOnly; - } - - /** - * Check if it's granting allowed. - */ - public boolean isGrantingAllowed(boolean isEphemeralApp, boolean supportsRuntimePermissions) { - return (!isEphemeralApp || isEphemeral()) - && (supportsRuntimePermissions || !isRuntimeOnly()); - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof Permission)) { - return false; - } - - Permission other = (Permission) o; - - if (!Objects.equals(getName(), other.getName()) || getFlags() != other.getFlags() - || isGranted() != other.isGranted()) { - return false; - } - - - // Only compare permission names, in order to avoid recursion - if (getBackgroundPermission() != null && other.getBackgroundPermission() != null) { - if (!Objects.equals(getBackgroundPermissionName(), - other.getBackgroundPermissionName())) { - return false; - } - } else if (getBackgroundPermission() != other.getBackgroundPermission()) { - return false; - } - - if (getForegroundPermissions() != null && other.getForegroundPermissions() != null) { - ArrayList<Permission> others = other.getForegroundPermissions(); - if (getForegroundPermissions().size() != others.size()) { - return false; - } - for (int i = 0; i < others.size(); i++) { - if (!getForegroundPermissions().get(i).getName().equals(others.get(i).getName())) { - return false; - } - } - } else if (getForegroundPermissions() != null || other.getForegroundPermissions() != null) { - return false; - } - - return Objects.equals(getAppOp(), other.getAppOp()) - && isAppOpAllowed() == other.isAppOpAllowed(); - } - - @Override - public int hashCode() { - ArrayList<String> linkedPermissionNames = new ArrayList<>(); - if (mBackgroundPermission != null) { - linkedPermissionNames.add(mBackgroundPermission.getName()); - } - if (mForegroundPermissions != null) { - for (Permission linkedPermission: mForegroundPermissions) { - if (linkedPermission != null) { - linkedPermissionNames.add(linkedPermission.getName()); - } - } - } - return Objects.hash(mName, mFlags, mGranted, mAppOp, mAppOpAllowed, linkedPermissionNames); - } -} diff --git a/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/ArrayUtils.java b/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/ArrayUtils.java deleted file mode 100644 index 7027528fc203..000000000000 --- a/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/ArrayUtils.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.companion.datatransfer.permbackup.utils; - -import android.annotation.Nullable; - -import java.util.Objects; - -/** - * Utils for array manipulation. - */ -public final class ArrayUtils { - private ArrayUtils() { /* cannot be instantiated */ } - - /** - * Checks if an array is null or has no elements. - * - * @param array the array to check for - * - * @return whether the array is null or has no elements. - */ - public static <T> boolean isEmpty(@Nullable T[] array) { - return array == null || array.length == 0; - } - - /** - * Checks that value is present as at least one of the elements of the array. - * @param array the array to check in - * @param value the value to check for - * @return true if the value is present in the array - */ - public static <T> boolean contains(T[] array, T value) { - return indexOf(array, value) != -1; - } - - /** - * Return first index of {@code value} in {@code array}, or {@code -1} if - * not found. - */ - public static <T> int indexOf(T[] array, T value) { - if (array == null) return -1; - for (int i = 0; i < array.length; i++) { - if (Objects.equals(array[i], value)) return i; - } - return -1; - } -} diff --git a/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/LocationUtils.java b/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/LocationUtils.java deleted file mode 100644 index 9402e46d16d9..000000000000 --- a/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/LocationUtils.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.companion.datatransfer.permbackup.utils; - -import static android.location.LocationManager.EXTRA_LOCATION_ENABLED; - -import android.Manifest; -import android.annotation.NonNull; -import android.content.ActivityNotFoundException; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.location.LocationManager; -import android.os.Handler; -import android.os.Looper; -import android.os.UserHandle; -import android.provider.Settings; -import android.util.Log; - -import java.util.ArrayList; - -/** - * Utils for location service. - */ -public class LocationUtils { - - public static final String LOCATION_PERMISSION = Manifest.permission_group.LOCATION; - public static final String ACTIVITY_RECOGNITION_PERMISSION = - Manifest.permission_group.ACTIVITY_RECOGNITION; - - private static final String TAG = LocationUtils.class.getSimpleName(); - private static final long LOCATION_UPDATE_DELAY_MS = 1000; - private static final Handler sMainHandler = new Handler(Looper.getMainLooper()); - - - /** Start the settings page for the location controller extra package. */ - public static void startLocationControllerExtraPackageSettings(@NonNull Context context, - @NonNull UserHandle user) { - try { - context.startActivityAsUser(new Intent( - Settings.ACTION_LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS), user); - } catch (ActivityNotFoundException e) { - // In rare cases where location controller extra package is set, but - // no activity exists to handle the location controller extra package settings - // intent, log an error instead of crashing permission controller. - Log.e(TAG, "No activity to handle " - + "android.settings.LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS"); - } - } - - /** - * Check if location is enabled. - */ - public static boolean isLocationEnabled(Context context) { - return context.getSystemService(LocationManager.class).isLocationEnabled(); - } - - /** Checks if the provided package is a location provider. */ - public static boolean isLocationProvider(Context context, String packageName) { - return context.getSystemService(LocationManager.class).isProviderPackage(packageName); - } - - /** - * Check if group is location and the package is a location provider. - */ - public static boolean isLocationGroupAndProvider(Context context, String groupName, - String packageName) { - return LOCATION_PERMISSION.equals(groupName) && isLocationProvider(context, packageName); - } - - /** - * Check if group is location and package is extra location controller. - */ - public static boolean isLocationGroupAndControllerExtraPackage(@NonNull Context context, - @NonNull String groupName, @NonNull String packageName) { - return (LOCATION_PERMISSION.equals(groupName) - || ACTIVITY_RECOGNITION_PERMISSION.equals(groupName)) - && packageName.equals(context.getSystemService(LocationManager.class) - .getExtraLocationControllerPackage()); - } - - /** Returns whether the location controller extra package is enabled. */ - public static boolean isExtraLocationControllerPackageEnabled(Context context) { - try { - return context.getSystemService(LocationManager.class) - .isExtraLocationControllerPackageEnabled(); - } catch (Exception e) { - return false; - } - - } - - /** - * A Listener which responds to enabling or disabling of location on the device - */ - public interface LocationListener { - - /** - * A callback run any time we receive a broadcast stating the location enable state has - * changed. - * @param enabled Whether or not location is enabled - */ - void onLocationStateChange(boolean enabled); - } - - private static final ArrayList<LocationListener> sLocationListeners = new ArrayList<>(); - - private static BroadcastReceiver sLocationBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - boolean isEnabled = intent.getBooleanExtra(EXTRA_LOCATION_ENABLED, true); - sMainHandler.postDelayed(() -> { - synchronized (sLocationListeners) { - for (LocationListener l : sLocationListeners) { - l.onLocationStateChange(isEnabled); - } - } - }, LOCATION_UPDATE_DELAY_MS); - } - }; -} diff --git a/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/SoftRestrictedPermissionPolicy.java b/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/SoftRestrictedPermissionPolicy.java deleted file mode 100644 index d4940502f1e2..000000000000 --- a/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/SoftRestrictedPermissionPolicy.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.companion.datatransfer.permbackup.utils; - -import static android.Manifest.permission.READ_EXTERNAL_STORAGE; -import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; - -import android.annotation.NonNull; -import android.content.pm.PackageInfo; -import android.os.Build; - -import com.android.server.companion.datatransfer.permbackup.model.Permission; - -/** - * The behavior of soft restricted permissions is different for each permission. This class collects - * the policies in one place. - * - * This is the twin of {@link com.android.server.policy.SoftRestrictedPermissionPolicy} - */ -public abstract class SoftRestrictedPermissionPolicy { - - /** - * Check if the permission should be shown in the UI. - * - * @param pkg the package the permission belongs to - * @param permission the permission - * - * @return {@code true} iff the permission should be shown in the UI. - */ - public static boolean shouldShow(@NonNull PackageInfo pkg, @NonNull Permission permission) { - switch (permission.getName()) { - case READ_EXTERNAL_STORAGE: - case WRITE_EXTERNAL_STORAGE: { - boolean isWhiteListed = - (permission.getFlags() & Utils.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) - != 0; - int targetSDK = pkg.applicationInfo.targetSdkVersion; - - return isWhiteListed || targetSDK >= Build.VERSION_CODES.Q; - } - default: - return true; - } - } - - /** - * Check if the permission should be shown in the UI. - * - * @param pkg the LightPackageInfo the permission belongs to - * @param permissionName the name of the permission - * @param permissionFlags the PermissionController flags (not the PermissionInfo flags) for - * the permission - * - * @return {@code true} iff the permission should be shown in the UI. - */ - public static boolean shouldShow(@NonNull PackageInfo pkg, @NonNull String permissionName, - int permissionFlags) { - switch (permissionName) { - case READ_EXTERNAL_STORAGE: - case WRITE_EXTERNAL_STORAGE: { - boolean isWhiteListed = - (permissionFlags & Utils.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0; - return isWhiteListed || pkg.applicationInfo.targetSdkVersion - >= Build.VERSION_CODES.Q; - } - default: - return true; - } - } -} diff --git a/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/Utils.java b/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/Utils.java deleted file mode 100644 index 9350549d233e..000000000000 --- a/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/Utils.java +++ /dev/null @@ -1,819 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.companion.datatransfer.permbackup.utils; - -import static android.Manifest.permission_group.ACTIVITY_RECOGNITION; -import static android.Manifest.permission_group.CALENDAR; -import static android.Manifest.permission_group.CALL_LOG; -import static android.Manifest.permission_group.CAMERA; -import static android.Manifest.permission_group.CONTACTS; -import static android.Manifest.permission_group.LOCATION; -import static android.Manifest.permission_group.MICROPHONE; -import static android.Manifest.permission_group.NEARBY_DEVICES; -import static android.Manifest.permission_group.NOTIFICATIONS; -import static android.Manifest.permission_group.PHONE; -import static android.Manifest.permission_group.READ_MEDIA_AURAL; -import static android.Manifest.permission_group.READ_MEDIA_VISUAL; -import static android.Manifest.permission_group.SENSORS; -import static android.Manifest.permission_group.SMS; -import static android.Manifest.permission_group.STORAGE; -import static android.app.AppOpsManager.MODE_ALLOWED; -import static android.app.AppOpsManager.OPSTR_LEGACY_STORAGE; -import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; -import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT; -import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT; -import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED; -import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED; -import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; - -import static java.lang.annotation.RetentionPolicy.SOURCE; - -import android.Manifest; -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.AppOpsManager; -import android.app.Application; -import android.app.role.RoleManager; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageInfo; -import android.content.pm.PackageItemInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.PermissionInfo; -import android.content.pm.ResolveInfo; -import android.hardware.SensorPrivacyManager; -import android.os.Build; -import android.os.Process; -import android.os.UserHandle; -import android.provider.DeviceConfig; -import android.provider.Settings; -import android.text.format.DateFormat; -import android.util.ArrayMap; -import android.util.ArraySet; -import android.util.Log; - -import com.android.server.companion.datatransfer.permbackup.model.AppPermissionGroup; - -import java.lang.annotation.Retention; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Set; - -/** - * Util class for BackupHelper - */ -public final class Utils { - - @Retention(SOURCE) - @IntDef(value = {LAST_24H_SENSOR_TODAY, LAST_24H_SENSOR_YESTERDAY, - LAST_24H_CONTENT_PROVIDER, NOT_IN_LAST_7D}) - public @interface AppPermsLastAccessType {} - public static final int LAST_24H_SENSOR_TODAY = 1; - public static final int LAST_24H_SENSOR_YESTERDAY = 2; - public static final int LAST_24H_CONTENT_PROVIDER = 3; - public static final int LAST_7D_SENSOR = 4; - public static final int LAST_7D_CONTENT_PROVIDER = 5; - public static final int NOT_IN_LAST_7D = 6; - - private static final List<String> SENSOR_DATA_PERMISSIONS = List.of( - Manifest.permission_group.LOCATION, - Manifest.permission_group.CAMERA, - Manifest.permission_group.MICROPHONE - ); - - public static final List<String> STORAGE_SUPERGROUP_PERMISSIONS = -// (SDK_INT < Build.VERSION_CODES.TIRAMISU) ? List.of() : - List.of( - Manifest.permission_group.STORAGE, - Manifest.permission_group.READ_MEDIA_AURAL, - Manifest.permission_group.READ_MEDIA_VISUAL - ); - - private static final String LOG_TAG = "Utils"; - - public static final String OS_PKG = "android"; - - public static final float DEFAULT_MAX_LABEL_SIZE_PX = 500f; - - /** The time an app needs to be unused in order to be hibernated */ - public static final String PROPERTY_HIBERNATION_UNUSED_THRESHOLD_MILLIS = - "auto_revoke_unused_threshold_millis2"; - - /** The frequency of running the job for hibernating apps */ - public static final String PROPERTY_HIBERNATION_CHECK_FREQUENCY_MILLIS = - "auto_revoke_check_frequency_millis"; - - /** Whether hibernation targets apps that target a pre-S SDK */ - public static final String PROPERTY_HIBERNATION_TARGETS_PRE_S_APPS = - "app_hibernation_targets_pre_s_apps"; - - /** Whether or not app hibernation is enabled on the device **/ - public static final String PROPERTY_APP_HIBERNATION_ENABLED = "app_hibernation_enabled"; - - /** Whether to show the Permissions Hub. */ - private static final String PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_enabled"; - - /** The timeout for one-time permissions */ - private static final String PROPERTY_ONE_TIME_PERMISSIONS_TIMEOUT_MILLIS = - "one_time_permissions_timeout_millis"; - - /** The delay before ending a one-time permission session when all processes are dead */ - private static final String PROPERTY_ONE_TIME_PERMISSIONS_KILLED_DELAY_MILLIS = - "one_time_permissions_killed_delay_millis"; - - /** Whether to show location access check notifications. */ - private static final String PROPERTY_LOCATION_ACCESS_CHECK_ENABLED = - "location_access_check_enabled"; - - /** The time an app needs to be unused in order to be hibernated */ - public static final String PROPERTY_PERMISSION_DECISIONS_CHECK_OLD_FREQUENCY_MILLIS = - "permission_decisions_check_old_frequency_millis"; - - /** The time an app needs to be unused in order to be hibernated */ - public static final String PROPERTY_PERMISSION_DECISIONS_MAX_DATA_AGE_MILLIS = - "permission_decisions_max_data_age_millis"; - - /** Whether or not warning banner is displayed when device sensors are off **/ - public static final String PROPERTY_WARNING_BANNER_DISPLAY_ENABLED = "warning_banner_enabled"; - - /** All permission whitelists. */ - public static final int FLAGS_PERMISSION_WHITELIST_ALL = - PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM - | PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE - | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER; - - /** All permission restriction exemptions. */ - public static final int FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT = - FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT - | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT - | FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT; - - /** - * The default length of the timeout for one-time permissions - */ - public static final long ONE_TIME_PERMISSIONS_TIMEOUT_MILLIS = 1 * 60 * 1000; // 1 minute - - /** - * The default length to wait before ending a one-time permission session after all processes - * are dead. - */ - public static final long ONE_TIME_PERMISSIONS_KILLED_DELAY_MILLIS = 5 * 1000; - - /** Mapping permission -> group for all dangerous platform permissions */ - private static final ArrayMap<String, String> PLATFORM_PERMISSIONS; - - /** Mapping group -> permissions for all dangerous platform permissions */ - private static final ArrayMap<String, ArrayList<String>> PLATFORM_PERMISSION_GROUPS; - - /** Set of groups that will be able to receive one-time grant */ - private static final ArraySet<String> ONE_TIME_PERMISSION_GROUPS; - - /** Permission -> Sensor codes */ - private static final ArrayMap<String, Integer> PERM_SENSOR_CODES; - - public static final int FLAGS_ALWAYS_USER_SENSITIVE = - FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED - | FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED; - - private static final String SYSTEM_PKG = "android"; - - private static final String SYSTEM_AMBIENT_AUDIO_INTELLIGENCE = - "android.app.role.SYSTEM_AMBIENT_AUDIO_INTELLIGENCE"; - private static final String SYSTEM_UI_INTELLIGENCE = - "android.app.role.SYSTEM_UI_INTELLIGENCE"; - private static final String SYSTEM_AUDIO_INTELLIGENCE = - "android.app.role.SYSTEM_AUDIO_INTELLIGENCE"; - private static final String SYSTEM_NOTIFICATION_INTELLIGENCE = - "android.app.role.SYSTEM_NOTIFICATION_INTELLIGENCE"; - private static final String SYSTEM_TEXT_INTELLIGENCE = - "android.app.role.SYSTEM_TEXT_INTELLIGENCE"; - private static final String SYSTEM_VISUAL_INTELLIGENCE = - "android.app.role.SYSTEM_VISUAL_INTELLIGENCE"; - - // TODO: theianchen Using hardcoded values here as a WIP solution for now. - private static final String[] EXEMPTED_ROLES = { - SYSTEM_AMBIENT_AUDIO_INTELLIGENCE, - SYSTEM_UI_INTELLIGENCE, - SYSTEM_AUDIO_INTELLIGENCE, - SYSTEM_NOTIFICATION_INTELLIGENCE, - SYSTEM_TEXT_INTELLIGENCE, - SYSTEM_VISUAL_INTELLIGENCE, - }; - - static { - PLATFORM_PERMISSIONS = new ArrayMap<>(); - - PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CONTACTS, CONTACTS); - PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_CONTACTS, CONTACTS); - PLATFORM_PERMISSIONS.put(Manifest.permission.GET_ACCOUNTS, CONTACTS); - - PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CALENDAR, CALENDAR); - PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_CALENDAR, CALENDAR); - - PLATFORM_PERMISSIONS.put(Manifest.permission.SEND_SMS, SMS); - PLATFORM_PERMISSIONS.put(Manifest.permission.RECEIVE_SMS, SMS); - PLATFORM_PERMISSIONS.put(Manifest.permission.READ_SMS, SMS); - PLATFORM_PERMISSIONS.put(Manifest.permission.RECEIVE_MMS, SMS); - PLATFORM_PERMISSIONS.put(Manifest.permission.RECEIVE_WAP_PUSH, SMS); - PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CELL_BROADCASTS, SMS); - - // If permissions are added to the Storage group, they must be added to the - // STORAGE_PERMISSIONS list in PermissionManagerService in frameworks/base - PLATFORM_PERMISSIONS.put(Manifest.permission.READ_EXTERNAL_STORAGE, STORAGE); - PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_EXTERNAL_STORAGE, STORAGE); -// if (SDK_INT < Build.VERSION_CODES.TIRAMISU) { -// PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_MEDIA_LOCATION, STORAGE); -// } - -// if (SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - PLATFORM_PERMISSIONS.put(Manifest.permission.READ_MEDIA_AUDIO, READ_MEDIA_AURAL); - PLATFORM_PERMISSIONS.put(Manifest.permission.READ_MEDIA_IMAGES, READ_MEDIA_VISUAL); - PLATFORM_PERMISSIONS.put(Manifest.permission.READ_MEDIA_VIDEO, READ_MEDIA_VISUAL); - PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_MEDIA_LOCATION, READ_MEDIA_VISUAL); -// } - - PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_FINE_LOCATION, LOCATION); - PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_COARSE_LOCATION, LOCATION); - PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_BACKGROUND_LOCATION, LOCATION); - -// if (SDK_INT >= Build.VERSION_CODES.S) { - PLATFORM_PERMISSIONS.put(Manifest.permission.BLUETOOTH_ADVERTISE, NEARBY_DEVICES); - PLATFORM_PERMISSIONS.put(Manifest.permission.BLUETOOTH_CONNECT, NEARBY_DEVICES); - PLATFORM_PERMISSIONS.put(Manifest.permission.BLUETOOTH_SCAN, NEARBY_DEVICES); - PLATFORM_PERMISSIONS.put(Manifest.permission.UWB_RANGING, NEARBY_DEVICES); -// } -// if (SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - PLATFORM_PERMISSIONS.put(Manifest.permission.NEARBY_WIFI_DEVICES, NEARBY_DEVICES); -// } - - PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CALL_LOG, CALL_LOG); - PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_CALL_LOG, CALL_LOG); - PLATFORM_PERMISSIONS.put(Manifest.permission.PROCESS_OUTGOING_CALLS, CALL_LOG); - - PLATFORM_PERMISSIONS.put(Manifest.permission.READ_PHONE_STATE, PHONE); - PLATFORM_PERMISSIONS.put(Manifest.permission.READ_PHONE_NUMBERS, PHONE); - PLATFORM_PERMISSIONS.put(Manifest.permission.CALL_PHONE, PHONE); - PLATFORM_PERMISSIONS.put(Manifest.permission.ADD_VOICEMAIL, PHONE); - PLATFORM_PERMISSIONS.put(Manifest.permission.USE_SIP, PHONE); - PLATFORM_PERMISSIONS.put(Manifest.permission.ANSWER_PHONE_CALLS, PHONE); - PLATFORM_PERMISSIONS.put(Manifest.permission.ACCEPT_HANDOVER, PHONE); - - PLATFORM_PERMISSIONS.put(Manifest.permission.RECORD_AUDIO, MICROPHONE); -// if (SDK_INT >= Build.VERSION_CODES.S) { - PLATFORM_PERMISSIONS.put(Manifest.permission.RECORD_BACKGROUND_AUDIO, MICROPHONE); -// } - - PLATFORM_PERMISSIONS.put(Manifest.permission.ACTIVITY_RECOGNITION, ACTIVITY_RECOGNITION); - - PLATFORM_PERMISSIONS.put(Manifest.permission.CAMERA, CAMERA); -// if (SDK_INT >= Build.VERSION_CODES.S) { - PLATFORM_PERMISSIONS.put(Manifest.permission.BACKGROUND_CAMERA, CAMERA); -// } - - PLATFORM_PERMISSIONS.put(Manifest.permission.BODY_SENSORS, SENSORS); - -// if (SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - PLATFORM_PERMISSIONS.put(Manifest.permission.POST_NOTIFICATIONS, NOTIFICATIONS); - PLATFORM_PERMISSIONS.put(Manifest.permission.BODY_SENSORS_BACKGROUND, SENSORS); -// } - - PLATFORM_PERMISSION_GROUPS = new ArrayMap<>(); - int numPlatformPermissions = PLATFORM_PERMISSIONS.size(); - for (int i = 0; i < numPlatformPermissions; i++) { - String permission = PLATFORM_PERMISSIONS.keyAt(i); - String permissionGroup = PLATFORM_PERMISSIONS.valueAt(i); - - ArrayList<String> permissionsOfThisGroup = PLATFORM_PERMISSION_GROUPS.get( - permissionGroup); - if (permissionsOfThisGroup == null) { - permissionsOfThisGroup = new ArrayList<>(); - PLATFORM_PERMISSION_GROUPS.put(permissionGroup, permissionsOfThisGroup); - } - - permissionsOfThisGroup.add(permission); - } - - ONE_TIME_PERMISSION_GROUPS = new ArraySet<>(); - ONE_TIME_PERMISSION_GROUPS.add(LOCATION); - ONE_TIME_PERMISSION_GROUPS.add(CAMERA); - ONE_TIME_PERMISSION_GROUPS.add(MICROPHONE); - - PERM_SENSOR_CODES = new ArrayMap<>(); -// if (SDK_INT >= Build.VERSION_CODES.S) { - PERM_SENSOR_CODES.put(CAMERA, SensorPrivacyManager.Sensors.CAMERA); - PERM_SENSOR_CODES.put(MICROPHONE, SensorPrivacyManager.Sensors.MICROPHONE); -// } - - } - - private Utils() { - /* do nothing - hide constructor */ - } - - private static ArrayMap<UserHandle, Context> sUserContexts = new ArrayMap<>(); - - /** - * Creates and caches a PackageContext for the requested user, or returns the previously cached - * value. The package of the PackageContext is the application's package. - * - * @param app The currently running application - * @param user The desired user for the context - * - * @return The generated or cached Context for the requested user - * - * @throws PackageManager.NameNotFoundException If the app has no package name attached - */ - public static @NonNull Context getUserContext(Application app, UserHandle user) throws - PackageManager.NameNotFoundException { - if (!sUserContexts.containsKey(user)) { - sUserContexts.put(user, app.getApplicationContext() - .createPackageContextAsUser(app.getPackageName(), 0, user)); - } - return sUserContexts.get(user); - } - - /** - * Returns true if a permission is dangerous, installed, and not removed - * @param permissionInfo The permission we wish to check - * @return If all of the conditions are met - */ - public static boolean isPermissionDangerousInstalledNotRemoved(PermissionInfo permissionInfo) { - return permissionInfo != null - && permissionInfo.getProtection() == PermissionInfo.PROTECTION_DANGEROUS - && (permissionInfo.flags & PermissionInfo.FLAG_INSTALLED) != 0 - && (permissionInfo.flags & PermissionInfo.FLAG_REMOVED) == 0; - } - - /** - * Get permission group a platform permission belongs to, or null if the permission is not a - * platform permission. - * - * @param permission the permission to resolve - * - * @return The group the permission belongs to - */ - public static @Nullable String getGroupOfPlatformPermission(@NonNull String permission) { - return PLATFORM_PERMISSIONS.get(permission); - } - - /** - * Get name of the permission group a permission belongs to. - * - * @param permission the {@link PermissionInfo info} of the permission to resolve - * - * @return The group the permission belongs to - */ - public static @Nullable String getGroupOfPermission(@NonNull PermissionInfo permission) { - String groupName = Utils.getGroupOfPlatformPermission(permission.name); - if (groupName == null) { - groupName = permission.group; - } - - return groupName; - } - - /** - * Get the names for all platform permissions belonging to a group. - * - * @param group the group - * - * @return The permission names or an empty list if the - * group is not does not have platform runtime permissions - */ - public static @NonNull List<String> getPlatformPermissionNamesOfGroup(@NonNull String group) { - final ArrayList<String> permissions = PLATFORM_PERMISSION_GROUPS.get(group); - return (permissions != null) ? permissions : Collections.emptyList(); - } - - /** - * Get the {@link PermissionInfo infos} for all platform permissions belonging to a group. - * - * @param pm Package manager to use to resolve permission infos - * @param group the group - * - * @return The infos for platform permissions belonging to the group or an empty list if the - * group is not does not have platform runtime permissions - */ - public static @NonNull List<PermissionInfo> getPlatformPermissionsOfGroup( - @NonNull PackageManager pm, @NonNull String group) { - ArrayList<PermissionInfo> permInfos = new ArrayList<>(); - - ArrayList<String> permissions = PLATFORM_PERMISSION_GROUPS.get(group); - if (permissions == null) { - return Collections.emptyList(); - } - - int numPermissions = permissions.size(); - for (int i = 0; i < numPermissions; i++) { - String permName = permissions.get(i); - PermissionInfo permInfo; - try { - permInfo = pm.getPermissionInfo(permName, 0); - } catch (PackageManager.NameNotFoundException e) { - throw new IllegalStateException(permName + " not defined by platform", e); - } - - permInfos.add(permInfo); - } - - return permInfos; - } - - /** - * Get the {@link PermissionInfo infos} for all permission infos belonging to a group. - * - * @param pm Package manager to use to resolve permission infos - * @param group the group - * - * @return The infos of permissions belonging to the group or an empty list if the group - * does not have runtime permissions - */ - public static @NonNull List<PermissionInfo> getPermissionInfosForGroup( - @NonNull PackageManager pm, @NonNull String group) - throws PackageManager.NameNotFoundException { - List<PermissionInfo> permissions = pm.queryPermissionsByGroup(group, 0); - permissions.addAll(getPlatformPermissionsOfGroup(pm, group)); - - /* - * If the undefined group is requested, the package manager will return all platform - * permissions, since they are marked as Undefined in the manifest. Do not return these - * permissions. - */ - if (group.equals(Manifest.permission_group.UNDEFINED)) { - List<PermissionInfo> undefinedPerms = new ArrayList<>(); - for (PermissionInfo permissionInfo : permissions) { - String permGroup = getGroupOfPlatformPermission(permissionInfo.name); - if (permGroup == null || permGroup.equals(Manifest.permission_group.UNDEFINED)) { - undefinedPerms.add(permissionInfo); - } - } - return undefinedPerms; - } - - return permissions; - } - - /** - * Get the {@link PermissionInfo infos} for all runtime installed permission infos belonging to - * a group. - * - * @param pm Package manager to use to resolve permission infos - * @param group the group - * - * @return The infos of installed runtime permissions belonging to the group or an empty list - * if the group does not have runtime permissions - */ - public static @NonNull List<PermissionInfo> getInstalledRuntimePermissionInfosForGroup( - @NonNull PackageManager pm, @NonNull String group) - throws PackageManager.NameNotFoundException { - List<PermissionInfo> permissions = pm.queryPermissionsByGroup(group, 0); - permissions.addAll(getPlatformPermissionsOfGroup(pm, group)); - - List<PermissionInfo> installedRuntime = new ArrayList<>(); - for (PermissionInfo permissionInfo: permissions) { - if (permissionInfo.getProtection() == PermissionInfo.PROTECTION_DANGEROUS - && (permissionInfo.flags & PermissionInfo.FLAG_INSTALLED) != 0 - && (permissionInfo.flags & PermissionInfo.FLAG_REMOVED) == 0) { - installedRuntime.add(permissionInfo); - } - } - - /* - * If the undefined group is requested, the package manager will return all platform - * permissions, since they are marked as Undefined in the manifest. Do not return these - * permissions. - */ - if (group.equals(Manifest.permission_group.UNDEFINED)) { - List<PermissionInfo> undefinedPerms = new ArrayList<>(); - for (PermissionInfo permissionInfo : installedRuntime) { - String permGroup = getGroupOfPlatformPermission(permissionInfo.name); - if (permGroup == null || permGroup.equals(Manifest.permission_group.UNDEFINED)) { - undefinedPerms.add(permissionInfo); - } - } - return undefinedPerms; - } - - return installedRuntime; - } - - /** - * Get the {@link PackageItemInfo infos} for the given permission group. - * - * @param groupName the group - * @param context the {@code Context} to retrieve {@code PackageManager} - * - * @return The info of permission group or null if the group does not have runtime permissions. - */ - public static @Nullable PackageItemInfo getGroupInfo(@NonNull String groupName, - @NonNull Context context) { - try { - return context.getPackageManager().getPermissionGroupInfo(groupName, 0); - } catch (NameNotFoundException e) { - /* ignore */ - } - try { - return context.getPackageManager().getPermissionInfo(groupName, 0); - } catch (NameNotFoundException e) { - /* ignore */ - } - return null; - } - - /** - * Get the {@link PermissionInfo infos} for all permission infos belonging to a group. - * - * @param groupName the group - * @param context the {@code Context} to retrieve {@code PackageManager} - * - * @return The infos of permissions belonging to the group or null if the group does not have - * runtime permissions. - */ - public static @Nullable List<PermissionInfo> getGroupPermissionInfos(@NonNull String groupName, - @NonNull Context context) { - try { - return Utils.getPermissionInfosForGroup(context.getPackageManager(), groupName); - } catch (NameNotFoundException e) { - /* ignore */ - } - try { - PermissionInfo permissionInfo = context.getPackageManager() - .getPermissionInfo(groupName, 0); - List<PermissionInfo> permissions = new ArrayList<>(); - permissions.add(permissionInfo); - return permissions; - } catch (NameNotFoundException e) { - /* ignore */ - } - return null; - } - - /** - * Get the names of the platform permission groups. - * - * @return the names of the platform permission groups. - */ - public static List<String> getPlatformPermissionGroups() { - return new ArrayList<>(PLATFORM_PERMISSION_GROUPS.keySet()); - } - - /** - * Get the names of the runtime platform permissions - * - * @return the names of the runtime platform permissions. - */ - public static List<String> getRuntimePlatformPermissionNames() { - return new ArrayList<>(PLATFORM_PERMISSIONS.keySet()); - } - - /** - * Is the permissions a platform runtime permission - * - * @return the names of the runtime platform permissions. - */ - public static boolean isRuntimePlatformPermission(@NonNull String permission) { - return PLATFORM_PERMISSIONS.containsKey(permission); - } - - /** - * Is the group or background group user sensitive? - * - * @param group The group that might be user sensitive - * - * @return {@code true} if the group (or it's subgroup) is user sensitive. - */ - public static boolean isGroupOrBgGroupUserSensitive(AppPermissionGroup group) { - return group.isUserSensitive() || (group.getBackgroundPermissions() != null - && group.getBackgroundPermissions().isUserSensitive()); - } - - /** - * Whether or not the given package has non-isolated storage permissions - * @param context The current context - * @param packageName The package name to check - * @return True if the package has access to non-isolated storage, false otherwise - * @throws NameNotFoundException - */ - public static boolean isNonIsolatedStorage(@NonNull Context context, - @NonNull String packageName) throws NameNotFoundException { - PackageInfo packageInfo = context.getPackageManager().getPackageInfo(packageName, 0); - AppOpsManager manager = context.getSystemService(AppOpsManager.class); - - - return packageInfo.applicationInfo.targetSdkVersion < Build.VERSION_CODES.P - || (packageInfo.applicationInfo.targetSdkVersion < Build.VERSION_CODES.R - && manager.unsafeCheckOpNoThrow(OPSTR_LEGACY_STORAGE, - packageInfo.applicationInfo.uid, packageInfo.packageName) == MODE_ALLOWED); - } - - /** - * Build a string representing the given time if it happened on the current day and the date - * otherwise. - * - * @param context the context. - * @param lastAccessTime the time in milliseconds. - * - * @return a string representing the time or date of the given time or null if the time is 0. - */ - public static @Nullable String getAbsoluteTimeString(@NonNull Context context, - long lastAccessTime) { - if (lastAccessTime == 0) { - return null; - } - if (isToday(lastAccessTime)) { - return DateFormat.getTimeFormat(context).format(lastAccessTime); - } else { - return DateFormat.getMediumDateFormat(context).format(lastAccessTime); - } - } - - /** - * Check whether the given time (in milliseconds) is in the current day. - * - * @param time the time in milliseconds - * - * @return whether the given time is in the current day. - */ - private static boolean isToday(long time) { - Calendar today = Calendar.getInstance(Locale.getDefault()); - today.setTimeInMillis(System.currentTimeMillis()); - today.set(Calendar.HOUR_OF_DAY, 0); - today.set(Calendar.MINUTE, 0); - today.set(Calendar.SECOND, 0); - today.set(Calendar.MILLISECOND, 0); - - Calendar date = Calendar.getInstance(Locale.getDefault()); - date.setTimeInMillis(time); - return !date.before(today); - } - - /** - * Whether the Location Access Check is enabled. - * - * @return {@code true} iff the Location Access Check is enabled. - */ - public static boolean isLocationAccessCheckEnabled() { - return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, - PROPERTY_LOCATION_ACCESS_CHECK_ENABLED, true); - } - - /** - * Get one time permissions timeout - */ - public static long getOneTimePermissionsTimeout() { - return DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS, - PROPERTY_ONE_TIME_PERMISSIONS_TIMEOUT_MILLIS, ONE_TIME_PERMISSIONS_TIMEOUT_MILLIS); - } - - /** - * Returns the delay in milliseconds before revoking permissions at the end of a one-time - * permission session if all processes have been killed. - * If the session was triggered by a self-revocation, then revocation should happen - * immediately. For a regular one-time permission session, a grace period allows a quick - * app restart without losing the permission. - * @param isSelfRevoked If true, return the delay for a self-revocation session. Otherwise, - * return delay for a regular one-time permission session. - */ - public static long getOneTimePermissionsKilledDelay(boolean isSelfRevoked) { - if (isSelfRevoked) { - // For a self-revoked session, we revoke immediately when the process dies. - return 0; - } - return DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS, - PROPERTY_ONE_TIME_PERMISSIONS_KILLED_DELAY_MILLIS, - ONE_TIME_PERMISSIONS_KILLED_DELAY_MILLIS); - } - - /** - * Whether the permission group supports one-time - * @param permissionGroup The permission group to check - * @return {@code true} iff the group supports one-time - */ - public static boolean supportsOneTimeGrant(String permissionGroup) { - return ONE_TIME_PERMISSION_GROUPS.contains(permissionGroup); - } - - /** - * Checks whether a package has an active one-time permission according to the system server's - * flags - * - * @param context the {@code Context} to retrieve {@code PackageManager} - * @param packageName The package to check for - * @return Whether a package has an active one-time permission - */ - public static boolean hasOneTimePermissions(Context context, String packageName) { - String[] permissions; - PackageManager pm = context.getPackageManager(); - try { - permissions = pm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS) - .requestedPermissions; - } catch (NameNotFoundException e) { - Log.w(LOG_TAG, "Checking for one-time permissions in nonexistent package"); - return false; - } - if (permissions == null) { - return false; - } - for (String permissionName : permissions) { - if ((pm.getPermissionFlags(permissionName, packageName, Process.myUserHandle()) - & PackageManager.FLAG_PERMISSION_ONE_TIME) != 0 - && pm.checkPermission(permissionName, packageName) - == PackageManager.PERMISSION_GRANTED) { - return true; - } - } - return false; - } - - /** - * Gets the label of the Settings application - * - * @param pm The packageManager used to get the activity resolution - * - * @return The CharSequence title of the settings app - */ - @Nullable - public static CharSequence getSettingsLabelForNotifications(PackageManager pm) { - // We pretend we're the Settings app sending the notification, so figure out its name. - Intent openSettingsIntent = new Intent(Settings.ACTION_SETTINGS); - ResolveInfo resolveInfo = pm.resolveActivity(openSettingsIntent, MATCH_SYSTEM_ONLY); - if (resolveInfo == null) { - return null; - } - return pm.getApplicationLabel(resolveInfo.activityInfo.applicationInfo); - } - - /** - * Get all the exempted packages. - */ - public static Set<String> getExemptedPackages(@NonNull RoleManager roleManager) { - Set<String> exemptedPackages = new HashSet<>(); - - exemptedPackages.add(SYSTEM_PKG); - for (int i = 0; i < EXEMPTED_ROLES.length; i++) { - exemptedPackages.addAll(roleManager.getRoleHolders(EXEMPTED_ROLES[i])); - } - - return exemptedPackages; - } - - /** - * Returns if the permission group is Camera or Microphone (status bar indicators). - **/ - public static boolean isStatusBarIndicatorPermission(@NonNull String permissionGroupName) { - return CAMERA.equals(permissionGroupName) || MICROPHONE.equals(permissionGroupName); - } - - /** - * Navigate to notification settings for all apps - * @param context The current Context - */ - public static void navigateToNotificationSettings(@NonNull Context context) { - Intent notificationIntent = new Intent(Settings.ACTION_ALL_APPS_NOTIFICATION_SETTINGS); - context.startActivity(notificationIntent); - } - - /** - * Navigate to notification settings for an app - * @param context The current Context - * @param packageName The package to navigate to - * @param user Specifies the user of the package which should be navigated to. If null, the - * current user is used. - */ - public static void navigateToAppNotificationSettings(@NonNull Context context, - @NonNull String packageName, @NonNull UserHandle user) { - Intent notificationIntent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS); - notificationIntent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName); - context.startActivityAsUser(notificationIntent, user); - } - - /** - * Returns if a card should be shown if the sensor is blocked - **/ - public static boolean shouldDisplayCardIfBlocked(@NonNull String permissionGroupName) { - return DeviceConfig.getBoolean( - DeviceConfig.NAMESPACE_PRIVACY, PROPERTY_WARNING_BANNER_DISPLAY_ENABLED, true) && ( - CAMERA.equals(permissionGroupName) || MICROPHONE.equals(permissionGroupName) - || LOCATION.equals(permissionGroupName)); - } -} diff --git a/services/core/java/com/android/server/AnimationThread.java b/services/core/java/com/android/server/AnimationThread.java index fad743eafdaa..826e7b52a9df 100644 --- a/services/core/java/com/android/server/AnimationThread.java +++ b/services/core/java/com/android/server/AnimationThread.java @@ -40,7 +40,7 @@ public final class AnimationThread extends ServiceThread { sInstance = new AnimationThread(); sInstance.start(); sInstance.getLooper().setTraceTag(Trace.TRACE_TAG_WINDOW_MANAGER); - sHandler = new Handler(sInstance.getLooper()); + sHandler = makeSharedHandler(sInstance.getLooper()); } } diff --git a/services/core/java/com/android/server/PermissionThread.java b/services/core/java/com/android/server/PermissionThread.java new file mode 100644 index 000000000000..3f747a8cf008 --- /dev/null +++ b/services/core/java/com/android/server/PermissionThread.java @@ -0,0 +1,94 @@ +/* + * 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; + +import android.os.Handler; +import android.os.HandlerExecutor; +import android.os.Looper; +import android.os.Trace; + +import com.android.internal.annotations.GuardedBy; + +import java.util.concurrent.Executor; + +/** + * Shared singleton thread for the system. This is a thread for handling + * calls to and from the PermissionController and handling synchronization + * between permissions and appops states. + */ +public final class PermissionThread extends ServiceThread { + private static final long SLOW_DISPATCH_THRESHOLD_MS = 100; + private static final long SLOW_DELIVERY_THRESHOLD_MS = 200; + + private static final Object sLock = new Object(); + + @GuardedBy("sLock") + private static PermissionThread sInstance; + private static Handler sHandler; + private static HandlerExecutor sHandlerExecutor; + + private PermissionThread() { + super("android.perm", android.os.Process.THREAD_PRIORITY_DEFAULT, /* allowIo= */ true); + } + + @GuardedBy("sLock") + private static void ensureThreadLocked() { + if (sInstance != null) { + return; + } + + sInstance = new PermissionThread(); + sInstance.start(); + final Looper looper = sInstance.getLooper(); + looper.setTraceTag(Trace.TRACE_TAG_SYSTEM_SERVER); + looper.setSlowLogThresholdMs( + SLOW_DISPATCH_THRESHOLD_MS, SLOW_DELIVERY_THRESHOLD_MS); + sHandler = new Handler(sInstance.getLooper()); + sHandlerExecutor = new HandlerExecutor(sHandler); + } + + /** + * Obtain a singleton instance of the PermissionThread. + */ + public static PermissionThread get() { + synchronized (sLock) { + ensureThreadLocked(); + return sInstance; + } + } + + /** + * Obtain a singleton instance of a handler executing in the PermissionThread. + */ + public static Handler getHandler() { + synchronized (sLock) { + ensureThreadLocked(); + return sHandler; + } + } + + + /** + * Obtain a singleton instance of an executor of the PermissionThread. + */ + public static Executor getExecutor() { + synchronized (sLock) { + ensureThreadLocked(); + return sHandlerExecutor; + } + } +} diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java index 06c11fa4a20c..b2fc574dea20 100644 --- a/services/core/java/com/android/server/PersistentDataBlockService.java +++ b/services/core/java/com/android/server/PersistentDataBlockService.java @@ -35,14 +35,17 @@ import android.util.Slog; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.DumpUtils; import libcore.io.IoUtils; import java.io.DataInputStream; import java.io.File; +import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.PrintWriter; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; @@ -679,6 +682,20 @@ public class PersistentDataBlockService extends SystemService { enforcePersistentDataBlockAccess(); return mContext.getString(R.string.config_persistentDataPackageName); } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; + + pw.println("mDataBlockFile: " + mDataBlockFile); + pw.println("mIsRunningDSU: " + mIsRunningDSU); + pw.println("mInitDoneSignal: " + mInitDoneSignal); + pw.println("mAllowedUid: " + mAllowedUid); + pw.println("mBlockDeviceSize: " + mBlockDeviceSize); + synchronized (mLock) { + pw.println("mIsWritable: " + mIsWritable); + } + } }; private PersistentDataBlockManagerInternal mInternalService = diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java index ad89afb791cc..049f4bea6fa1 100644 --- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java +++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java @@ -19,7 +19,7 @@ package com.android.server.adb; import static com.android.internal.util.dump.DumpUtils.writeStringIfNotNull; import android.annotation.NonNull; -import android.annotation.TestApi; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.Notification; import android.app.NotificationChannel; @@ -102,11 +102,26 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; /** - * Provides communication to the Android Debug Bridge daemon to allow, deny, or clear public keysi + * Provides communication to the Android Debug Bridge daemon to allow, deny, or clear public keys * that are authorized to connect to the ADB service itself. + * + * <p>The AdbDebuggingManager controls two files: + * <ol> + * <li>adb_keys + * <li>adb_temp_keys.xml + * </ol> + * + * <p>The ADB Daemon (adbd) reads <em>only</em> the adb_keys file for authorization. Public keys + * from registered hosts are stored in adb_keys, one entry per line. + * + * <p>AdbDebuggingManager also keeps adb_temp_keys.xml, which is used for two things + * <ol> + * <li>Removing unused keys from the adb_keys file + * <li>Managing authorized WiFi access points for ADB over WiFi + * </ol> */ public class AdbDebuggingManager { - private static final String TAG = "AdbDebuggingManager"; + private static final String TAG = AdbDebuggingManager.class.getSimpleName(); private static final boolean DEBUG = false; private static final boolean MDNS_DEBUG = false; @@ -118,18 +133,20 @@ public class AdbDebuggingManager { // as a subsequent connection occurs within the allowed duration. private static final String ADB_TEMP_KEYS_FILE = "adb_temp_keys.xml"; private static final int BUFFER_SIZE = 65536; + private static final Ticker SYSTEM_TICKER = () -> System.currentTimeMillis(); private final Context mContext; private final ContentResolver mContentResolver; - private final Handler mHandler; - private AdbDebuggingThread mThread; + @VisibleForTesting final AdbDebuggingHandler mHandler; + @Nullable private AdbDebuggingThread mThread; private boolean mAdbUsbEnabled = false; private boolean mAdbWifiEnabled = false; private String mFingerprints; // A key can be used more than once (e.g. USB, wifi), so need to keep a refcount - private final Map<String, Integer> mConnectedKeys; - private String mConfirmComponent; - private final File mTestUserKeyFile; + private final Map<String, Integer> mConnectedKeys = new HashMap<>(); + private final String mConfirmComponent; + @Nullable private final File mUserKeyFile; + @Nullable private final File mTempKeysFile; private static final String WIFI_PERSISTENT_CONFIG_PROPERTY = "persist.adb.tls_server.enable"; @@ -138,37 +155,44 @@ public class AdbDebuggingManager { private static final int PAIRING_CODE_LENGTH = 6; private PairingThread mPairingThread = null; // A list of keys connected via wifi - private final Set<String> mWifiConnectedKeys; + private final Set<String> mWifiConnectedKeys = new HashSet<>(); // The current info of the adbwifi connection. - private AdbConnectionInfo mAdbConnectionInfo; + private AdbConnectionInfo mAdbConnectionInfo = new AdbConnectionInfo(); // Polls for a tls port property when adb wifi is enabled private AdbConnectionPortPoller mConnectionPortPoller; private final PortListenerImpl mPortListener = new PortListenerImpl(); + private final Ticker mTicker; public AdbDebuggingManager(Context context) { - mHandler = new AdbDebuggingHandler(FgThread.get().getLooper()); - mContext = context; - mContentResolver = mContext.getContentResolver(); - mTestUserKeyFile = null; - mConnectedKeys = new HashMap<String, Integer>(); - mWifiConnectedKeys = new HashSet<String>(); - mAdbConnectionInfo = new AdbConnectionInfo(); + this( + context, + /* confirmComponent= */ null, + getAdbFile(ADB_KEYS_FILE), + getAdbFile(ADB_TEMP_KEYS_FILE), + /* adbDebuggingThread= */ null, + SYSTEM_TICKER); } /** * Constructor that accepts the component to be invoked to confirm if the user wants to allow * an adb connection from the key. */ - @TestApi - protected AdbDebuggingManager(Context context, String confirmComponent, File testUserKeyFile) { - mHandler = new AdbDebuggingHandler(FgThread.get().getLooper()); + @VisibleForTesting + AdbDebuggingManager( + Context context, + String confirmComponent, + File testUserKeyFile, + File tempKeysFile, + AdbDebuggingThread adbDebuggingThread, + Ticker ticker) { mContext = context; mContentResolver = mContext.getContentResolver(); mConfirmComponent = confirmComponent; - mTestUserKeyFile = testUserKeyFile; - mConnectedKeys = new HashMap<String, Integer>(); - mWifiConnectedKeys = new HashSet<String>(); - mAdbConnectionInfo = new AdbConnectionInfo(); + mUserKeyFile = testUserKeyFile; + mTempKeysFile = tempKeysFile; + mThread = adbDebuggingThread; + mTicker = ticker; + mHandler = new AdbDebuggingHandler(FgThread.get().getLooper(), mThread); } static void sendBroadcastWithDebugPermission(@NonNull Context context, @NonNull Intent intent, @@ -189,8 +213,7 @@ public class AdbDebuggingManager { // consisting of only letters, digits, and hyphens, must begin and end // with a letter or digit, must not contain consecutive hyphens, and // must contain at least one letter. - @VisibleForTesting - static final String SERVICE_PROTOCOL = "adb-tls-pairing"; + @VisibleForTesting static final String SERVICE_PROTOCOL = "adb-tls-pairing"; private final String mServiceType = String.format("_%s._tcp.", SERVICE_PROTOCOL); private int mPort; @@ -352,16 +375,24 @@ public class AdbDebuggingManager { } } - class AdbDebuggingThread extends Thread { + @VisibleForTesting + static class AdbDebuggingThread extends Thread { private boolean mStopped; private LocalSocket mSocket; private OutputStream mOutputStream; private InputStream mInputStream; + private Handler mHandler; + @VisibleForTesting AdbDebuggingThread() { super(TAG); } + @VisibleForTesting + void setHandler(Handler handler) { + mHandler = handler; + } + @Override public void run() { if (DEBUG) Slog.d(TAG, "Entering thread"); @@ -536,7 +567,7 @@ public class AdbDebuggingManager { } } - class AdbConnectionInfo { + private static class AdbConnectionInfo { private String mBssid; private String mSsid; private int mPort; @@ -743,11 +774,14 @@ public class AdbDebuggingManager { // Notification when adbd socket is disconnected. static final int MSG_ADBD_SOCKET_DISCONNECTED = 27; + // === Messages from other parts of the system + private static final int MESSAGE_KEY_FILES_UPDATED = 28; + // === Messages we can send to adbd =========== static final String MSG_DISCONNECT_DEVICE = "DD"; static final String MSG_DISABLE_ADBDWIFI = "DA"; - private AdbKeyStore mAdbKeyStore; + @Nullable @VisibleForTesting AdbKeyStore mAdbKeyStore; // Usb, Wi-Fi transports can be enabled together or separately, so don't break the framework // connection unless all transport types are disconnected. @@ -762,19 +796,19 @@ public class AdbDebuggingManager { } }; - AdbDebuggingHandler(Looper looper) { + /** Constructor that accepts the AdbDebuggingThread to which responses should be sent. */ + @VisibleForTesting + AdbDebuggingHandler(Looper looper, AdbDebuggingThread thread) { super(looper); + mThread = thread; } - /** - * Constructor that accepts the AdbDebuggingThread to which responses should be sent - * and the AdbKeyStore to be used to store the temporary grants. - */ - @TestApi - AdbDebuggingHandler(Looper looper, AdbDebuggingThread thread, AdbKeyStore adbKeyStore) { - super(looper); - mThread = thread; - mAdbKeyStore = adbKeyStore; + /** Initialize the AdbKeyStore so tests can grab mAdbKeyStore immediately. */ + @VisibleForTesting + void initKeyStore() { + if (mAdbKeyStore == null) { + mAdbKeyStore = new AdbKeyStore(); + } } // Show when at least one device is connected. @@ -805,6 +839,7 @@ public class AdbDebuggingManager { registerForAuthTimeChanges(); mThread = new AdbDebuggingThread(); + mThread.setHandler(mHandler); mThread.start(); mAdbKeyStore.updateKeyStore(); @@ -825,8 +860,7 @@ public class AdbDebuggingManager { if (!mConnectedKeys.isEmpty()) { for (Map.Entry<String, Integer> entry : mConnectedKeys.entrySet()) { - mAdbKeyStore.setLastConnectionTime(entry.getKey(), - System.currentTimeMillis()); + mAdbKeyStore.setLastConnectionTime(entry.getKey(), mTicker.currentTimeMillis()); } sendPersistKeyStoreMessage(); mConnectedKeys.clear(); @@ -836,9 +870,7 @@ public class AdbDebuggingManager { } public void handleMessage(Message msg) { - if (mAdbKeyStore == null) { - mAdbKeyStore = new AdbKeyStore(); - } + initKeyStore(); switch (msg.what) { case MESSAGE_ADB_ENABLED: @@ -873,7 +905,7 @@ public class AdbDebuggingManager { if (!mConnectedKeys.containsKey(key)) { mConnectedKeys.put(key, 1); } - mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis()); + mAdbKeyStore.setLastConnectionTime(key, mTicker.currentTimeMillis()); sendPersistKeyStoreMessage(); scheduleJobToUpdateAdbKeyStore(); } @@ -911,9 +943,7 @@ public class AdbDebuggingManager { mConnectedKeys.clear(); // If the key store has not yet been instantiated then do so now; this avoids // the unnecessary creation of the key store when adb is not enabled. - if (mAdbKeyStore == null) { - mAdbKeyStore = new AdbKeyStore(); - } + initKeyStore(); mWifiConnectedKeys.clear(); mAdbKeyStore.deleteKeyStore(); cancelJobToUpdateAdbKeyStore(); @@ -928,7 +958,8 @@ public class AdbDebuggingManager { alwaysAllow = true; int refcount = mConnectedKeys.get(key) - 1; if (refcount == 0) { - mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis()); + mAdbKeyStore.setLastConnectionTime( + key, mTicker.currentTimeMillis()); sendPersistKeyStoreMessage(); scheduleJobToUpdateAdbKeyStore(); mConnectedKeys.remove(key); @@ -954,7 +985,7 @@ public class AdbDebuggingManager { if (!mConnectedKeys.isEmpty()) { for (Map.Entry<String, Integer> entry : mConnectedKeys.entrySet()) { mAdbKeyStore.setLastConnectionTime(entry.getKey(), - System.currentTimeMillis()); + mTicker.currentTimeMillis()); } sendPersistKeyStoreMessage(); scheduleJobToUpdateAdbKeyStore(); @@ -975,7 +1006,7 @@ public class AdbDebuggingManager { } else { mConnectedKeys.put(key, mConnectedKeys.get(key) + 1); } - mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis()); + mAdbKeyStore.setLastConnectionTime(key, mTicker.currentTimeMillis()); sendPersistKeyStoreMessage(); scheduleJobToUpdateAdbKeyStore(); logAdbConnectionChanged(key, AdbProtoEnums.AUTOMATICALLY_ALLOWED, true); @@ -1197,6 +1228,10 @@ public class AdbDebuggingManager { } break; } + case MESSAGE_KEY_FILES_UPDATED: { + mAdbKeyStore.reloadKeyMap(); + break; + } } } @@ -1368,8 +1403,7 @@ public class AdbDebuggingManager { AdbDebuggingManager.sendBroadcastWithDebugPermission(mContext, intent, UserHandle.ALL); // Add the key into the keystore - mAdbKeyStore.setLastConnectionTime(publicKey, - System.currentTimeMillis()); + mAdbKeyStore.setLastConnectionTime(publicKey, mTicker.currentTimeMillis()); sendPersistKeyStoreMessage(); scheduleJobToUpdateAdbKeyStore(); } @@ -1440,19 +1474,13 @@ public class AdbDebuggingManager { extras.add(new AbstractMap.SimpleEntry<String, String>("ssid", ssid)); extras.add(new AbstractMap.SimpleEntry<String, String>("bssid", bssid)); int currentUserId = ActivityManager.getCurrentUser(); - UserInfo userInfo = UserManager.get(mContext).getUserInfo(currentUserId); - String componentString; - if (userInfo.isAdmin()) { - componentString = Resources.getSystem().getString( - com.android.internal.R.string.config_customAdbWifiNetworkConfirmationComponent); - } else { - componentString = Resources.getSystem().getString( - com.android.internal.R.string.config_customAdbWifiNetworkConfirmationComponent); - } + String componentString = + Resources.getSystem().getString( + R.string.config_customAdbWifiNetworkConfirmationComponent); ComponentName componentName = ComponentName.unflattenFromString(componentString); + UserInfo userInfo = UserManager.get(mContext).getUserInfo(currentUserId); if (startConfirmationActivity(componentName, userInfo.getUserHandle(), extras) - || startConfirmationService(componentName, userInfo.getUserHandle(), - extras)) { + || startConfirmationService(componentName, userInfo.getUserHandle(), extras)) { return; } Slog.e(TAG, "Unable to start customAdbWifiNetworkConfirmation[SecondaryUser]Component " @@ -1534,7 +1562,7 @@ public class AdbDebuggingManager { /** * Returns a new File with the specified name in the adb directory. */ - private File getAdbFile(String fileName) { + private static File getAdbFile(String fileName) { File dataDir = Environment.getDataDirectory(); File adbDir = new File(dataDir, ADB_DIRECTORY); @@ -1547,66 +1575,38 @@ public class AdbDebuggingManager { } File getAdbTempKeysFile() { - return getAdbFile(ADB_TEMP_KEYS_FILE); + return mTempKeysFile; } File getUserKeyFile() { - return mTestUserKeyFile == null ? getAdbFile(ADB_KEYS_FILE) : mTestUserKeyFile; + return mUserKeyFile; } - private void writeKey(String key) { - try { - File keyFile = getUserKeyFile(); - - if (keyFile == null) { - return; - } - - FileOutputStream fo = new FileOutputStream(keyFile, true); - fo.write(key.getBytes()); - fo.write('\n'); - fo.close(); - - FileUtils.setPermissions(keyFile.toString(), - FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP, -1, -1); - } catch (IOException ex) { - Slog.e(TAG, "Error writing key:" + ex); + private void writeKeys(Iterable<String> keys) { + if (mUserKeyFile == null) { + return; } - } - private void writeKeys(Iterable<String> keys) { - AtomicFile atomicKeyFile = null; + AtomicFile atomicKeyFile = new AtomicFile(mUserKeyFile); + // Note: Do not use a try-with-resources with the FileOutputStream, because AtomicFile + // requires that it's cleaned up with AtomicFile.failWrite(); FileOutputStream fo = null; try { - File keyFile = getUserKeyFile(); - - if (keyFile == null) { - return; - } - - atomicKeyFile = new AtomicFile(keyFile); fo = atomicKeyFile.startWrite(); for (String key : keys) { fo.write(key.getBytes()); fo.write('\n'); } atomicKeyFile.finishWrite(fo); - - FileUtils.setPermissions(keyFile.toString(), - FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP, -1, -1); } catch (IOException ex) { Slog.e(TAG, "Error writing keys: " + ex); - if (atomicKeyFile != null) { - atomicKeyFile.failWrite(fo); - } + atomicKeyFile.failWrite(fo); + return; } - } - private void deleteKeyFile() { - File keyFile = getUserKeyFile(); - if (keyFile != null) { - keyFile.delete(); - } + FileUtils.setPermissions( + mUserKeyFile.toString(), + FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP, -1, -1); } /** @@ -1736,6 +1736,13 @@ public class AdbDebuggingManager { } /** + * Notify that they key files were updated so the AdbKeyManager reloads the keys. + */ + public void notifyKeyFilesUpdated() { + mHandler.sendEmptyMessage(AdbDebuggingHandler.MESSAGE_KEY_FILES_UPDATED); + } + + /** * Sends a message to the handler to persist the keystore. */ private void sendPersistKeyStoreMessage() { @@ -1769,7 +1776,7 @@ public class AdbDebuggingManager { try { dump.write("keystore", AdbDebuggingManagerProto.KEYSTORE, - FileUtils.readTextFile(getAdbTempKeysFile(), 0, null)); + FileUtils.readTextFile(mTempKeysFile, 0, null)); } catch (IOException e) { Slog.i(TAG, "Cannot read keystore: ", e); } @@ -1783,12 +1790,12 @@ public class AdbDebuggingManager { * ADB_ALLOWED_CONNECTION_TIME setting. */ class AdbKeyStore { - private Map<String, Long> mKeyMap; - private Set<String> mSystemKeys; - private File mKeyFile; private AtomicFile mAtomicKeyFile; - private List<String> mTrustedNetworks; + private final Set<String> mSystemKeys; + private final Map<String, Long> mKeyMap = new HashMap<>(); + private final List<String> mTrustedNetworks = new ArrayList<>(); + private static final int KEYSTORE_VERSION = 1; private static final int MAX_SUPPORTED_KEYSTORE_VERSION = 1; private static final String XML_KEYSTORE_START_TAG = "keyStore"; @@ -1810,26 +1817,22 @@ public class AdbDebuggingManager { public static final long NO_PREVIOUS_CONNECTION = 0; /** - * Constructor that uses the default location for the persistent adb keystore. + * Create an AdbKeyStore instance. + * + * <p>Upon creation, we parse {@link #mTempKeysFile} to determine authorized WiFi APs and + * retrieve the map of stored ADB keys and their last connected times. After that, we read + * the {@link #mUserKeyFile}, and any keys that exist in that file that do not exist in the + * map are added to the map (for backwards compatibility). */ AdbKeyStore() { - init(); - } - - /** - * Constructor that uses the specified file as the location for the persistent adb keystore. - */ - AdbKeyStore(File keyFile) { - mKeyFile = keyFile; - init(); - } - - private void init() { initKeyFile(); - mKeyMap = getKeyMap(); - mTrustedNetworks = getTrustedNetworks(); + readTempKeysFile(); mSystemKeys = getSystemKeysFromFile(SYSTEM_KEY_FILE); - addUserKeysToKeyStore(); + addExistingUserKeysToKeyStore(); + } + + public void reloadKeyMap() { + readTempKeysFile(); } public void addTrustedNetwork(String bssid) { @@ -1868,7 +1871,6 @@ public class AdbDebuggingManager { public void removeKey(String key) { if (mKeyMap.containsKey(key)) { mKeyMap.remove(key); - writeKeys(mKeyMap.keySet()); sendPersistKeyStoreMessage(); } } @@ -1877,12 +1879,9 @@ public class AdbDebuggingManager { * Initializes the key file that will be used to persist the adb grants. */ private void initKeyFile() { - if (mKeyFile == null) { - mKeyFile = getAdbTempKeysFile(); - } - // getAdbTempKeysFile can return null if the adb file cannot be obtained - if (mKeyFile != null) { - mAtomicKeyFile = new AtomicFile(mKeyFile); + // mTempKeysFile can be null if the adb file cannot be obtained + if (mTempKeysFile != null) { + mAtomicKeyFile = new AtomicFile(mTempKeysFile); } } @@ -1923,201 +1922,108 @@ public class AdbDebuggingManager { } /** - * Returns the key map with the keys and last connection times from the key file. + * Update the key map and the trusted networks list with values parsed from the temp keys + * file. */ - private Map<String, Long> getKeyMap() { - Map<String, Long> keyMap = new HashMap<String, Long>(); - // if the AtomicFile could not be instantiated before attempt again; if it still fails - // return an empty key map. + private void readTempKeysFile() { + mKeyMap.clear(); + mTrustedNetworks.clear(); if (mAtomicKeyFile == null) { initKeyFile(); if (mAtomicKeyFile == null) { - Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for reading"); - return keyMap; + Slog.e( + TAG, + "Unable to obtain the key file, " + mTempKeysFile + ", for reading"); + return; } } if (!mAtomicKeyFile.exists()) { - return keyMap; + return; } try (FileInputStream keyStream = mAtomicKeyFile.openRead()) { - TypedXmlPullParser parser = Xml.resolvePullParser(keyStream); - // Check for supported keystore version. - XmlUtils.beginDocument(parser, XML_KEYSTORE_START_TAG); - if (parser.next() != XmlPullParser.END_DOCUMENT) { - String tagName = parser.getName(); - if (tagName == null || !XML_KEYSTORE_START_TAG.equals(tagName)) { - Slog.e(TAG, "Expected " + XML_KEYSTORE_START_TAG + ", but got tag=" - + tagName); - return keyMap; - } + TypedXmlPullParser parser; + try { + parser = Xml.resolvePullParser(keyStream); + XmlUtils.beginDocument(parser, XML_KEYSTORE_START_TAG); + int keystoreVersion = parser.getAttributeInt(null, XML_ATTRIBUTE_VERSION); if (keystoreVersion > MAX_SUPPORTED_KEYSTORE_VERSION) { Slog.e(TAG, "Keystore version=" + keystoreVersion + " not supported (max_supported=" + MAX_SUPPORTED_KEYSTORE_VERSION + ")"); - return keyMap; - } - } - while (parser.next() != XmlPullParser.END_DOCUMENT) { - String tagName = parser.getName(); - if (tagName == null) { - break; - } else if (!tagName.equals(XML_TAG_ADB_KEY)) { - XmlUtils.skipCurrentTag(parser); - continue; - } - String key = parser.getAttributeValue(null, XML_ATTRIBUTE_KEY); - long connectionTime; - try { - connectionTime = parser.getAttributeLong(null, - XML_ATTRIBUTE_LAST_CONNECTION); - } catch (XmlPullParserException e) { - Slog.e(TAG, - "Caught a NumberFormatException parsing the last connection time: " - + e); - XmlUtils.skipCurrentTag(parser); - continue; + return; } - keyMap.put(key, connectionTime); + } catch (XmlPullParserException e) { + // This could be because the XML document doesn't start with + // XML_KEYSTORE_START_TAG. Try again, instead just starting the document with + // the adbKey tag (the old format). + parser = Xml.resolvePullParser(keyStream); } + readKeyStoreContents(parser); } catch (IOException e) { Slog.e(TAG, "Caught an IOException parsing the XML key file: ", e); } catch (XmlPullParserException e) { - Slog.w(TAG, "Caught XmlPullParserException parsing the XML key file: ", e); - // The file could be written in a format prior to introducing keystore tag. - return getKeyMapBeforeKeystoreVersion(); + Slog.e(TAG, "Caught XmlPullParserException parsing the XML key file: ", e); + } + } + + private void readKeyStoreContents(TypedXmlPullParser parser) + throws XmlPullParserException, IOException { + // This parser is very forgiving. For backwards-compatibility, we simply iterate through + // all the tags in the file, skipping over anything that's not an <adbKey> tag or a + // <wifiAP> tag. Invalid tags (such as ones that don't have a valid "lastConnection" + // attribute) are simply ignored. + while ((parser.next()) != XmlPullParser.END_DOCUMENT) { + String tagName = parser.getName(); + if (XML_TAG_ADB_KEY.equals(tagName)) { + addAdbKeyToKeyMap(parser); + } else if (XML_TAG_WIFI_ACCESS_POINT.equals(tagName)) { + addTrustedNetworkToTrustedNetworks(parser); + } else { + Slog.w(TAG, "Ignoring tag '" + tagName + "'. Not recognized."); + } + XmlUtils.skipCurrentTag(parser); } - return keyMap; } - - /** - * Returns the key map with the keys and last connection times from the key file. - * This implementation was prior to adding the XML_KEYSTORE_START_TAG. - */ - private Map<String, Long> getKeyMapBeforeKeystoreVersion() { - Map<String, Long> keyMap = new HashMap<String, Long>(); - // if the AtomicFile could not be instantiated before attempt again; if it still fails - // return an empty key map. - if (mAtomicKeyFile == null) { - initKeyFile(); - if (mAtomicKeyFile == null) { - Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for reading"); - return keyMap; - } - } - if (!mAtomicKeyFile.exists()) { - return keyMap; - } - try (FileInputStream keyStream = mAtomicKeyFile.openRead()) { - TypedXmlPullParser parser = Xml.resolvePullParser(keyStream); - XmlUtils.beginDocument(parser, XML_TAG_ADB_KEY); - while (parser.next() != XmlPullParser.END_DOCUMENT) { - String tagName = parser.getName(); - if (tagName == null) { - break; - } else if (!tagName.equals(XML_TAG_ADB_KEY)) { - XmlUtils.skipCurrentTag(parser); - continue; - } - String key = parser.getAttributeValue(null, XML_ATTRIBUTE_KEY); - long connectionTime; - try { - connectionTime = parser.getAttributeLong(null, - XML_ATTRIBUTE_LAST_CONNECTION); - } catch (XmlPullParserException e) { - Slog.e(TAG, - "Caught a NumberFormatException parsing the last connection time: " - + e); - XmlUtils.skipCurrentTag(parser); - continue; - } - keyMap.put(key, connectionTime); - } - } catch (IOException | XmlPullParserException e) { - Slog.e(TAG, "Caught an exception parsing the XML key file: ", e); + private void addAdbKeyToKeyMap(TypedXmlPullParser parser) { + String key = parser.getAttributeValue(null, XML_ATTRIBUTE_KEY); + try { + long connectionTime = + parser.getAttributeLong(null, XML_ATTRIBUTE_LAST_CONNECTION); + mKeyMap.put(key, connectionTime); + } catch (XmlPullParserException e) { + Slog.e(TAG, "Error reading adbKey attributes", e); } - return keyMap; } - /** - * Returns the map of trusted networks from the keystore file. - * - * This was implemented in keystore version 1. - */ - private List<String> getTrustedNetworks() { - List<String> trustedNetworks = new ArrayList<String>(); - // if the AtomicFile could not be instantiated before attempt again; if it still fails - // return an empty key map. - if (mAtomicKeyFile == null) { - initKeyFile(); - if (mAtomicKeyFile == null) { - Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for reading"); - return trustedNetworks; - } - } - if (!mAtomicKeyFile.exists()) { - return trustedNetworks; - } - try (FileInputStream keyStream = mAtomicKeyFile.openRead()) { - TypedXmlPullParser parser = Xml.resolvePullParser(keyStream); - // Check for supported keystore version. - XmlUtils.beginDocument(parser, XML_KEYSTORE_START_TAG); - if (parser.next() != XmlPullParser.END_DOCUMENT) { - String tagName = parser.getName(); - if (tagName == null || !XML_KEYSTORE_START_TAG.equals(tagName)) { - Slog.e(TAG, "Expected " + XML_KEYSTORE_START_TAG + ", but got tag=" - + tagName); - return trustedNetworks; - } - int keystoreVersion = parser.getAttributeInt(null, XML_ATTRIBUTE_VERSION); - if (keystoreVersion > MAX_SUPPORTED_KEYSTORE_VERSION) { - Slog.e(TAG, "Keystore version=" + keystoreVersion - + " not supported (max_supported=" - + MAX_SUPPORTED_KEYSTORE_VERSION); - return trustedNetworks; - } - } - while (parser.next() != XmlPullParser.END_DOCUMENT) { - String tagName = parser.getName(); - if (tagName == null) { - break; - } else if (!tagName.equals(XML_TAG_WIFI_ACCESS_POINT)) { - XmlUtils.skipCurrentTag(parser); - continue; - } - String bssid = parser.getAttributeValue(null, XML_ATTRIBUTE_WIFI_BSSID); - trustedNetworks.add(bssid); - } - } catch (IOException | XmlPullParserException | NumberFormatException e) { - Slog.e(TAG, "Caught an exception parsing the XML key file: ", e); - } - return trustedNetworks; + private void addTrustedNetworkToTrustedNetworks(TypedXmlPullParser parser) { + String bssid = parser.getAttributeValue(null, XML_ATTRIBUTE_WIFI_BSSID); + mTrustedNetworks.add(bssid); } /** * Updates the keystore with keys that were previously set to be always allowed before the * connection time of keys was tracked. */ - private void addUserKeysToKeyStore() { - File userKeyFile = getUserKeyFile(); + private void addExistingUserKeysToKeyStore() { + if (mUserKeyFile == null || !mUserKeyFile.exists()) { + return; + } boolean mapUpdated = false; - if (userKeyFile != null && userKeyFile.exists()) { - try (BufferedReader in = new BufferedReader(new FileReader(userKeyFile))) { - long time = System.currentTimeMillis(); - String key; - while ((key = in.readLine()) != null) { - // if the keystore does not contain the key from the user key file then add - // it to the Map with the current system time to prevent it from expiring - // immediately if the user is actively using this key. - if (!mKeyMap.containsKey(key)) { - mKeyMap.put(key, time); - mapUpdated = true; - } + try (BufferedReader in = new BufferedReader(new FileReader(mUserKeyFile))) { + String key; + while ((key = in.readLine()) != null) { + // if the keystore does not contain the key from the user key file then add + // it to the Map with the current system time to prevent it from expiring + // immediately if the user is actively using this key. + if (!mKeyMap.containsKey(key)) { + mKeyMap.put(key, mTicker.currentTimeMillis()); + mapUpdated = true; } - } catch (IOException e) { - Slog.e(TAG, "Caught an exception reading " + userKeyFile + ": " + e); } + } catch (IOException e) { + Slog.e(TAG, "Caught an exception reading " + mUserKeyFile + ": " + e); } if (mapUpdated) { sendPersistKeyStoreMessage(); @@ -2138,7 +2044,9 @@ public class AdbDebuggingManager { if (mAtomicKeyFile == null) { initKeyFile(); if (mAtomicKeyFile == null) { - Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for writing"); + Slog.e( + TAG, + "Unable to obtain the key file, " + mTempKeysFile + ", for writing"); return; } } @@ -2169,17 +2077,21 @@ public class AdbDebuggingManager { Slog.e(TAG, "Caught an exception writing the key map: ", e); mAtomicKeyFile.failWrite(keyStream); } + writeKeys(mKeyMap.keySet()); } private boolean filterOutOldKeys() { - boolean keysDeleted = false; long allowedTime = getAllowedConnectionTime(); - long systemTime = System.currentTimeMillis(); + if (allowedTime == 0) { + return false; + } + boolean keysDeleted = false; + long systemTime = mTicker.currentTimeMillis(); Iterator<Map.Entry<String, Long>> keyMapIterator = mKeyMap.entrySet().iterator(); while (keyMapIterator.hasNext()) { Map.Entry<String, Long> keyEntry = keyMapIterator.next(); long connectionTime = keyEntry.getValue(); - if (allowedTime != 0 && systemTime > (connectionTime + allowedTime)) { + if (systemTime > (connectionTime + allowedTime)) { keyMapIterator.remove(); keysDeleted = true; } @@ -2203,7 +2115,7 @@ public class AdbDebuggingManager { if (allowedTime == 0) { return minExpiration; } - long systemTime = System.currentTimeMillis(); + long systemTime = mTicker.currentTimeMillis(); Iterator<Map.Entry<String, Long>> keyMapIterator = mKeyMap.entrySet().iterator(); while (keyMapIterator.hasNext()) { Map.Entry<String, Long> keyEntry = keyMapIterator.next(); @@ -2224,7 +2136,9 @@ public class AdbDebuggingManager { public void deleteKeyStore() { mKeyMap.clear(); mTrustedNetworks.clear(); - deleteKeyFile(); + if (mUserKeyFile != null) { + mUserKeyFile.delete(); + } if (mAtomicKeyFile == null) { return; } @@ -2251,7 +2165,8 @@ public class AdbDebuggingManager { * is set to true the time will be set even if it is older than the previously written * connection time. */ - public void setLastConnectionTime(String key, long connectionTime, boolean force) { + @VisibleForTesting + void setLastConnectionTime(String key, long connectionTime, boolean force) { // Do not set the connection time to a value that is earlier than what was previously // stored as the last connection time unless force is set. if (mKeyMap.containsKey(key) && mKeyMap.get(key) >= connectionTime && !force) { @@ -2262,11 +2177,6 @@ public class AdbDebuggingManager { if (mSystemKeys.contains(key)) { return; } - // if this is the first time the key is being added then write it to the key file as - // well. - if (!mKeyMap.containsKey(key)) { - writeKey(key); - } mKeyMap.put(key, connectionTime); } @@ -2298,12 +2208,8 @@ public class AdbDebuggingManager { long allowedConnectionTime = getAllowedConnectionTime(); // if the allowed connection time is 0 then revert to the previous behavior of always // allowing previously granted adb grants. - if (allowedConnectionTime == 0 || (System.currentTimeMillis() < (lastConnectionTime - + allowedConnectionTime))) { - return true; - } else { - return false; - } + return allowedConnectionTime == 0 + || (mTicker.currentTimeMillis() < (lastConnectionTime + allowedConnectionTime)); } /** @@ -2315,4 +2221,15 @@ public class AdbDebuggingManager { return mTrustedNetworks.contains(bssid); } } + + /** + * A Guava-like interface for getting the current system time. + * + * This allows us to swap a fake ticker in for testing to reduce "Thread.sleep()" calls and test + * for exact expected times instead of random ones. + */ + @VisibleForTesting + interface Ticker { + long currentTimeMillis(); + } } diff --git a/services/core/java/com/android/server/adb/AdbService.java b/services/core/java/com/android/server/adb/AdbService.java index 5d0c732d5f48..55d8dba69626 100644 --- a/services/core/java/com/android/server/adb/AdbService.java +++ b/services/core/java/com/android/server/adb/AdbService.java @@ -152,6 +152,14 @@ public class AdbService extends IAdbManager.Stub { } @Override + public void notifyKeyFilesUpdated() { + if (mDebuggingManager == null) { + return; + } + mDebuggingManager.notifyKeyFilesUpdated(); + } + + @Override public void startAdbdForTransport(byte transportType) { FgThread.getHandler().sendMessage(obtainMessage( AdbService::setAdbdEnabledForTransport, AdbService.this, true, transportType)); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 36cd3dba9d5d..d63fd53a383d 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -8790,8 +8790,12 @@ public class ActivityManagerService extends IActivityManager.Stub // otherwise the watchdog may be prevented from resetting the system. // Bail early if not published yet - if (ServiceManager.getService(Context.DROPBOX_SERVICE) == null) return; - final DropBoxManager dbox = mContext.getSystemService(DropBoxManager.class); + final DropBoxManager dbox; + try { + dbox = mContext.getSystemService(DropBoxManager.class); + } catch (Exception e) { + return; + } // Exit early if the dropbox isn't configured to accept this report type. final String dropboxTag = processClass(process) + "_" + eventType; @@ -15283,6 +15287,10 @@ public class ActivityManagerService extends IActivityManager.Stub app.processName, app.toShortString(), cpuLimit, app)) { mHandler.post(() -> { synchronized (ActivityManagerService.this) { + if (app.getThread() == null + || app.mState.getSetProcState() < ActivityManager.PROCESS_STATE_HOME) { + return; + } app.killLocked("excessive cpu " + cpuTimeUsed + " during " + uptimeSince + " dur=" + checkDur + " limit=" + cpuLimit, ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE, @@ -15308,6 +15316,10 @@ public class ActivityManagerService extends IActivityManager.Stub app.processName, r.toString(), cpuLimit, app)) { mHandler.post(() -> { synchronized (ActivityManagerService.this) { + if (app.getThread() == null + || app.mState.getSetProcState() < ActivityManager.PROCESS_STATE_HOME) { + return; + } mPhantomProcessList.killPhantomProcessGroupLocked(app, r, ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE, ApplicationExitInfo.SUBREASON_EXCESSIVE_CPU, diff --git a/services/core/java/com/android/server/am/OWNERS b/services/core/java/com/android/server/am/OWNERS index c4efbd7e8f51..15887f0df406 100644 --- a/services/core/java/com/android/server/am/OWNERS +++ b/services/core/java/com/android/server/am/OWNERS @@ -1,4 +1,3 @@ - # Applications & Processes yamasani@google.com jsharkey@google.com @@ -18,8 +17,6 @@ jji@google.com ogunwale@google.com # Permissions & Packages -svetoslavganov@google.com -toddke@google.com patb@google.com # Battery Stats @@ -35,8 +32,8 @@ narayan@google.com per-file *Assist* = file:/core/java/android/service/voice/OWNERS per-file *Voice* = file:/core/java/android/service/voice/OWNERS -per-file SettingsToPropertiesMapper.java = omakoto@google.com, svetoslavganov@google.com, yamasani@google.com +per-file SettingsToPropertiesMapper.java = omakoto@google.com, yamasani@google.com -per-file CarUserSwitchingDialog.java = keunyoung@google.com, felipeal@google.com, gurunagarajan@google.com +per-file CarUserSwitchingDialog.java = file:platform/packages/services/Car:/OWNERS per-file ContentProviderHelper.java = varunshah@google.com, omakoto@google.com, jsharkey@google.com, yamasani@google.com diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 0d9463958276..178b6bbe755e 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -2794,15 +2794,6 @@ public final class ProcessList { } int N = procs.size(); - for (int i = 0; i < N; ++i) { - final ProcessRecord proc = procs.get(i).first; - try { - Process.setProcessFrozen(proc.getPid(), proc.uid, true); - } catch (Exception e) { - Slog.w(TAG, "Unable to freeze " + proc.getPid() + " " + proc.processName); - } - } - for (int i=0; i<N; i++) { final Pair<ProcessRecord, Boolean> proc = procs.get(i); removeProcessLocked(proc.first, callerWillRestart, allowRestart || proc.second, diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING index e4f624d4ac28..f8558e80588c 100644 --- a/services/core/java/com/android/server/am/TEST_MAPPING +++ b/services/core/java/com/android/server/am/TEST_MAPPING @@ -58,8 +58,7 @@ "name": "FrameworksServicesTests", "options": [ { "include-filter": "com.android.server.am.BatteryStatsServiceTest" }, - { "include-filter": "com.android.server.am.MeasuredEnergySnapshotTest" }, - { "include-filter": "com.android.server.am.BatteryExternalStatsWorkerTest" } + { "include-filter": "com.android.server.power.stats.BatteryStatsTests" } ] } ], diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index a545ed682f0f..845e932cdff9 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -2712,8 +2712,8 @@ public class Vpn { */ @NonNull private final ScheduledThreadPoolExecutor mExecutor; - @Nullable private ScheduledFuture<?> mScheduledHandleNetworkLostTimeout; - @Nullable private ScheduledFuture<?> mScheduledHandleRetryIkeSessionTimeout; + @Nullable private ScheduledFuture<?> mScheduledHandleNetworkLostFuture; + @Nullable private ScheduledFuture<?> mScheduledHandleRetryIkeSessionFuture; /** Signal to ensure shutdown is honored even if a new Network is connected. */ private boolean mIsRunning = true; @@ -2955,6 +2955,8 @@ public class Vpn { } try { + mTunnelIface.setUnderlyingNetwork(mIkeConnectionInfo.getNetwork()); + // Transforms do not need to be persisted; the IkeSession will keep // them alive for us mIpSecManager.applyTunnelModeTransform(mTunnelIface, direction, transform); @@ -3136,13 +3138,13 @@ public class Vpn { // If the default network is lost during the retry delay, the mActiveNetwork will be // null, and the new IKE session won't be established until there is a new default // network bringing up. - mScheduledHandleRetryIkeSessionTimeout = + mScheduledHandleRetryIkeSessionFuture = mExecutor.schedule(() -> { startOrMigrateIkeSession(mActiveNetwork); - // Reset mScheduledHandleRetryIkeSessionTimeout since it's already run on + // Reset mScheduledHandleRetryIkeSessionFuture since it's already run on // executor thread. - mScheduledHandleRetryIkeSessionTimeout = null; + mScheduledHandleRetryIkeSessionFuture = null; }, retryDelay, TimeUnit.SECONDS); } @@ -3185,12 +3187,10 @@ public class Vpn { mActiveNetwork = null; } - if (mScheduledHandleNetworkLostTimeout != null - && !mScheduledHandleNetworkLostTimeout.isCancelled() - && !mScheduledHandleNetworkLostTimeout.isDone()) { + if (mScheduledHandleNetworkLostFuture != null) { final IllegalStateException exception = new IllegalStateException( - "Found a pending mScheduledHandleNetworkLostTimeout"); + "Found a pending mScheduledHandleNetworkLostFuture"); Log.i( TAG, "Unexpected error in onDefaultNetworkLost. Tear down session", @@ -3207,13 +3207,26 @@ public class Vpn { + " on session with token " + mCurrentToken); + final int token = mCurrentToken; // Delay the teardown in case a new network will be available soon. For example, // during handover between two WiFi networks, Android will disconnect from the // first WiFi and then connects to the second WiFi. - mScheduledHandleNetworkLostTimeout = + mScheduledHandleNetworkLostFuture = mExecutor.schedule( () -> { - handleSessionLost(null, network); + if (isActiveToken(token)) { + handleSessionLost(null, network); + } else { + Log.d( + TAG, + "Scheduled handleSessionLost fired for " + + "obsolete token " + + token); + } + + // Reset mScheduledHandleNetworkLostFuture since it's + // already run on executor thread. + mScheduledHandleNetworkLostFuture = null; }, NETWORK_LOST_TIMEOUT_MS, TimeUnit.MILLISECONDS); @@ -3224,28 +3237,26 @@ public class Vpn { } private void cancelHandleNetworkLostTimeout() { - if (mScheduledHandleNetworkLostTimeout != null - && !mScheduledHandleNetworkLostTimeout.isDone()) { + if (mScheduledHandleNetworkLostFuture != null) { // It does not matter what to put in #cancel(boolean), because it is impossible - // that the task tracked by mScheduledHandleNetworkLostTimeout is + // that the task tracked by mScheduledHandleNetworkLostFuture is // in-progress since both that task and onDefaultNetworkChanged are submitted to // mExecutor who has only one thread. Log.d(TAG, "Cancel the task for handling network lost timeout"); - mScheduledHandleNetworkLostTimeout.cancel(false /* mayInterruptIfRunning */); - mScheduledHandleNetworkLostTimeout = null; + mScheduledHandleNetworkLostFuture.cancel(false /* mayInterruptIfRunning */); + mScheduledHandleNetworkLostFuture = null; } } private void cancelRetryNewIkeSessionFuture() { - if (mScheduledHandleRetryIkeSessionTimeout != null - && !mScheduledHandleRetryIkeSessionTimeout.isDone()) { + if (mScheduledHandleRetryIkeSessionFuture != null) { // It does not matter what to put in #cancel(boolean), because it is impossible - // that the task tracked by mScheduledHandleRetryIkeSessionTimeout is + // that the task tracked by mScheduledHandleRetryIkeSessionFuture is // in-progress since both that task and onDefaultNetworkChanged are submitted to // mExecutor who has only one thread. Log.d(TAG, "Cancel the task for handling new ike session timeout"); - mScheduledHandleRetryIkeSessionTimeout.cancel(false /* mayInterruptIfRunning */); - mScheduledHandleRetryIkeSessionTimeout = null; + mScheduledHandleRetryIkeSessionFuture.cancel(false /* mayInterruptIfRunning */); + mScheduledHandleRetryIkeSessionFuture = null; } } @@ -3285,7 +3296,7 @@ public class Vpn { } private void handleSessionLost(@Nullable Exception exception, @Nullable Network network) { - // Cancel mScheduledHandleNetworkLostTimeout if the session it is going to terminate is + // Cancel mScheduledHandleNetworkLostFuture if the session it is going to terminate is // already terminated due to other failures. cancelHandleNetworkLostTimeout(); diff --git a/services/core/java/com/android/server/display/BrightnessThrottler.java b/services/core/java/com/android/server/display/BrightnessThrottler.java index 767b2d18a69a..eccee52f37aa 100644 --- a/services/core/java/com/android/server/display/BrightnessThrottler.java +++ b/services/core/java/com/android/server/display/BrightnessThrottler.java @@ -16,21 +16,31 @@ package com.android.server.display; +import android.annotation.NonNull; import android.content.Context; import android.hardware.display.BrightnessInfo; +import android.hardware.display.DisplayManager; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.IThermalEventListener; import android.os.IThermalService; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; import android.os.Temperature; +import android.provider.DeviceConfig; +import android.provider.DeviceConfigInterface; import android.util.Slog; -import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData; +import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.Executor; /** * This class monitors various conditions, such as skin temperature throttling status, and limits @@ -44,28 +54,54 @@ class BrightnessThrottler { private final Injector mInjector; private final Handler mHandler; - private BrightnessThrottlingData mThrottlingData; + // We need a separate handler for unit testing. These two handlers are the same throughout the + // non-test code. + private final Handler mDeviceConfigHandler; private final Runnable mThrottlingChangeCallback; private final SkinThermalStatusObserver mSkinThermalStatusObserver; + private final DeviceConfigListener mDeviceConfigListener; + private final DeviceConfigInterface mDeviceConfig; + private int mThrottlingStatus; + private BrightnessThrottlingData mThrottlingData; + private BrightnessThrottlingData mDdcThrottlingData; private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX; private @BrightnessInfo.BrightnessMaxReason int mBrightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; + private String mUniqueDisplayId; + + // The most recent string that has been set from DeviceConfig + private String mBrightnessThrottlingDataString; + + // This is a collection of brightness throttling data that has been written as overrides from + // the DeviceConfig. This will always take priority over the display device config data. + private HashMap<String, BrightnessThrottlingData> mBrightnessThrottlingDataOverride = + new HashMap<>(1); BrightnessThrottler(Handler handler, BrightnessThrottlingData throttlingData, - Runnable throttlingChangeCallback) { - this(new Injector(), handler, throttlingData, throttlingChangeCallback); + Runnable throttlingChangeCallback, String uniqueDisplayId) { + this(new Injector(), handler, handler, throttlingData, throttlingChangeCallback, + uniqueDisplayId); } - BrightnessThrottler(Injector injector, Handler handler, BrightnessThrottlingData throttlingData, - Runnable throttlingChangeCallback) { + @VisibleForTesting + BrightnessThrottler(Injector injector, Handler handler, Handler deviceConfigHandler, + BrightnessThrottlingData throttlingData, Runnable throttlingChangeCallback, + String uniqueDisplayId) { mInjector = injector; + mHandler = handler; + mDeviceConfigHandler = deviceConfigHandler; mThrottlingData = throttlingData; + mDdcThrottlingData = throttlingData; mThrottlingChangeCallback = throttlingChangeCallback; mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler); - resetThrottlingData(mThrottlingData); + mUniqueDisplayId = uniqueDisplayId; + mDeviceConfig = injector.getDeviceConfig(); + mDeviceConfigListener = new DeviceConfigListener(); + + resetThrottlingData(mThrottlingData, mUniqueDisplayId); } boolean deviceSupportsThrottling() { @@ -86,7 +122,7 @@ class BrightnessThrottler { void stop() { mSkinThermalStatusObserver.stopObserving(); - + mDeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigListener); // We're asked to stop throttling, so reset brightness restrictions. mBrightnessCap = PowerManager.BRIGHTNESS_MAX; mBrightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; @@ -97,9 +133,19 @@ class BrightnessThrottler { mThrottlingStatus = THROTTLING_INVALID; } - void resetThrottlingData(BrightnessThrottlingData throttlingData) { + private void resetThrottlingData() { + resetThrottlingData(mDdcThrottlingData, mUniqueDisplayId); + } + + void resetThrottlingData(BrightnessThrottlingData throttlingData, String displayId) { stop(); - mThrottlingData = throttlingData; + + mUniqueDisplayId = displayId; + mDdcThrottlingData = throttlingData; + mDeviceConfigListener.startListening(); + reloadBrightnessThrottlingDataOverride(); + mThrottlingData = mBrightnessThrottlingDataOverride.getOrDefault(mUniqueDisplayId, + throttlingData); if (deviceSupportsThrottling()) { mSkinThermalStatusObserver.startObserving(); @@ -173,14 +219,148 @@ class BrightnessThrottler { private void dumpLocal(PrintWriter pw) { pw.println("BrightnessThrottler:"); pw.println(" mThrottlingData=" + mThrottlingData); + pw.println(" mDdcThrottlingData=" + mDdcThrottlingData); + pw.println(" mUniqueDisplayId=" + mUniqueDisplayId); pw.println(" mThrottlingStatus=" + mThrottlingStatus); pw.println(" mBrightnessCap=" + mBrightnessCap); pw.println(" mBrightnessMaxReason=" + BrightnessInfo.briMaxReasonToString(mBrightnessMaxReason)); + pw.println(" mBrightnessThrottlingDataOverride=" + mBrightnessThrottlingDataOverride); + pw.println(" mBrightnessThrottlingDataString=" + mBrightnessThrottlingDataString); mSkinThermalStatusObserver.dump(pw); } + private String getBrightnessThrottlingDataString() { + return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA, + /* defaultValue= */ null); + } + + private boolean parseAndSaveData(@NonNull String strArray, + @NonNull HashMap<String, BrightnessThrottlingData> tempBrightnessThrottlingData) { + boolean validConfig = true; + String[] items = strArray.split(","); + int i = 0; + + try { + String uniqueDisplayId = items[i++]; + + // number of throttling points + int noOfThrottlingPoints = Integer.parseInt(items[i++]); + List<ThrottlingLevel> throttlingLevels = new ArrayList<>(noOfThrottlingPoints); + + // throttling level and point + for (int j = 0; j < noOfThrottlingPoints; j++) { + String severity = items[i++]; + int status = parseThermalStatus(severity); + + float brightnessPoint = parseBrightness(items[i++]); + + throttlingLevels.add(new ThrottlingLevel(status, brightnessPoint)); + } + BrightnessThrottlingData toSave = + DisplayDeviceConfig.BrightnessThrottlingData.create(throttlingLevels); + tempBrightnessThrottlingData.put(uniqueDisplayId, toSave); + } catch (NumberFormatException | IndexOutOfBoundsException + | UnknownThermalStatusException e) { + validConfig = false; + Slog.e(TAG, "Throttling data is invalid array: '" + strArray + "'", e); + } + + if (i != items.length) { + validConfig = false; + } + + return validConfig; + } + + public void reloadBrightnessThrottlingDataOverride() { + HashMap<String, BrightnessThrottlingData> tempBrightnessThrottlingData = + new HashMap<>(1); + mBrightnessThrottlingDataString = getBrightnessThrottlingDataString(); + boolean validConfig = true; + mBrightnessThrottlingDataOverride.clear(); + if (mBrightnessThrottlingDataString != null) { + String[] throttlingDataSplits = mBrightnessThrottlingDataString.split(";"); + for (String s : throttlingDataSplits) { + if (!parseAndSaveData(s, tempBrightnessThrottlingData)) { + validConfig = false; + break; + } + } + + if (validConfig) { + mBrightnessThrottlingDataOverride.putAll(tempBrightnessThrottlingData); + tempBrightnessThrottlingData.clear(); + } + + } else { + Slog.w(TAG, "DeviceConfig BrightnessThrottlingData is null"); + } + } + + /** + * Listens to config data change and updates the brightness throttling data using + * DisplayManager#KEY_BRIGHTNESS_THROTTLING_DATA. + * The format should be a string similar to: "local:4619827677550801152,2,moderate,0.5,severe, + * 0.379518072;local:4619827677550801151,1,moderate,0.75" + * In this order: + * <displayId>,<no of throttling levels>,[<severity as string>,<brightness cap>] + * Where the latter part is repeated for each throttling level, and the entirety is repeated + * for each display, separated by a semicolon. + */ + public class DeviceConfigListener implements DeviceConfig.OnPropertiesChangedListener { + public Executor mExecutor = new HandlerExecutor(mDeviceConfigHandler); + + public void startListening() { + mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + mExecutor, this); + } + + @Override + public void onPropertiesChanged(DeviceConfig.Properties properties) { + reloadBrightnessThrottlingDataOverride(); + resetThrottlingData(); + } + } + + private float parseBrightness(String intVal) throws NumberFormatException { + float value = Float.parseFloat(intVal); + if (value < PowerManager.BRIGHTNESS_MIN || value > PowerManager.BRIGHTNESS_MAX) { + throw new NumberFormatException("Brightness constraint value out of bounds."); + } + return value; + } + + @PowerManager.ThermalStatus private int parseThermalStatus(@NonNull String value) + throws UnknownThermalStatusException { + switch (value) { + case "none": + return PowerManager.THERMAL_STATUS_NONE; + case "light": + return PowerManager.THERMAL_STATUS_LIGHT; + case "moderate": + return PowerManager.THERMAL_STATUS_MODERATE; + case "severe": + return PowerManager.THERMAL_STATUS_SEVERE; + case "critical": + return PowerManager.THERMAL_STATUS_CRITICAL; + case "emergency": + return PowerManager.THERMAL_STATUS_EMERGENCY; + case "shutdown": + return PowerManager.THERMAL_STATUS_SHUTDOWN; + default: + throw new UnknownThermalStatusException("Invalid Thermal Status: " + value); + } + } + + private static class UnknownThermalStatusException extends Exception { + UnknownThermalStatusException(String message) { + super(message); + } + } + private final class SkinThermalStatusObserver extends IThermalEventListener.Stub { private final Injector mInjector; private final Handler mHandler; @@ -258,5 +438,10 @@ class BrightnessThrottler { return IThermalService.Stub.asInterface( ServiceManager.getService(Context.THERMAL_SERVICE)); } + + @NonNull + public DeviceConfigInterface getDeviceConfig() { + return DeviceConfigInterface.REAL; + } } } diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index a25ac210f9c8..2322280d8a9b 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -279,9 +279,13 @@ public class DisplayDeviceConfig { private HighBrightnessModeData mHbmData; private DensityMapping mDensityMapping; private String mLoadedFrom = null; + private Spline mSdrToHdrRatioSpline; + // Brightness Throttling data may be updated via the DeviceConfig. Here we store the original + // data, which comes from the ddc, and the current one, which may be the DeviceConfig + // overwritten value. private BrightnessThrottlingData mBrightnessThrottlingData; - private Spline mSdrToHdrRatioSpline; + private BrightnessThrottlingData mOriginalBrightnessThrottlingData; private DisplayDeviceConfig(Context context) { mContext = context; @@ -422,6 +426,10 @@ public class DisplayDeviceConfig { return config; } + void setBrightnessThrottlingData(BrightnessThrottlingData brightnessThrottlingData) { + mBrightnessThrottlingData = brightnessThrottlingData; + } + /** * Return the brightness mapping nits array. * @@ -637,6 +645,7 @@ public class DisplayDeviceConfig { + ", mHbmData=" + mHbmData + ", mSdrToHdrRatioSpline=" + mSdrToHdrRatioSpline + ", mBrightnessThrottlingData=" + mBrightnessThrottlingData + + ", mOriginalBrightnessThrottlingData=" + mOriginalBrightnessThrottlingData + ", mBrightnessRampFastDecrease=" + mBrightnessRampFastDecrease + ", mBrightnessRampFastIncrease=" + mBrightnessRampFastIncrease + ", mBrightnessRampSlowDecrease=" + mBrightnessRampSlowDecrease @@ -932,6 +941,7 @@ public class DisplayDeviceConfig { if (!badConfig) { mBrightnessThrottlingData = BrightnessThrottlingData.create(throttlingLevels); + mOriginalBrightnessThrottlingData = mBrightnessThrottlingData; } } @@ -1407,7 +1417,9 @@ public class DisplayDeviceConfig { /** * Container for brightness throttling data. */ - static class BrightnessThrottlingData { + public static class BrightnessThrottlingData { + public List<ThrottlingLevel> throttlingLevels; + static class ThrottlingLevel { public @PowerManager.ThermalStatus int thermalStatus; public float brightness; @@ -1421,9 +1433,25 @@ public class DisplayDeviceConfig { public String toString() { return "[" + thermalStatus + "," + brightness + "]"; } - } - public List<ThrottlingLevel> throttlingLevels; + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ThrottlingLevel)) { + return false; + } + ThrottlingLevel otherThrottlingLevel = (ThrottlingLevel) obj; + + return otherThrottlingLevel.thermalStatus == this.thermalStatus + && otherThrottlingLevel.brightness == this.brightness; + } + @Override + public int hashCode() { + int result = 1; + result = 31 * result + thermalStatus; + result = 31 * result + Float.hashCode(brightness); + return result; + } + } static public BrightnessThrottlingData create(List<ThrottlingLevel> throttlingLevels) { @@ -1482,12 +1510,30 @@ public class DisplayDeviceConfig { + "} "; } + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (!(obj instanceof BrightnessThrottlingData)) { + return false; + } + + BrightnessThrottlingData otherBrightnessThrottlingData = (BrightnessThrottlingData) obj; + return throttlingLevels.equals(otherBrightnessThrottlingData.throttlingLevels); + } + + @Override + public int hashCode() { + return throttlingLevels.hashCode(); + } + private BrightnessThrottlingData(List<ThrottlingLevel> inLevels) { throttlingLevels = new ArrayList<>(inLevels.size()); for (ThrottlingLevel level : inLevels) { throttlingLevels.add(new ThrottlingLevel(level.thermalStatus, level.brightness)); } } - } } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index d05a902c6593..95c8fef12976 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -461,6 +461,18 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private boolean mIsRbcActive; + // Whether there's a callback to tell listeners the display has changed scheduled to run. When + // true it implies a wakelock is being held to guarantee the update happens before we collapse + // into suspend and so needs to be cleaned up if the thread is exiting. + // Should only be accessed on the Handler thread. + private boolean mOnStateChangedPending; + + // Count of proximity messages currently on this DPC's Handler. Used to keep track of how many + // suspend blocker acquisitions are pending when shutting down this DPC. + // Should only be accessed on the Handler thread. + private int mOnProximityPositiveMessages; + private int mOnProximityNegativeMessages; + // Animators. private ObjectAnimator mColorFadeOnAnimator; private ObjectAnimator mColorFadeOffAnimator; @@ -861,7 +873,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } }); mBrightnessThrottler.resetThrottlingData( - mDisplayDeviceConfig.getBrightnessThrottlingData()); + mDisplayDeviceConfig.getBrightnessThrottlingData(), mUniqueDisplayId); } private void sendUpdatePowerState() { @@ -1091,10 +1103,24 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mHbmController.stop(); mBrightnessThrottler.stop(); mHandler.removeCallbacksAndMessages(null); + + // Release any outstanding wakelocks we're still holding because of pending messages. if (mUnfinishedBusiness) { mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdUnfinishedBusiness); mUnfinishedBusiness = false; } + if (mOnStateChangedPending) { + mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdOnStateChanged); + mOnStateChangedPending = false; + } + for (int i = 0; i < mOnProximityPositiveMessages; i++) { + mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxPositive); + } + mOnProximityPositiveMessages = 0; + for (int i = 0; i < mOnProximityNegativeMessages; i++) { + mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxNegative); + } + mOnProximityNegativeMessages = 0; final float brightness = mPowerState != null ? mPowerState.getScreenBrightness() @@ -1816,7 +1842,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call () -> { sendUpdatePowerStateLocked(); postBrightnessChangeRunnable(); - }); + }, mUniqueDisplayId); } private void blockScreenOn() { @@ -2248,8 +2274,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } private void sendOnStateChangedWithWakelock() { - mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdOnStateChanged); - mHandler.post(mOnStateChangedRunnable); + if (!mOnStateChangedPending) { + mOnStateChangedPending = true; + mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdOnStateChanged); + mHandler.post(mOnStateChangedRunnable); + } } private void logDisplayPolicyChanged(int newPolicy) { @@ -2408,6 +2437,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private final Runnable mOnStateChangedRunnable = new Runnable() { @Override public void run() { + mOnStateChangedPending = false; mCallbacks.onStateChanged(); mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdOnStateChanged); } @@ -2416,17 +2446,20 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private void sendOnProximityPositiveWithWakelock() { mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxPositive); mHandler.post(mOnProximityPositiveRunnable); + mOnProximityPositiveMessages++; } private final Runnable mOnProximityPositiveRunnable = new Runnable() { @Override public void run() { + mOnProximityPositiveMessages--; mCallbacks.onProximityPositive(); mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxPositive); } }; private void sendOnProximityNegativeWithWakelock() { + mOnProximityNegativeMessages++; mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxNegative); mHandler.post(mOnProximityNegativeRunnable); } @@ -2434,6 +2467,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private final Runnable mOnProximityNegativeRunnable = new Runnable() { @Override public void run() { + mOnProximityNegativeMessages--; mCallbacks.onProximityNegative(); mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxNegative); } @@ -2533,6 +2567,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call pw.println(" mReportedToPolicy=" + reportedToPolicyToString(mReportedScreenStateToPolicy)); pw.println(" mIsRbcActive=" + mIsRbcActive); + pw.println(" mOnStateChangePending=" + mOnStateChangedPending); + pw.println(" mOnProximityPositiveMessages=" + mOnProximityPositiveMessages); + pw.println(" mOnProximityNegativeMessages=" + mOnProximityNegativeMessages); if (mScreenBrightnessRampAnimator != null) { pw.println(" mScreenBrightnessRampAnimator.isAnimating()=" diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java index 8de150ac4124..223b8c181fea 100644 --- a/services/core/java/com/android/server/display/color/ColorDisplayService.java +++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java @@ -956,6 +956,8 @@ public final class ColorDisplayService extends SystemService { R.array.config_availableColorModes); if (availableColorModes.length > 0) { colorMode = availableColorModes[0]; + } else { + colorMode = NOT_SET; } } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java index 1a568c30c899..97e9c6458376 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecController.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java @@ -49,7 +49,6 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; -import java.util.LinkedList; import java.util.List; import java.util.NoSuchElementException; import java.util.concurrent.ArrayBlockingQueue; @@ -464,7 +463,7 @@ final class HdmiCecController { } int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK; - LinkedList<Integer> pollingCandidates = new LinkedList<>(); + ArrayList<Integer> pollingCandidates = new ArrayList<>(); switch (iterationStrategy) { case Constants.POLL_ITERATION_IN_ORDER: for (int i = Constants.ADDR_TV; i <= Constants.ADDR_SPECIFIC_USE; ++i) { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index fb2d2ee08cbd..16bffd9a597b 100755 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -71,7 +71,7 @@ abstract class HdmiCecLocalDevice { protected final int mDeviceType; protected int mPreferredAddress; @GuardedBy("mLock") - protected HdmiDeviceInfo mDeviceInfo; + private HdmiDeviceInfo mDeviceInfo; protected int mLastKeycode = HdmiCecKeycode.UNSUPPORTED_KEYCODE; protected int mLastKeyRepeatCount = 0; @@ -666,11 +666,9 @@ abstract class HdmiCecLocalDevice { * Computes the set of supported device features, and updates local state to match. */ private void updateDeviceFeatures() { - synchronized (mLock) { - setDeviceInfo(getDeviceInfo().toBuilder() - .setDeviceFeatures(computeDeviceFeatures()) - .build()); - } + setDeviceInfo(getDeviceInfo().toBuilder() + .setDeviceFeatures(computeDeviceFeatures()) + .build()); } /** @@ -678,9 +676,7 @@ abstract class HdmiCecLocalDevice { */ protected final DeviceFeatures getDeviceFeatures() { updateDeviceFeatures(); - synchronized (mLock) { - return getDeviceInfo().getDeviceFeatures(); - } + return getDeviceInfo().getDeviceFeatures(); } @Constants.HandleMessageResult @@ -982,14 +978,12 @@ abstract class HdmiCecLocalDevice { return mDeviceType; } - @GuardedBy("mLock") HdmiDeviceInfo getDeviceInfo() { synchronized (mLock) { return mDeviceInfo; } } - @GuardedBy("mLock") void setDeviceInfo(HdmiDeviceInfo info) { synchronized (mLock) { mDeviceInfo = info; @@ -1042,10 +1036,8 @@ abstract class HdmiCecLocalDevice { // Send <Give Features> if using CEC 2.0 or above. if (mService.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0) { - synchronized (mLock) { - mService.sendCecCommand(HdmiCecMessageBuilder.buildGiveFeatures( - getDeviceInfo().getLogicalAddress(), targetAddress)); - } + mService.sendCecCommand(HdmiCecMessageBuilder.buildGiveFeatures( + getDeviceInfo().getLogicalAddress(), targetAddress)); } // If we don't already have a {@link SetAudioVolumeLevelDiscoveryAction} for the target diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java index 26e38bdb6d51..5cfe27a62e1b 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java @@ -138,9 +138,7 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { // does not poll local devices, we should put device info of local device // manually here. for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) { - synchronized (device.mLock) { - mService.getHdmiCecNetwork().addCecDevice(device.getDeviceInfo()); - } + mService.getHdmiCecNetwork().addCecDevice(device.getDeviceInfo()); } List<HotplugDetectionAction> hotplugActions = @@ -179,11 +177,9 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { @ServiceThreadOnly void deviceSelect(int id, IHdmiControlCallback callback) { assertRunOnServiceThread(); - synchronized (mLock) { - if (id == getDeviceInfo().getId()) { - mService.oneTouchPlay(callback); - return; - } + if (id == getDeviceInfo().getId()) { + mService.oneTouchPlay(callback); + return; } HdmiDeviceInfo targetDevice = mService.getHdmiCecNetwork().getDeviceInfo(id); if (targetDevice == null) { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java index c0c02027a7a1..7e8a2cc6d835 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java @@ -82,7 +82,7 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice { callback); if (action == null) { Slog.w(TAG, "Cannot initiate queryDisplayStatus"); - invokeCallback(callback, HdmiControlManager.RESULT_EXCEPTION); + invokeCallback(callback, HdmiControlManager.POWER_STATUS_UNKNOWN); return; } addAndStartAction(action); diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index f8a74f4f3f55..6c37bf1bb9e2 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -1331,13 +1331,11 @@ public class HdmiControlService extends SystemService { */ private boolean sourceAddressIsLocal(HdmiCecMessage message) { for (HdmiCecLocalDevice device : getAllLocalDevices()) { - synchronized (device.mLock) { - if (message.getSource() == device.getDeviceInfo().getLogicalAddress() - && message.getSource() != Constants.ADDR_UNREGISTERED) { - HdmiLogger.warning( - "Unexpected source: message sent from device itself, " + message); - return true; - } + if (message.getSource() == device.getDeviceInfo().getLogicalAddress() + && message.getSource() != Constants.ADDR_UNREGISTERED) { + HdmiLogger.warning( + "Unexpected source: message sent from device itself, " + message); + return true; } } return false; @@ -1560,9 +1558,7 @@ public class HdmiControlService extends SystemService { if (deviceInfo.getDisplayName().equals(newDisplayName)) { continue; } - synchronized (device.mLock) { - device.setDeviceInfo(deviceInfo.toBuilder().setDisplayName(newDisplayName).build()); - } + device.setDeviceInfo(deviceInfo.toBuilder().setDisplayName(newDisplayName).build()); sendCecCommand( HdmiCecMessageBuilder.buildSetOsdNameCommand( deviceInfo.getLogicalAddress(), Constants.ADDR_TV, newDisplayName)); @@ -2728,6 +2724,15 @@ public class HdmiControlService extends SystemService { return mIsCecAvailable; } + /** + * Queries the display status of the TV and calls {@code callback} upon completion. + * + * If this is a non-source device, or if the query fails for any reason, the callback will + * be called with {@link HdmiControlManager.POWER_STATUS_UNKNOWN}. + * + * If the query succeeds, the callback will be called with one of the other power status + * constants. + */ @ServiceThreadOnly protected void queryDisplayStatus(final IHdmiControlCallback callback) { assertRunOnServiceThread(); @@ -2745,7 +2750,7 @@ public class HdmiControlService extends SystemService { if (source == null) { Slog.w(TAG, "Local source device not available"); - invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); + invokeCallback(callback, HdmiControlManager.POWER_STATUS_UNKNOWN); return; } source.queryDisplayStatus(callback); @@ -3114,13 +3119,7 @@ public class HdmiControlService extends SystemService { if (isEnabled == HdmiControlManager.HDMI_CEC_CONTROL_ENABLED) { queryDisplayStatus(new IHdmiControlCallback.Stub() { public void onComplete(int status) { - if (status == HdmiControlManager.POWER_STATUS_UNKNOWN - || status == HdmiControlManager.RESULT_EXCEPTION - || status == HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE) { - mIsCecAvailable = false; - } else { - mIsCecAvailable = true; - } + mIsCecAvailable = status != HdmiControlManager.POWER_STATUS_UNKNOWN; if (!listeners.isEmpty()) { invokeHdmiControlStatusChangeListenerLocked(listeners, isEnabled, mIsCecAvailable); @@ -4096,10 +4095,7 @@ public class HdmiControlService extends SystemService { public void onAudioDeviceVolumeChanged( @NonNull AudioDeviceAttributes audioDevice, @NonNull VolumeInfo volumeInfo) { - int localDeviceAddress; - synchronized (mLocalDevice.mLock) { - localDeviceAddress = mLocalDevice.getDeviceInfo().getLogicalAddress(); - } + int localDeviceAddress = mLocalDevice.getDeviceInfo().getLogicalAddress(); sendCecCommand(SetAudioVolumeLevelMessage.build( localDeviceAddress, mSystemAudioDevice.getLogicalAddress(), diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java index 0186fe5c357c..4ffad91bbfad 100644 --- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java +++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java @@ -149,10 +149,11 @@ final class IInputMethodInvoker { @AnyThread void startInput(IBinder startInputToken, IRemoteInputConnection inputConnection, - EditorInfo attribute, boolean restarting, @InputMethodNavButtonFlags int navButtonFlags, + EditorInfo editorInfo, boolean restarting, + @InputMethodNavButtonFlags int navButtonFlags, @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { try { - mTarget.startInput(startInputToken, inputConnection, attribute, restarting, + mTarget.startInput(startInputToken, inputConnection, editorInfo, restarting, navButtonFlags, imeDispatcher); } catch (RemoteException e) { logRemoteException(e); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java new file mode 100644 index 000000000000..68753ab909b3 --- /dev/null +++ b/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java @@ -0,0 +1,332 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.inputmethod; + +import static com.android.server.inputmethod.SubtypeUtils.SUBTYPE_MODE_ANY; +import static com.android.server.inputmethod.SubtypeUtils.SUBTYPE_MODE_KEYBOARD; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.Slog; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodSubtype; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; + +/** + * This class provides utility methods to generate or filter {@link InputMethodInfo} for + * {@link InputMethodManagerService}. + * + * <p>This class is intentionally package-private. Utility methods here are tightly coupled with + * implementation details in {@link InputMethodManagerService}. Hence this class is not suitable + * for other components to directly use.</p> + */ +final class InputMethodInfoUtils { + private static final String TAG = "InputMethodInfoUtils"; + + /** + * Used in {@link #getFallbackLocaleForDefaultIme(ArrayList, Context)} to find the fallback IMEs + * that are mainly used until the system becomes ready. Note that {@link Locale} in this array + * is checked with {@link Locale#equals(Object)}, which means that {@code Locale.ENGLISH} + * doesn't automatically match {@code Locale("en", "IN")}. + */ + private static final Locale[] SEARCH_ORDER_OF_FALLBACK_LOCALES = { + Locale.ENGLISH, // "en" + Locale.US, // "en_US" + Locale.UK, // "en_GB" + }; + private static final Locale ENGLISH_LOCALE = new Locale("en"); + + private static final class InputMethodListBuilder { + // Note: We use LinkedHashSet instead of android.util.ArraySet because the enumeration + // order can have non-trivial effect in the call sites. + @NonNull + private final LinkedHashSet<InputMethodInfo> mInputMethodSet = new LinkedHashSet<>(); + + InputMethodListBuilder fillImes(ArrayList<InputMethodInfo> imis, Context context, + boolean checkDefaultAttribute, @Nullable Locale locale, boolean checkCountry, + String requiredSubtypeMode) { + for (int i = 0; i < imis.size(); ++i) { + final InputMethodInfo imi = imis.get(i); + if (isSystemImeThatHasSubtypeOf(imi, context, + checkDefaultAttribute, locale, checkCountry, requiredSubtypeMode)) { + mInputMethodSet.add(imi); + } + } + return this; + } + + // TODO: The behavior of InputMethodSubtype#overridesImplicitlyEnabledSubtype() should be + // documented more clearly. + InputMethodListBuilder fillAuxiliaryImes(ArrayList<InputMethodInfo> imis, Context context) { + // If one or more auxiliary input methods are available, OK to stop populating the list. + for (final InputMethodInfo imi : mInputMethodSet) { + if (imi.isAuxiliaryIme()) { + return this; + } + } + boolean added = false; + for (int i = 0; i < imis.size(); ++i) { + final InputMethodInfo imi = imis.get(i); + if (isSystemAuxilialyImeThatHasAutomaticSubtype(imi, context, + true /* checkDefaultAttribute */)) { + mInputMethodSet.add(imi); + added = true; + } + } + if (added) { + return this; + } + for (int i = 0; i < imis.size(); ++i) { + final InputMethodInfo imi = imis.get(i); + if (isSystemAuxilialyImeThatHasAutomaticSubtype(imi, context, + false /* checkDefaultAttribute */)) { + mInputMethodSet.add(imi); + } + } + return this; + + } + + public boolean isEmpty() { + return mInputMethodSet.isEmpty(); + } + + @NonNull + public ArrayList<InputMethodInfo> build() { + return new ArrayList<>(mInputMethodSet); + } + } + + private static InputMethodListBuilder getMinimumKeyboardSetWithSystemLocale( + ArrayList<InputMethodInfo> imis, Context context, @Nullable Locale systemLocale, + @Nullable Locale fallbackLocale) { + // Once the system becomes ready, we pick up at least one keyboard in the following order. + // Secondary users fall into this category in general. + // 1. checkDefaultAttribute: true, locale: systemLocale, checkCountry: true + // 2. checkDefaultAttribute: true, locale: systemLocale, checkCountry: false + // 3. checkDefaultAttribute: true, locale: fallbackLocale, checkCountry: true + // 4. checkDefaultAttribute: true, locale: fallbackLocale, checkCountry: false + // 5. checkDefaultAttribute: false, locale: fallbackLocale, checkCountry: true + // 6. checkDefaultAttribute: false, locale: fallbackLocale, checkCountry: false + // TODO: We should check isAsciiCapable instead of relying on fallbackLocale. + + final InputMethodListBuilder builder = new InputMethodListBuilder(); + builder.fillImes(imis, context, true /* checkDefaultAttribute */, systemLocale, + true /* checkCountry */, SUBTYPE_MODE_KEYBOARD); + if (!builder.isEmpty()) { + return builder; + } + builder.fillImes(imis, context, true /* checkDefaultAttribute */, systemLocale, + false /* checkCountry */, SUBTYPE_MODE_KEYBOARD); + if (!builder.isEmpty()) { + return builder; + } + builder.fillImes(imis, context, true /* checkDefaultAttribute */, fallbackLocale, + true /* checkCountry */, SUBTYPE_MODE_KEYBOARD); + if (!builder.isEmpty()) { + return builder; + } + builder.fillImes(imis, context, true /* checkDefaultAttribute */, fallbackLocale, + false /* checkCountry */, SUBTYPE_MODE_KEYBOARD); + if (!builder.isEmpty()) { + return builder; + } + builder.fillImes(imis, context, false /* checkDefaultAttribute */, fallbackLocale, + true /* checkCountry */, SUBTYPE_MODE_KEYBOARD); + if (!builder.isEmpty()) { + return builder; + } + builder.fillImes(imis, context, false /* checkDefaultAttribute */, fallbackLocale, + false /* checkCountry */, SUBTYPE_MODE_KEYBOARD); + if (!builder.isEmpty()) { + return builder; + } + Slog.w(TAG, "No software keyboard is found. imis=" + Arrays.toString(imis.toArray()) + + " systemLocale=" + systemLocale + " fallbackLocale=" + fallbackLocale); + return builder; + } + + static ArrayList<InputMethodInfo> getDefaultEnabledImes( + Context context, ArrayList<InputMethodInfo> imis, boolean onlyMinimum) { + final Locale fallbackLocale = getFallbackLocaleForDefaultIme(imis, context); + // We will primarily rely on the system locale, but also keep relying on the fallback locale + // as a last resort. + // Also pick up suitable IMEs regardless of the software keyboard support (e.g. Voice IMEs), + // then pick up suitable auxiliary IMEs when necessary (e.g. Voice IMEs with "automatic" + // subtype) + final Locale systemLocale = LocaleUtils.getSystemLocaleFromContext(context); + final InputMethodListBuilder builder = + getMinimumKeyboardSetWithSystemLocale(imis, context, systemLocale, fallbackLocale); + if (!onlyMinimum) { + builder.fillImes(imis, context, true /* checkDefaultAttribute */, systemLocale, + true /* checkCountry */, SUBTYPE_MODE_ANY) + .fillAuxiliaryImes(imis, context); + } + return builder.build(); + } + + static ArrayList<InputMethodInfo> getDefaultEnabledImes( + Context context, ArrayList<InputMethodInfo> imis) { + return getDefaultEnabledImes(context, imis, false /* onlyMinimum */); + } + + /** + * Chooses an eligible system voice IME from the given IMEs. + * + * @param methodMap Map from the IME ID to {@link InputMethodInfo}. + * @param systemSpeechRecognizerPackageName System speech recognizer configured by the system + * config. + * @param currentDefaultVoiceImeId IME ID currently set to + * {@link Settings.Secure#DEFAULT_VOICE_INPUT_METHOD} + * @return {@link InputMethodInfo} that is found in {@code methodMap} and most suitable for + * the system voice IME. + */ + @Nullable + static InputMethodInfo chooseSystemVoiceIme( + @NonNull ArrayMap<String, InputMethodInfo> methodMap, + @Nullable String systemSpeechRecognizerPackageName, + @Nullable String currentDefaultVoiceImeId) { + if (TextUtils.isEmpty(systemSpeechRecognizerPackageName)) { + return null; + } + final InputMethodInfo defaultVoiceIme = methodMap.get(currentDefaultVoiceImeId); + // If the config matches the package of the setting, use the current one. + if (defaultVoiceIme != null && defaultVoiceIme.isSystem() + && defaultVoiceIme.getPackageName().equals(systemSpeechRecognizerPackageName)) { + return defaultVoiceIme; + } + InputMethodInfo firstMatchingIme = null; + final int methodCount = methodMap.size(); + for (int i = 0; i < methodCount; ++i) { + final InputMethodInfo imi = methodMap.valueAt(i); + if (!imi.isSystem()) { + continue; + } + if (!TextUtils.equals(imi.getPackageName(), systemSpeechRecognizerPackageName)) { + continue; + } + if (firstMatchingIme != null) { + Slog.e(TAG, "At most one InputMethodService can be published in " + + "systemSpeechRecognizer: " + systemSpeechRecognizerPackageName + + ". Ignoring all of them."); + return null; + } + firstMatchingIme = imi; + } + return firstMatchingIme; + } + + static InputMethodInfo getMostApplicableDefaultIME(List<InputMethodInfo> enabledImes) { + if (enabledImes == null || enabledImes.isEmpty()) { + return null; + } + // We'd prefer to fall back on a system IME, since that is safer. + int i = enabledImes.size(); + int firstFoundSystemIme = -1; + while (i > 0) { + i--; + final InputMethodInfo imi = enabledImes.get(i); + if (imi.isAuxiliaryIme()) { + continue; + } + if (imi.isSystem() && SubtypeUtils.containsSubtypeOf(imi, ENGLISH_LOCALE, + false /* checkCountry */, SUBTYPE_MODE_KEYBOARD)) { + return imi; + } + if (firstFoundSystemIme < 0 && imi.isSystem()) { + firstFoundSystemIme = i; + } + } + return enabledImes.get(Math.max(firstFoundSystemIme, 0)); + } + + private static boolean isSystemAuxilialyImeThatHasAutomaticSubtype(InputMethodInfo imi, + Context context, boolean checkDefaultAttribute) { + if (!imi.isSystem()) { + return false; + } + if (checkDefaultAttribute && !imi.isDefault(context)) { + return false; + } + if (!imi.isAuxiliaryIme()) { + return false; + } + final int subtypeCount = imi.getSubtypeCount(); + for (int i = 0; i < subtypeCount; ++i) { + final InputMethodSubtype s = imi.getSubtypeAt(i); + if (s.overridesImplicitlyEnabledSubtype()) { + return true; + } + } + return false; + } + + @Nullable + private static Locale getFallbackLocaleForDefaultIme(ArrayList<InputMethodInfo> imis, + Context context) { + // At first, find the fallback locale from the IMEs that are declared as "default" in the + // current locale. Note that IME developers can declare an IME as "default" only for + // some particular locales but "not default" for other locales. + for (final Locale fallbackLocale : SEARCH_ORDER_OF_FALLBACK_LOCALES) { + for (int i = 0; i < imis.size(); ++i) { + if (isSystemImeThatHasSubtypeOf(imis.get(i), context, + true /* checkDefaultAttribute */, fallbackLocale, + true /* checkCountry */, SubtypeUtils.SUBTYPE_MODE_KEYBOARD)) { + return fallbackLocale; + } + } + } + // If no fallback locale is found in the above condition, find fallback locales regardless + // of the "default" attribute as a last resort. + for (final Locale fallbackLocale : SEARCH_ORDER_OF_FALLBACK_LOCALES) { + for (int i = 0; i < imis.size(); ++i) { + if (isSystemImeThatHasSubtypeOf(imis.get(i), context, + false /* checkDefaultAttribute */, fallbackLocale, + true /* checkCountry */, SubtypeUtils.SUBTYPE_MODE_KEYBOARD)) { + return fallbackLocale; + } + } + } + Slog.w(TAG, "Found no fallback locale. imis=" + Arrays.toString(imis.toArray())); + return null; + } + + private static boolean isSystemImeThatHasSubtypeOf(InputMethodInfo imi, Context context, + boolean checkDefaultAttribute, @Nullable Locale requiredLocale, boolean checkCountry, + String requiredSubtypeMode) { + if (!imi.isSystem()) { + return false; + } + if (checkDefaultAttribute && !imi.isDefault(context)) { + return false; + } + if (!SubtypeUtils.containsSubtypeOf(imi, requiredLocale, checkCountry, + requiredSubtypeMode)) { + return false; + } + return true; + } +} diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 7c4803b2b477..5a8190a833e3 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -596,9 +596,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Nullable IRemoteAccessibilityInputConnection mCurRemoteAccessibilityInputConnection; /** - * The attributes last provided by the current client. + * The {@link EditorInfo} last provided by the current client. */ - EditorInfo mCurAttribute; + EditorInfo mCurEditorInfo; /** * A special {@link Matrix} to convert virtual screen coordinates to the IME target display @@ -1787,7 +1787,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (selectedMethodId != null && !mMethodMap.get(selectedMethodId).isSystem()) { return; } - final List<InputMethodInfo> suitableImes = InputMethodUtils.getDefaultEnabledImes( + final List<InputMethodInfo> suitableImes = InputMethodInfoUtils.getDefaultEnabledImes( context, mSettings.getEnabledInputMethodListLocked()); if (suitableImes.isEmpty()) { Slog.i(TAG, "No default found"); @@ -2393,7 +2393,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub getCurTokenLocked(), mCurTokenDisplayId, getCurIdLocked(), startInputReason, restarting, UserHandle.getUserId(mCurClient.uid), mCurClient.selfReportedDisplayId, - mCurFocusedWindow, mCurAttribute, mCurFocusedWindowSoftInputMode, + mCurFocusedWindow, mCurEditorInfo, mCurFocusedWindowSoftInputMode, getSequenceNumberLocked()); mImeTargetWindowMap.put(startInputToken, mCurFocusedWindow); mStartInputHistory.addEntry(info); @@ -2413,7 +2413,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final int navButtonFlags = getInputMethodNavButtonFlagsLocked(); final SessionState session = mCurClient.curSession; setEnabledSessionLocked(session); - session.method.startInput(startInputToken, mCurInputConnection, mCurAttribute, restarting, + session.method.startInput(startInputToken, mCurInputConnection, mCurEditorInfo, restarting, navButtonFlags, mCurImeDispatcher); if (mShowRequested) { if (DEBUG) Slog.v(TAG, "Attach new input asks to show input"); @@ -2479,7 +2479,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final Binder startInputToken = new Binder(); setEnabledSessionForAccessibilityLocked(mCurClient.mAccessibilitySessions); AccessibilityManagerInternal.get().startInput(mCurRemoteAccessibilityInputConnection, - mCurAttribute, !initial /* restarting */); + mCurEditorInfo, !initial /* restarting */); } if (accessibilitySession != null) { @@ -2522,7 +2522,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub private InputBindResult startInputUncheckedLocked(@NonNull ClientState cs, IRemoteInputConnection inputConnection, @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, - @NonNull EditorInfo attribute, @StartInputFlags int startInputFlags, + @NonNull EditorInfo editorInfo, @StartInputFlags int startInputFlags, @StartInputReason int startInputReason, int unverifiedTargetSdkVersion, @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { @@ -2541,9 +2541,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } if (!InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager, cs.uid, - attribute.packageName)) { + editorInfo.packageName)) { Slog.e(TAG, "Rejecting this client as it reported an invalid package name." - + " uid=" + cs.uid + " package=" + attribute.packageName); + + " uid=" + cs.uid + " package=" + editorInfo.packageName); return InputBindResult.INVALID_PACKAGE_NAME; } @@ -2573,7 +2573,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mCurVirtualDisplayToScreenMatrix = getVirtualDisplayToScreenMatrixLocked(cs.selfReportedDisplayId, mDisplayIdToShowIme); - mCurAttribute = attribute; + mCurEditorInfo = editorInfo; // If configured, we want to avoid starting up the IME if it is not supposed to be showing if (shouldPreventImeStartupLocked(selectedMethodId, startInputFlags, @@ -3578,12 +3578,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub public InputBindResult startInputOrWindowGainedFocus( @StartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken, @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode, - int windowFlags, @Nullable EditorInfo attribute, IRemoteInputConnection inputConnection, + int windowFlags, @Nullable EditorInfo editorInfo, + IRemoteInputConnection inputConnection, IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, int unverifiedTargetSdkVersion, @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { return startInputOrWindowGainedFocusInternal(startInputReason, client, windowToken, - startInputFlags, softInputMode, windowFlags, attribute, inputConnection, + startInputFlags, softInputMode, windowFlags, editorInfo, inputConnection, remoteAccessibilityInputConnection, unverifiedTargetSdkVersion, imeDispatcher); } @@ -3592,7 +3593,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub private InputBindResult startInputOrWindowGainedFocusInternal( @StartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken, @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode, - int windowFlags, @Nullable EditorInfo attribute, + int windowFlags, @Nullable EditorInfo editorInfo, @Nullable IRemoteInputConnection inputConnection, @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, int unverifiedTargetSdkVersion, @@ -3608,13 +3609,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub "InputMethodManagerService#startInputOrWindowGainedFocus"); final int callingUserId = UserHandle.getCallingUserId(); final int userId; - if (attribute != null && attribute.targetInputMethodUser != null - && attribute.targetInputMethodUser.getIdentifier() != callingUserId) { + if (editorInfo != null && editorInfo.targetInputMethodUser != null + && editorInfo.targetInputMethodUser.getIdentifier() != callingUserId) { mContext.enforceCallingPermission( Manifest.permission.INTERACT_ACROSS_USERS_FULL, "Using EditorInfo.targetInputMethodUser requires" + " INTERACT_ACROSS_USERS_FULL."); - userId = attribute.targetInputMethodUser.getIdentifier(); + userId = editorInfo.targetInputMethodUser.getIdentifier(); if (!mUserManagerInternal.isUserRunning(userId)) { // There is a chance that we hit here because of race condition. Let's just // return an error code instead of crashing the caller process, which at @@ -3632,7 +3633,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub try { result = startInputOrWindowGainedFocusInternalLocked(startInputReason, client, windowToken, startInputFlags, softInputMode, windowFlags, - attribute, inputConnection, remoteAccessibilityInputConnection, + editorInfo, inputConnection, remoteAccessibilityInputConnection, unverifiedTargetSdkVersion, userId, imeDispatcher); } finally { Binder.restoreCallingIdentity(ident); @@ -3643,7 +3644,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub Slog.wtf(TAG, "InputBindResult is @NonNull. startInputReason=" + InputMethodDebug.startInputReasonToString(startInputReason) + " windowFlags=#" + Integer.toHexString(windowFlags) - + " editorInfo=" + attribute); + + " editorInfo=" + editorInfo); return InputBindResult.NULL; } @@ -3658,7 +3659,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub private InputBindResult startInputOrWindowGainedFocusInternalLocked( @StartInputReason int startInputReason, IInputMethodClient client, @NonNull IBinder windowToken, @StartInputFlags int startInputFlags, - @SoftInputModeFlags int softInputMode, int windowFlags, EditorInfo attribute, + @SoftInputModeFlags int softInputMode, int windowFlags, EditorInfo editorInfo, IRemoteInputConnection inputContext, @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, int unverifiedTargetSdkVersion, @UserIdInt int userId, @@ -3668,7 +3669,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub + InputMethodDebug.startInputReasonToString(startInputReason) + " client=" + client.asBinder() + " inputContext=" + inputContext - + " attribute=" + attribute + + " editorInfo=" + editorInfo + " startInputFlags=" + InputMethodDebug.startInputFlagsToString(startInputFlags) + " softInputMode=" + InputMethodDebug.softInputModeToString(softInputMode) @@ -3751,13 +3752,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (sameWindowFocused && isTextEditor) { if (DEBUG) { Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client - + " attribute=" + attribute + ", token = " + windowToken + + " editorInfo=" + editorInfo + ", token = " + windowToken + ", startInputReason=" + InputMethodDebug.startInputReasonToString(startInputReason)); } - if (attribute != null) { + if (editorInfo != null) { return startInputUncheckedLocked(cs, inputContext, - remoteAccessibilityInputConnection, attribute, startInputFlags, + remoteAccessibilityInputConnection, editorInfo, startInputFlags, startInputReason, unverifiedTargetSdkVersion, imeDispatcher); } return new InputBindResult( @@ -3796,10 +3797,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // state is ALWAYS_HIDDEN or STATE_HIDDEN with forward navigation). // Because the app might leverage these flags to hide soft-keyboard with showing their own // UI for input. - if (isTextEditor && attribute != null + if (isTextEditor && editorInfo != null && shouldRestoreImeVisibility(windowToken, softInputMode)) { res = startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection, - attribute, startInputFlags, startInputReason, unverifiedTargetSdkVersion, + editorInfo, startInputFlags, startInputReason, unverifiedTargetSdkVersion, imeDispatcher); showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null, SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY); @@ -3837,9 +3838,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // by the IME) or if running on a large screen where there // is more room for the target window + IME. if (DEBUG) Slog.v(TAG, "Unspecified window will show input"); - if (attribute != null) { + if (editorInfo != null) { res = startInputUncheckedLocked(cs, inputContext, - remoteAccessibilityInputConnection, attribute, startInputFlags, + remoteAccessibilityInputConnection, editorInfo, startInputFlags, startInputReason, unverifiedTargetSdkVersion, imeDispatcher); didStart = true; @@ -3870,9 +3871,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (DEBUG) Slog.v(TAG, "Window asks to show input going forward"); if (isSoftInputModeStateVisibleAllowed( unverifiedTargetSdkVersion, startInputFlags)) { - if (attribute != null) { + if (editorInfo != null) { res = startInputUncheckedLocked(cs, inputContext, - remoteAccessibilityInputConnection, attribute, startInputFlags, + remoteAccessibilityInputConnection, editorInfo, startInputFlags, startInputReason, unverifiedTargetSdkVersion, imeDispatcher); didStart = true; @@ -3891,9 +3892,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (isSoftInputModeStateVisibleAllowed( unverifiedTargetSdkVersion, startInputFlags)) { if (!sameWindowFocused) { - if (attribute != null) { + if (editorInfo != null) { res = startInputUncheckedLocked(cs, inputContext, - remoteAccessibilityInputConnection, attribute, startInputFlags, + remoteAccessibilityInputConnection, editorInfo, startInputFlags, startInputReason, unverifiedTargetSdkVersion, imeDispatcher); didStart = true; @@ -3910,7 +3911,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } if (!didStart) { - if (attribute != null) { + if (editorInfo != null) { if (sameWindowFocused) { // On previous platforms, when Dialogs re-gained focus, the Activity behind // would briefly gain focus first, and dismiss the IME. @@ -3924,7 +3925,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } } res = startInputUncheckedLocked(cs, inputContext, - remoteAccessibilityInputConnection, attribute, startInputFlags, + remoteAccessibilityInputConnection, editorInfo, startInputFlags, startInputReason, unverifiedTargetSdkVersion, imeDispatcher); } else { @@ -4048,7 +4049,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } if (subtype != null) { setInputMethodWithSubtypeIdLocked(token, id, - InputMethodUtils.getSubtypeIdFromHashCode(mMethodMap.get(id), + SubtypeUtils.getSubtypeIdFromHashCode(mMethodMap.get(id), subtype.hashCode())); } else { setInputMethod(token, id); @@ -4092,7 +4093,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // defined, there is no need to switch to the last IME. if (!imiIdIsSame || lastSubtypeHash != currentSubtypeHash) { targetLastImiId = lastIme.first; - subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash); + subtypeId = SubtypeUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash); } } @@ -4111,13 +4112,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final InputMethodInfo imi = enabled.get(i); if (imi.getSubtypeCount() > 0 && imi.isSystem()) { InputMethodSubtype keyboardSubtype = - InputMethodUtils.findLastResortApplicableSubtypeLocked(mRes, - InputMethodUtils.getSubtypes(imi), - InputMethodUtils.SUBTYPE_MODE_KEYBOARD, locale, true); + SubtypeUtils.findLastResortApplicableSubtypeLocked(mRes, + SubtypeUtils.getSubtypes(imi), + SubtypeUtils.SUBTYPE_MODE_KEYBOARD, locale, true); if (keyboardSubtype != null) { targetLastImiId = imi.getId(); - subtypeId = InputMethodUtils.getSubtypeIdFromHashCode( - imi, keyboardSubtype.hashCode()); + subtypeId = SubtypeUtils.getSubtypeIdFromHashCode(imi, + keyboardSubtype.hashCode()); if(keyboardSubtype.getLocale().equals(locale)) { break; } @@ -4187,8 +4188,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (lastImi == null) return null; try { final int lastSubtypeHash = Integer.parseInt(lastIme.second); - final int lastSubtypeId = - InputMethodUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash); + final int lastSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(lastImi, + lastSubtypeHash); if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) { return null; } @@ -4486,8 +4487,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mWindowManagerInternal.getWindowName(mLastImeTargetWindow)); proto.write(CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE, InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode)); - if (mCurAttribute != null) { - mCurAttribute.dumpDebug(proto, CUR_ATTRIBUTE); + if (mCurEditorInfo != null) { + mCurEditorInfo.dumpDebug(proto, CUR_ATTRIBUTE); } proto.write(CUR_ID, getCurIdLocked()); proto.write(SHOW_REQUESTED, mShowRequested); @@ -4602,7 +4603,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mWindowManagerInternal.onToggleImeRequested( show, mCurFocusedWindow, requestToken, mCurTokenDisplayId); mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry( - mCurFocusedWindowClient, mCurAttribute, info.focusedWindowName, + mCurFocusedWindowClient, mCurEditorInfo, info.focusedWindowName, mCurFocusedWindowSoftInputMode, reason, mInFullscreenMode, info.requestWindowName, info.imeControlTargetName, info.imeLayerTargetName)); } @@ -4867,7 +4868,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") private boolean chooseNewDefaultIMELocked() { - final InputMethodInfo imi = InputMethodUtils.getMostApplicableDefaultIME( + final InputMethodInfo imi = InputMethodInfoUtils.getMostApplicableDefaultIME( mSettings.getEnabledInputMethodListLocked()); if (imi != null) { if (DEBUG) { @@ -5010,7 +5011,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (resetDefaultEnabledIme || reenableMinimumNonAuxSystemImes) { final ArrayList<InputMethodInfo> defaultEnabledIme = - InputMethodUtils.getDefaultEnabledImes(mContext, mMethodList, + InputMethodInfoUtils.getDefaultEnabledImes(mContext, mMethodList, reenableMinimumNonAuxSystemImes); final int N = defaultEnabledIme.size(); for (int i = 0; i < N; ++i) { @@ -5066,7 +5067,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final String systemSpeechRecognizer = mContext.getString(com.android.internal.R.string.config_systemSpeechRecognizer); final String currentDefaultVoiceImeId = mSettings.getDefaultVoiceInputMethod(); - final InputMethodInfo newSystemVoiceIme = InputMethodUtils.chooseSystemVoiceIme( + final InputMethodInfo newSystemVoiceIme = InputMethodInfoUtils.chooseSystemVoiceIme( mMethodMap, systemSpeechRecognizer, currentDefaultVoiceImeId); if (newSystemVoiceIme == null) { if (DEBUG) { @@ -5192,8 +5193,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme); if (subtypeHashCode != null) { try { - lastSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode( - imi, Integer.parseInt(subtypeHashCode)); + lastSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(imi, + Integer.parseInt(subtypeHashCode)); } catch (NumberFormatException e) { Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e); } @@ -5228,7 +5229,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return null; } if (!subtypeIsSelected || mCurrentSubtype == null - || !InputMethodUtils.isValidSubtypeId(imi, mCurrentSubtype.hashCode())) { + || !SubtypeUtils.isValidSubtypeId(imi, mCurrentSubtype.hashCode())) { int subtypeId = mSettings.getSelectedInputMethodSubtypeId(selectedMethodId); if (subtypeId == NOT_A_SUBTYPE_ID) { // If there are no selected subtypes, the framework will try to find @@ -5241,17 +5242,16 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) { mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0); } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) { - mCurrentSubtype = InputMethodUtils.findLastResortApplicableSubtypeLocked( + mCurrentSubtype = SubtypeUtils.findLastResortApplicableSubtypeLocked( mRes, explicitlyOrImplicitlyEnabledSubtypes, - InputMethodUtils.SUBTYPE_MODE_KEYBOARD, null, true); + SubtypeUtils.SUBTYPE_MODE_KEYBOARD, null, true); if (mCurrentSubtype == null) { - mCurrentSubtype = InputMethodUtils.findLastResortApplicableSubtypeLocked( - mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null, - true); + mCurrentSubtype = SubtypeUtils.findLastResortApplicableSubtypeLocked( + mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null, true); } } } else { - mCurrentSubtype = InputMethodUtils.getSubtypes(imi).get(subtypeId); + mCurrentSubtype = SubtypeUtils.getSubtypes(imi).get(subtypeId); } } return mCurrentSubtype; @@ -5511,9 +5511,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // We cannot simply distinguish a bad IME that reports an arbitrary package name from // an unfortunate IME whose internal state is already obsolete due to the asynchronous // nature of our system. Let's compare it with our internal record. - if (!TextUtils.equals(mCurAttribute.packageName, packageName)) { - Slog.e(TAG, "Ignoring createInputContentUriToken mCurAttribute.packageName=" - + mCurAttribute.packageName + " packageName=" + packageName); + if (!TextUtils.equals(mCurEditorInfo.packageName, packageName)) { + Slog.e(TAG, "Ignoring createInputContentUriToken mCurEditorInfo.packageName=" + + mCurEditorInfo.packageName + " packageName=" + packageName); return null; } // This user ID can never bee spoofed. @@ -6168,8 +6168,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub setInputMethodEnabledLocked(inputMethodInfo.getId(), false); } // Re-enable with default enabled IMEs. - for (InputMethodInfo imi : - InputMethodUtils.getDefaultEnabledImes(mContext, mMethodList)) { + for (InputMethodInfo imi : InputMethodInfoUtils.getDefaultEnabledImes( + mContext, mMethodList)) { setInputMethodEnabledLocked(imi.getId(), true); } updateInputMethodsFromSettingsLocked(true /* enabledMayChange */); @@ -6190,8 +6190,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mContext.getResources(), mContext.getContentResolver(), methodMap, userId, false); - nextEnabledImes = InputMethodUtils.getDefaultEnabledImes(mContext, methodList); - nextIme = InputMethodUtils.getMostApplicableDefaultIME(nextEnabledImes).getId(); + nextEnabledImes = InputMethodInfoUtils.getDefaultEnabledImes(mContext, + methodList); + nextIme = InputMethodInfoUtils.getMostApplicableDefaultIME( + nextEnabledImes).getId(); // Reset enabled IMEs. settings.putEnabledInputMethodsStr(""); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java index c255fe14c03e..11e6923aa75a 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java @@ -105,7 +105,7 @@ final class InputMethodMenuController { if (currentSubtype != null) { final String curMethodId = mService.getSelectedMethodIdLocked(); final InputMethodInfo currentImi = mMethodMap.get(curMethodId); - lastInputMethodSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode( + lastInputMethodSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode( currentImi, currentSubtype.hashCode()); } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java index f8894c64304d..a64322625797 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java @@ -241,8 +241,8 @@ final class InputMethodSubtypeSwitchingController { } private static int calculateSubtypeId(InputMethodInfo imi, InputMethodSubtype subtype) { - return subtype != null ? InputMethodUtils.getSubtypeIdFromHashCode(imi, - subtype.hashCode()) : NOT_A_SUBTYPE_ID; + return subtype != null ? SubtypeUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode()) + : NOT_A_SUBTYPE_ID; } private static class StaticRotationList { diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java index 2d1a22e7552d..70132670e68e 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java @@ -27,7 +27,6 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Resources; import android.os.Build; -import android.os.LocaleList; import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; @@ -40,8 +39,6 @@ import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; import android.view.textservice.SpellCheckerInfo; -import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.inputmethod.StartInputFlags; import com.android.server.LocalServices; import com.android.server.pm.UserManagerInternal; @@ -50,9 +47,7 @@ import com.android.server.textservices.TextServicesManagerInternal; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Locale; import java.util.function.Predicate; /** @@ -66,40 +61,13 @@ import java.util.function.Predicate; final class InputMethodUtils { public static final boolean DEBUG = false; static final int NOT_A_SUBTYPE_ID = -1; - private static final String SUBTYPE_MODE_ANY = null; - static final String SUBTYPE_MODE_KEYBOARD = "keyboard"; private static final String TAG = "InputMethodUtils"; - private static final Locale ENGLISH_LOCALE = new Locale("en"); private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID); - private static final String TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE = - "EnabledWhenDefaultIsNotAsciiCapable"; // The string for enabled input method is saved as follows: // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0") private static final char INPUT_METHOD_SEPARATOR = ':'; private static final char INPUT_METHOD_SUBTYPE_SEPARATOR = ';'; - /** - * Used in {@link #getFallbackLocaleForDefaultIme(ArrayList, Context)} to find the fallback IMEs - * that are mainly used until the system becomes ready. Note that {@link Locale} in this array - * is checked with {@link Locale#equals(Object)}, which means that {@code Locale.ENGLISH} - * doesn't automatically match {@code Locale("en", "IN")}. - */ - private static final Locale[] SEARCH_ORDER_OF_FALLBACK_LOCALES = { - Locale.ENGLISH, // "en" - Locale.US, // "en_US" - Locale.UK, // "en_GB" - }; - - // A temporary workaround for the performance concerns in - // #getImplicitlyApplicableSubtypesLocked(Resources, InputMethodInfo). - // TODO: Optimize all the critical paths including this one. - private static final Object sCacheLock = new Object(); - @GuardedBy("sCacheLock") - private static LocaleList sCachedSystemLocales; - @GuardedBy("sCacheLock") - private static InputMethodInfo sCachedInputMethodInfo; - @GuardedBy("sCacheLock") - private static ArrayList<InputMethodSubtype> sCachedResult; private InputMethodUtils() { // This utility class is not publicly instantiable. @@ -130,533 +98,6 @@ final class InputMethodUtils { } // ---------------------------------------------------------------------- - private static boolean isSystemImeThatHasSubtypeOf(InputMethodInfo imi, Context context, - boolean checkDefaultAttribute, @Nullable Locale requiredLocale, boolean checkCountry, - String requiredSubtypeMode) { - if (!imi.isSystem()) { - return false; - } - if (checkDefaultAttribute && !imi.isDefault(context)) { - return false; - } - if (!containsSubtypeOf(imi, requiredLocale, checkCountry, requiredSubtypeMode)) { - return false; - } - return true; - } - - @Nullable - private static Locale getFallbackLocaleForDefaultIme(ArrayList<InputMethodInfo> imis, - Context context) { - // At first, find the fallback locale from the IMEs that are declared as "default" in the - // current locale. Note that IME developers can declare an IME as "default" only for - // some particular locales but "not default" for other locales. - for (final Locale fallbackLocale : SEARCH_ORDER_OF_FALLBACK_LOCALES) { - for (int i = 0; i < imis.size(); ++i) { - if (isSystemImeThatHasSubtypeOf(imis.get(i), context, - true /* checkDefaultAttribute */, fallbackLocale, - true /* checkCountry */, SUBTYPE_MODE_KEYBOARD)) { - return fallbackLocale; - } - } - } - // If no fallback locale is found in the above condition, find fallback locales regardless - // of the "default" attribute as a last resort. - for (final Locale fallbackLocale : SEARCH_ORDER_OF_FALLBACK_LOCALES) { - for (int i = 0; i < imis.size(); ++i) { - if (isSystemImeThatHasSubtypeOf(imis.get(i), context, - false /* checkDefaultAttribute */, fallbackLocale, - true /* checkCountry */, SUBTYPE_MODE_KEYBOARD)) { - return fallbackLocale; - } - } - } - Slog.w(TAG, "Found no fallback locale. imis=" + Arrays.toString(imis.toArray())); - return null; - } - - private static boolean isSystemAuxilialyImeThatHasAutomaticSubtype(InputMethodInfo imi, - Context context, boolean checkDefaultAttribute) { - if (!imi.isSystem()) { - return false; - } - if (checkDefaultAttribute && !imi.isDefault(context)) { - return false; - } - if (!imi.isAuxiliaryIme()) { - return false; - } - final int subtypeCount = imi.getSubtypeCount(); - for (int i = 0; i < subtypeCount; ++i) { - final InputMethodSubtype s = imi.getSubtypeAt(i); - if (s.overridesImplicitlyEnabledSubtype()) { - return true; - } - } - return false; - } - - private static Locale getSystemLocaleFromContext(Context context) { - try { - return context.getResources().getConfiguration().locale; - } catch (Resources.NotFoundException ex) { - return null; - } - } - - private static final class InputMethodListBuilder { - // Note: We use LinkedHashSet instead of android.util.ArraySet because the enumeration - // order can have non-trivial effect in the call sites. - @NonNull - private final LinkedHashSet<InputMethodInfo> mInputMethodSet = new LinkedHashSet<>(); - - InputMethodListBuilder fillImes(ArrayList<InputMethodInfo> imis, Context context, - boolean checkDefaultAttribute, @Nullable Locale locale, boolean checkCountry, - String requiredSubtypeMode) { - for (int i = 0; i < imis.size(); ++i) { - final InputMethodInfo imi = imis.get(i); - if (isSystemImeThatHasSubtypeOf(imi, context, checkDefaultAttribute, locale, - checkCountry, requiredSubtypeMode)) { - mInputMethodSet.add(imi); - } - } - return this; - } - - // TODO: The behavior of InputMethodSubtype#overridesImplicitlyEnabledSubtype() should be - // documented more clearly. - InputMethodListBuilder fillAuxiliaryImes(ArrayList<InputMethodInfo> imis, Context context) { - // If one or more auxiliary input methods are available, OK to stop populating the list. - for (final InputMethodInfo imi : mInputMethodSet) { - if (imi.isAuxiliaryIme()) { - return this; - } - } - boolean added = false; - for (int i = 0; i < imis.size(); ++i) { - final InputMethodInfo imi = imis.get(i); - if (isSystemAuxilialyImeThatHasAutomaticSubtype(imi, context, - true /* checkDefaultAttribute */)) { - mInputMethodSet.add(imi); - added = true; - } - } - if (added) { - return this; - } - for (int i = 0; i < imis.size(); ++i) { - final InputMethodInfo imi = imis.get(i); - if (isSystemAuxilialyImeThatHasAutomaticSubtype(imi, context, - false /* checkDefaultAttribute */)) { - mInputMethodSet.add(imi); - } - } - return this; - } - - public boolean isEmpty() { - return mInputMethodSet.isEmpty(); - } - - @NonNull - public ArrayList<InputMethodInfo> build() { - return new ArrayList<>(mInputMethodSet); - } - } - - private static InputMethodListBuilder getMinimumKeyboardSetWithSystemLocale( - ArrayList<InputMethodInfo> imis, Context context, @Nullable Locale systemLocale, - @Nullable Locale fallbackLocale) { - // Once the system becomes ready, we pick up at least one keyboard in the following order. - // Secondary users fall into this category in general. - // 1. checkDefaultAttribute: true, locale: systemLocale, checkCountry: true - // 2. checkDefaultAttribute: true, locale: systemLocale, checkCountry: false - // 3. checkDefaultAttribute: true, locale: fallbackLocale, checkCountry: true - // 4. checkDefaultAttribute: true, locale: fallbackLocale, checkCountry: false - // 5. checkDefaultAttribute: false, locale: fallbackLocale, checkCountry: true - // 6. checkDefaultAttribute: false, locale: fallbackLocale, checkCountry: false - // TODO: We should check isAsciiCapable instead of relying on fallbackLocale. - - final InputMethodListBuilder builder = new InputMethodListBuilder(); - builder.fillImes(imis, context, true /* checkDefaultAttribute */, systemLocale, - true /* checkCountry */, SUBTYPE_MODE_KEYBOARD); - if (!builder.isEmpty()) { - return builder; - } - builder.fillImes(imis, context, true /* checkDefaultAttribute */, systemLocale, - false /* checkCountry */, SUBTYPE_MODE_KEYBOARD); - if (!builder.isEmpty()) { - return builder; - } - builder.fillImes(imis, context, true /* checkDefaultAttribute */, fallbackLocale, - true /* checkCountry */, SUBTYPE_MODE_KEYBOARD); - if (!builder.isEmpty()) { - return builder; - } - builder.fillImes(imis, context, true /* checkDefaultAttribute */, fallbackLocale, - false /* checkCountry */, SUBTYPE_MODE_KEYBOARD); - if (!builder.isEmpty()) { - return builder; - } - builder.fillImes(imis, context, false /* checkDefaultAttribute */, fallbackLocale, - true /* checkCountry */, SUBTYPE_MODE_KEYBOARD); - if (!builder.isEmpty()) { - return builder; - } - builder.fillImes(imis, context, false /* checkDefaultAttribute */, fallbackLocale, - false /* checkCountry */, SUBTYPE_MODE_KEYBOARD); - if (!builder.isEmpty()) { - return builder; - } - Slog.w(TAG, "No software keyboard is found. imis=" + Arrays.toString(imis.toArray()) - + " systemLocale=" + systemLocale + " fallbackLocale=" + fallbackLocale); - return builder; - } - - static ArrayList<InputMethodInfo> getDefaultEnabledImes( - Context context, ArrayList<InputMethodInfo> imis, boolean onlyMinimum) { - final Locale fallbackLocale = getFallbackLocaleForDefaultIme(imis, context); - // We will primarily rely on the system locale, but also keep relying on the fallback locale - // as a last resort. - // Also pick up suitable IMEs regardless of the software keyboard support (e.g. Voice IMEs), - // then pick up suitable auxiliary IMEs when necessary (e.g. Voice IMEs with "automatic" - // subtype) - final Locale systemLocale = getSystemLocaleFromContext(context); - final InputMethodListBuilder builder = - getMinimumKeyboardSetWithSystemLocale(imis, context, systemLocale, fallbackLocale); - if (!onlyMinimum) { - builder.fillImes(imis, context, true /* checkDefaultAttribute */, systemLocale, - true /* checkCountry */, SUBTYPE_MODE_ANY) - .fillAuxiliaryImes(imis, context); - } - return builder.build(); - } - - static ArrayList<InputMethodInfo> getDefaultEnabledImes( - Context context, ArrayList<InputMethodInfo> imis) { - return getDefaultEnabledImes(context, imis, false /* onlyMinimum */); - } - - /** - * Chooses an eligible system voice IME from the given IMEs. - * - * @param methodMap Map from the IME ID to {@link InputMethodInfo}. - * @param systemSpeechRecognizerPackageName System speech recognizer configured by the system - * config. - * @param currentDefaultVoiceImeId IME ID currently set to - * {@link Settings.Secure#DEFAULT_VOICE_INPUT_METHOD} - * @return {@link InputMethodInfo} that is found in {@code methodMap} and most suitable for - * the system voice IME. - */ - @Nullable - static InputMethodInfo chooseSystemVoiceIme( - @NonNull ArrayMap<String, InputMethodInfo> methodMap, - @Nullable String systemSpeechRecognizerPackageName, - @Nullable String currentDefaultVoiceImeId) { - if (TextUtils.isEmpty(systemSpeechRecognizerPackageName)) { - return null; - } - final InputMethodInfo defaultVoiceIme = methodMap.get(currentDefaultVoiceImeId); - // If the config matches the package of the setting, use the current one. - if (defaultVoiceIme != null && defaultVoiceIme.isSystem() - && defaultVoiceIme.getPackageName().equals(systemSpeechRecognizerPackageName)) { - return defaultVoiceIme; - } - InputMethodInfo firstMatchingIme = null; - final int methodCount = methodMap.size(); - for (int i = 0; i < methodCount; ++i) { - final InputMethodInfo imi = methodMap.valueAt(i); - if (!imi.isSystem()) { - continue; - } - if (!TextUtils.equals(imi.getPackageName(), systemSpeechRecognizerPackageName)) { - continue; - } - if (firstMatchingIme != null) { - Slog.e(TAG, "At most one InputMethodService can be published in " - + "systemSpeechRecognizer: " + systemSpeechRecognizerPackageName - + ". Ignoring all of them."); - return null; - } - firstMatchingIme = imi; - } - return firstMatchingIme; - } - - static boolean containsSubtypeOf(InputMethodInfo imi, @Nullable Locale locale, - boolean checkCountry, String mode) { - if (locale == null) { - return false; - } - final int N = imi.getSubtypeCount(); - for (int i = 0; i < N; ++i) { - final InputMethodSubtype subtype = imi.getSubtypeAt(i); - if (checkCountry) { - final Locale subtypeLocale = subtype.getLocaleObject(); - if (subtypeLocale == null || - !TextUtils.equals(subtypeLocale.getLanguage(), locale.getLanguage()) || - !TextUtils.equals(subtypeLocale.getCountry(), locale.getCountry())) { - continue; - } - } else { - final Locale subtypeLocale = new Locale(getLanguageFromLocaleString( - subtype.getLocale())); - if (!TextUtils.equals(subtypeLocale.getLanguage(), locale.getLanguage())) { - continue; - } - } - if (mode == SUBTYPE_MODE_ANY || TextUtils.isEmpty(mode) || - mode.equalsIgnoreCase(subtype.getMode())) { - return true; - } - } - return false; - } - - static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) { - ArrayList<InputMethodSubtype> subtypes = new ArrayList<>(); - final int subtypeCount = imi.getSubtypeCount(); - for (int i = 0; i < subtypeCount; ++i) { - subtypes.add(imi.getSubtypeAt(i)); - } - return subtypes; - } - - static InputMethodInfo getMostApplicableDefaultIME(List<InputMethodInfo> enabledImes) { - if (enabledImes == null || enabledImes.isEmpty()) { - return null; - } - // We'd prefer to fall back on a system IME, since that is safer. - int i = enabledImes.size(); - int firstFoundSystemIme = -1; - while (i > 0) { - i--; - final InputMethodInfo imi = enabledImes.get(i); - if (imi.isAuxiliaryIme()) { - continue; - } - if (imi.isSystem() && containsSubtypeOf( - imi, ENGLISH_LOCALE, false /* checkCountry */, SUBTYPE_MODE_KEYBOARD)) { - return imi; - } - if (firstFoundSystemIme < 0 && imi.isSystem()) { - firstFoundSystemIme = i; - } - } - return enabledImes.get(Math.max(firstFoundSystemIme, 0)); - } - - static boolean isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode) { - return getSubtypeIdFromHashCode(imi, subtypeHashCode) != NOT_A_SUBTYPE_ID; - } - - static int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) { - if (imi != null) { - final int subtypeCount = imi.getSubtypeCount(); - for (int i = 0; i < subtypeCount; ++i) { - InputMethodSubtype ims = imi.getSubtypeAt(i); - if (subtypeHashCode == ims.hashCode()) { - return i; - } - } - } - return NOT_A_SUBTYPE_ID; - } - - private static final LocaleUtils.LocaleExtractor<InputMethodSubtype> sSubtypeToLocale = - new LocaleUtils.LocaleExtractor<InputMethodSubtype>() { - @Override - public Locale get(InputMethodSubtype source) { - return source != null ? source.getLocaleObject() : null; - } - }; - - @VisibleForTesting - static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked( - Resources res, InputMethodInfo imi) { - final LocaleList systemLocales = res.getConfiguration().getLocales(); - - synchronized (sCacheLock) { - // We intentionally do not use InputMethodInfo#equals(InputMethodInfo) here because - // it does not check if subtypes are also identical. - if (systemLocales.equals(sCachedSystemLocales) && sCachedInputMethodInfo == imi) { - return new ArrayList<>(sCachedResult); - } - } - - // Note: Only resource info in "res" is used in getImplicitlyApplicableSubtypesLockedImpl(). - // TODO: Refactor getImplicitlyApplicableSubtypesLockedImpl() so that it can receive - // LocaleList rather than Resource. - final ArrayList<InputMethodSubtype> result = - getImplicitlyApplicableSubtypesLockedImpl(res, imi); - synchronized (sCacheLock) { - // Both LocaleList and InputMethodInfo are immutable. No need to copy them here. - sCachedSystemLocales = systemLocales; - sCachedInputMethodInfo = imi; - sCachedResult = new ArrayList<>(result); - } - return result; - } - - private static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLockedImpl( - Resources res, InputMethodInfo imi) { - final List<InputMethodSubtype> subtypes = InputMethodUtils.getSubtypes(imi); - final LocaleList systemLocales = res.getConfiguration().getLocales(); - final String systemLocale = systemLocales.get(0).toString(); - if (TextUtils.isEmpty(systemLocale)) return new ArrayList<>(); - final int numSubtypes = subtypes.size(); - - // Handle overridesImplicitlyEnabledSubtype mechanism. - final ArrayMap<String, InputMethodSubtype> applicableModeAndSubtypesMap = new ArrayMap<>(); - for (int i = 0; i < numSubtypes; ++i) { - // scan overriding implicitly enabled subtypes. - final InputMethodSubtype subtype = subtypes.get(i); - if (subtype.overridesImplicitlyEnabledSubtype()) { - final String mode = subtype.getMode(); - if (!applicableModeAndSubtypesMap.containsKey(mode)) { - applicableModeAndSubtypesMap.put(mode, subtype); - } - } - } - if (applicableModeAndSubtypesMap.size() > 0) { - return new ArrayList<>(applicableModeAndSubtypesMap.values()); - } - - final ArrayMap<String, ArrayList<InputMethodSubtype>> nonKeyboardSubtypesMap = - new ArrayMap<>(); - final ArrayList<InputMethodSubtype> keyboardSubtypes = new ArrayList<>(); - - for (int i = 0; i < numSubtypes; ++i) { - final InputMethodSubtype subtype = subtypes.get(i); - final String mode = subtype.getMode(); - if (SUBTYPE_MODE_KEYBOARD.equals(mode)) { - keyboardSubtypes.add(subtype); - } else { - if (!nonKeyboardSubtypesMap.containsKey(mode)) { - nonKeyboardSubtypesMap.put(mode, new ArrayList<>()); - } - nonKeyboardSubtypesMap.get(mode).add(subtype); - } - } - - final ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<>(); - LocaleUtils.filterByLanguage(keyboardSubtypes, sSubtypeToLocale, systemLocales, - applicableSubtypes); - - if (!applicableSubtypes.isEmpty()) { - boolean hasAsciiCapableKeyboard = false; - final int numApplicationSubtypes = applicableSubtypes.size(); - for (int i = 0; i < numApplicationSubtypes; ++i) { - final InputMethodSubtype subtype = applicableSubtypes.get(i); - if (subtype.isAsciiCapable()) { - hasAsciiCapableKeyboard = true; - break; - } - } - if (!hasAsciiCapableKeyboard) { - final int numKeyboardSubtypes = keyboardSubtypes.size(); - for (int i = 0; i < numKeyboardSubtypes; ++i) { - final InputMethodSubtype subtype = keyboardSubtypes.get(i); - final String mode = subtype.getMode(); - if (SUBTYPE_MODE_KEYBOARD.equals(mode) && subtype.containsExtraValueKey( - TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE)) { - applicableSubtypes.add(subtype); - } - } - } - } - - if (applicableSubtypes.isEmpty()) { - InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked( - res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true); - if (lastResortKeyboardSubtype != null) { - applicableSubtypes.add(lastResortKeyboardSubtype); - } - } - - // For each non-keyboard mode, extract subtypes with system locales. - for (final ArrayList<InputMethodSubtype> subtypeList : nonKeyboardSubtypesMap.values()) { - LocaleUtils.filterByLanguage(subtypeList, sSubtypeToLocale, systemLocales, - applicableSubtypes); - } - - return applicableSubtypes; - } - - /** - * Returns the language component of a given locale string. - * TODO: Use {@link Locale#toLanguageTag()} and {@link Locale#forLanguageTag(String)} - */ - private static String getLanguageFromLocaleString(String locale) { - final int idx = locale.indexOf('_'); - if (idx < 0) { - return locale; - } else { - return locale.substring(0, idx); - } - } - - /** - * If there are no selected subtypes, tries finding the most applicable one according to the - * given locale. - * @param subtypes this function will search the most applicable subtype in subtypes - * @param mode subtypes will be filtered by mode - * @param locale subtypes will be filtered by locale - * @param canIgnoreLocaleAsLastResort if this function can't find the most applicable subtype, - * it will return the first subtype matched with mode - * @return the most applicable subtypeId - */ - static InputMethodSubtype findLastResortApplicableSubtypeLocked( - Resources res, List<InputMethodSubtype> subtypes, String mode, String locale, - boolean canIgnoreLocaleAsLastResort) { - if (subtypes == null || subtypes.size() == 0) { - return null; - } - if (TextUtils.isEmpty(locale)) { - locale = res.getConfiguration().locale.toString(); - } - final String language = getLanguageFromLocaleString(locale); - boolean partialMatchFound = false; - InputMethodSubtype applicableSubtype = null; - InputMethodSubtype firstMatchedModeSubtype = null; - final int N = subtypes.size(); - for (int i = 0; i < N; ++i) { - InputMethodSubtype subtype = subtypes.get(i); - final String subtypeLocale = subtype.getLocale(); - final String subtypeLanguage = getLanguageFromLocaleString(subtypeLocale); - // An applicable subtype should match "mode". If mode is null, mode will be ignored, - // and all subtypes with all modes can be candidates. - if (mode == null || subtypes.get(i).getMode().equalsIgnoreCase(mode)) { - if (firstMatchedModeSubtype == null) { - firstMatchedModeSubtype = subtype; - } - if (locale.equals(subtypeLocale)) { - // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US") - applicableSubtype = subtype; - break; - } else if (!partialMatchFound && language.equals(subtypeLanguage)) { - // Partial match (e.g. system locale is "en_US" and subtype locale is "en") - applicableSubtype = subtype; - partialMatchFound = true; - } - } - } - - if (applicableSubtype == null && canIgnoreLocaleAsLastResort) { - return firstMatchedModeSubtype; - } - - // The first subtype applicable to the system locale will be defined as the most applicable - // subtype. - if (DEBUG) { - if (applicableSubtype != null) { - Slog.d(TAG, "Applicable InputMethodSubtype was found: " - + applicableSubtype.getMode() + "," + applicableSubtype.getLocale()); - } - } - return applicableSubtype; - } - static boolean canAddToLastInputMethod(InputMethodSubtype subtype) { if (subtype == null) return true; return !subtype.isAuxiliary(); @@ -790,6 +231,7 @@ final class InputMethodUtils { /** * Utility class for putting and getting settings for InputMethod * TODO: Move all putters and getters of settings to this class. + * TODO(b/235661780): Make the setting supports multi-users. */ public static class InputMethodSettings { private final TextUtils.SimpleStringSplitter mInputMethodSplitter = @@ -967,7 +409,7 @@ final class InputMethodUtils { List<InputMethodSubtype> enabledSubtypes = getEnabledInputMethodSubtypeListLocked(imi); if (allowsImplicitlySelectedSubtypes && enabledSubtypes.isEmpty()) { - enabledSubtypes = InputMethodUtils.getImplicitlyApplicableSubtypesLocked( + enabledSubtypes = SubtypeUtils.getImplicitlyApplicableSubtypesLocked( context.getResources(), imi); } return InputMethodSubtype.sort(context, 0, imi, enabledSubtypes); @@ -1198,7 +640,7 @@ final class InputMethodUtils { // are enabled implicitly, so needs to treat them to be enabled. if (imi != null && imi.getSubtypeCount() > 0) { List<InputMethodSubtype> implicitlySelectedSubtypes = - getImplicitlyApplicableSubtypesLocked(mRes, imi); + SubtypeUtils.getImplicitlyApplicableSubtypesLocked(mRes, imi); if (implicitlySelectedSubtypes != null) { final int N = implicitlySelectedSubtypes.size(); for (int i = 0; i < N; ++i) { @@ -1216,7 +658,7 @@ final class InputMethodUtils { try { final int hashCode = Integer.parseInt(subtypeHashCode); // Check whether the subtype id is valid or not - if (isValidSubtypeId(imi, hashCode)) { + if (SubtypeUtils.isValidSubtypeId(imi, hashCode)) { return s; } else { return NOT_A_SUBTYPE_ID_STR; @@ -1336,7 +778,7 @@ final class InputMethodUtils { return NOT_A_SUBTYPE_ID; } final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode(); - return getSubtypeIdFromHashCode(imi, subtypeHashCode); + return SubtypeUtils.getSubtypeIdFromHashCode(imi, subtypeHashCode); } void saveCurrentInputMethodAndSubtypeToHistory(String curMethodId, diff --git a/services/core/java/com/android/server/inputmethod/LocaleUtils.java b/services/core/java/com/android/server/inputmethod/LocaleUtils.java index 7a6853a25e5b..3d02b3af6bc1 100644 --- a/services/core/java/com/android/server/inputmethod/LocaleUtils.java +++ b/services/core/java/com/android/server/inputmethod/LocaleUtils.java @@ -19,6 +19,8 @@ package com.android.server.inputmethod; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Resources; import android.icu.util.ULocale; import android.os.LocaleList; import android.text.TextUtils; @@ -207,4 +209,25 @@ final class LocaleUtils { dest.add(sources.get(entry.mIndex)); } } + + /** + * Returns the language component of a given locale string. + * TODO: Use {@link Locale#toLanguageTag()} and {@link Locale#forLanguageTag(String)} + */ + static String getLanguageFromLocaleString(String locale) { + final int idx = locale.indexOf('_'); + if (idx < 0) { + return locale; + } else { + return locale.substring(0, idx); + } + } + + static Locale getSystemLocaleFromContext(Context context) { + try { + return context.getResources().getConfiguration().locale; + } catch (Resources.NotFoundException ex) { + return null; + } + } } diff --git a/services/core/java/com/android/server/inputmethod/SubtypeUtils.java b/services/core/java/com/android/server/inputmethod/SubtypeUtils.java new file mode 100644 index 000000000000..eb85dd011288 --- /dev/null +++ b/services/core/java/com/android/server/inputmethod/SubtypeUtils.java @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.inputmethod; + +import android.annotation.Nullable; +import android.content.res.Resources; +import android.os.LocaleList; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.Slog; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodSubtype; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +/** + * This class provides utility methods to handle and manage {@link InputMethodSubtype} for + * {@link InputMethodManagerService}. + * + * <p>This class is intentionally package-private. Utility methods here are tightly coupled with + * implementation details in {@link InputMethodManagerService}. Hence this class is not suitable + * for other components to directly use.</p> + */ +final class SubtypeUtils { + private static final String TAG = "SubtypeUtils"; + public static final boolean DEBUG = false; + + static final String SUBTYPE_MODE_ANY = null; + static final String SUBTYPE_MODE_KEYBOARD = "keyboard"; + + static final int NOT_A_SUBTYPE_ID = -1; + private static final String TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE = + "EnabledWhenDefaultIsNotAsciiCapable"; + + // A temporary workaround for the performance concerns in + // #getImplicitlyApplicableSubtypesLocked(Resources, InputMethodInfo). + // TODO: Optimize all the critical paths including this one. + // TODO(b/235661780): Make the cache supports multi-users. + private static final Object sCacheLock = new Object(); + @GuardedBy("sCacheLock") + private static LocaleList sCachedSystemLocales; + @GuardedBy("sCacheLock") + private static InputMethodInfo sCachedInputMethodInfo; + @GuardedBy("sCacheLock") + private static ArrayList<InputMethodSubtype> sCachedResult; + + static boolean containsSubtypeOf(InputMethodInfo imi, @Nullable Locale locale, + boolean checkCountry, String mode) { + if (locale == null) { + return false; + } + final int N = imi.getSubtypeCount(); + for (int i = 0; i < N; ++i) { + final InputMethodSubtype subtype = imi.getSubtypeAt(i); + if (checkCountry) { + final Locale subtypeLocale = subtype.getLocaleObject(); + if (subtypeLocale == null || + !TextUtils.equals(subtypeLocale.getLanguage(), locale.getLanguage()) || + !TextUtils.equals(subtypeLocale.getCountry(), locale.getCountry())) { + continue; + } + } else { + final Locale subtypeLocale = new Locale(LocaleUtils.getLanguageFromLocaleString( + subtype.getLocale())); + if (!TextUtils.equals(subtypeLocale.getLanguage(), locale.getLanguage())) { + continue; + } + } + if (mode == SUBTYPE_MODE_ANY || TextUtils.isEmpty(mode) || + mode.equalsIgnoreCase(subtype.getMode())) { + return true; + } + } + return false; + } + + static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) { + ArrayList<InputMethodSubtype> subtypes = new ArrayList<>(); + final int subtypeCount = imi.getSubtypeCount(); + for (int i = 0; i < subtypeCount; ++i) { + subtypes.add(imi.getSubtypeAt(i)); + } + return subtypes; + } + + static boolean isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode) { + return getSubtypeIdFromHashCode(imi, subtypeHashCode) != NOT_A_SUBTYPE_ID; + } + + static int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) { + if (imi != null) { + final int subtypeCount = imi.getSubtypeCount(); + for (int i = 0; i < subtypeCount; ++i) { + InputMethodSubtype ims = imi.getSubtypeAt(i); + if (subtypeHashCode == ims.hashCode()) { + return i; + } + } + } + return NOT_A_SUBTYPE_ID; + } + + private static final LocaleUtils.LocaleExtractor<InputMethodSubtype> sSubtypeToLocale = + source -> source != null ? source.getLocaleObject() : null; + + @VisibleForTesting + static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked( + Resources res, InputMethodInfo imi) { + final LocaleList systemLocales = res.getConfiguration().getLocales(); + + synchronized (sCacheLock) { + // We intentionally do not use InputMethodInfo#equals(InputMethodInfo) here because + // it does not check if subtypes are also identical. + if (systemLocales.equals(sCachedSystemLocales) && sCachedInputMethodInfo == imi) { + return new ArrayList<>(sCachedResult); + } + } + + // Note: Only resource info in "res" is used in getImplicitlyApplicableSubtypesLockedImpl(). + // TODO: Refactor getImplicitlyApplicableSubtypesLockedImpl() so that it can receive + // LocaleList rather than Resource. + final ArrayList<InputMethodSubtype> result = + getImplicitlyApplicableSubtypesLockedImpl(res, imi); + synchronized (sCacheLock) { + // Both LocaleList and InputMethodInfo are immutable. No need to copy them here. + sCachedSystemLocales = systemLocales; + sCachedInputMethodInfo = imi; + sCachedResult = new ArrayList<>(result); + } + return result; + } + + private static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLockedImpl( + Resources res, InputMethodInfo imi) { + final List<InputMethodSubtype> subtypes = getSubtypes(imi); + final LocaleList systemLocales = res.getConfiguration().getLocales(); + final String systemLocale = systemLocales.get(0).toString(); + if (TextUtils.isEmpty(systemLocale)) return new ArrayList<>(); + final int numSubtypes = subtypes.size(); + + // Handle overridesImplicitlyEnabledSubtype mechanism. + final ArrayMap<String, InputMethodSubtype> applicableModeAndSubtypesMap = new ArrayMap<>(); + for (int i = 0; i < numSubtypes; ++i) { + // scan overriding implicitly enabled subtypes. + final InputMethodSubtype subtype = subtypes.get(i); + if (subtype.overridesImplicitlyEnabledSubtype()) { + final String mode = subtype.getMode(); + if (!applicableModeAndSubtypesMap.containsKey(mode)) { + applicableModeAndSubtypesMap.put(mode, subtype); + } + } + } + if (applicableModeAndSubtypesMap.size() > 0) { + return new ArrayList<>(applicableModeAndSubtypesMap.values()); + } + + final ArrayMap<String, ArrayList<InputMethodSubtype>> nonKeyboardSubtypesMap = + new ArrayMap<>(); + final ArrayList<InputMethodSubtype> keyboardSubtypes = new ArrayList<>(); + + for (int i = 0; i < numSubtypes; ++i) { + final InputMethodSubtype subtype = subtypes.get(i); + final String mode = subtype.getMode(); + if (SUBTYPE_MODE_KEYBOARD.equals(mode)) { + keyboardSubtypes.add(subtype); + } else { + if (!nonKeyboardSubtypesMap.containsKey(mode)) { + nonKeyboardSubtypesMap.put(mode, new ArrayList<>()); + } + nonKeyboardSubtypesMap.get(mode).add(subtype); + } + } + + final ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<>(); + LocaleUtils.filterByLanguage(keyboardSubtypes, sSubtypeToLocale, systemLocales, + applicableSubtypes); + + if (!applicableSubtypes.isEmpty()) { + boolean hasAsciiCapableKeyboard = false; + final int numApplicationSubtypes = applicableSubtypes.size(); + for (int i = 0; i < numApplicationSubtypes; ++i) { + final InputMethodSubtype subtype = applicableSubtypes.get(i); + if (subtype.isAsciiCapable()) { + hasAsciiCapableKeyboard = true; + break; + } + } + if (!hasAsciiCapableKeyboard) { + final int numKeyboardSubtypes = keyboardSubtypes.size(); + for (int i = 0; i < numKeyboardSubtypes; ++i) { + final InputMethodSubtype subtype = keyboardSubtypes.get(i); + final String mode = subtype.getMode(); + if (SUBTYPE_MODE_KEYBOARD.equals(mode) && subtype.containsExtraValueKey( + TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE)) { + applicableSubtypes.add(subtype); + } + } + } + } + + if (applicableSubtypes.isEmpty()) { + InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked( + res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true); + if (lastResortKeyboardSubtype != null) { + applicableSubtypes.add(lastResortKeyboardSubtype); + } + } + + // For each non-keyboard mode, extract subtypes with system locales. + for (final ArrayList<InputMethodSubtype> subtypeList : nonKeyboardSubtypesMap.values()) { + LocaleUtils.filterByLanguage(subtypeList, sSubtypeToLocale, systemLocales, + applicableSubtypes); + } + + return applicableSubtypes; + } + + /** + * If there are no selected subtypes, tries finding the most applicable one according to the + * given locale. + * @param subtypes this function will search the most applicable subtype in subtypes + * @param mode subtypes will be filtered by mode + * @param locale subtypes will be filtered by locale + * @param canIgnoreLocaleAsLastResort if this function can't find the most applicable subtype, + * it will return the first subtype matched with mode + * @return the most applicable subtypeId + */ + static InputMethodSubtype findLastResortApplicableSubtypeLocked( + Resources res, List<InputMethodSubtype> subtypes, String mode, String locale, + boolean canIgnoreLocaleAsLastResort) { + if (subtypes == null || subtypes.size() == 0) { + return null; + } + if (TextUtils.isEmpty(locale)) { + locale = res.getConfiguration().locale.toString(); + } + final String language = LocaleUtils.getLanguageFromLocaleString(locale); + boolean partialMatchFound = false; + InputMethodSubtype applicableSubtype = null; + InputMethodSubtype firstMatchedModeSubtype = null; + final int N = subtypes.size(); + for (int i = 0; i < N; ++i) { + InputMethodSubtype subtype = subtypes.get(i); + final String subtypeLocale = subtype.getLocale(); + final String subtypeLanguage = LocaleUtils.getLanguageFromLocaleString(subtypeLocale); + // An applicable subtype should match "mode". If mode is null, mode will be ignored, + // and all subtypes with all modes can be candidates. + if (mode == null || subtypes.get(i).getMode().equalsIgnoreCase(mode)) { + if (firstMatchedModeSubtype == null) { + firstMatchedModeSubtype = subtype; + } + if (locale.equals(subtypeLocale)) { + // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US") + applicableSubtype = subtype; + break; + } else if (!partialMatchFound && language.equals(subtypeLanguage)) { + // Partial match (e.g. system locale is "en_US" and subtype locale is "en") + applicableSubtype = subtype; + partialMatchFound = true; + } + } + } + + if (applicableSubtype == null && canIgnoreLocaleAsLastResort) { + return firstMatchedModeSubtype; + } + + // The first subtype applicable to the system locale will be defined as the most applicable + // subtype. + if (DEBUG) { + if (applicableSubtype != null) { + Slog.d(TAG, "Applicable InputMethodSubtype was found: " + + applicableSubtype.getMode() + "," + applicableSubtype.getLocale()); + } + } + return applicableSubtype; + } +} diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index af0a20ddf337..6cfe093df6d0 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -936,12 +936,15 @@ public class PackageDexOptimizer { String classLoaderContext, int profileAnalysisResult, boolean downgrade, int dexoptFlags, String oatDir) { final boolean shouldBePublic = (dexoptFlags & DEXOPT_PUBLIC) != 0; - // If the artifacts should be public while the current artifacts are not, we should - // re-compile anyway. - if (shouldBePublic && isOdexPrivate(packageName, path, isa, oatDir)) { - // Ensure compilation by pretending a compiler filter change on the apk/odex location - // (the reason for the '-'. A positive value means the 'oat' location). - return adjustDexoptNeeded(-DexFile.DEX2OAT_FOR_FILTER); + final boolean isProfileGuidedFilter = (dexoptFlags & DEXOPT_PROFILE_GUIDED) != 0; + boolean newProfile = profileAnalysisResult == PROFILE_ANALYSIS_OPTIMIZE; + + if (!newProfile && isProfileGuidedFilter && shouldBePublic + && isOdexPrivate(packageName, path, isa, oatDir)) { + // The profile that will be used is a cloud profile, while the profile used previously + // is a user profile. Typically, this happens after an app starts being used by other + // apps. + newProfile = true; } int dexoptNeeded; @@ -959,7 +962,6 @@ public class PackageDexOptimizer { && profileAnalysisResult == PROFILE_ANALYSIS_DONT_OPTIMIZE_EMPTY_PROFILES) { actualCompilerFilter = "verify"; } - boolean newProfile = profileAnalysisResult == PROFILE_ANALYSIS_OPTIMIZE; dexoptNeeded = DexFile.getDexOptNeeded(path, isa, actualCompilerFilter, classLoaderContext, newProfile, downgrade); } catch (IOException ioe) { diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index a01942d0dfa8..bb23d89d218f 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -1346,7 +1346,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements private String getDeviceOwnerDeletedPackageMsg() { DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); return dpm.getResources().getString(PACKAGE_DELETED_BY_DO, - () -> mContext.getString(R.string.package_updated_device_owner)); + () -> mContext.getString(R.string.package_deleted_device_owner)); } @Override diff --git a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java index 881f8707fdd8..661161f05d23 100644 --- a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java +++ b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java @@ -33,6 +33,7 @@ import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; +import com.android.server.PermissionThread; /** * Class that handles one-time permissions for a user @@ -79,7 +80,8 @@ public class OneTimePermissionUserManager { mContext = context; mActivityManager = context.getSystemService(ActivityManager.class); mAlarmManager = context.getSystemService(AlarmManager.class); - mPermissionControllerManager = context.getSystemService(PermissionControllerManager.class); + mPermissionControllerManager = new PermissionControllerManager( + mContext, PermissionThread.getHandler()); mHandler = context.getMainThreadHandler(); } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index ccd906646d60..7d4cfdff2bfa 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -122,6 +122,7 @@ import com.android.internal.util.Preconditions; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.FgThread; import com.android.server.LocalServices; +import com.android.server.PermissionThread; import com.android.server.ServiceThread; import com.android.server.SystemConfig; import com.android.server.Watchdog; @@ -2004,7 +2005,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt Preconditions.checkArgumentNonNegative(userId, "userId"); CompletableFuture<byte[]> backup = new CompletableFuture<>(); mPermissionControllerManager.getRuntimePermissionBackup(UserHandle.of(userId), - mContext.getMainExecutor(), backup::complete); + PermissionThread.getExecutor(), backup::complete); try { return backup.get(BACKUP_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); @@ -2055,7 +2056,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } } mPermissionControllerManager.applyStagedRuntimePermissionBackup(packageName, - UserHandle.of(userId), mContext.getMainExecutor(), (hasMoreBackup) -> { + UserHandle.of(userId), PermissionThread.getExecutor(), (hasMoreBackup) -> { if (hasMoreBackup) { return; } @@ -4443,9 +4444,9 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } } - mPermissionControllerManager = mContext.getSystemService(PermissionControllerManager.class); - mPermissionPolicyInternal = LocalServices.getService(PermissionPolicyInternal.class); - } + mPermissionControllerManager = new PermissionControllerManager( + mContext, PermissionThread.getHandler()); + mPermissionPolicyInternal = LocalServices.getService(PermissionPolicyInternal.class); } private static String getVolumeUuidForPackage(AndroidPackage pkg) { if (pkg == null) { diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java index b56e1120f16a..9c9576984820 100644 --- a/services/core/java/com/android/server/policy/PermissionPolicyService.java +++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java @@ -92,6 +92,7 @@ import com.android.internal.util.IntPair; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.FgThread; import com.android.server.LocalServices; +import com.android.server.PermissionThread; import com.android.server.SystemService; import com.android.server.notification.NotificationManagerInternal; import com.android.server.pm.UserManagerInternal; @@ -335,7 +336,7 @@ public final class PermissionPolicyService extends SystemService { PermissionControllerManager manager = mPermControllerManagers.get(user); if (manager == null) { manager = new PermissionControllerManager( - getUserContext(getContext(), user), FgThread.getHandler()); + getUserContext(getContext(), user), PermissionThread.getHandler()); mPermControllerManagers.put(user, manager); } manager.updateUserSensitiveForApp(uid); @@ -343,8 +344,9 @@ public final class PermissionPolicyService extends SystemService { }, UserHandle.ALL, intentFilter, null, null); PermissionControllerManager manager = new PermissionControllerManager( - getUserContext(getContext(), Process.myUserHandle()), FgThread.getHandler()); - FgThread.getHandler().postDelayed(manager::updateUserSensitive, + getUserContext(getContext(), Process.myUserHandle()), + PermissionThread.getHandler()); + PermissionThread.getHandler().postDelayed(manager::updateUserSensitive, USER_SENSITIVE_UPDATE_DELAY_MS); } @@ -371,6 +373,11 @@ public final class PermissionPolicyService extends SystemService { if (isStarted(changedUserId)) { synchronized (mLock) { if (mIsPackageSyncsScheduled.add(new Pair<>(packageName, changedUserId))) { + // TODO(b/165030092): migrate this to PermissionThread.getHandler(). + // synchronizePackagePermissionsAndAppOpsForUser is a heavy operation. + // Dispatched on a PermissionThread, it interferes with user switch. + // FgThread is busy and schedules it after most of the switch is done. + // A possible solution is to delay the callback. FgThread.getHandler().sendMessage(PooledLambda.obtainMessage( PermissionPolicyService ::synchronizePackagePermissionsAndAppOpsForUser, @@ -584,9 +591,9 @@ public final class PermissionPolicyService extends SystemService { final PermissionControllerManager permissionControllerManager = new PermissionControllerManager( getUserContext(getContext(), UserHandle.of(userId)), - FgThread.getHandler()); + PermissionThread.getHandler()); permissionControllerManager.grantOrUpgradeDefaultRuntimePermissions( - FgThread.getExecutor(), successful -> { + PermissionThread.getExecutor(), successful -> { if (successful) { future.complete(null); } else { @@ -690,7 +697,7 @@ public final class PermissionPolicyService extends SystemService { synchronized (mLock) { if (!mIsUidSyncScheduled.get(uid)) { mIsUidSyncScheduled.put(uid, true); - FgThread.getHandler().sendMessage(PooledLambda.obtainMessage( + PermissionThread.getHandler().sendMessage(PooledLambda.obtainMessage( PermissionPolicyService::resetAppOpPermissionsIfNotRequestedForUid, this, uid)); } diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java index 97a57e066fc7..b79ac6f68be2 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java @@ -64,7 +64,6 @@ public class KeyguardServiceDelegate { reset(); } boolean showing; - boolean showingAndNotOccluded; boolean inputRestricted; volatile boolean occluded; boolean secure; @@ -83,7 +82,7 @@ public class KeyguardServiceDelegate { // the event something checks before the service is actually started. // KeyguardService itself should default to this state until the real state is known. showing = true; - showingAndNotOccluded = true; + occluded = false; secure = true; deviceHasKeyguard = true; enabled = true; @@ -148,7 +147,6 @@ public class KeyguardServiceDelegate { Context.BIND_AUTO_CREATE, mHandler, UserHandle.SYSTEM)) { Log.v(TAG, "*** Keyguard: can't bind to " + keyguardComponent); mKeyguardState.showing = false; - mKeyguardState.showingAndNotOccluded = false; mKeyguardState.secure = false; synchronized (mKeyguardState) { // TODO: Fix synchronisation model in this class. The other state in this class @@ -440,7 +438,6 @@ public class KeyguardServiceDelegate { pw.println(prefix + TAG); prefix += " "; pw.println(prefix + "showing=" + mKeyguardState.showing); - pw.println(prefix + "showingAndNotOccluded=" + mKeyguardState.showingAndNotOccluded); pw.println(prefix + "inputRestricted=" + mKeyguardState.inputRestricted); pw.println(prefix + "occluded=" + mKeyguardState.occluded); pw.println(prefix + "secure=" + mKeyguardState.secure); diff --git a/services/core/java/com/android/server/testharness/TestHarnessModeService.java b/services/core/java/com/android/server/testharness/TestHarnessModeService.java index b6a413524c5c..452bdf409828 100644 --- a/services/core/java/com/android/server/testharness/TestHarnessModeService.java +++ b/services/core/java/com/android/server/testharness/TestHarnessModeService.java @@ -189,6 +189,7 @@ public class TestHarnessModeService extends SystemService { if (adbManager.getAdbTempKeysFile() != null) { writeBytesToFile(persistentData.mAdbTempKeys, adbManager.getAdbTempKeysFile().toPath()); } + adbManager.notifyKeyFilesUpdated(); } private void configureUser() { diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java index 78b1c20ac4b2..d79837be3583 100644 --- a/services/core/java/com/android/server/vibrator/Vibration.java +++ b/services/core/java/com/android/server/vibrator/Vibration.java @@ -61,12 +61,11 @@ final class Vibration { IGNORED_BACKGROUND, IGNORED_UNKNOWN_VIBRATION, IGNORED_UNSUPPORTED, - IGNORED_FOR_ALARM, IGNORED_FOR_EXTERNAL, + IGNORED_FOR_HIGHER_IMPORTANCE, IGNORED_FOR_ONGOING, IGNORED_FOR_POWER, IGNORED_FOR_RINGER_MODE, - IGNORED_FOR_RINGTONE, IGNORED_FOR_SETTINGS, IGNORED_SUPERSEDED, } diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index f0911ca62027..5ac2f4f27452 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -713,14 +713,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { case IGNORED_ERROR_APP_OPS: Slog.w(TAG, "Would be an error: vibrate from uid " + uid); break; - case IGNORED_FOR_ALARM: + case IGNORED_FOR_EXTERNAL: if (DEBUG) { - Slog.d(TAG, "Ignoring incoming vibration in favor of alarm vibration"); + Slog.d(TAG, "Ignoring incoming vibration for current external vibration"); } break; - case IGNORED_FOR_EXTERNAL: + case IGNORED_FOR_HIGHER_IMPORTANCE: if (DEBUG) { - Slog.d(TAG, "Ignoring incoming vibration for current external vibration"); + Slog.d(TAG, "Ignoring incoming vibration in favor of ongoing vibration" + + " with higher importance"); } break; case IGNORED_FOR_ONGOING: @@ -734,12 +735,6 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { + attrs); } break; - case IGNORED_FOR_RINGTONE: - if (DEBUG) { - Slog.d(TAG, "Ignoring incoming vibration in favor of ringtone vibration"); - } - break; - default: if (DEBUG) { Slog.d(TAG, "Vibration for uid=" + uid + " and with attrs=" + attrs @@ -812,20 +807,43 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { return null; } - if (currentVibration.attrs.getUsage() == VibrationAttributes.USAGE_ALARM) { - return Vibration.Status.IGNORED_FOR_ALARM; - } - - if (currentVibration.attrs.getUsage() == VibrationAttributes.USAGE_RINGTONE) { - return Vibration.Status.IGNORED_FOR_RINGTONE; + int currentUsage = currentVibration.attrs.getUsage(); + int newUsage = vib.attrs.getUsage(); + if (getVibrationImportance(currentUsage) > getVibrationImportance(newUsage)) { + // Current vibration has higher importance than this one and should not be cancelled. + return Vibration.Status.IGNORED_FOR_HIGHER_IMPORTANCE; } if (currentVibration.isRepeating()) { + // Current vibration is repeating, assume it's more important. return Vibration.Status.IGNORED_FOR_ONGOING; } + return null; } + private static int getVibrationImportance(@VibrationAttributes.Usage int usage) { + switch (usage) { + case VibrationAttributes.USAGE_RINGTONE: + return 5; + case VibrationAttributes.USAGE_ALARM: + return 4; + case VibrationAttributes.USAGE_NOTIFICATION: + return 3; + case VibrationAttributes.USAGE_COMMUNICATION_REQUEST: + case VibrationAttributes.USAGE_ACCESSIBILITY: + return 2; + case VibrationAttributes.USAGE_HARDWARE_FEEDBACK: + case VibrationAttributes.USAGE_PHYSICAL_EMULATION: + return 1; + case VibrationAttributes.USAGE_MEDIA: + case VibrationAttributes.USAGE_TOUCH: + case VibrationAttributes.USAGE_UNKNOWN: + default: + return 0; + } + } + /** * Check if given vibration should be ignored by this service. * diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 0350dfcd8230..b70f7a0fd213 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -2467,8 +2467,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (!newTask && taskSwitch && processRunning && !activityCreated && task.intent != null && mActivityComponent.equals(task.intent.getComponent())) { final ActivityRecord topAttached = task.getActivity(ActivityRecord::attachedToProcess); - if (topAttached != null && topAttached.isSnapshotCompatible(snapshot)) { - return STARTING_WINDOW_TYPE_SNAPSHOT; + if (topAttached != null) { + if (topAttached.isSnapshotCompatible(snapshot) + // This trampoline must be the same rotation. + && mDisplayContent.getDisplayRotation().rotationForOrientation(mOrientation, + mDisplayContent.getRotation()) == snapshot.getRotation()) { + return STARTING_WINDOW_TYPE_SNAPSHOT; + } + // No usable snapshot. And a splash screen may also be weird because an existing + // activity may be shown right after the trampoline is finished. + return STARTING_WINDOW_TYPE_NONE; } } final boolean isActivityHome = isActivityTypeHome(); @@ -8174,7 +8182,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A resolvedBounds.set(containingBounds); final float letterboxAspectRatioOverride = - mWmService.mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio(); + mLetterboxUiController.getFixedOrientationLetterboxAspectRatio(); final float desiredAspectRatio = letterboxAspectRatioOverride > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO ? letterboxAspectRatioOverride : computeAspectRatio(parentBounds); @@ -8727,18 +8735,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * Returns the min aspect ratio of this activity. */ private float getMinAspectRatio() { - float infoAspectRatio = info.getMinAspectRatio(getRequestedOrientation()); - // Complying with the CDD 7.1.1.2 requirement for unresizble apps: - // https://source.android.com/compatibility/12/android-12-cdd#7112_screen_aspect_ratio - return infoAspectRatio < 1f && info.resizeMode == RESIZE_MODE_UNRESIZEABLE - // TODO(233582832): Consider removing fixed-orientation condition. - // Some apps switching from tablet to phone layout at the certain size - // threshold. This may lead to flickering on tablets in landscape orientation - // if an app sets orientation to portrait dynamically because of aspect ratio - // restriction applied here. - && getRequestedConfigurationOrientation() != ORIENTATION_UNDEFINED - ? mLetterboxUiController.getDefaultMinAspectRatioForUnresizableApps() - : infoAspectRatio; + return info.getMinAspectRatio(getRequestedOrientation()); } /** diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index e7ce1b5a4c91..963345f2f49f 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -116,7 +116,6 @@ public class AppTransitionController { private final DisplayContent mDisplayContent; private final WallpaperController mWallpaperControllerLocked; private RemoteAnimationDefinition mRemoteAnimationDefinition = null; - private static final int KEYGUARD_GOING_AWAY_ANIMATION_DURATION = 400; private static final int TYPE_NONE = 0; private static final int TYPE_ACTIVITY = 1; @@ -737,14 +736,17 @@ public class AppTransitionController { */ private void overrideWithRemoteAnimationIfSet(@Nullable ActivityRecord animLpActivity, @TransitionOldType int transit, ArraySet<Integer> activityTypes) { + RemoteAnimationAdapter adapter = null; if (transit == TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE) { // The crash transition has higher priority than any involved remote animations. - return; - } - final RemoteAnimationAdapter adapter = - getRemoteAnimationOverride(animLpActivity, transit, activityTypes); - if (adapter != null - && mDisplayContent.mAppTransition.getRemoteAnimationController() == null) { + } else if (AppTransition.isKeyguardGoingAwayTransitOld(transit)) { + adapter = mRemoteAnimationDefinition != null + ? mRemoteAnimationDefinition.getAdapter(transit, activityTypes) + : null; + } else if (mDisplayContent.mAppTransition.getRemoteAnimationController() == null) { + adapter = getRemoteAnimationOverride(animLpActivity, transit, activityTypes); + } + if (adapter != null) { mDisplayContent.mAppTransition.overridePendingAppTransitionRemote(adapter); } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index afcb21a45106..68f60152ef95 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -2942,9 +2942,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // Set some sort of reasonable bounds on the size of the display that we will try // to emulate. final int minSize = 200; - final int maxScale = 2; - width = Math.min(Math.max(width, minSize), mInitialDisplayWidth * maxScale); - height = Math.min(Math.max(height, minSize), mInitialDisplayHeight * maxScale); + final int maxScale = 3; + final int maxSize = Math.max(mInitialDisplayWidth, mInitialDisplayHeight) * maxScale; + width = Math.min(Math.max(width, minSize), maxSize); + height = Math.min(Math.max(height, minSize), maxSize); } Slog.i(TAG_WM, "Using new display size: " + width + "x" + height); @@ -6194,6 +6195,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp .getKeyguardController().isAodShowing(mDisplayId); } + /** + * @return whether the keyguard is occluded on this display + */ + boolean isKeyguardOccluded() { + return mRootWindowContainer.mTaskSupervisor + .getKeyguardController().isDisplayOccluded(mDisplayId); + } + @VisibleForTesting void removeAllTasks() { forAllTasks((t) -> { t.getRootTask().removeChild(t, "removeAllTasks"); }); diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 5c1fc653586e..cff8b93ac947 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -1212,7 +1212,8 @@ public class DisplayPolicy { break; default: if (attrs.providedInsets != null) { - for (InsetsFrameProvider provider : attrs.providedInsets) { + for (int i = attrs.providedInsets.length - 1; i >= 0; i--) { + final InsetsFrameProvider provider = attrs.providedInsets[i]; switch (provider.type) { case ITYPE_STATUS_BAR: mStatusBarAlt = win; @@ -1231,21 +1232,29 @@ public class DisplayPolicy { mExtraNavBarAltPosition = getAltBarPosition(attrs); break; } + // The index of the provider and corresponding insets types cannot change at + // runtime as ensured in WMS. Make use of the index in the provider directly + // to access the latest provided size at runtime. + final int index = i; final TriConsumer<DisplayFrames, WindowContainer, Rect> frameProvider = provider.insetsSize != null ? (displayFrames, windowContainer, inOutFrame) -> { inOutFrame.inset(win.mGivenContentInsets); + final InsetsFrameProvider ifp = + win.mAttrs.forRotation(displayFrames.mRotation) + .providedInsets[index]; calculateInsetsFrame(displayFrames, windowContainer, - inOutFrame, provider.source, - provider.insetsSize); + inOutFrame, ifp.source, ifp.insetsSize); } : null; final TriConsumer<DisplayFrames, WindowContainer, Rect> imeFrameProvider = provider.imeInsetsSize != null ? (displayFrames, windowContainer, inOutFrame) -> { inOutFrame.inset(win.mGivenContentInsets); + final InsetsFrameProvider ifp = + win.mAttrs.forRotation(displayFrames.mRotation) + .providedInsets[index]; calculateInsetsFrame(displayFrames, windowContainer, - inOutFrame, provider.source, - provider.imeInsetsSize); + inOutFrame, ifp.source, ifp.imeInsetsSize); } : null; mDisplayContent.setInsetProvider(provider.type, win, frameProvider, imeFrameProvider); @@ -1256,14 +1265,14 @@ public class DisplayPolicy { } } - private void calculateInsetsFrame(DisplayFrames df, WindowContainer coutainer, Rect inOutFrame, + private void calculateInsetsFrame(DisplayFrames df, WindowContainer container, Rect inOutFrame, int source, Insets insetsSize) { if (source == InsetsFrameProvider.SOURCE_DISPLAY) { inOutFrame.set(df.mUnrestricted); } else if (source == InsetsFrameProvider.SOURCE_CONTAINER_BOUNDS) { - inOutFrame.set(coutainer.getBounds()); + inOutFrame.set(container.getBounds()); } - if (insetsSize == null || insetsSize.equals(Insets.NONE)) { + if (insetsSize == null) { return; } // Only one side of the provider shall be applied. Check in the order of left - top - @@ -1276,6 +1285,8 @@ public class DisplayPolicy { inOutFrame.left = inOutFrame.right - insetsSize.right; } else if (insetsSize.bottom != 0) { inOutFrame.top = inOutFrame.bottom - insetsSize.bottom; + } else { + inOutFrame.setEmpty(); } } @@ -1523,13 +1534,14 @@ public class DisplayPolicy { */ void simulateLayoutDisplay(DisplayFrames displayFrames) { final InsetsStateController controller = mDisplayContent.getInsetsStateController(); + sTmpClientFrames.attachedFrame = null; for (int i = mInsetsSourceWindowsExceptIme.size() - 1; i >= 0; i--) { final WindowState win = mInsetsSourceWindowsExceptIme.valueAt(i); mWindowLayout.computeFrames(win.mAttrs.forRotation(displayFrames.mRotation), displayFrames.mInsetsState, displayFrames.mDisplayCutoutSafe, displayFrames.mUnrestricted, win.getWindowingMode(), UNSPECIFIED_LENGTH, - UNSPECIFIED_LENGTH, win.getRequestedVisibilities(), - null /* attachedWindowFrame */, win.mGlobalScale, sTmpClientFrames); + UNSPECIFIED_LENGTH, win.getRequestedVisibilities(), win.mGlobalScale, + sTmpClientFrames); final SparseArray<InsetsSource> sources = win.getProvidedInsetsSources(); final InsetsState state = displayFrames.mInsetsState; for (int index = sources.size() - 1; index >= 0; index--) { @@ -1541,13 +1553,14 @@ public class DisplayPolicy { } void updateInsetsSourceFramesExceptIme(DisplayFrames displayFrames) { + sTmpClientFrames.attachedFrame = null; for (int i = mInsetsSourceWindowsExceptIme.size() - 1; i >= 0; i--) { final WindowState win = mInsetsSourceWindowsExceptIme.valueAt(i); mWindowLayout.computeFrames(win.mAttrs.forRotation(displayFrames.mRotation), displayFrames.mInsetsState, displayFrames.mDisplayCutoutSafe, displayFrames.mUnrestricted, win.getWindowingMode(), UNSPECIFIED_LENGTH, - UNSPECIFIED_LENGTH, win.getRequestedVisibilities(), - null /* attachedWindowFrame */, win.mGlobalScale, sTmpClientFrames); + UNSPECIFIED_LENGTH, win.getRequestedVisibilities(), win.mGlobalScale, + sTmpClientFrames); win.updateSourceFrame(sTmpClientFrames.frame); } } @@ -1577,7 +1590,7 @@ public class DisplayPolicy { displayFrames = win.getDisplayFrames(displayFrames); final WindowManager.LayoutParams attrs = win.mAttrs.forRotation(displayFrames.mRotation); - final Rect attachedWindowFrame = attached != null ? attached.getFrame() : null; + sTmpClientFrames.attachedFrame = attached != null ? attached.getFrame() : null; // If this window has different LayoutParams for rotations, we cannot trust its requested // size. Because it might have not sent its requested size for the new rotation. @@ -1587,8 +1600,7 @@ public class DisplayPolicy { mWindowLayout.computeFrames(attrs, win.getInsetsState(), displayFrames.mDisplayCutoutSafe, win.getBounds(), win.getWindowingMode(), requestedWidth, requestedHeight, - win.getRequestedVisibilities(), attachedWindowFrame, win.mGlobalScale, - sTmpClientFrames); + win.getRequestedVisibilities(), win.mGlobalScale, sTmpClientFrames); win.setFrames(sTmpClientFrames, win.mRequestedWidth, win.mRequestedHeight); } diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java index f2d4d5427291..475dd17ef727 100644 --- a/services/core/java/com/android/server/wm/DragState.java +++ b/services/core/java/com/android/server/wm/DragState.java @@ -367,10 +367,15 @@ class DragState { mDragWindowHandle.ownerUid = MY_UID; mDragWindowHandle.scaleFactor = 1.0f; - // Keep the default behavior of this window to be focusable, which allows the system - // to consume keys when dragging is active. This can also be used to modify the drag - // state on key press. For example, cancel drag on escape key. - mDragWindowHandle.inputConfig = InputConfig.PREVENT_SPLITTING; + // InputConfig.PREVENT_SPLITTING: To keep the default behavior of this window to be + // focusable, which allows the system to consume keys when dragging is active. This can + // also be used to modify the drag state on key press. For example, cancel drag on + // escape key. + // InputConfig.TRUSTED_OVERLAY: To not block any touches while D&D ongoing and allowing + // touches to pass through to windows underneath. This allows user to interact with the + // UI to navigate while dragging. + mDragWindowHandle.inputConfig = + InputConfig.PREVENT_SPLITTING | InputConfig.TRUSTED_OVERLAY; // The drag window cannot receive new touches. mDragWindowHandle.touchableRegion.setEmpty(); diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java index 2d227b66b3ce..08715b160b9a 100644 --- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java +++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java @@ -37,11 +37,6 @@ final class LetterboxConfiguration { */ static final float MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO = 1.0f; - // Min allowed aspect ratio for unresizable apps which is used when an app doesn't specify - // android:minAspectRatio in accordance with the CDD 7.1.1.2 requirement: - // https://source.android.com/compatibility/12/android-12-cdd#7112_screen_aspect_ratio - static final float MIN_UNRESIZABLE_ASPECT_RATIO = 4 / 3f; - /** Enum for Letterbox background type. */ @Retention(RetentionPolicy.SOURCE) @IntDef({LETTERBOX_BACKGROUND_SOLID_COLOR, LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND, @@ -109,9 +104,7 @@ final class LetterboxConfiguration { // MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO will be ignored. private float mFixedOrientationLetterboxAspectRatio; - // Default min aspect ratio for unresizable apps which is used when an app doesn't specify - // android:minAspectRatio in accordance with the CDD 7.1.1.2 requirement: - // https://source.android.com/compatibility/12/android-12-cdd#7112_screen_aspect_ratio + // Default min aspect ratio for unresizable apps that are eligible for the size compat mode. private float mDefaultMinAspectRatioForUnresizableApps; // Corners radius for activities presented in the letterbox mode, values < 0 will be ignored. @@ -250,13 +243,7 @@ final class LetterboxConfiguration { } /** - * Resets the min aspect ratio for unresizable apps which is used when an app doesn't specify - * {@code android:minAspectRatio} to {@link - * R.dimen.config_letterboxDefaultMinAspectRatioForUnresizableApps}. - * - * @throws AssertionError if {@link - * R.dimen.config_letterboxDefaultMinAspectRatioForUnresizableApps} is < {@link - * #MIN_UNRESIZABLE_ASPECT_RATIO}. + * Resets the min aspect ratio for unresizable apps that are eligible for size compat mode. */ void resetDefaultMinAspectRatioForUnresizableApps() { setDefaultMinAspectRatioForUnresizableApps(mContext.getResources().getFloat( @@ -264,25 +251,16 @@ final class LetterboxConfiguration { } /** - * Gets the min aspect ratio for unresizable apps which is used when an app doesn't specify - * {@code android:minAspectRatio}. + * Gets the min aspect ratio for unresizable apps that are eligible for size compat mode. */ float getDefaultMinAspectRatioForUnresizableApps() { return mDefaultMinAspectRatioForUnresizableApps; } /** - * Overrides the min aspect ratio for unresizable apps which is used when an app doesn't - * specify {@code android:minAspectRatio}. - * - * @throws AssertionError if given value is < {@link #MIN_UNRESIZABLE_ASPECT_RATIO}. + * Overrides the min aspect ratio for unresizable apps that are eligible for size compat mode. */ void setDefaultMinAspectRatioForUnresizableApps(float aspectRatio) { - if (aspectRatio < MIN_UNRESIZABLE_ASPECT_RATIO) { - throw new AssertionError( - "Unexpected min aspect ratio for unresizable apps, it should be <= " - + MIN_UNRESIZABLE_ASPECT_RATIO + " but was " + aspectRatio); - } mDefaultMinAspectRatioForUnresizableApps = aspectRatio; } diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index df9a87ea1ab0..f849d2886ba1 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -28,6 +28,7 @@ import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_ import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING; import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR; import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_WALLPAPER; +import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO; import static com.android.server.wm.LetterboxConfiguration.letterboxBackgroundTypeToString; import android.annotation.Nullable; @@ -211,10 +212,19 @@ final class LetterboxUiController { : mLetterboxConfiguration.getLetterboxVerticalPositionMultiplier(); } - float getDefaultMinAspectRatioForUnresizableApps() { + float getFixedOrientationLetterboxAspectRatio() { + return mActivityRecord.shouldCreateCompatDisplayInsets() + ? getDefaultMinAspectRatioForUnresizableApps() + : mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio(); + } + + private float getDefaultMinAspectRatioForUnresizableApps() { if (!mLetterboxConfiguration.getIsSplitScreenAspectRatioForUnresizableAppsEnabled() || mActivityRecord.getDisplayContent() == null) { - return mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps(); + return mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps() + > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO + ? mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps() + : mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio(); } int dividerWindowWidth = @@ -226,10 +236,10 @@ final class LetterboxUiController { // Getting the same aspect ratio that apps get in split screen. Rect bounds = new Rect(mActivityRecord.getDisplayContent().getBounds()); if (bounds.width() >= bounds.height()) { - bounds.inset(/* dx */ dividerSize, /* dy */ 0); + bounds.inset(/* dx */ dividerSize / 2, /* dy */ 0); bounds.right = bounds.centerX(); } else { - bounds.inset(/* dx */ 0, /* dy */ dividerSize); + bounds.inset(/* dx */ 0, /* dy */ dividerSize / 2); bounds.bottom = bounds.centerY(); } return computeAspectRatio(bounds); diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java index ad158c7b45b9..ac1a2b17603a 100644 --- a/services/core/java/com/android/server/wm/RemoteAnimationController.java +++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java @@ -331,8 +331,10 @@ class RemoteAnimationController implements DeathRecipient { private void invokeAnimationCancelled(String reason) { ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "cancelAnimation(): reason=%s", reason); + final boolean isKeyguardOccluded = mDisplayContent.isKeyguardOccluded(); + try { - mRemoteAnimationAdapter.getRunner().onAnimationCancelled(); + mRemoteAnimationAdapter.getRunner().onAnimationCancelled(isKeyguardOccluded); } catch (RemoteException e) { Slog.e(TAG, "Failed to notify cancel", e); } diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 30b50839cd35..9b013dac6adf 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -115,8 +115,6 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { private float mLastReportedAnimatorScale; private String mPackageName; private String mRelayoutTag; - private String mUpdateViewVisibilityTag; - private String mUpdateWindowLayoutTag; private final InsetsVisibilities mDummyRequestedVisibilities = new InsetsVisibilities(); private final InsetsSourceControl[] mDummyControls = new InsetsSourceControl[0]; final boolean mSetsUnrestrictedKeepClearAreas; @@ -195,27 +193,28 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, InsetsVisibilities requestedVisibilities, InputChannel outInputChannel, InsetsState outInsetsState, - InsetsSourceControl[] outActiveControls) { + InsetsSourceControl[] outActiveControls, Rect outAttachedFrame) { return mService.addWindow(this, window, attrs, viewVisibility, displayId, UserHandle.getUserId(mUid), requestedVisibilities, outInputChannel, outInsetsState, - outActiveControls); + outActiveControls, outAttachedFrame); } @Override public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities, InputChannel outInputChannel, InsetsState outInsetsState, - InsetsSourceControl[] outActiveControls) { + InsetsSourceControl[] outActiveControls, Rect outAttachedFrame) { return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId, - requestedVisibilities, outInputChannel, outInsetsState, outActiveControls); + requestedVisibilities, outInputChannel, outInsetsState, outActiveControls, + outAttachedFrame); } @Override public int addToDisplayWithoutInputChannel(IWindow window, WindowManager.LayoutParams attrs, - int viewVisibility, int displayId, InsetsState outInsetsState) { + int viewVisibility, int displayId, InsetsState outInsetsState, Rect outAttachedFrame) { return mService.addWindow(this, window, attrs, viewVisibility, displayId, UserHandle.getUserId(mUid), mDummyRequestedVisibilities, null /* outInputChannel */, - outInsetsState, mDummyControls); + outInsetsState, mDummyControls, outAttachedFrame); } @Override @@ -224,29 +223,13 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { } @Override - public int updateVisibility(IWindow client, WindowManager.LayoutParams attrs, - int viewVisibility, MergedConfiguration outMergedConfiguration, - SurfaceControl outSurfaceControl, InsetsState outInsetsState, - InsetsSourceControl[] outActiveControls) { - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mUpdateViewVisibilityTag); - int res = mService.updateViewVisibility(this, client, attrs, viewVisibility, - outMergedConfiguration, outSurfaceControl, outInsetsState, outActiveControls); - Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); - return res; - } - - @Override - public void updateLayout(IWindow window, WindowManager.LayoutParams attrs, int flags, - ClientWindowFrames clientFrames, int requestedWidth, int requestedHeight) { - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mUpdateWindowLayoutTag); - mService.updateWindowLayout(this, window, attrs, flags, clientFrames, requestedWidth, - requestedHeight); - Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + public void prepareToReplaceWindows(IBinder appToken, boolean childrenOnly) { + mService.setWillReplaceWindows(appToken, childrenOnly); } @Override - public void prepareToReplaceWindows(IBinder appToken, boolean childrenOnly) { - mService.setWillReplaceWindows(appToken, childrenOnly); + public boolean cancelDraw(IWindow window) { + return mService.cancelDraw(this, window); } @Override @@ -711,8 +694,6 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { if (wpc != null) { mPackageName = wpc.mInfo.packageName; mRelayoutTag = "relayoutWindow: " + mPackageName; - mUpdateViewVisibilityTag = "updateVisibility: " + mPackageName; - mUpdateWindowLayoutTag = "updateLayout: " + mPackageName; } else { Slog.e(TAG_WM, "Unknown process pid=" + mPid); } diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java index 68dbb0607ac1..0bb773ae5e41 100644 --- a/services/core/java/com/android/server/wm/StartingSurfaceController.java +++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java @@ -158,14 +158,13 @@ public class StartingSurfaceController { + topFullscreenActivity); return null; } - if (topFullscreenActivity.getWindowConfiguration().getRotation() - != taskSnapshot.getRotation()) { + if (activity.mDisplayContent.getRotation() != taskSnapshot.getRotation()) { // The snapshot should have been checked by ActivityRecord#isSnapshotCompatible // that the activity will be updated to the same rotation as the snapshot. Since // the transition is not started yet, fixed rotation transform needs to be applied // earlier to make the snapshot show in a rotated container. activity.mDisplayContent.handleTopActivityLaunchingInDifferentOrientation( - topFullscreenActivity, false /* checkOpening */); + activity, false /* checkOpening */); } mService.mAtmService.mTaskOrganizerController.addStartingWindow(task, activity, 0 /* launchTheme */, taskSnapshot); diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationThread.java b/services/core/java/com/android/server/wm/SurfaceAnimationThread.java index 1259ee901e4c..8ea715c4084e 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimationThread.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimationThread.java @@ -40,7 +40,7 @@ public final class SurfaceAnimationThread extends ServiceThread { sInstance = new SurfaceAnimationThread(); sInstance.start(); sInstance.getLooper().setTraceTag(Trace.TRACE_TAG_WINDOW_MANAGER); - sHandler = new Handler(sInstance.getLooper()); + sHandler = makeSharedHandler(sInstance.getLooper()); } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 9f11763c4780..9d5b9455da13 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -89,6 +89,7 @@ import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED; import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_RELAUNCH; import static android.view.WindowManagerGlobal.ADD_OKAY; +import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW; import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED; import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID; import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER; @@ -121,6 +122,7 @@ import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; +import static com.android.server.wm.WindowContainer.SYNC_STATE_NONE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT_METHOD; @@ -1438,7 +1440,7 @@ public class WindowManagerService extends IWindowManager.Stub public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility, int displayId, int requestUserId, InsetsVisibilities requestedVisibilities, InputChannel outInputChannel, InsetsState outInsetsState, - InsetsSourceControl[] outActiveControls) { + InsetsSourceControl[] outActiveControls, Rect outAttachedFrame) { Arrays.fill(outActiveControls, null); int[] appOp = new int[1]; final boolean isRoundedCornerOverlay = (attrs.privateFlags @@ -1853,6 +1855,13 @@ public class WindowManagerService extends IWindowManager.Stub outInsetsState.set(win.getCompatInsetsState(), win.isClientLocal()); getInsetsSourceControls(win, outActiveControls); + + if (win.mLayoutAttached) { + outAttachedFrame.set(win.getParentWindow().getCompatFrame()); + } else { + // Make this invalid which indicates a null attached frame. + outAttachedFrame.set(0, 0, -1, -1); + } } Binder.restoreCallingIdentity(origId); @@ -2205,6 +2214,20 @@ public class WindowManagerService extends IWindowManager.Stub == PackageManager.PERMISSION_GRANTED; } + /** + * Returns whether this window can proceed with drawing or needs to retry later. + */ + public boolean cancelDraw(Session session, IWindow client) { + synchronized (mGlobalLock) { + final WindowState win = windowForClientLocked(session, client, false); + if (win == null) { + return false; + } + + return win.cancelAndRedraw(); + } + } + public int relayoutWindow(Session session, IWindow client, LayoutParams attrs, int requestedWidth, int requestedHeight, int viewVisibility, int flags, ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration, @@ -2221,6 +2244,11 @@ public class WindowManagerService extends IWindowManager.Stub if (win == null) { return 0; } + + if (win.cancelAndRedraw()) { + result |= RELAYOUT_RES_CANCEL_AND_REDRAW; + } + final DisplayContent displayContent = win.getDisplayContent(); final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy(); @@ -2523,6 +2551,11 @@ public class WindowManagerService extends IWindowManager.Stub win.mLastSeqIdSentToRelayout = win.mSyncSeqId; outSyncIdBundle.putInt("seqid", win.mSyncSeqId); + // Only mark mAlreadyRequestedSync if there's an explicit sync request, and not if + // we're syncing due to mDrawHandlers + if (win.mSyncState != SYNC_STATE_NONE) { + win.mAlreadyRequestedSync = true; + } } else { outSyncIdBundle.putInt("seqid", -1); } @@ -2635,29 +2668,6 @@ public class WindowManagerService extends IWindowManager.Stub return result; } - int updateViewVisibility(Session session, IWindow client, LayoutParams attrs, - int viewVisibility, MergedConfiguration outMergedConfiguration, - SurfaceControl outSurfaceControl, InsetsState outInsetsState, - InsetsSourceControl[] outActiveControls) { - // TODO(b/161810301): Finish the implementation. - return 0; - } - - void updateWindowLayout(Session session, IWindow client, LayoutParams attrs, int flags, - ClientWindowFrames clientWindowFrames, int requestedWidth, int requestedHeight) { - final long origId = Binder.clearCallingIdentity(); - synchronized (mGlobalLock) { - final WindowState win = windowForClientLocked(session, client, false); - if (win == null) { - return; - } - win.setFrames(clientWindowFrames, requestedWidth, requestedHeight); - - // TODO(b/161810301): Finish the implementation. - } - Binder.restoreCallingIdentity(origId); - } - public boolean outOfMemoryWindow(Session session, IWindow client) { final long origId = Binder.clearCallingIdentity(); @@ -8762,6 +8772,41 @@ public class WindowManagerService extends IWindowManager.Stub } } + /** + * Toggle active transaction tracing. + * Setting to true increases the buffer size for active debugging. + * Setting to false resets the buffer size and dumps the trace to file. + */ + public void setActiveTransactionTracing(boolean active) { + if (!checkCallingPermission( + android.Manifest.permission.DUMP, "setActiveTransactionTracing()")) { + throw new SecurityException("Requires DUMP permission"); + } + + final long token = Binder.clearCallingIdentity(); + try { + Parcel data = null; + try { + IBinder sf = ServiceManager.getService("SurfaceFlinger"); + if (sf != null) { + data = Parcel.obtain(); + data.writeInterfaceToken("android.ui.ISurfaceComposer"); + data.writeInt(active ? 1 : 0); + sf.transact(/* TRANSACTION_TRACE_CONTROL_CODE */ 1041, data, + null, 0 /* flags */); + } + } catch (RemoteException e) { + Slog.e(TAG, "Failed to set transaction tracing"); + } finally { + if (data != null) { + data.recycle(); + } + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + @Override public boolean mirrorDisplay(int displayId, SurfaceControl outSurfaceControl) { if (!checkCallingPermission(READ_FRAME_BUFFER, "mirrorDisplay()")) { diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java index 02f056cd33af..ff43a96d6afc 100644 --- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java +++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java @@ -1370,9 +1370,10 @@ public class WindowManagerShellCommand extends ShellCommand { pw.println(" be ignored and framework implementation will determine aspect ratio."); pw.println(" --minAspectRatioForUnresizable aspectRatio"); pw.println(" Default min aspect ratio for unresizable apps which is used when an"); - pw.println(" app doesn't specify android:minAspectRatio. An exception will be"); - pw.println(" thrown if aspectRatio < " - + LetterboxConfiguration.MIN_UNRESIZABLE_ASPECT_RATIO); + pw.println(" app is eligible for the size compat mode. If aspectRatio <= " + + LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO); + pw.println(" both it and R.dimen.config_fixedOrientationLetterboxAspectRatio will"); + pw.println(" be ignored and framework implementation will determine aspect ratio."); pw.println(" --cornerRadius radius"); pw.println(" Corners radius for activities in the letterbox mode. If radius < 0,"); pw.println(" both it and R.integer.config_letterboxActivityCornersRadius will be"); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 74e15cf08c69..6728e63d055f 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -391,6 +391,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP */ int mSyncSeqId = 0; int mLastSeqIdSentToRelayout = 0; + boolean mAlreadyRequestedSync; /** * {@code true} when the client was still drawing for sync when the sync-set was finished or @@ -1247,7 +1248,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mSession.windowAddedLocked(); } - boolean updateGlobalScale() { + void updateGlobalScale() { if (hasCompatScale()) { if (mOverrideScale != 1f) { mGlobalScale = mToken.hasSizeCompatBounds() @@ -1257,11 +1258,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mGlobalScale = mToken.getSizeCompatScale(); } mInvGlobalScale = 1f / mGlobalScale; - return true; + return; } mGlobalScale = mInvGlobalScale = 1f; - return false; } /** @@ -1514,23 +1514,23 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final boolean dragResizingChanged = isDragResizeChanged() && !isDragResizingChangeReported(); + final boolean attachedFrameChanged = LOCAL_LAYOUT + && mLayoutAttached && getParentWindow().frameChanged(); + if (DEBUG) { Slog.v(TAG_WM, "Resizing " + this + ": configChanged=" + configChanged + " dragResizingChanged=" + dragResizingChanged + " last=" + mWindowFrames.mLastFrame + " frame=" + mWindowFrames.mFrame); } - // We update mLastFrame always rather than in the conditional with the last inset - // variables, because mFrameSizeChanged only tracks the width and height changing. - updateLastFrames(); - // Add a window that is using blastSync to the resizing list if it hasn't been reported // already. This because the window is waiting on a finishDrawing from the client. if (didFrameInsetsChange || configChanged || insetsChanged || dragResizingChanged - || shouldSendRedrawForSync()) { + || shouldSendRedrawForSync() + || attachedFrameChanged) { ProtoLog.v(WM_DEBUG_RESIZE, "Resize reasons for w=%s: %s configChanged=%b dragResizingChanged=%b", this, mWindowFrames.getInsetsChangedInfo(), @@ -1586,6 +1586,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } } + private boolean frameChanged() { + return !mWindowFrames.mFrame.equals(mWindowFrames.mLastFrame); + } + boolean getOrientationChanging() { // In addition to the local state flag, we must also consider the difference in the last // reported configuration vs. the current state. If the client code has not been informed of @@ -3837,6 +3841,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (mInvGlobalScale != 1.0f && hasCompatScale()) { outFrames.displayFrame.scale(mInvGlobalScale); } + if (mLayoutAttached) { + if (outFrames.attachedFrame == null) { + outFrames.attachedFrame = new Rect(); + } + outFrames.attachedFrame.set(getParentWindow().getCompatFrame()); + } // Note: in the cases where the window is tied to an activity, we should not send a // configuration update when the window has requested to be hidden. Doing so can lead to @@ -3888,6 +3898,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mDragResizingChangeReported = true; mWindowFrames.clearReportResizeHints(); + // We update mLastFrame always rather than in the conditional with the last inset + // variables, because mFrameSizeChanged only tracks the width and height changing. + updateLastFrames(); + final int prevRotation = mLastReportedConfiguration .getMergedConfiguration().windowConfiguration.getRotation(); fillClientWindowFramesAndConfiguration(mClientWindowFrames, mLastReportedConfiguration, @@ -4406,6 +4420,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP pw.println(prefix + "Requested visibilities: " + visibilityString); } } + + pw.println(prefix + "mAlreadyRequestedSync=" + mAlreadyRequestedSync); } @Override @@ -5933,6 +5949,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mRedrawForSyncReported) { mClientWasDrawingForSync = true; } + mAlreadyRequestedSync = false; super.finishSync(outMergedTransaction, cancel); } @@ -6199,4 +6216,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @WindowTraceLogLevel int logLevel) { dumpDebug(proto, fieldId, logLevel); } + + public boolean cancelAndRedraw() { + return mSyncState != SYNC_STATE_NONE && mAlreadyRequestedSync; + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java index 0c69067ab131..222a96d88166 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java @@ -315,8 +315,6 @@ class ActiveAdmin { public String mOrganizationId; public String mEnrollmentSpecificId; public boolean mAdminCanGrantSensorsPermissions; - public boolean mPreferentialNetworkServiceEnabled = - DevicePolicyManager.PREFERENTIAL_NETWORK_SERVICE_ENABLED_DEFAULT; public List<PreferentialNetworkServiceConfig> mPreferentialNetworkServiceConfigs = List.of(PreferentialNetworkServiceConfig.DEFAULT); @@ -848,15 +846,15 @@ class ActiveAdmin { } else if (TAG_ALWAYS_ON_VPN_LOCKDOWN.equals(tag)) { mAlwaysOnVpnLockdown = parser.getAttributeBoolean(null, ATTR_VALUE, false); } else if (TAG_PREFERENTIAL_NETWORK_SERVICE_ENABLED.equals(tag)) { - mPreferentialNetworkServiceEnabled = parser.getAttributeBoolean(null, ATTR_VALUE, + boolean preferentialNetworkServiceEnabled = parser.getAttributeBoolean(null, + ATTR_VALUE, DevicePolicyManager.PREFERENTIAL_NETWORK_SERVICE_ENABLED_DEFAULT); - if (mPreferentialNetworkServiceEnabled) { + if (preferentialNetworkServiceEnabled) { PreferentialNetworkServiceConfig.Builder configBuilder = new PreferentialNetworkServiceConfig.Builder(); - configBuilder.setEnabled(mPreferentialNetworkServiceEnabled); + configBuilder.setEnabled(preferentialNetworkServiceEnabled); configBuilder.setNetworkId(NET_ENTERPRISE_ID_1); mPreferentialNetworkServiceConfigs = List.of(configBuilder.build()); - mPreferentialNetworkServiceEnabled = false; } } else if (TAG_COMMON_CRITERIA_MODE.equals(tag)) { mCommonCriteriaMode = parser.getAttributeBoolean(null, ATTR_VALUE, false); @@ -1274,9 +1272,6 @@ class ActiveAdmin { pw.print("mAlwaysOnVpnLockdown="); pw.println(mAlwaysOnVpnLockdown); - pw.print("mPreferentialNetworkServiceEnabled="); - pw.println(mPreferentialNetworkServiceEnabled); - pw.print("mCommonCriteriaMode="); pw.println(mCommonCriteriaMode); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 76da21824494..3402262e34dd 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -409,7 +409,7 @@ public final class SystemServer implements Dumpable { "/apex/com.android.uwb/javalib/service-uwb.jar"; private static final String UWB_SERVICE_CLASS = "com.android.server.uwb.UwbService"; private static final String BLUETOOTH_APEX_SERVICE_JAR_PATH = - "/apex/com.android.bluetooth/javalib/service-bluetooth.jar"; + "/apex/com.android.btservices/javalib/service-bluetooth.jar"; private static final String BLUETOOTH_SERVICE_CLASS = "com.android.server.bluetooth.BluetoothService"; private static final String SAFETY_CENTER_SERVICE_CLASS = diff --git a/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java b/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java index 5db8867658be..de27d773504e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java @@ -204,7 +204,7 @@ public class SettingsToPropertiesMapperTest { @Test public void testUpdatePropertiesFromSettings_PropertyAndSettingNotPresent() { - // Test that empty property will not not be set if setting is not set + // Test that empty property will not be set if setting is not set mTestMapper.updatePropertiesFromSettings(); String propValue = mSystemSettingsMap.get("TestProperty"); Assert.assertNull("Property should not be set if setting is null", propValue); diff --git a/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java index b36aa0617be5..e87dd4b423b2 100644 --- a/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java @@ -36,8 +36,6 @@ import android.util.Log; import androidx.test.InstrumentationRegistry; -import com.android.server.FgThread; - import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -48,6 +46,11 @@ import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; import java.io.FileReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; @@ -88,6 +91,7 @@ public final class AdbDebuggingManagerTest { private long mOriginalAllowedConnectionTime; private File mAdbKeyXmlFile; private File mAdbKeyFile; + private FakeTicker mFakeTicker; @Before public void setUp() throws Exception { @@ -96,14 +100,25 @@ public final class AdbDebuggingManagerTest { if (mAdbKeyFile.exists()) { mAdbKeyFile.delete(); } - mManager = new AdbDebuggingManager(mContext, ADB_CONFIRM_COMPONENT, mAdbKeyFile); mAdbKeyXmlFile = new File(mContext.getFilesDir(), "test_adb_keys.xml"); if (mAdbKeyXmlFile.exists()) { mAdbKeyXmlFile.delete(); } + + mFakeTicker = new FakeTicker(); + // Set the ticker time to October 22, 2008 (the day the T-Mobile G1 was released) + mFakeTicker.advance(1224658800L); + mThread = new AdbDebuggingThreadTest(); - mKeyStore = mManager.new AdbKeyStore(mAdbKeyXmlFile); - mHandler = mManager.new AdbDebuggingHandler(FgThread.get().getLooper(), mThread, mKeyStore); + mManager = new AdbDebuggingManager( + mContext, ADB_CONFIRM_COMPONENT, mAdbKeyFile, mAdbKeyXmlFile, mThread, mFakeTicker); + + mHandler = mManager.mHandler; + mThread.setHandler(mHandler); + + mHandler.initKeyStore(); + mKeyStore = mHandler.mAdbKeyStore; + mOriginalAllowedConnectionTime = mKeyStore.getAllowedConnectionTime(); mBlockingQueue = new ArrayBlockingQueue<>(1); } @@ -122,7 +137,7 @@ public final class AdbDebuggingManagerTest { private void setAllowedConnectionTime(long connectionTime) { Settings.Global.putLong(mContext.getContentResolver(), Settings.Global.ADB_ALLOWED_CONNECTION_TIME, connectionTime); - }; + } @Test public void testAllowNewKeyOnce() throws Exception { @@ -158,20 +173,15 @@ public final class AdbDebuggingManagerTest { // Allow a connection from a new key with the 'Always allow' option selected. runAdbTest(TEST_KEY_1, true, true, false); - // Get the last connection time for the currently connected key to verify that it is updated - // after the disconnect. - long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1); - - // Sleep for a small amount of time to ensure a difference can be observed in the last - // connection time after a disconnect. - Thread.sleep(10); + // Advance the clock by 10ms to ensure there's a difference + mFakeTicker.advance(10 * 1_000_000); // Send the disconnect message for the currently connected key to trigger an update of the // last connection time. disconnectKey(TEST_KEY_1); - assertNotEquals( + assertEquals( "The last connection time was not updated after the disconnect", - lastConnectionTime, + mFakeTicker.currentTimeMillis(), mKeyStore.getLastConnectionTime(TEST_KEY_1)); } @@ -244,8 +254,8 @@ public final class AdbDebuggingManagerTest { // Get the current last connection time for comparison after the scheduled job is run long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1); - // Sleep a small amount of time to ensure that the updated connection time changes - Thread.sleep(10); + // Advance a small amount of time to ensure that the updated connection time changes + mFakeTicker.advance(10); // Send a message to the handler to update the last connection time for the active key updateKeyStore(); @@ -269,13 +279,13 @@ public final class AdbDebuggingManagerTest { persistKeyStore(); assertTrue( "The key with the 'Always allow' option selected was not persisted in the keystore", - mManager.new AdbKeyStore(mAdbKeyXmlFile).isKeyAuthorized(TEST_KEY_1)); + mManager.new AdbKeyStore().isKeyAuthorized(TEST_KEY_1)); // Get the current last connection time to ensure it is updated in the persisted keystore. long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1); - // Sleep a small amount of time to ensure the last connection time is updated. - Thread.sleep(10); + // Advance a small amount of time to ensure the last connection time is updated. + mFakeTicker.advance(10); // Send a message to the handler to update the last connection time for the active key. updateKeyStore(); @@ -286,7 +296,7 @@ public final class AdbDebuggingManagerTest { assertNotEquals( "The last connection time in the key file was not updated after the update " + "connection time message", lastConnectionTime, - mManager.new AdbKeyStore(mAdbKeyXmlFile).getLastConnectionTime(TEST_KEY_1)); + mManager.new AdbKeyStore().getLastConnectionTime(TEST_KEY_1)); // Verify that the key is in the adb_keys file assertTrue("The key was not in the adb_keys file after persisting the keystore", isKeyInFile(TEST_KEY_1, mAdbKeyFile)); @@ -327,8 +337,8 @@ public final class AdbDebuggingManagerTest { // Set the allowed window to a small value to ensure the time is beyond the allowed window. setAllowedConnectionTime(1); - // Sleep for a small amount of time to exceed the allowed window. - Thread.sleep(10); + // Advance a small amount of time to exceed the allowed window. + mFakeTicker.advance(10); // The AdbKeyStore has a method to get the time of the next key expiration to ensure the // scheduled job runs at the time of the next expiration or after 24 hours, whichever occurs @@ -478,9 +488,12 @@ public final class AdbDebuggingManagerTest { // Set the current expiration time to a minute from expiration and verify this new value is // returned. final long newExpirationTime = 60000; - mKeyStore.setLastConnectionTime(TEST_KEY_1, - System.currentTimeMillis() - Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME - + newExpirationTime, true); + mKeyStore.setLastConnectionTime( + TEST_KEY_1, + mFakeTicker.currentTimeMillis() + - Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME + + newExpirationTime, + true); expirationTime = mKeyStore.getNextExpirationTime(); if (Math.abs(expirationTime - newExpirationTime) > epsilon) { fail("The expiration time for a key about to expire, " + expirationTime @@ -525,7 +538,7 @@ public final class AdbDebuggingManagerTest { // Get the last connection time for the key to verify that it is updated when the connected // key message is sent. long connectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1); - Thread.sleep(10); + mFakeTicker.advance(10); mHandler.obtainMessage(AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_CONNECTED_KEY, TEST_KEY_1).sendToTarget(); flushHandlerQueue(); @@ -536,7 +549,7 @@ public final class AdbDebuggingManagerTest { // Verify that the scheduled job updates the connection time of the key. connectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1); - Thread.sleep(10); + mFakeTicker.advance(10); updateKeyStore(); assertNotEquals( "The connection time for the key must be updated when the update keystore message" @@ -545,7 +558,7 @@ public final class AdbDebuggingManagerTest { // Verify that the connection time is updated when the key is disconnected. connectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1); - Thread.sleep(10); + mFakeTicker.advance(10); disconnectKey(TEST_KEY_1); assertNotEquals( "The connection time for the key must be updated when the disconnected message is" @@ -628,11 +641,11 @@ public final class AdbDebuggingManagerTest { setAllowedConnectionTime(Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME); // The untracked keys should be added to the keystore as part of the constructor. - AdbDebuggingManager.AdbKeyStore adbKeyStore = mManager.new AdbKeyStore(mAdbKeyXmlFile); + AdbDebuggingManager.AdbKeyStore adbKeyStore = mManager.new AdbKeyStore(); // Verify that the connection time for each test key is within a small value of the current // time. - long time = System.currentTimeMillis(); + long time = mFakeTicker.currentTimeMillis(); for (String key : testKeys) { long connectionTime = adbKeyStore.getLastConnectionTime(key); if (Math.abs(time - connectionTime) > epsilon) { @@ -651,11 +664,11 @@ public final class AdbDebuggingManagerTest { runAdbTest(TEST_KEY_1, true, true, false); runAdbTest(TEST_KEY_2, true, true, false); - // Sleep a small amount of time to ensure the connection time is updated by the scheduled + // Advance a small amount of time to ensure the connection time is updated by the scheduled // job. long connectionTime1 = mKeyStore.getLastConnectionTime(TEST_KEY_1); long connectionTime2 = mKeyStore.getLastConnectionTime(TEST_KEY_2); - Thread.sleep(10); + mFakeTicker.advance(10); updateKeyStore(); assertNotEquals( "The connection time for test key 1 must be updated after the scheduled job runs", @@ -669,7 +682,7 @@ public final class AdbDebuggingManagerTest { disconnectKey(TEST_KEY_2); connectionTime1 = mKeyStore.getLastConnectionTime(TEST_KEY_1); connectionTime2 = mKeyStore.getLastConnectionTime(TEST_KEY_2); - Thread.sleep(10); + mFakeTicker.advance(10); updateKeyStore(); assertNotEquals( "The connection time for test key 1 must be updated after another key is " @@ -686,8 +699,6 @@ public final class AdbDebuggingManagerTest { // to clear the adb authorizations when adb is disabled after a boot a NullPointerException // was thrown as deleteKeyStore is invoked against the key store. This test ensures the // key store can be successfully cleared when adb is disabled. - mHandler = mManager.new AdbDebuggingHandler(FgThread.get().getLooper()); - clearKeyStore(); } @@ -723,6 +734,9 @@ public final class AdbDebuggingManagerTest { // Now remove one of the keys and make sure the other key is still there mKeyStore.removeKey(TEST_KEY_1); + // Wait for the handler queue to receive the MESSAGE_ADB_PERSIST_KEYSTORE + flushHandlerQueue(); + assertFalse("The key was still in the adb_keys file after removing the key", isKeyInFile(TEST_KEY_1, mAdbKeyFile)); assertTrue("The key was not in the adb_keys file after removing a different key", @@ -730,6 +744,95 @@ public final class AdbDebuggingManagerTest { } @Test + public void testAdbKeyStore_addDuplicateKey_doesNotAddDuplicateToAdbKeyFile() throws Exception { + setAllowedConnectionTime(0); + + runAdbTest(TEST_KEY_1, true, true, false); + persistKeyStore(); + runAdbTest(TEST_KEY_1, true, true, false); + persistKeyStore(); + + assertEquals("adb_keys contains duplicate keys", 1, adbKeyFileKeys(mAdbKeyFile).size()); + } + + @Test + public void testAdbKeyStore_adbTempKeysFile_readsLastConnectionTimeFromXml() throws Exception { + long insertTime = mFakeTicker.currentTimeMillis(); + runAdbTest(TEST_KEY_1, true, true, false); + persistKeyStore(); + + mFakeTicker.advance(10); + AdbDebuggingManager.AdbKeyStore newKeyStore = mManager.new AdbKeyStore(); + + assertEquals( + "KeyStore not populated from the XML file.", + insertTime, + newKeyStore.getLastConnectionTime(TEST_KEY_1)); + } + + @Test + public void test_notifyKeyFilesUpdated_filesDeletedRemovesPreviouslyAddedKey() + throws Exception { + runAdbTest(TEST_KEY_1, true, true, false); + persistKeyStore(); + + Files.delete(mAdbKeyXmlFile.toPath()); + Files.delete(mAdbKeyFile.toPath()); + + mManager.notifyKeyFilesUpdated(); + flushHandlerQueue(); + + assertFalse( + "Key is authorized after reloading deleted key files. Was state preserved?", + mKeyStore.isKeyAuthorized(TEST_KEY_1)); + } + + @Test + public void test_notifyKeyFilesUpdated_newKeyIsAuthorized() throws Exception { + runAdbTest(TEST_KEY_1, true, true, false); + persistKeyStore(); + + // Back up the existing key files + Path tempXmlFile = Files.createTempFile("adbKeyXmlFile", ".tmp"); + Path tempAdbKeysFile = Files.createTempFile("adb_keys", ".tmp"); + Files.copy(mAdbKeyXmlFile.toPath(), tempXmlFile, StandardCopyOption.REPLACE_EXISTING); + Files.copy(mAdbKeyFile.toPath(), tempAdbKeysFile, StandardCopyOption.REPLACE_EXISTING); + + // Delete the existing key files + Files.delete(mAdbKeyXmlFile.toPath()); + Files.delete(mAdbKeyFile.toPath()); + + // Notify the manager that adb key files have changed. + mManager.notifyKeyFilesUpdated(); + flushHandlerQueue(); + + // Copy the files back + Files.copy(tempXmlFile, mAdbKeyXmlFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + Files.copy(tempAdbKeysFile, mAdbKeyFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + + // Tell the manager that the key files have changed. + mManager.notifyKeyFilesUpdated(); + flushHandlerQueue(); + + assertTrue( + "Key is not authorized after reloading key files.", + mKeyStore.isKeyAuthorized(TEST_KEY_1)); + } + + @Test + public void testAdbKeyStore_adbWifiConnect_storesBssidWhenAlwaysAllow() throws Exception { + String trustedNetwork = "My Network"; + mKeyStore.addTrustedNetwork(trustedNetwork); + persistKeyStore(); + + AdbDebuggingManager.AdbKeyStore newKeyStore = mManager.new AdbKeyStore(); + + assertTrue( + "Persisted trusted network not found in new keystore instance.", + newKeyStore.isTrustedNetwork(trustedNetwork)); + } + + @Test public void testIsValidMdnsServiceName() { // Longer than 15 characters assertFalse(isValidMdnsServiceName("abcd1234abcd1234")); @@ -1030,28 +1133,27 @@ public final class AdbDebuggingManagerTest { if (key == null) { return false; } + return adbKeyFileKeys(keyFile).contains(key); + } + + private static List<String> adbKeyFileKeys(File keyFile) throws Exception { + List<String> keys = new ArrayList<>(); if (keyFile.exists()) { try (BufferedReader in = new BufferedReader(new FileReader(keyFile))) { String currKey; while ((currKey = in.readLine()) != null) { - if (key.equals(currKey)) { - return true; - } + keys.add(currKey); } } } - return false; + return keys; } /** * Helper class that extends AdbDebuggingThread to receive the response from AdbDebuggingManager * indicating whether the key should be allowed to connect. */ - class AdbDebuggingThreadTest extends AdbDebuggingManager.AdbDebuggingThread { - AdbDebuggingThreadTest() { - mManager.super(); - } - + private class AdbDebuggingThreadTest extends AdbDebuggingManager.AdbDebuggingThread { @Override public void sendResponse(String msg) { TestResult result = new TestResult(TestResult.RESULT_RESPONSE_RECEIVED, msg); @@ -1091,4 +1193,17 @@ public final class AdbDebuggingManagerTest { return "{mReturnCode = " + mReturnCode + ", mMessage = " + mMessage + "}"; } } + + private static class FakeTicker implements AdbDebuggingManager.Ticker { + private long mCurrentTime; + + private void advance(long milliseconds) { + mCurrentTime += milliseconds; + } + + @Override + public long currentTimeMillis() { + return mCurrentTime; + } + } } diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index f242fda15f06..c80547ce61c0 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -213,7 +213,7 @@ public class VirtualDeviceManagerServiceTest { mContext.getSystemService(WindowManager.class), threadVerifier); mAssociationInfo = new AssociationInfo(1, 0, null, - MacAddress.BROADCAST_ADDRESS, "", null, true, false, 0, 0); + MacAddress.BROADCAST_ADDRESS, "", null, true, false, false, 0, 0); VirtualDeviceParams params = new VirtualDeviceParams .Builder() diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java index 0ed90d27db99..6a6cd6c914a2 100644 --- a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java @@ -16,13 +16,11 @@ package com.android.server.display; -import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.any; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -34,17 +32,18 @@ import android.os.IThermalEventListener; import android.os.IThermalService; import android.os.Message; import android.os.PowerManager; -import android.os.Temperature.ThrottlingStatus; import android.os.Temperature; +import android.os.Temperature.ThrottlingStatus; import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.os.BackgroundThread; import com.android.server.display.BrightnessThrottler.Injector; -import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel; import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData; +import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel; import org.junit.Before; import org.junit.Test; @@ -55,7 +54,6 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; @SmallTest @@ -70,6 +68,8 @@ public class BrightnessThrottlerTest { @Mock IThermalService mThermalServiceMock; @Mock Injector mInjectorMock; + DisplayModeDirectorTest.FakeDeviceConfig mDeviceConfigFake; + @Captor ArgumentCaptor<IThermalEventListener> mThermalEventListenerCaptor; @Before @@ -83,6 +83,8 @@ public class BrightnessThrottlerTest { return true; } }); + mDeviceConfigFake = new DisplayModeDirectorTest.FakeDeviceConfig(); + when(mInjectorMock.getDeviceConfig()).thenReturn(mDeviceConfigFake); } @@ -292,6 +294,170 @@ public class BrightnessThrottlerTest { assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason()); } + @Test public void testUpdateThrottlingData() throws Exception { + // Initialise brightness throttling levels + // Ensure that they are overridden by setting the data through device config. + final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, + 0.25f); + List<ThrottlingLevel> levels = new ArrayList<>(); + levels.add(level); + final BrightnessThrottlingData data = BrightnessThrottlingData.create(levels); + mDeviceConfigFake.setBrightnessThrottlingData("123,1,critical,0.4"); + final BrightnessThrottler throttler = createThrottlerSupported(data); + + verify(mThermalServiceMock).registerThermalEventListenerWithType( + mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN)); + final IThermalEventListener listener = mThermalEventListenerCaptor.getValue(); + + // Set status too low to trigger throttling + listener.notifyThrottling(getSkinTemp(level.thermalStatus - 1)); + mTestLooper.dispatchAll(); + assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f); + assertFalse(throttler.isThrottled()); + + // Set status high enough to trigger throttling + listener.notifyThrottling(getSkinTemp(level.thermalStatus)); + mTestLooper.dispatchAll(); + assertEquals(0.4f, throttler.getBrightnessCap(), 0f); + assertTrue(throttler.isThrottled()); + + // Update thresholds + // This data is equivalent to the string "123,1,critical,0.8", passed below + final ThrottlingLevel newLevel = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, + 0.8f); + // Set new (valid) data from device config + mDeviceConfigFake.setBrightnessThrottlingData("123,1,critical,0.8"); + + // Set status too low to trigger throttling + listener.notifyThrottling(getSkinTemp(newLevel.thermalStatus - 1)); + mTestLooper.dispatchAll(); + assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f); + assertFalse(throttler.isThrottled()); + + // Set status high enough to trigger throttling + listener.notifyThrottling(getSkinTemp(newLevel.thermalStatus)); + mTestLooper.dispatchAll(); + assertEquals(newLevel.brightness, throttler.getBrightnessCap(), 0f); + assertTrue(throttler.isThrottled()); + } + + @Test public void testInvalidThrottlingStrings() throws Exception { + // Initialise brightness throttling levels + // Ensure that they are not overridden by invalid data through device config. + final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, + 0.25f); + List<ThrottlingLevel> levels = new ArrayList<>(); + levels.add(level); + final BrightnessThrottlingData data = BrightnessThrottlingData.create(levels); + final BrightnessThrottler throttler = createThrottlerSupported(data); + verify(mThermalServiceMock).registerThermalEventListenerWithType( + mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN)); + final IThermalEventListener listener = mThermalEventListenerCaptor.getValue(); + + // None of these are valid so shouldn't override the original data + mDeviceConfigFake.setBrightnessThrottlingData("321,1,critical,0.4"); // Not the current id + testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f); + mDeviceConfigFake.setBrightnessThrottlingData("123,0,critical,0.4"); // Incorrect number + testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f); + mDeviceConfigFake.setBrightnessThrottlingData("123,2,critical,0.4"); // Incorrect number + testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f); + mDeviceConfigFake.setBrightnessThrottlingData("123,1,invalid,0.4"); // Invalid level + testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f); + mDeviceConfigFake.setBrightnessThrottlingData("123,1,critical,none"); // Invalid brightness + testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f); + mDeviceConfigFake.setBrightnessThrottlingData("123,1,critical,-3"); // Invalid brightness + testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f); + mDeviceConfigFake.setBrightnessThrottlingData("invalid string"); // Invalid format + testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f); + mDeviceConfigFake.setBrightnessThrottlingData(""); // Invalid format + testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f); + } + + private void testThrottling(BrightnessThrottler throttler, IThermalEventListener listener, + float tooLowCap, float tooHighCap) throws Exception { + final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, + tooHighCap); + + // Set status too low to trigger throttling + listener.notifyThrottling(getSkinTemp(level.thermalStatus - 1)); + mTestLooper.dispatchAll(); + assertEquals(tooLowCap, throttler.getBrightnessCap(), 0f); + assertFalse(throttler.isThrottled()); + + // Set status high enough to trigger throttling + listener.notifyThrottling(getSkinTemp(level.thermalStatus)); + mTestLooper.dispatchAll(); + assertEquals(tooHighCap, throttler.getBrightnessCap(), 0f); + assertTrue(throttler.isThrottled()); + } + + @Test public void testMultipleConfigPoints() throws Exception { + // Initialise brightness throttling levels + final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, + 0.25f); + List<ThrottlingLevel> levels = new ArrayList<>(); + levels.add(level); + final BrightnessThrottlingData data = BrightnessThrottlingData.create(levels); + + // These are identical to the string set below + final ThrottlingLevel levelSevere = new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, + 0.9f); + final ThrottlingLevel levelCritical = new ThrottlingLevel( + PowerManager.THERMAL_STATUS_CRITICAL, 0.5f); + final ThrottlingLevel levelEmergency = new ThrottlingLevel( + PowerManager.THERMAL_STATUS_EMERGENCY, 0.1f); + + mDeviceConfigFake.setBrightnessThrottlingData( + "123,3,severe,0.9,critical,0.5,emergency,0.1"); + final BrightnessThrottler throttler = createThrottlerSupported(data); + + verify(mThermalServiceMock).registerThermalEventListenerWithType( + mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN)); + final IThermalEventListener listener = mThermalEventListenerCaptor.getValue(); + + // Ensure that the multiple levels set via the string through the device config correctly + // override the original display device config ones. + + // levelSevere + // Set status too low to trigger throttling + listener.notifyThrottling(getSkinTemp(levelSevere.thermalStatus - 1)); + mTestLooper.dispatchAll(); + assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f); + assertFalse(throttler.isThrottled()); + + // Set status high enough to trigger throttling + listener.notifyThrottling(getSkinTemp(levelSevere.thermalStatus)); + mTestLooper.dispatchAll(); + assertEquals(0.9f, throttler.getBrightnessCap(), 0f); + assertTrue(throttler.isThrottled()); + + // levelCritical + // Set status too low to trigger throttling + listener.notifyThrottling(getSkinTemp(levelCritical.thermalStatus - 1)); + mTestLooper.dispatchAll(); + assertEquals(0.9f, throttler.getBrightnessCap(), 0f); + assertTrue(throttler.isThrottled()); + + // Set status high enough to trigger throttling + listener.notifyThrottling(getSkinTemp(levelCritical.thermalStatus)); + mTestLooper.dispatchAll(); + assertEquals(0.5f, throttler.getBrightnessCap(), 0f); + assertTrue(throttler.isThrottled()); + + //levelEmergency + // Set status too low to trigger throttling + listener.notifyThrottling(getSkinTemp(levelEmergency.thermalStatus - 1)); + mTestLooper.dispatchAll(); + assertEquals(0.5f, throttler.getBrightnessCap(), 0f); + assertTrue(throttler.isThrottled()); + + // Set status high enough to trigger throttling + listener.notifyThrottling(getSkinTemp(levelEmergency.thermalStatus)); + mTestLooper.dispatchAll(); + assertEquals(0.1f, throttler.getBrightnessCap(), 0f); + assertTrue(throttler.isThrottled()); + } + private void assertThrottlingLevelsEquals( List<ThrottlingLevel> expected, List<ThrottlingLevel> actual) { @@ -307,12 +473,13 @@ public class BrightnessThrottlerTest { } private BrightnessThrottler createThrottlerUnsupported() { - return new BrightnessThrottler(mInjectorMock, mHandler, null, () -> {}); + return new BrightnessThrottler(mInjectorMock, mHandler, mHandler, null, () -> {}, null); } private BrightnessThrottler createThrottlerSupported(BrightnessThrottlingData data) { assertNotNull(data); - return new BrightnessThrottler(mInjectorMock, mHandler, data, () -> {}); + return new BrightnessThrottler(mInjectorMock, mHandler, BackgroundThread.getHandler(), + data, () -> {}, "123"); } private Temperature getSkinTemp(@ThrottlingStatus int status) { 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 864f31552ae3..968e1d8c546b 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java @@ -16,6 +16,7 @@ package com.android.server.display; +import static android.hardware.display.DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA; import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS; import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS; import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS; @@ -1902,6 +1903,11 @@ public class DisplayModeDirectorTest { KEY_REFRESH_RATE_IN_HBM_HDR, String.valueOf(fps)); } + void setBrightnessThrottlingData(String brightnessThrottlingData) { + putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + KEY_BRIGHTNESS_THROTTLING_DATA, brightnessThrottlingData); + } + void setLowDisplayBrightnessThresholds(int[] brightnessThresholds) { String thresholds = toPropertyValue(brightnessThresholds); diff --git a/services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java b/services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java index 363c26b63bae..bbed1b60f8bf 100644 --- a/services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java @@ -16,11 +16,14 @@ package com.android.server.display.color; +import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -1130,6 +1133,15 @@ public class ColorDisplayServiceTest { eq(ColorDisplayManager.COLOR_MODE_BOOSTED), any(), eq(Display.COLOR_MODE_INVALID)); } + @Test + public void getColorMode_noAvailableModes_returnsNotSet() { + when(mResourcesSpy.getIntArray(R.array.config_availableColorModes)) + .thenReturn(new int[] {}); + startService(); + verify(mDisplayTransformManager, never()).setColorMode(anyInt(), any(), anyInt()); + assertThat(mBinderService.getColorMode()).isEqualTo(-1); + } + /** * Configures Night display to use a custom schedule. * diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java index e06877f9144e..8ff87e35f933 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java @@ -212,9 +212,7 @@ public abstract class BaseAbsoluteVolumeControlTest { } protected int getLogicalAddress() { - synchronized (mHdmiCecLocalDevice.mLock) { - return mHdmiCecLocalDevice.getDeviceInfo().getLogicalAddress(); - } + return mHdmiCecLocalDevice.getDeviceInfo().getLogicalAddress(); } /** diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java index d7fef90456ab..eb7a76182054 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java @@ -147,10 +147,9 @@ public class DeviceSelectActionFromPlaybackTest { // The addresses depend on local device's LA. // This help the tests to pass with every local device LA. - synchronized (mHdmiCecLocalDevicePlayback.mLock) { - mPlaybackLogicalAddress1 = - mHdmiCecLocalDevicePlayback.getDeviceInfo().getLogicalAddress(); - } + mPlaybackLogicalAddress1 = + mHdmiCecLocalDevicePlayback.getDeviceInfo().getLogicalAddress(); + mPlaybackLogicalAddress2 = mPlaybackLogicalAddress1 == ADDR_PLAYBACK_2 ? ADDR_PLAYBACK_1 : ADDR_PLAYBACK_2; mPlaybackLogicalAddress3 = mPlaybackLogicalAddress1 == ADDR_PLAYBACK_3 diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java index 0cba10669c85..367f41d938d3 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java @@ -132,9 +132,7 @@ public class HdmiCecControllerTest { mHdmiControlServiceSpy.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); mTestLooper.dispatchAll(); - synchronized (playbackDevice.mLock) { - mPlaybackLogicalAddress = playbackDevice.getDeviceInfo().getLogicalAddress(); - } + mPlaybackLogicalAddress = playbackDevice.getDeviceInfo().getLogicalAddress(); mTestLooper.dispatchAll(); } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java index 6266571d33d4..674e47168392 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -669,6 +669,52 @@ public class HdmiControlServiceTest { } @Test + public void initCec_statusListener_CecEnabled_CecAvailable_TvTransientToOn() { + HdmiControlStatusCallback hdmiControlStatusCallback = new HdmiControlStatusCallback(); + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED); + mTestLooper.dispatchAll(); + + mHdmiControlServiceSpy.addHdmiControlStatusChangeListener(hdmiControlStatusCallback); + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); + mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mTestLooper.dispatchAll(); + + HdmiCecMessage reportPowerStatus = + HdmiCecMessageBuilder.buildReportPowerStatus( + Constants.ADDR_TV, + mHdmiControlServiceSpy.playback().getDeviceInfo().getLogicalAddress(), + HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON); + mNativeWrapper.onCecMessage(reportPowerStatus); + mTestLooper.dispatchAll(); + + assertThat(hdmiControlStatusCallback.mCecEnabled).isTrue(); + assertThat(hdmiControlStatusCallback.mCecAvailable).isTrue(); + } + + @Test + public void initCec_statusListener_CecEnabled_CecAvailable_TvTransientToStandby() { + HdmiControlStatusCallback hdmiControlStatusCallback = new HdmiControlStatusCallback(); + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED); + mTestLooper.dispatchAll(); + + mHdmiControlServiceSpy.addHdmiControlStatusChangeListener(hdmiControlStatusCallback); + mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); + mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mTestLooper.dispatchAll(); + + HdmiCecMessage reportPowerStatus = + HdmiCecMessageBuilder.buildReportPowerStatus( + Constants.ADDR_TV, + mHdmiControlServiceSpy.playback().getDeviceInfo().getLogicalAddress(), + HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY); + mNativeWrapper.onCecMessage(reportPowerStatus); + mTestLooper.dispatchAll(); + + assertThat(hdmiControlStatusCallback.mCecEnabled).isTrue(); + assertThat(hdmiControlStatusCallback.mCecAvailable).isTrue(); + } + + @Test public void handleCecCommand_errorParameter_returnsAbortInvalidOperand() { // Validity ERROR_PARAMETER. Taken from HdmiCecMessageValidatorTest#isValid_menuStatus HdmiCecMessage message = HdmiUtils.buildMessage("80:8D:03"); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java index 3228e82b566b..8b314cd88878 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java @@ -132,9 +132,7 @@ public class RequestSadActionTest { mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mNativeWrapper.setPhysicalAddress(0x0000); mTestLooper.dispatchAll(); - synchronized (mHdmiCecLocalDeviceTv.mLock) { - mTvLogicalAddress = mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(); - } + mTvLogicalAddress = mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress(); mNativeWrapper.clearResultMessages(); } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java index 087e407e314c..dadf81571e30 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java @@ -112,9 +112,7 @@ public class SetAudioVolumeLevelDiscoveryActionTest { mHdmiControlServiceSpy.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); mTestLooper.dispatchAll(); - synchronized (mPlaybackDevice.mLock) { - mPlaybackLogicalAddress = mPlaybackDevice.getDeviceInfo().getLogicalAddress(); - } + mPlaybackLogicalAddress = mPlaybackDevice.getDeviceInfo().getLogicalAddress(); // Setup specific to these tests mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java index 6f1268e5de24..cc6f2cc5ba3e 100644 --- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java @@ -275,7 +275,7 @@ public class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - InputMethodUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypesLocked( getResourcesForLocales(LOCALE_EN_US), imi); assertEquals(1, result.size()); verifyEquality(autoSubtype, result.get(0)); @@ -299,7 +299,7 @@ public class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - InputMethodUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypesLocked( getResourcesForLocales(LOCALE_EN_US), imi); assertEquals(2, result.size()); verifyEquality(nonAutoEnUS, result.get(0)); @@ -323,7 +323,7 @@ public class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - InputMethodUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypesLocked( getResourcesForLocales(LOCALE_EN_GB), imi); assertEquals(2, result.size()); verifyEquality(nonAutoEnGB, result.get(0)); @@ -348,7 +348,7 @@ public class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - InputMethodUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypesLocked( getResourcesForLocales(LOCALE_FR), imi); assertEquals(2, result.size()); verifyEquality(nonAutoFrCA, result.get(0)); @@ -369,7 +369,7 @@ public class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - InputMethodUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypesLocked( getResourcesForLocales(LOCALE_FR_CA), imi); assertEquals(2, result.size()); verifyEquality(nonAutoFrCA, result.get(0)); @@ -391,7 +391,7 @@ public class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - InputMethodUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypesLocked( getResourcesForLocales(LOCALE_JA_JP), imi); assertEquals(3, result.size()); verifyEquality(nonAutoJa, result.get(0)); @@ -413,7 +413,7 @@ public class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - InputMethodUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypesLocked( getResourcesForLocales(LOCALE_JA_JP), imi); assertEquals(1, result.size()); verifyEquality(nonAutoHi, result.get(0)); @@ -430,7 +430,7 @@ public class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - InputMethodUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypesLocked( getResourcesForLocales(LOCALE_JA_JP), imi); assertEquals(1, result.size()); verifyEquality(nonAutoEnUS, result.get(0)); @@ -447,7 +447,7 @@ public class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - InputMethodUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypesLocked( getResourcesForLocales(LOCALE_JA_JP), imi); assertEquals(1, result.size()); verifyEquality(nonAutoEnUS, result.get(0)); @@ -469,7 +469,7 @@ public class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - InputMethodUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypesLocked( getResourcesForLocales(Locale.forLanguageTag("sr-Latn-RS")), imi); assertEquals(2, result.size()); assertThat(nonAutoSrLatn, is(in(result))); @@ -489,7 +489,7 @@ public class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - InputMethodUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypesLocked( getResourcesForLocales(Locale.forLanguageTag("sr-Cyrl-RS")), imi); assertEquals(2, result.size()); assertThat(nonAutoSrCyrl, is(in(result))); @@ -515,7 +515,7 @@ public class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - InputMethodUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypesLocked( getResourcesForLocales( Locale.forLanguageTag("sr-Latn-RS-x-android"), Locale.forLanguageTag("ja-JP"), @@ -542,7 +542,7 @@ public class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - InputMethodUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypesLocked( getResourcesForLocales(LOCALE_FIL_PH), imi); assertEquals(1, result.size()); verifyEquality(nonAutoFil, result.get(0)); @@ -560,7 +560,7 @@ public class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - InputMethodUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypesLocked( getResourcesForLocales(LOCALE_FI), imi); assertEquals(1, result.size()); verifyEquality(nonAutoJa, result.get(0)); @@ -576,7 +576,7 @@ public class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - InputMethodUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypesLocked( getResourcesForLocales(LOCALE_IN), imi); assertEquals(1, result.size()); verifyEquality(nonAutoIn, result.get(0)); @@ -590,7 +590,7 @@ public class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - InputMethodUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypesLocked( getResourcesForLocales(LOCALE_ID), imi); assertEquals(1, result.size()); verifyEquality(nonAutoIn, result.get(0)); @@ -604,7 +604,7 @@ public class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - InputMethodUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypesLocked( getResourcesForLocales(LOCALE_IN), imi); assertEquals(1, result.size()); verifyEquality(nonAutoId, result.get(0)); @@ -618,7 +618,7 @@ public class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - InputMethodUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypesLocked( getResourcesForLocales(LOCALE_ID), imi); assertEquals(1, result.size()); verifyEquality(nonAutoId, result.get(0)); @@ -640,7 +640,7 @@ public class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); final ArrayList<InputMethodSubtype> result = - InputMethodUtils.getImplicitlyApplicableSubtypesLocked( + SubtypeUtils.getImplicitlyApplicableSubtypesLocked( getResourcesForLocales(LOCALE_FR, LOCALE_EN_US, LOCALE_JA_JP), imi); assertThat(nonAutoFrCA, is(in(result))); assertThat(nonAutoEnUS, is(in(result))); @@ -680,26 +680,26 @@ public class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); - assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN, !CHECK_COUNTRY, + assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN, !CHECK_COUNTRY, SUBTYPE_MODE_KEYBOARD)); - assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN, CHECK_COUNTRY, + assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN, CHECK_COUNTRY, SUBTYPE_MODE_KEYBOARD)); - assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN_US, !CHECK_COUNTRY, + assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN_US, !CHECK_COUNTRY, SUBTYPE_MODE_KEYBOARD)); - assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN_US, CHECK_COUNTRY, + assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN_US, CHECK_COUNTRY, SUBTYPE_MODE_KEYBOARD)); - assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN_US, !CHECK_COUNTRY, + assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN_US, !CHECK_COUNTRY, SUBTYPE_MODE_VOICE)); - assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN_US, CHECK_COUNTRY, + assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN_US, CHECK_COUNTRY, SUBTYPE_MODE_VOICE)); - assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN_US, !CHECK_COUNTRY, + assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN_US, !CHECK_COUNTRY, SUBTYPE_MODE_ANY)); - assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN_US, CHECK_COUNTRY, + assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN_US, CHECK_COUNTRY, SUBTYPE_MODE_ANY)); - assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN_GB, !CHECK_COUNTRY, + assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN_GB, !CHECK_COUNTRY, SUBTYPE_MODE_KEYBOARD)); - assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN_GB, CHECK_COUNTRY, + assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN_GB, CHECK_COUNTRY, SUBTYPE_MODE_KEYBOARD)); } @@ -711,22 +711,22 @@ public class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); - assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL, !CHECK_COUNTRY, + assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FIL, !CHECK_COUNTRY, SUBTYPE_MODE_KEYBOARD)); - assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL, CHECK_COUNTRY, + assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FIL, CHECK_COUNTRY, SUBTYPE_MODE_KEYBOARD)); - assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL_PH, !CHECK_COUNTRY, + assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FIL_PH, !CHECK_COUNTRY, SUBTYPE_MODE_KEYBOARD)); - assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL_PH, CHECK_COUNTRY, + assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FIL_PH, CHECK_COUNTRY, SUBTYPE_MODE_KEYBOARD)); - assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FI, !CHECK_COUNTRY, + assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FI, !CHECK_COUNTRY, SUBTYPE_MODE_KEYBOARD)); - assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FI, CHECK_COUNTRY, + assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FI, CHECK_COUNTRY, SUBTYPE_MODE_KEYBOARD)); - assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FI_FI, !CHECK_COUNTRY, + assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FI_FI, !CHECK_COUNTRY, SUBTYPE_MODE_KEYBOARD)); - assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FI_FI, CHECK_COUNTRY, + assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FI_FI, CHECK_COUNTRY, SUBTYPE_MODE_KEYBOARD)); } @@ -738,22 +738,22 @@ public class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); - assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL, !CHECK_COUNTRY, + assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FIL, !CHECK_COUNTRY, SUBTYPE_MODE_KEYBOARD)); - assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL, CHECK_COUNTRY, + assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FIL, CHECK_COUNTRY, SUBTYPE_MODE_KEYBOARD)); - assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL_PH, !CHECK_COUNTRY, + assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FIL_PH, !CHECK_COUNTRY, SUBTYPE_MODE_KEYBOARD)); - assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL_PH, CHECK_COUNTRY, + assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FIL_PH, CHECK_COUNTRY, SUBTYPE_MODE_KEYBOARD)); - assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FI, !CHECK_COUNTRY, + assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FI, !CHECK_COUNTRY, SUBTYPE_MODE_KEYBOARD)); - assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FI, CHECK_COUNTRY, + assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FI, CHECK_COUNTRY, SUBTYPE_MODE_KEYBOARD)); - assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FI_FI, !CHECK_COUNTRY, + assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FI_FI, !CHECK_COUNTRY, SUBTYPE_MODE_KEYBOARD)); - assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FI_FI, CHECK_COUNTRY, + assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FI_FI, CHECK_COUNTRY, SUBTYPE_MODE_KEYBOARD)); } @@ -766,13 +766,13 @@ public class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); - assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_IN, !CHECK_COUNTRY, + assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_IN, !CHECK_COUNTRY, SUBTYPE_MODE_KEYBOARD)); - assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_IN, CHECK_COUNTRY, + assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_IN, CHECK_COUNTRY, SUBTYPE_MODE_KEYBOARD)); - assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_ID, !CHECK_COUNTRY, + assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_ID, !CHECK_COUNTRY, SUBTYPE_MODE_KEYBOARD)); - assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_ID, CHECK_COUNTRY, + assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_ID, CHECK_COUNTRY, SUBTYPE_MODE_KEYBOARD)); } @@ -785,13 +785,13 @@ public class InputMethodUtilsTest { "com.android.apps.inputmethod.latin", "com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT, subtypes); - assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_IN, !CHECK_COUNTRY, + assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_IN, !CHECK_COUNTRY, SUBTYPE_MODE_KEYBOARD)); - assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_IN, CHECK_COUNTRY, + assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_IN, CHECK_COUNTRY, SUBTYPE_MODE_KEYBOARD)); - assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_ID, !CHECK_COUNTRY, + assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_ID, !CHECK_COUNTRY, SUBTYPE_MODE_KEYBOARD)); - assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_ID, CHECK_COUNTRY, + assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_ID, CHECK_COUNTRY, SUBTYPE_MODE_KEYBOARD)); } } @@ -805,19 +805,19 @@ public class InputMethodUtilsTest { { final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); methodMap.put(systemIme.getId(), systemIme); - assertNull(InputMethodUtils.chooseSystemVoiceIme(methodMap, null, "")); + assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(methodMap, null, "")); } // Returns null when the config value is empty. { final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); methodMap.put(systemIme.getId(), systemIme); - assertNull(InputMethodUtils.chooseSystemVoiceIme(methodMap, "", "")); + assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(methodMap, "", "")); } // Returns null when the configured package doesn't have an IME. { - assertNull(InputMethodUtils.chooseSystemVoiceIme(new ArrayMap<>(), + assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(new ArrayMap<>(), systemIme.getPackageName(), "")); } @@ -825,7 +825,7 @@ public class InputMethodUtilsTest { { final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); methodMap.put(systemIme.getId(), systemIme); - assertEquals(systemIme, InputMethodUtils.chooseSystemVoiceIme(methodMap, + assertEquals(systemIme, InputMethodInfoUtils.chooseSystemVoiceIme(methodMap, systemIme.getPackageName(), null)); } @@ -833,13 +833,13 @@ public class InputMethodUtilsTest { { final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); methodMap.put(systemIme.getId(), systemIme); - assertEquals(systemIme, InputMethodUtils.chooseSystemVoiceIme(methodMap, + assertEquals(systemIme, InputMethodInfoUtils.chooseSystemVoiceIme(methodMap, systemIme.getPackageName(), "")); } // Returns null when the current default isn't found. { - assertNull(InputMethodUtils.chooseSystemVoiceIme(new ArrayMap<>(), + assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(new ArrayMap<>(), systemIme.getPackageName(), systemIme.getId())); } @@ -850,8 +850,8 @@ public class InputMethodUtilsTest { final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); methodMap.put(systemIme.getId(), systemIme); methodMap.put(secondIme.getId(), secondIme); - assertNull(InputMethodUtils.chooseSystemVoiceIme(methodMap, systemIme.getPackageName(), - "")); + assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(methodMap, + systemIme.getPackageName(), "")); } // Returns the current one when the current default and config point to the same package. @@ -861,7 +861,7 @@ public class InputMethodUtilsTest { final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); methodMap.put(systemIme.getId(), systemIme); methodMap.put(secondIme.getId(), secondIme); - assertEquals(systemIme, InputMethodUtils.chooseSystemVoiceIme(methodMap, + assertEquals(systemIme, InputMethodInfoUtils.chooseSystemVoiceIme(methodMap, systemIme.getPackageName(), systemIme.getId())); } @@ -871,7 +871,7 @@ public class InputMethodUtilsTest { final InputMethodInfo nonSystemIme = createFakeInputMethodInfo("NonSystemIme", "fake.voice0", false /* isSystem */); methodMap.put(nonSystemIme.getId(), nonSystemIme); - assertNull(InputMethodUtils.chooseSystemVoiceIme(methodMap, + assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(methodMap, nonSystemIme.getPackageName(), nonSystemIme.getId())); } @@ -882,7 +882,7 @@ public class InputMethodUtilsTest { "FakeDefaultAutoVoiceIme", "fake.voice0", false /* isSystem */); methodMap.put(systemIme.getId(), systemIme); methodMap.put(nonSystemIme.getId(), nonSystemIme); - assertNull(InputMethodUtils.chooseSystemVoiceIme(methodMap, + assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(methodMap, nonSystemIme.getPackageName(), "")); } } @@ -891,7 +891,7 @@ public class InputMethodUtilsTest { final Locale systemLocale, String... expectedImeNames) { final Context context = createTargetContextWithLocales(new LocaleList(systemLocale)); final String[] actualImeNames = getPackageNames( - InputMethodUtils.getDefaultEnabledImes(context, preinstalledImes)); + InputMethodInfoUtils.getDefaultEnabledImes(context, preinstalledImes)); assertEquals(expectedImeNames.length, actualImeNames.length); for (int i = 0; i < expectedImeNames.length; ++i) { assertEquals(expectedImeNames[i], actualImeNames[i]); @@ -902,7 +902,7 @@ public class InputMethodUtilsTest { final Locale systemLocale, String... expectedImeNames) { final Context context = createTargetContextWithLocales(new LocaleList(systemLocale)); final String[] actualImeNames = getPackageNames( - InputMethodUtils.getDefaultEnabledImes(context, preinstalledImes, + InputMethodInfoUtils.getDefaultEnabledImes(context, preinstalledImes, true /* onlyMinimum */)); assertEquals(expectedImeNames.length, actualImeNames.length); for (int i = 0; i < expectedImeNames.length; ++i) { diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java index c735bb7add0a..8a96febcd1e9 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -778,8 +778,7 @@ public class VibratorManagerServiceTest { assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(), service, TEST_TIMEOUT_MILLIS)); - vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), - HAPTIC_FEEDBACK_ATTRS); + vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), HAPTIC_FEEDBACK_ATTRS); // Wait before checking it never played a second effect. assertFalse(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 1, @@ -793,49 +792,78 @@ public class VibratorManagerServiceTest { } @Test - public void vibrate_withOngoingAlarmVibration_ignoresEffect() throws Exception { + public void vibrate_withNewRepeatingVibration_cancelsOngoingEffect() throws Exception { mockVibrators(1); mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); VibratorManagerService service = createSystemReadyService(); VibrationEffect alarmEffect = VibrationEffect.createWaveform( new long[]{10_000, 10_000}, new int[]{128, 255}, -1); - vibrate(service, alarmEffect, new VibrationAttributes.Builder().setUsage( - VibrationAttributes.USAGE_ALARM).build()); + vibrate(service, alarmEffect, ALARM_ATTRS); // VibrationThread will start this vibration async, so wait before checking it started. assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(), service, TEST_TIMEOUT_MILLIS)); - vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), - HAPTIC_FEEDBACK_ATTRS); + VibrationEffect repeatingEffect = VibrationEffect.createWaveform( + new long[]{10_000, 10_000}, new int[]{128, 255}, 1); + vibrate(service, repeatingEffect, NOTIFICATION_ATTRS); - // Wait before checking it never played a second effect. - assertFalse(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 1, - service, /* timeout= */ 50)); + // VibrationThread will start this vibration async, so wait before checking it started. + assertTrue(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 1, + service, TEST_TIMEOUT_MILLIS)); + + // The second vibration should have recorded that the vibrators were turned on. + verify(mBatteryStatsMock, times(2)).noteVibratorOn(anyInt(), anyLong()); } @Test - public void vibrate_withOngoingRingtoneVibration_ignoresEffect() throws Exception { + public void vibrate_withOngoingHigherImportanceVibration_ignoresEffect() throws Exception { mockVibrators(1); mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); VibratorManagerService service = createSystemReadyService(); - VibrationEffect alarmEffect = VibrationEffect.createWaveform( + VibrationEffect effect = VibrationEffect.createWaveform( new long[]{10_000, 10_000}, new int[]{128, 255}, -1); - vibrate(service, alarmEffect, new VibrationAttributes.Builder().setUsage( - VibrationAttributes.USAGE_RINGTONE).build()); + vibrate(service, effect, ALARM_ATTRS); // VibrationThread will start this vibration async, so wait before checking it started. assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(), service, TEST_TIMEOUT_MILLIS)); - vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), - HAPTIC_FEEDBACK_ATTRS); + vibrate(service, effect, HAPTIC_FEEDBACK_ATTRS); // Wait before checking it never played a second effect. assertFalse(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 1, service, /* timeout= */ 50)); + + // The second vibration shouldn't have recorded that the vibrators were turned on. + verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong()); + } + + @Test + public void vibrate_withOngoingLowerImportanceVibration_cancelsOngoingEffect() + throws Exception { + mockVibrators(1); + mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); + VibratorManagerService service = createSystemReadyService(); + + VibrationEffect effect = VibrationEffect.createWaveform( + new long[]{10_000, 10_000}, new int[]{128, 255}, -1); + vibrate(service, effect, HAPTIC_FEEDBACK_ATTRS); + + // VibrationThread will start this vibration async, so wait before checking it started. + assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(), + service, TEST_TIMEOUT_MILLIS)); + + vibrate(service, effect, RINGTONE_ATTRS); + + // VibrationThread will start this vibration async, so wait before checking it started. + assertTrue(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 1, + service, TEST_TIMEOUT_MILLIS)); + + // The second vibration should have recorded that the vibrators were turned on. + verify(mBatteryStatsMock, times(2)).noteVibratorOn(anyInt(), anyLong()); } @Test @@ -1052,15 +1080,15 @@ public class VibratorManagerServiceTest { IVibrator.CAP_COMPOSE_EFFECTS); VibratorManagerService service = createSystemReadyService(); - vibrate(service, CombinedVibration.startSequential() - .addNext(1, VibrationEffect.createOneShot(100, 125)) - .combine(), NOTIFICATION_ATTRS); - assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 1, - service, TEST_TIMEOUT_MILLIS)); - vibrate(service, VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f) .compose(), HAPTIC_FEEDBACK_ATTRS); + assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 1, + service, TEST_TIMEOUT_MILLIS)); + + vibrate(service, CombinedVibration.startSequential() + .addNext(1, VibrationEffect.createOneShot(100, 125)) + .combine(), NOTIFICATION_ATTRS); assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 2, service, TEST_TIMEOUT_MILLIS)); @@ -1070,25 +1098,25 @@ public class VibratorManagerServiceTest { assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 3, service, TEST_TIMEOUT_MILLIS)); + // Ring vibrations have intensity OFF and are not played. vibrate(service, VibrationEffect.createOneShot(100, 125), RINGTONE_ATTRS); assertFalse(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() > 3, - service, TEST_TIMEOUT_MILLIS)); + service, /* timeout= */ 50)); + // Only 3 effects played successfully. assertEquals(3, fakeVibrator.getAllEffectSegments().size()); + // Haptic feedback vibrations will be scaled with SCALE_LOW or none if default is low. + assertEquals(defaultTouchIntensity > Vibrator.VIBRATION_INTENSITY_LOW, + 0.5 > ((PrimitiveSegment) fakeVibrator.getAllEffectSegments().get(0)).getScale()); + // Notification vibrations will be scaled with SCALE_HIGH or none if default is high. assertEquals(defaultNotificationIntensity < Vibrator.VIBRATION_INTENSITY_HIGH, 0.6 < fakeVibrator.getAmplitudes().get(0)); - // Haptic feedback vibrations will be scaled with SCALE_LOW or none if default is low. - assertEquals(defaultTouchIntensity > Vibrator.VIBRATION_INTENSITY_LOW, - 0.5 > ((PrimitiveSegment) fakeVibrator.getAllEffectSegments().get(1)).getScale()); - // Alarm vibration will be scaled with SCALE_NONE. assertEquals(1f, ((PrimitiveSegment) fakeVibrator.getAllEffectSegments().get(2)).getScale(), 1e-5); - - // Ring vibrations have intensity OFF and are not played. } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 2477f6c41d4f..9de9582af6b5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -779,7 +779,7 @@ public class ActivityRecordTests extends WindowTestsBase { } @Override - public void onAnimationCancelled() { + public void onAnimationCancelled(boolean isKeyguardOccluded) { } }, 0, 0)); activity.updateOptionsLocked(opts); @@ -1996,7 +1996,8 @@ public class ActivityRecordTests extends WindowTestsBase { any() /* window */, any() /* attrs */, anyInt() /* viewVisibility */, anyInt() /* displayId */, any() /* requestedVisibilities */, any() /* outInputChannel */, - any() /* outInsetsState */, any() /* outActiveControls */); + any() /* outInsetsState */, any() /* outActiveControls */, + any() /* outAttachedFrame */); mAtm.mWindowManager.mStartingSurfaceController .createTaskSnapshotSurface(activity, snapshot); } catch (RemoteException ignored) { diff --git a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java index 71f19148d616..b5764f54ff92 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java @@ -87,7 +87,7 @@ public class AppChangeTransitionTests extends WindowTestsBase { } @Override - public void onAnimationCancelled() { + public void onAnimationCancelled(boolean isKeyguardOccluded) { } @Override diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java index 8656a4fecef1..f2d6273f2b26 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java @@ -806,7 +806,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { } @Override - public void onAnimationCancelled() throws RemoteException { + public void onAnimationCancelled(boolean isKeyguardOccluded) throws RemoteException { mFinishedCallback = null; } diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java index 436cf36587d8..74154609b22e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java @@ -522,7 +522,7 @@ public class AppTransitionTests extends WindowTestsBase { } @Override - public void onAnimationCancelled() { + public void onAnimationCancelled(boolean isKeyguardOccluded) { mCancelled = true; } diff --git a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java index db3a51ca4791..2956c14155b9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java @@ -39,7 +39,6 @@ import static com.android.server.wm.SizeCompatTests.rotateDisplay; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -211,10 +210,8 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { assertThat(activityConfigBounds.width()).isEqualTo(activityBounds.width()); assertThat(activityConfigBounds.height()).isEqualTo(activityBounds.height()); assertThat(activitySizeCompatBounds.height()).isEqualTo(newTaskBounds.height()); - final float defaultAspectRatio = mFirstActivity.mWmService.mLetterboxConfiguration - .getDefaultMinAspectRatioForUnresizableApps(); - assertEquals(activitySizeCompatBounds.width(), - newTaskBounds.height() / defaultAspectRatio, 0.5); + assertThat(activitySizeCompatBounds.width()).isEqualTo( + newTaskBounds.height() * newTaskBounds.height() / newTaskBounds.width()); } @Test @@ -234,9 +231,8 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { assertThat(mFirstActivity.inSizeCompatMode()).isFalse(); assertThat(taskBounds).isEqualTo(dagBounds); assertThat(activityBounds.width()).isEqualTo(dagBounds.width()); - final float defaultAspectRatio = mFirstActivity.mWmService.mLetterboxConfiguration - .getDefaultMinAspectRatioForUnresizableApps(); - assertEquals(activityBounds.height(), dagBounds.width() / defaultAspectRatio, 0.5); + assertThat(activityBounds.height()) + .isEqualTo(dagBounds.width() * dagBounds.width() / dagBounds.height()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java index 204c7e6fb8d4..027f5218f820 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java @@ -43,6 +43,7 @@ import static org.junit.Assert.assertEquals; 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.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; @@ -210,7 +211,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN); adapter.onAnimationCancelled(mMockLeash); - verify(mMockRunner).onAnimationCancelled(); + verify(mMockRunner).onAnimationCancelled(anyBoolean()); } @Test @@ -226,7 +227,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { mClock.fastForward(10500); mHandler.timeAdvance(); - verify(mMockRunner).onAnimationCancelled(); + verify(mMockRunner).onAnimationCancelled(anyBoolean()); verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION), eq(adapter)); } @@ -247,12 +248,12 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { mClock.fastForward(10500); mHandler.timeAdvance(); - verify(mMockRunner, never()).onAnimationCancelled(); + verify(mMockRunner, never()).onAnimationCancelled(anyBoolean()); mClock.fastForward(52500); mHandler.timeAdvance(); - verify(mMockRunner).onAnimationCancelled(); + verify(mMockRunner).onAnimationCancelled(anyBoolean()); verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION), eq(adapter)); } finally { @@ -264,7 +265,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { public void testZeroAnimations() throws Exception { mController.goodToGo(TRANSIT_OLD_NONE); verify(mMockRunner, never()).onAnimationStart(anyInt(), any(), any(), any(), any()); - verify(mMockRunner).onAnimationCancelled(); + verify(mMockRunner).onAnimationCancelled(anyBoolean()); } @Test @@ -274,7 +275,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { new Point(50, 100), null, new Rect(50, 100, 150, 150), null, false); mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN); verify(mMockRunner, never()).onAnimationStart(anyInt(), any(), any(), any(), any()); - verify(mMockRunner).onAnimationCancelled(); + verify(mMockRunner).onAnimationCancelled(anyBoolean()); } @Test @@ -316,7 +317,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { win.mActivityRecord.removeImmediately(); mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN); verify(mMockRunner, never()).onAnimationStart(anyInt(), any(), any(), any(), any()); - verify(mMockRunner).onAnimationCancelled(); + verify(mMockRunner).onAnimationCancelled(anyBoolean()); verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION), eq(adapter)); } @@ -574,7 +575,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { // Cancel the wallpaper window animator and ensure the runner is not canceled wallpaperWindowToken.cancelAnimation(); - verify(mMockRunner, never()).onAnimationCancelled(); + verify(mMockRunner, never()).onAnimationCancelled(anyBoolean()); } finally { mDisplayContent.mOpeningApps.clear(); } 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 7f70882a70fc..324e244c46f5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -1471,6 +1471,8 @@ public class SizeCompatTests extends WindowTestsBase { final float fixedOrientationLetterboxAspectRatio = 1.1f; mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio( fixedOrientationLetterboxAspectRatio); + mActivity.mWmService.mLetterboxConfiguration.setDefaultMinAspectRatioForUnresizableApps( + 1.5f); prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); final Rect displayBounds = new Rect(mActivity.mDisplayContent.getBounds()); @@ -1496,7 +1498,9 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testSplitAspectRatioForUnresizablePortraitApps() { // Set up a display in landscape and ignoring orientation request. - setUpDisplaySizeWithApp(1600, 1400); + int screenWidth = 1600; + int screenHeight = 1400; + setUpDisplaySizeWithApp(screenWidth, screenHeight); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); mActivity.mWmService.mLetterboxConfiguration .setIsSplitScreenAspectRatioForUnresizableAppsEnabled(true); @@ -1520,6 +1524,7 @@ public class SizeCompatTests extends WindowTestsBase { new TestSplitOrganizer(mAtm, mActivity.getDisplayContent()); // Move activity to split screen which takes half of the screen. mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test"); + organizer.mPrimary.setBounds(0, 0, getExpectedSplitSize(screenWidth), screenHeight); assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode()); assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode()); // Checking that there is no size compat mode. @@ -1528,8 +1533,10 @@ public class SizeCompatTests extends WindowTestsBase { @Test public void testSplitAspectRatioForUnresizableLandscapeApps() { - // Set up a display in landscape and ignoring orientation request. - setUpDisplaySizeWithApp(1400, 1600); + // Set up a display in portrait and ignoring orientation request. + int screenWidth = 1400; + int screenHeight = 1600; + setUpDisplaySizeWithApp(screenWidth, screenHeight); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); mActivity.mWmService.mLetterboxConfiguration .setIsSplitScreenAspectRatioForUnresizableAppsEnabled(true); @@ -1553,6 +1560,7 @@ public class SizeCompatTests extends WindowTestsBase { new TestSplitOrganizer(mAtm, mActivity.getDisplayContent()); // Move activity to split screen which takes half of the screen. mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test"); + organizer.mPrimary.setBounds(0, 0, screenWidth, getExpectedSplitSize(screenHeight)); assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode()); assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode()); // Checking that there is no size compat mode. @@ -2071,12 +2079,7 @@ public class SizeCompatTests extends WindowTestsBase { // Activity bounds fill split screen. final Rect primarySplitBounds = new Rect(organizer.mPrimary.getBounds()); final Rect letterboxedBounds = new Rect(mActivity.getBounds()); - // Activity is letterboxed for aspect ratio. - assertEquals(primarySplitBounds.height(), letterboxedBounds.height()); - final float defaultAspectRatio = mActivity.mWmService.mLetterboxConfiguration - .getDefaultMinAspectRatioForUnresizableApps(); - assertEquals(primarySplitBounds.height() / defaultAspectRatio, - letterboxedBounds.width(), 0.5); + assertEquals(primarySplitBounds, letterboxedBounds); } @Test @@ -2618,6 +2621,16 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(newDensity, mActivity.getConfiguration().densityDpi); } + private int getExpectedSplitSize(int dimensionToSplit) { + int dividerWindowWidth = + mActivity.mWmService.mContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.docked_stack_divider_thickness); + int dividerInsets = + mActivity.mWmService.mContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.docked_stack_divider_insets); + return (dimensionToSplit - (dividerWindowWidth - dividerInsets * 2)) / 2; + } + private void assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity( float letterboxHorizontalPositionMultiplier) { // Set up a display in landscape and ignoring orientation request. diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java index 53c2a5b8967d..486486869d05 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java @@ -47,10 +47,6 @@ class SystemServiceTestsBase { mLockRule.waitForLocked(mSystemServicesTestRule::waitUntilWindowAnimatorIdle); } - void cleanupWindowManagerHandlers() { - mLockRule.waitForLocked(mSystemServicesTestRule::cleanupWindowManagerHandlers); - } - boolean waitHandlerIdle(Handler handler) { return waitHandlerIdle(handler, 0 /* timeout */); } diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java index 859eba600af5..ab7e8eab28c3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -370,8 +370,6 @@ public class SystemServicesTestRule implements TestRule { // This makes sure the posted messages without delay are processed, e.g. // DisplayPolicy#release, WindowManagerService#setAnimationScale. waitUntilWindowManagerHandlersIdle(); - // Clear all posted messages with delay, so they don't be executed at unexpected times. - cleanupWindowManagerHandlers(); // Needs to explicitly dispose current static threads because there could be messages // scheduled at a later time, and all mocks are invalid when it's executed. DisplayThread.dispose(); @@ -460,18 +458,6 @@ public class SystemServicesTestRule implements TestRule { return proc; } - void cleanupWindowManagerHandlers() { - final WindowManagerService wm = getWindowManagerService(); - if (wm == null) { - return; - } - wm.mH.removeCallbacksAndMessages(null); - wm.mAnimationHandler.removeCallbacksAndMessages(null); - // This is a different handler object than the wm.mAnimationHandler above. - AnimationThread.getHandler().removeCallbacksAndMessages(null); - SurfaceAnimationThread.getHandler().removeCallbacksAndMessages(null); - } - void waitUntilWindowManagerHandlersIdle() { final WindowManagerService wm = getWindowManagerService(); if (wm == null) { diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java index 5743922d0428..1715a295ded3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java @@ -979,7 +979,7 @@ public class WindowContainerTests extends WindowTestsBase { } @Override - public void onAnimationCancelled() { + public void onAnimationCancelled(boolean isKeyguardOccluded) { } }, 0, 0, false); adapter.setCallingPidUid(123, 456); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java index ea18e58fe999..739e783e7c1b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java @@ -72,7 +72,7 @@ public class WindowLayoutTests { private static final Insets WATERFALL_INSETS = Insets.of(6, 0, 12, 0); private final WindowLayout mWindowLayout = new WindowLayout(); - private final ClientWindowFrames mOutFrames = new ClientWindowFrames(); + private final ClientWindowFrames mFrames = new ClientWindowFrames(); private WindowManager.LayoutParams mAttrs; private InsetsState mState; @@ -82,7 +82,6 @@ public class WindowLayoutTests { private int mRequestedWidth; private int mRequestedHeight; private InsetsVisibilities mRequestedVisibilities; - private Rect mAttachedWindowFrame; private float mCompatScale; @Before @@ -100,14 +99,14 @@ public class WindowLayoutTests { mRequestedWidth = DISPLAY_WIDTH; mRequestedHeight = DISPLAY_HEIGHT; mRequestedVisibilities = new InsetsVisibilities(); - mAttachedWindowFrame = null; mCompatScale = 1f; + mFrames.attachedFrame = null; } private void computeFrames() { mWindowLayout.computeFrames(mAttrs, mState, mDisplayCutoutSafe, mWindowBounds, mWindowingMode, mRequestedWidth, mRequestedHeight, mRequestedVisibilities, - mAttachedWindowFrame, mCompatScale, mOutFrames); + mCompatScale, mFrames); } private void addDisplayCutout() { @@ -145,9 +144,9 @@ public class WindowLayoutTests { public void defaultParams() { computeFrames(); - assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.displayFrame); - assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.parentFrame); - assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.frame); + assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame); + assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.parentFrame); + assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.frame); } @Test @@ -156,9 +155,9 @@ public class WindowLayoutTests { mRequestedHeight = UNSPECIFIED_LENGTH; computeFrames(); - assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.displayFrame); - assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.parentFrame); - assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.frame); + assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame); + assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.parentFrame); + assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.frame); } @Test @@ -172,9 +171,9 @@ public class WindowLayoutTests { mAttrs.gravity = Gravity.LEFT | Gravity.TOP; computeFrames(); - assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.displayFrame); - assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.parentFrame); - assertRect(0, STATUS_BAR_HEIGHT, width, STATUS_BAR_HEIGHT + height, mOutFrames.frame); + assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame); + assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.parentFrame); + assertRect(0, STATUS_BAR_HEIGHT, width, STATUS_BAR_HEIGHT + height, mFrames.frame); } @Test @@ -186,11 +185,24 @@ public class WindowLayoutTests { computeFrames(); assertRect(0, top, DISPLAY_WIDTH, DISPLAY_HEIGHT - NAVIGATION_BAR_HEIGHT, - mOutFrames.displayFrame); + mFrames.displayFrame); assertRect(0, top, DISPLAY_WIDTH, DISPLAY_HEIGHT - NAVIGATION_BAR_HEIGHT, - mOutFrames.parentFrame); + mFrames.parentFrame); assertRect(0, top, DISPLAY_WIDTH, DISPLAY_HEIGHT - NAVIGATION_BAR_HEIGHT, - mOutFrames.frame); + mFrames.frame); + } + + @Test + public void attachedFrame() { + final int bottom = (DISPLAY_HEIGHT - STATUS_BAR_HEIGHT - NAVIGATION_BAR_HEIGHT) / 2; + mFrames.attachedFrame = new Rect(0, STATUS_BAR_HEIGHT, DISPLAY_WIDTH, bottom); + mRequestedWidth = UNSPECIFIED_LENGTH; + mRequestedHeight = UNSPECIFIED_LENGTH; + computeFrames(); + + assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame); + assertEquals(mFrames.attachedFrame, mFrames.parentFrame); + assertEquals(mFrames.attachedFrame, mFrames.frame); } @Test @@ -198,9 +210,9 @@ public class WindowLayoutTests { mAttrs.setFitInsetsTypes(WindowInsets.Type.statusBars()); computeFrames(); - assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mOutFrames.displayFrame); - assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mOutFrames.parentFrame); - assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mOutFrames.frame); + assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mFrames.displayFrame); + assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mFrames.parentFrame); + assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mFrames.frame); } @Test @@ -208,9 +220,9 @@ public class WindowLayoutTests { mAttrs.setFitInsetsTypes(WindowInsets.Type.navigationBars()); computeFrames(); - assertInsetByTopBottom(0, NAVIGATION_BAR_HEIGHT, mOutFrames.displayFrame); - assertInsetByTopBottom(0, NAVIGATION_BAR_HEIGHT, mOutFrames.parentFrame); - assertInsetByTopBottom(0, NAVIGATION_BAR_HEIGHT, mOutFrames.frame); + assertInsetByTopBottom(0, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame); + assertInsetByTopBottom(0, NAVIGATION_BAR_HEIGHT, mFrames.parentFrame); + assertInsetByTopBottom(0, NAVIGATION_BAR_HEIGHT, mFrames.frame); } @Test @@ -218,9 +230,9 @@ public class WindowLayoutTests { mAttrs.setFitInsetsTypes(0); computeFrames(); - assertInsetByTopBottom(0, 0, mOutFrames.displayFrame); - assertInsetByTopBottom(0, 0, mOutFrames.parentFrame); - assertInsetByTopBottom(0, 0, mOutFrames.frame); + assertInsetByTopBottom(0, 0, mFrames.displayFrame); + assertInsetByTopBottom(0, 0, mFrames.parentFrame); + assertInsetByTopBottom(0, 0, mFrames.frame); } @Test @@ -228,9 +240,9 @@ public class WindowLayoutTests { mAttrs.setFitInsetsSides(WindowInsets.Side.all()); computeFrames(); - assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.displayFrame); - assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.parentFrame); - assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.frame); + assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame); + assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.parentFrame); + assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.frame); } @Test @@ -238,9 +250,9 @@ public class WindowLayoutTests { mAttrs.setFitInsetsSides(WindowInsets.Side.TOP); computeFrames(); - assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mOutFrames.displayFrame); - assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mOutFrames.parentFrame); - assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mOutFrames.frame); + assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mFrames.displayFrame); + assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mFrames.parentFrame); + assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mFrames.frame); } @Test @@ -248,9 +260,9 @@ public class WindowLayoutTests { mAttrs.setFitInsetsSides(0); computeFrames(); - assertInsetByTopBottom(0, 0, mOutFrames.displayFrame); - assertInsetByTopBottom(0, 0, mOutFrames.parentFrame); - assertInsetByTopBottom(0, 0, mOutFrames.frame); + assertInsetByTopBottom(0, 0, mFrames.displayFrame); + assertInsetByTopBottom(0, 0, mFrames.parentFrame); + assertInsetByTopBottom(0, 0, mFrames.frame); } @Test @@ -259,9 +271,9 @@ public class WindowLayoutTests { mState.getSource(ITYPE_NAVIGATION_BAR).setVisible(false); computeFrames(); - assertInsetByTopBottom(0, 0, mOutFrames.displayFrame); - assertInsetByTopBottom(0, 0, mOutFrames.parentFrame); - assertInsetByTopBottom(0, 0, mOutFrames.frame); + assertInsetByTopBottom(0, 0, mFrames.displayFrame); + assertInsetByTopBottom(0, 0, mFrames.parentFrame); + assertInsetByTopBottom(0, 0, mFrames.frame); } @Test @@ -271,9 +283,9 @@ public class WindowLayoutTests { mAttrs.setFitInsetsIgnoringVisibility(true); computeFrames(); - assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.displayFrame); - assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.parentFrame); - assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.frame); + assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame); + assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.parentFrame); + assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.frame); } @Test @@ -284,9 +296,9 @@ public class WindowLayoutTests { mAttrs.privateFlags |= PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME; computeFrames(); - assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.displayFrame); - assertInsetByTopBottom(STATUS_BAR_HEIGHT, IME_HEIGHT, mOutFrames.parentFrame); - assertInsetByTopBottom(STATUS_BAR_HEIGHT, IME_HEIGHT, mOutFrames.frame); + assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame); + assertInsetByTopBottom(STATUS_BAR_HEIGHT, IME_HEIGHT, mFrames.parentFrame); + assertInsetByTopBottom(STATUS_BAR_HEIGHT, IME_HEIGHT, mFrames.frame); } @Test @@ -297,11 +309,11 @@ public class WindowLayoutTests { computeFrames(); assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0, - mOutFrames.displayFrame); + mFrames.displayFrame); assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0, - mOutFrames.parentFrame); + mFrames.parentFrame); assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0, - mOutFrames.frame); + mFrames.frame); } @Test @@ -312,11 +324,11 @@ public class WindowLayoutTests { computeFrames(); assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0, - mOutFrames.displayFrame); + mFrames.displayFrame); assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0, - mOutFrames.parentFrame); + mFrames.parentFrame); assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0, - mOutFrames.frame); + mFrames.frame); } @Test @@ -327,9 +339,9 @@ public class WindowLayoutTests { mAttrs.setFitInsetsTypes(0); computeFrames(); - assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mOutFrames.displayFrame); - assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mOutFrames.parentFrame); - assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mOutFrames.frame); + assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mFrames.displayFrame); + assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mFrames.parentFrame); + assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mFrames.frame); } @Test @@ -344,9 +356,9 @@ public class WindowLayoutTests { mAttrs.privateFlags |= PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT; computeFrames(); - assertRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, mOutFrames.displayFrame); - assertRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, mOutFrames.parentFrame); - assertRect(0, 0, DISPLAY_WIDTH, height + DISPLAY_CUTOUT_HEIGHT, mOutFrames.frame); + assertRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, mFrames.displayFrame); + assertRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, mFrames.parentFrame); + assertRect(0, 0, DISPLAY_WIDTH, height + DISPLAY_CUTOUT_HEIGHT, mFrames.frame); } @Test @@ -359,11 +371,11 @@ public class WindowLayoutTests { computeFrames(); assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0, - mOutFrames.displayFrame); + mFrames.displayFrame); assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0, - mOutFrames.parentFrame); + mFrames.parentFrame); assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0, - mOutFrames.frame); + mFrames.frame); } @Test @@ -373,9 +385,9 @@ public class WindowLayoutTests { mAttrs.setFitInsetsTypes(0); computeFrames(); - assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mOutFrames.displayFrame); - assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mOutFrames.parentFrame); - assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mOutFrames.frame); + assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mFrames.displayFrame); + assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mFrames.parentFrame); + assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mFrames.frame); } @Test @@ -386,11 +398,11 @@ public class WindowLayoutTests { computeFrames(); assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0, - mOutFrames.displayFrame); + mFrames.displayFrame); assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0, - mOutFrames.parentFrame); + mFrames.parentFrame); assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0, - mOutFrames.frame); + mFrames.frame); } @Test @@ -400,8 +412,8 @@ public class WindowLayoutTests { mAttrs.setFitInsetsTypes(0); computeFrames(); - assertInsetByTopBottom(0, 0, mOutFrames.displayFrame); - assertInsetByTopBottom(0, 0, mOutFrames.parentFrame); - assertInsetByTopBottom(0, 0, mOutFrames.frame); + assertInsetByTopBottom(0, 0, mFrames.displayFrame); + assertInsetByTopBottom(0, 0, mFrames.parentFrame); + assertInsetByTopBottom(0, 0, mFrames.frame); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index a0c20c2c6b68..e09a94f3bcf9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -49,6 +49,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.pm.PackageManager; +import android.graphics.Rect; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; @@ -281,7 +282,7 @@ public class WindowManagerServiceTests extends WindowTestsBase { mWm.addWindow(session, new TestIWindow(), params, View.VISIBLE, DEFAULT_DISPLAY, UserHandle.USER_SYSTEM, new InsetsVisibilities(), null, new InsetsState(), - new InsetsSourceControl[0]); + new InsetsSourceControl[0], new Rect()); verify(mWm.mWindowContextListenerController, never()).registerWindowContainerListener(any(), any(), anyInt(), anyInt(), any()); diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb10ACInputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/Usb10ACInputTerminal.java index 75531d172761..a4de8f2994dc 100644 --- a/services/usb/java/com/android/server/usb/descriptors/Usb10ACInputTerminal.java +++ b/services/usb/java/com/android/server/usb/descriptors/Usb10ACInputTerminal.java @@ -22,7 +22,7 @@ import com.android.server.usb.descriptors.report.ReportCanvas; * An audio class-specific Input Terminal interface. * see audio10.pdf section 4.3.2.1 */ -public final class Usb10ACInputTerminal extends UsbACTerminal { +public final class Usb10ACInputTerminal extends UsbACTerminal implements UsbAudioChannelCluster { private static final String TAG = "Usb10ACInputTerminal"; private byte mNrChannels; // 7:1 1 Channel (0x01) @@ -36,14 +36,17 @@ public final class Usb10ACInputTerminal extends UsbACTerminal { super(length, type, subtype, subclass); } - public byte getNrChannels() { + @Override + public byte getChannelCount() { return mNrChannels; } + @Override public int getChannelConfig() { return mChannelConfig; } + @Override public byte getChannelNames() { return mChannelNames; } @@ -69,9 +72,7 @@ public final class Usb10ACInputTerminal extends UsbACTerminal { super.report(canvas); canvas.openList(); - canvas.writeListItem("Associated Terminal: " - + ReportCanvas.getHexString(getAssocTerminal())); - canvas.writeListItem("" + getNrChannels() + " Chans. Config: " + canvas.writeListItem("" + getChannelCount() + " Chans. Config: " + ReportCanvas.getHexString(getChannelConfig())); canvas.closeList(); } diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb10ACMixerUnit.java b/services/usb/java/com/android/server/usb/descriptors/Usb10ACMixerUnit.java index c7634ba7dc63..e89b8f50b698 100644 --- a/services/usb/java/com/android/server/usb/descriptors/Usb10ACMixerUnit.java +++ b/services/usb/java/com/android/server/usb/descriptors/Usb10ACMixerUnit.java @@ -22,7 +22,7 @@ import com.android.server.usb.descriptors.report.ReportCanvas; * An audio class-specific Mixer Interface. * see audio10.pdf section 4.3.2.3 */ -public final class Usb10ACMixerUnit extends UsbACMixerUnit { +public final class Usb10ACMixerUnit extends UsbACMixerUnit implements UsbAudioChannelCluster { private static final String TAG = "Usb10ACMixerUnit"; private int mChannelConfig; // Spatial location of output channels @@ -34,11 +34,18 @@ public final class Usb10ACMixerUnit extends UsbACMixerUnit { super(length, type, subtype, subClass); } + @Override + public byte getChannelCount() { + return mNumOutputs; + } + + @Override public int getChannelConfig() { return mChannelConfig; } - public byte getChanNameID() { + @Override + public byte getChannelNames() { return mChanNameID; } diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ACInputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ACInputTerminal.java index ee1b32c1b544..c9c10ce20b13 100644 --- a/services/usb/java/com/android/server/usb/descriptors/Usb20ACInputTerminal.java +++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ACInputTerminal.java @@ -22,7 +22,7 @@ import com.android.server.usb.descriptors.report.ReportCanvas; * An audio class-specific Input Terminal interface. * see Audio20.pdf section 3.13.2 Input Terminal */ -public final class Usb20ACInputTerminal extends UsbACTerminal { +public final class Usb20ACInputTerminal extends UsbACTerminal implements UsbAudioChannelCluster { private static final String TAG = "Usb20ACInputTerminal"; // See Audio20.pdf - Table 4-9 @@ -47,14 +47,21 @@ public final class Usb20ACInputTerminal extends UsbACTerminal { return mClkSourceID; } - public byte getNumChannels() { + @Override + public byte getChannelCount() { return mNumChannels; } - public int getChanConfig() { + @Override + public int getChannelConfig() { return mChanConfig; } + @Override + public byte getChannelNames() { + return mChanNames; + } + public int getControls() { return mControls; } @@ -79,8 +86,8 @@ public final class Usb20ACInputTerminal extends UsbACTerminal { canvas.openList(); canvas.writeListItem("Clock Source: " + getClkSourceID()); - canvas.writeListItem("" + getNumChannels() + " Channels. Config: " - + ReportCanvas.getHexString(getChanConfig())); + canvas.writeListItem("" + getChannelCount() + " Channels. Config: " + + ReportCanvas.getHexString(getChannelConfig())); canvas.closeList(); } } diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ACMixerUnit.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ACMixerUnit.java index ab965856bb5d..be932ccd0ffd 100644 --- a/services/usb/java/com/android/server/usb/descriptors/Usb20ACMixerUnit.java +++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ACMixerUnit.java @@ -20,7 +20,7 @@ package com.android.server.usb.descriptors; * An audio class-specific Mixer Unit interface. * see Audio20.pdf section 4.7.2.6 Mixer Unit Descriptor */ -public final class Usb20ACMixerUnit extends UsbACMixerUnit { +public final class Usb20ACMixerUnit extends UsbACMixerUnit implements UsbAudioChannelCluster { private static final String TAG = "Usb20ACMixerUnit"; private int mChanConfig; // 6+p:4 Describes the spatial location of the @@ -38,6 +38,21 @@ public final class Usb20ACMixerUnit extends UsbACMixerUnit { } @Override + public byte getChannelCount() { + return mNumOutputs; + } + + @Override + public int getChannelConfig() { + return mChanConfig; + } + + @Override + public byte getChannelNames() { + return mChanNames; + } + + @Override public int parseRawDescriptors(ByteStream stream) { super.parseRawDescriptors(stream); diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java index 20a97af49bc5..9c5b100d16a8 100644 --- a/services/usb/java/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java +++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java @@ -71,6 +71,7 @@ public final class Usb20ACOutputTerminal extends UsbACTerminal { super.report(canvas); canvas.openList(); + canvas.writeListItem("Source ID:" + getSourceID()); canvas.writeListItem("Clock Source ID: " + getClkSourceID()); canvas.writeListItem("Controls: " + ReportCanvas.getHexString(getControls())); canvas.writeListItem("Terminal Name ID: " + getTerminalID()); diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ASGeneral.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ASGeneral.java index de2073882e3a..211ac6821470 100644 --- a/services/usb/java/com/android/server/usb/descriptors/Usb20ASGeneral.java +++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ASGeneral.java @@ -21,7 +21,7 @@ import com.android.server.usb.descriptors.report.ReportCanvas; * Audio20.pdf - 4.9.2 Class-Specific AS Interface Descriptor * 16 bytes */ -public final class Usb20ASGeneral extends UsbACInterface { +public final class Usb20ASGeneral extends UsbACInterface implements UsbAudioChannelCluster { private static final String TAG = "Usb20ASGeneral"; // Audio20.pdf - Table 4-27 @@ -61,14 +61,17 @@ public final class Usb20ASGeneral extends UsbACInterface { return mFormats; } - public byte getNumChannels() { + @Override + public byte getChannelCount() { return mNumChannels; } + @Override public int getChannelConfig() { return mChannelConfig; } + @Override public byte getChannelNames() { return mChannelNames; } @@ -96,7 +99,7 @@ public final class Usb20ASGeneral extends UsbACInterface { canvas.writeListItem("Controls: " + ReportCanvas.getHexString(getControls())); canvas.writeListItem("Format Type: " + ReportCanvas.getHexString(getFormatType())); canvas.writeListItem("Formats: " + ReportCanvas.getHexString(getFormats())); - canvas.writeListItem("Num Channels: " + getNumChannels()); + canvas.writeListItem("Channel Count: " + getChannelCount()); canvas.writeListItem("Channel Config: " + ReportCanvas.getHexString(getChannelConfig())); canvas.writeListItem("Channel Names String ID: " + getChannelNames()); canvas.closeList(); diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java b/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java index 36139d6c6900..819e73df955b 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java @@ -66,6 +66,8 @@ public abstract class UsbACTerminal extends UsbACInterface { canvas.writeListItem("Type: " + ReportCanvas.getHexString(terminalType) + ": " + UsbStrings.getTerminalName(terminalType)); canvas.writeListItem("ID: " + ReportCanvas.getHexString(getTerminalID())); + canvas.writeListItem("Associated terminal: " + + ReportCanvas.getHexString(getAssocTerminal())); canvas.closeList(); } } diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbAudioChannelCluster.java b/services/usb/java/com/android/server/usb/descriptors/UsbAudioChannelCluster.java new file mode 100644 index 000000000000..218ba6735cd0 --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbAudioChannelCluster.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.usb.descriptors; + +/** + * @hide + * Group of logical audio channels that carry tightly related synchronous audio information. + * See Audio10.pdf section 3.7.2.3 Audio Channel Cluster format and Audio20.pdf section 3.13.1 + * audio channel cluster. + */ +public interface UsbAudioChannelCluster { + /** + * @return logical channels in the cluster. + */ + byte getChannelCount(); + + /** + * @return a bit field that indicates which spatial locations are present in the cluster. + */ + int getChannelConfig(); + + /** + * @return index to a string descriptor that describes the spatial location of the first + * non-predefined logical channel in the cluster. + */ + byte getChannelNames(); +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java index cd6ea681db07..f13fcd8d81a4 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java @@ -832,31 +832,47 @@ public final class UsbDescriptorParser { return getInputHeadsetProbability() >= IN_HEADSET_TRIGGER; } + // TODO: Up/Downmix process descriptor is not yet parsed, which may affect the result here. + private int getMaximumChannelCount() { + int maxChannelCount = 0; + for (UsbDescriptor descriptor : mDescriptors) { + if (descriptor instanceof UsbAudioChannelCluster) { + maxChannelCount = Math.max(maxChannelCount, + ((UsbAudioChannelCluster) descriptor).getChannelCount()); + } + } + return maxChannelCount; + } + /** * @hide */ - public float getOutputHeadsetProbability() { + public float getOutputHeadsetLikelihood() { if (hasMIDIInterface()) { return 0.0f; } - float probability = 0.0f; + float likelihood = 0.0f; ArrayList<UsbDescriptor> acDescriptors; // Look for a "speaker" boolean hasSpeaker = false; + boolean hasAssociatedInputTerminal = false; + boolean hasHeadphoneOrHeadset = false; acDescriptors = getACInterfaceDescriptors(UsbACInterface.ACI_OUTPUT_TERMINAL, UsbACInterface.AUDIO_AUDIOCONTROL); for (UsbDescriptor descriptor : acDescriptors) { if (descriptor instanceof UsbACTerminal) { UsbACTerminal outDescr = (UsbACTerminal) descriptor; - if (outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_OUT_SPEAKER - || outDescr.getTerminalType() - == UsbTerminalTypes.TERMINAL_OUT_HEADPHONES - || outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_HEADSET) { + if (outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_OUT_SPEAKER) { hasSpeaker = true; - break; + if (outDescr.getAssocTerminal() != 0x0) { + hasAssociatedInputTerminal = true; + } + } else if (outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_OUT_HEADPHONES + || outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_HEADSET) { + hasHeadphoneOrHeadset = true; } } else { Log.w(TAG, "Undefined Audio Output terminal l: " + descriptor.getLength() @@ -864,15 +880,27 @@ public final class UsbDescriptorParser { } } - if (hasSpeaker) { - probability += 0.75f; + if (hasHeadphoneOrHeadset) { + likelihood += 0.75f; + } else if (hasSpeaker) { + // The device only reports output terminal as speaker. Try to figure out if the device + // is a headset or not by checking if it has associated input terminal and if multiple + // channels are supported or not. + likelihood += 0.5f; + if (hasAssociatedInputTerminal) { + likelihood += 0.25f; + } + if (getMaximumChannelCount() > 2) { + // When multiple channels are supported, it is less likely to be a headset. + likelihood -= 0.25f; + } } - if (hasSpeaker && hasHIDInterface()) { - probability += 0.25f; + if ((hasHeadphoneOrHeadset || hasSpeaker) && hasHIDInterface()) { + likelihood += 0.25f; } - return probability; + return likelihood; } /** @@ -882,7 +910,7 @@ public final class UsbDescriptorParser { * to count on the peripheral being a headset. */ public boolean isOutputHeadset() { - return getOutputHeadsetProbability() >= OUT_HEADSET_TRIGGER; + return getOutputHeadsetLikelihood() >= OUT_HEADSET_TRIGGER; } /** diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java index 0ddd52dfc76d..64a86db38396 100644 --- a/telecomm/java/android/telecom/InCallService.java +++ b/telecomm/java/android/telecom/InCallService.java @@ -22,6 +22,7 @@ import android.annotation.SystemApi; import android.app.Service; import android.app.UiModeManager; import android.bluetooth.BluetoothDevice; +import android.content.ComponentName; import android.content.Intent; import android.hardware.camera2.CameraManager; import android.net.Uri; @@ -47,7 +48,7 @@ import java.util.List; * in a call. It also provides the user with a means to initiate calls and see a history of calls * on their device. A device is bundled with a system provided default dialer/phone app. The user * may choose a single app to take over this role from the system app. An app which wishes to - * fulfill one this role uses the {@link android.app.role.RoleManager} to request that they fill the + * fulfill this role uses the {@link android.app.role.RoleManager} to request that they fill the * {@link android.app.role.RoleManager#ROLE_DIALER} role. * <p> * The default phone app provides a user interface while the device is in a call, and the device is @@ -63,13 +64,23 @@ import java.util.List; * UI, as well as an ongoing call UI.</li> * </ul> * <p> - * Note: If the app filling the {@link android.app.role.RoleManager#ROLE_DIALER} crashes during - * {@link InCallService} binding, the Telecom framework will automatically fall back to using the - * dialer app pre-loaded on the device. The system will display a notification to the user to let - * them know that the app has crashed and that their call was continued using the pre-loaded dialer - * app. + * Note: If the app filling the {@link android.app.role.RoleManager#ROLE_DIALER} returns a + * {@code null} {@link InCallService} during binding, the Telecom framework will automatically fall + * back to using the dialer app preloaded on the device. The system will display a notification to + * the user to let them know that their call was continued using the preloaded dialer app. Your + * app should never return a {@code null} binding; doing so means it does not fulfil the + * requirements of {@link android.app.role.RoleManager#ROLE_DIALER}. * <p> - * The pre-loaded dialer will ALWAYS be used when the user places an emergency call, even if your + * Note: If your app fills {@link android.app.role.RoleManager#ROLE_DIALER} and makes changes at + * runtime which cause it to no longer fulfil the requirements of this role, + * {@link android.app.role.RoleManager} will automatically remove your app from the role and close + * your app. For example, if you use + * {@link android.content.pm.PackageManager#setComponentEnabledSetting(ComponentName, int, int)} to + * programmatically disable the {@link InCallService} your app declares in its manifest, your app + * will no longer fulfil the requirements expected of + * {@link android.app.role.RoleManager#ROLE_DIALER}. + * <p> + * The preloaded dialer will ALWAYS be used when the user places an emergency call, even if your * app fills the {@link android.app.role.RoleManager#ROLE_DIALER} role. To ensure an optimal * experience when placing an emergency call, the default dialer should ALWAYS use * {@link android.telecom.TelecomManager#placeCall(Uri, Bundle)} to place calls (including diff --git a/telephony/java/android/telephony/AccessNetworkUtils.java b/telephony/java/android/telephony/AccessNetworkUtils.java index b0f207cd9ed3..b5d97abdd3eb 100644 --- a/telephony/java/android/telephony/AccessNetworkUtils.java +++ b/telephony/java/android/telephony/AccessNetworkUtils.java @@ -4,8 +4,8 @@ import static android.telephony.ServiceState.DUPLEX_MODE_FDD; import static android.telephony.ServiceState.DUPLEX_MODE_TDD; import static android.telephony.ServiceState.DUPLEX_MODE_UNKNOWN; -import android.telephony.AccessNetworkConstants.EutranBand; import android.telephony.AccessNetworkConstants.EutranBandArfcnFrequency; +import android.telephony.AccessNetworkConstants.EutranBand; import android.telephony.AccessNetworkConstants.GeranBand; import android.telephony.AccessNetworkConstants.GeranBandArfcnFrequency; import android.telephony.AccessNetworkConstants.NgranArfcnFrequency; @@ -13,6 +13,7 @@ import android.telephony.AccessNetworkConstants.NgranBands; import android.telephony.AccessNetworkConstants.UtranBand; import android.telephony.AccessNetworkConstants.UtranBandArfcnFrequency; import android.telephony.ServiceState.DuplexMode; +import android.util.Log; import java.util.Arrays; import java.util.HashSet; @@ -231,110 +232,6 @@ public class AccessNetworkUtils { } /** - * Gets the NR Operating band for a given downlink NRARFCN. - * - * <p>See 3GPP TS 38.104 Table 5.2-1 NR operating bands in FR1 and - * Table 5.2-2 NR operating bands in FR2 - * - * @param nrarfcn The downlink NRARFCN - * @return Operating band number, or {@link #INVALID_BAND} if no corresponding band exists - */ - public static int getOperatingBandForNrarfcn(int nrarfcn) { - if (nrarfcn >= 2110 && nrarfcn <= 2170) { - return NgranBands.BAND_1; - } else if (nrarfcn >= 1930 && nrarfcn <= 1990) { - return NgranBands.BAND_2; - } else if (nrarfcn >= 1805 && nrarfcn <= 1880) { - return NgranBands.BAND_3; - } else if (nrarfcn >= 869 && nrarfcn <= 894) { - return NgranBands.BAND_5; - } else if (nrarfcn >= 2620 && nrarfcn <= 2690) { - return NgranBands.BAND_7; - } else if (nrarfcn >= 925 && nrarfcn <= 960) { - return NgranBands.BAND_8; - } else if (nrarfcn >= 729 && nrarfcn <= 746) { - return NgranBands.BAND_12; - } else if (nrarfcn >= 758 && nrarfcn <= 768) { - return NgranBands.BAND_14; - } else if (nrarfcn >= 860 && nrarfcn <= 875) { - return NgranBands.BAND_18; - } else if (nrarfcn >= 791 && nrarfcn <= 821) { - return NgranBands.BAND_20; - } else if (nrarfcn >= 1930 && nrarfcn <= 1995) { - return NgranBands.BAND_25; - } else if (nrarfcn >= 859 && nrarfcn <= 894) { - return NgranBands.BAND_26; - } else if (nrarfcn >= 758 && nrarfcn <= 803) { - return NgranBands.BAND_28; - } else if (nrarfcn >= 717 && nrarfcn <= 728) { - return NgranBands.BAND_29; - } else if (nrarfcn >= 2350 && nrarfcn <= 2360) { - return NgranBands.BAND_30; - } else if (nrarfcn >= 2010 && nrarfcn <= 2025) { - return NgranBands.BAND_34; - } else if (nrarfcn >= 2570 && nrarfcn <= 2620) { - return NgranBands.BAND_38; - } else if (nrarfcn >= 1880 && nrarfcn <= 1920) { - return NgranBands.BAND_39; - } else if (nrarfcn >= 2300 && nrarfcn <= 2400) { - return NgranBands.BAND_40; - } else if (nrarfcn >= 2496 && nrarfcn <= 2690) { - return NgranBands.BAND_41; - } else if (nrarfcn >= 5150 && nrarfcn <= 5925) { - return NgranBands.BAND_46; - } else if (nrarfcn >= 3550 && nrarfcn <= 3700) { - return NgranBands.BAND_48; - } else if (nrarfcn >= 1432 && nrarfcn <= 1517) { - return NgranBands.BAND_50; - } else if (nrarfcn >= 1427 && nrarfcn <= 1432) { - return NgranBands.BAND_51; - } else if (nrarfcn >= 2483 && nrarfcn <= 2495) { - return NgranBands.BAND_53; - } else if (nrarfcn >= 2110 && nrarfcn <= 2200) { - return NgranBands.BAND_65; // BAND_66 has the same channels - } else if (nrarfcn >= 1995 && nrarfcn <= 2020) { - return NgranBands.BAND_70; - } else if (nrarfcn >= 617 && nrarfcn <= 652) { - return NgranBands.BAND_71; - } else if (nrarfcn >= 1475 && nrarfcn <= 1518) { - return NgranBands.BAND_74; - } else if (nrarfcn >= 1432 && nrarfcn <= 1517) { - return NgranBands.BAND_75; - } else if (nrarfcn >= 1427 && nrarfcn <= 1432) { - return NgranBands.BAND_76; - } else if (nrarfcn >= 3300 && nrarfcn <= 4200) { - return NgranBands.BAND_77; - } else if (nrarfcn >= 3300 && nrarfcn <= 3800) { - return NgranBands.BAND_78; - } else if (nrarfcn >= 4400 && nrarfcn <= 5000) { - return NgranBands.BAND_79; - } else if (nrarfcn >= 2496 && nrarfcn <= 2690) { - return NgranBands.BAND_90; - } else if (nrarfcn >= 1427 && nrarfcn <= 1432) { - return NgranBands.BAND_91; - } else if (nrarfcn >= 1427 && nrarfcn <= 1432) { - return NgranBands.BAND_92; - } else if (nrarfcn >= 1432 && nrarfcn <= 1517) { - return NgranBands.BAND_93; - } else if (nrarfcn >= 1427 && nrarfcn <= 1432) { - return NgranBands.BAND_94; - } else if (nrarfcn >= 1432 && nrarfcn <= 1517) { - return NgranBands.BAND_94; - } else if (nrarfcn >= 5925 && nrarfcn <= 7125) { - return NgranBands.BAND_96; - } else if (nrarfcn >= 26500 && nrarfcn <= 29500) { - return NgranBands.BAND_257; - } else if (nrarfcn >= 24250 && nrarfcn <= 27500) { - return NgranBands.BAND_258; - } else if (nrarfcn >= 37000 && nrarfcn <= 40000) { - return NgranBands.BAND_260; - } else if (nrarfcn >= 27500 && nrarfcn <= 28350) { - return NgranBands.BAND_261; - } - return INVALID_BAND; - } - - /** * Gets the GERAN Operating band for a given ARFCN. * * <p>See 3GPP TS 45.005 clause 2 for calculation. diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index d1729f886f6e..432d08725e87 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -17069,4 +17069,35 @@ public class TelephonyManager { } return false; } + + /** + * Fetches the EFPSISMSC value from the SIM that contains the Public Service Identity + * of the SM-SC (either a SIP URI or tel URI), the value is common for both appType + * {@link #APPTYPE_ISIM} and {@link #APPTYPE_SIM}. + * The EFPSISMSC value is used by the ME to submit SMS over IP as defined in 24.341 [55]. + * + * @param appType ICC Application type {@link #APPTYPE_ISIM} or {@link #APPTYPE_USIM} + * @return SIP URI or tel URI of the Public Service Identity of the SM-SC + * @throws SecurityException if the caller does not have the required permission/privileges + * @hide + */ + @NonNull + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION) + public String getSmscIdentity(int appType) { + try { + IPhoneSubInfo info = getSubscriberInfoService(); + if (info == null) { + Rlog.e(TAG, "getSmscIdentity(): IPhoneSubInfo instance is NULL"); + return null; + } + /** Fetches the SIM PSISMSC params based on subId and appType */ + return info.getSmscIdentity(getSubId(), appType); + } catch (RemoteException ex) { + Rlog.e(TAG, "getSmscIdentity(): RemoteException: " + ex.getMessage()); + } catch (NullPointerException ex) { + Rlog.e(TAG, "getSmscIdentity(): NullPointerException: " + ex.getMessage()); + } + return null; + } } diff --git a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl index ce2017bb9a35..78335fd54917 100644 --- a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl +++ b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl @@ -218,4 +218,18 @@ interface IPhoneSubInfo { */ String getIccSimChallengeResponse(int subId, int appType, int authType, String data, String callingPackage, String callingFeatureId); + + /** + * Fetches the EFPSISMSC value from the SIM that contains the Public Service Identity + * of the SM-SC (either a SIP URI or tel URI), the value is common for both appType + * {@link #APPTYPE_ISIM} and {@link #APPTYPE_SIM}. + * The EFPSISMSC value is used by the ME to submit SMS over IP as defined in 24.341 [55]. + * + * @param appType ICC Application type {@link #APPTYPE_ISIM} or {@link #APPTYPE_USIM} + * @return SIP URI or tel URI of the Public Service Identity of the SM-SC + * @throws SecurityException if the caller does not have the required permission/privileges + * @hide + */ + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)") + String getSmscIdentity(int subId, int appType); } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt index 0e5a1775bd6a..315c40ffa9ba 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt @@ -21,8 +21,10 @@ import com.android.server.wm.flicker.helpers.WindowUtils import com.android.server.wm.flicker.traces.region.RegionSubject import com.android.server.wm.traces.common.FlickerComponentName -val LAUNCHER_COMPONENT = FlickerComponentName("com.google.android.apps.nexuslauncher", - "com.google.android.apps.nexuslauncher.NexusLauncherActivity") +val LAUNCHER_COMPONENT = FlickerComponentName( + "com.google.android.apps.nexuslauncher", + "com.google.android.apps.nexuslauncher.NexusLauncherActivity" +) /** * Checks that [FlickerComponentName.STATUS_BAR] window is visible and above the app windows in @@ -110,9 +112,9 @@ fun FlickerTestParameter.statusBarLayerIsVisible() { fun FlickerTestParameter.navBarLayerPositionStart() { assertLayersStart { val display = this.entry.displays.minByOrNull { it.id } - ?: throw RuntimeException("There is no display!") + ?: throw RuntimeException("There is no display!") this.visibleRegion(FlickerComponentName.NAV_BAR) - .coversExactly(WindowUtils.getNavigationBarPosition(display)) + .coversExactly(WindowUtils.getNavigationBarPosition(display, isGesturalNavigation)) } } @@ -123,9 +125,9 @@ fun FlickerTestParameter.navBarLayerPositionStart() { fun FlickerTestParameter.navBarLayerPositionEnd() { assertLayersEnd { val display = this.entry.displays.minByOrNull { it.id } - ?: throw RuntimeException("There is no display!") + ?: throw RuntimeException("There is no display!") this.visibleRegion(FlickerComponentName.NAV_BAR) - .coversExactly(WindowUtils.getNavigationBarPosition(display)) + .coversExactly(WindowUtils.getNavigationBarPosition(display, isGesturalNavigation)) } } @@ -244,11 +246,11 @@ fun FlickerTestParameter.replacesLayer( assertLayersStart { this.isVisible(originalLayer) - .isInvisible(newLayer) + .isInvisible(newLayer) } assertLayersEnd { this.isInvisible(originalLayer) - .isVisible(newLayer) + .isVisible(newLayer) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt index 23baac29b6c1..eee7cf3acbe6 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt @@ -16,14 +16,16 @@ package com.android.server.wm.flicker.launch +import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.RequiresDevice -import android.platform.test.annotations.FlakyTest import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group1 import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.navBarLayerPositionEnd +import com.android.server.wm.flicker.statusBarLayerPositionEnd import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -43,8 +45,8 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group1 @Postsubmit -open class OpenAppFromLockNotificationCold(testSpec: FlickerTestParameter) - : OpenAppFromNotificationCold(testSpec) { +open class OpenAppFromLockNotificationCold(testSpec: FlickerTestParameter) : + OpenAppFromNotificationCold(testSpec) { override val openingNotificationsFromLockScreen = true @@ -85,6 +87,62 @@ open class OpenAppFromLockNotificationCold(testSpec: FlickerTestParameter) @Test override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow() + /** + * Checks the position of the navigation bar at the start and end of the transition + * + * Differently from the normal usage of this assertion, check only the final state of the + * transition because the display is off at the start and the NavBar is never visible + */ + @Postsubmit + @Test + override fun navBarLayerRotatesAndScales() = testSpec.navBarLayerPositionEnd() + + /** + * Checks the position of the status bar at the start and end of the transition + * + * Differently from the normal usage of this assertion, check only the final state of the + * transition because the display is off at the start and the NavBar is never visible + */ + @Postsubmit + @Test + override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerPositionEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun navBarWindowIsVisible() = super.navBarWindowIsVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun appLayerBecomesVisible() = super.appLayerBecomesVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun appWindowBecomesVisible() = super.appWindowBecomesVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + companion object { /** * Creates the test configurations. @@ -95,8 +153,8 @@ open class OpenAppFromLockNotificationCold(testSpec: FlickerTestParameter) @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTestParameter> { - return com.android.server.wm.flicker.FlickerTestParameterFactory.getInstance() + return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests(repetitions = 3) } } -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt index 49d8ea628585..b156eb1a0933 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt @@ -16,14 +16,16 @@ package com.android.server.wm.flicker.launch +import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.RequiresDevice -import android.platform.test.annotations.FlakyTest import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.annotation.Group1 import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.navBarLayerPositionEnd +import com.android.server.wm.flicker.statusBarLayerPositionEnd import com.android.server.wm.traces.common.FlickerComponentName import org.junit.FixMethodOrder import org.junit.Test @@ -44,8 +46,8 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group1 @Postsubmit -open class OpenAppFromLockNotificationWarm(testSpec: FlickerTestParameter) - : OpenAppFromNotificationWarm(testSpec) { +open class OpenAppFromLockNotificationWarm(testSpec: FlickerTestParameter) : + OpenAppFromNotificationWarm(testSpec) { override val openingNotificationsFromLockScreen = true @@ -111,6 +113,67 @@ open class OpenAppFromLockNotificationWarm(testSpec: FlickerTestParameter) override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = super.visibleWindowsShownMoreThanOneConsecutiveEntry() + /** + * Checks the position of the navigation bar at the start and end of the transition + * + * Differently from the normal usage of this assertion, check only the final state of the + * transition because the display is off at the start and the NavBar is never visible + */ + @Postsubmit + @Test + override fun navBarLayerRotatesAndScales() = testSpec.navBarLayerPositionEnd() + + /** + * Checks the position of the status bar at the start and end of the transition + * + * Differently from the normal usage of this assertion, check only the final state of the + * transition because the display is off at the start and the NavBar is never visible + */ + @Postsubmit + @Test + override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerPositionEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun navBarWindowIsVisible() = super.navBarWindowIsVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun appLayerBecomesVisible() = super.appLayerBecomesVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun appWindowBecomesVisible() = super.appWindowBecomesVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + companion object { /** * Creates the test configurations. @@ -121,8 +184,8 @@ open class OpenAppFromLockNotificationWarm(testSpec: FlickerTestParameter) @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTestParameter> { - return com.android.server.wm.flicker.FlickerTestParameterFactory.getInstance() + return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests(repetitions = 3) } } -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt index 950f52ab57e1..5c86a4278b8c 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt @@ -16,9 +16,9 @@ package com.android.server.wm.flicker.launch +import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.RequiresDevice -import android.platform.test.annotations.FlakyTest import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory @@ -26,6 +26,8 @@ import com.android.server.wm.flicker.annotation.Group1 import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.ShowWhenLockedAppHelper import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen +import com.android.server.wm.flicker.navBarLayerPositionEnd +import com.android.server.wm.flicker.statusBarLayerPositionEnd import com.android.server.wm.traces.common.FlickerComponentName import org.junit.FixMethodOrder import org.junit.Test @@ -45,8 +47,8 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group1 @Postsubmit -class OpenAppFromLockNotificationWithLockOverlayApp(testSpec: FlickerTestParameter) - : OpenAppFromLockNotificationCold(testSpec) { +class OpenAppFromLockNotificationWithLockOverlayApp(testSpec: FlickerTestParameter) : + OpenAppFromLockNotificationCold(testSpec) { private val showWhenLockedApp: ShowWhenLockedAppHelper = ShowWhenLockedAppHelper(instrumentation) @@ -109,6 +111,73 @@ class OpenAppFromLockNotificationWithLockOverlayApp(testSpec: FlickerTestParamet @Test override fun entireScreenCovered() = super.entireScreenCovered() + /** + * Checks the position of the navigation bar at the start and end of the transition + * + * Differently from the normal usage of this assertion, check only the final state of the + * transition because the display is off at the start and the NavBar is never visible + */ + @Postsubmit + @Test + override fun navBarLayerRotatesAndScales() = testSpec.navBarLayerPositionEnd() + + /** + * Checks the position of the status bar at the start and end of the transition + * + * Differently from the normal usage of this assertion, check only the final state of the + * transition because the display is off at the start and the NavBar is never visible + */ + @Postsubmit + @Test + override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerPositionEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun navBarWindowIsVisible() = super.navBarWindowIsVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun appLayerBecomesVisible() = super.appLayerBecomesVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun appWindowBecomesVisible() = super.appWindowBecomesVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + companion object { /** * Creates the test configurations. @@ -123,4 +192,4 @@ class OpenAppFromLockNotificationWithLockOverlayApp(testSpec: FlickerTestParamet .getConfigNonRotationTests(repetitions = 3) } } -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt index 2d00f3f92dcc..844311259a06 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt @@ -16,11 +16,11 @@ package com.android.server.wm.flicker.launch +import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.RequiresDevice import android.view.WindowInsets import android.view.WindowManager -import android.platform.test.annotations.FlakyTest import androidx.test.uiautomator.By import androidx.test.uiautomator.Until import com.android.launcher3.tapl.LauncherInstrumentation @@ -53,8 +53,8 @@ import org.junit.runners.Parameterized @FixMethodOrder(MethodSorters.NAME_ASCENDING) @Group1 @Postsubmit -open class OpenAppFromNotificationWarm(testSpec: FlickerTestParameter) - : OpenAppTransition(testSpec) { +open class OpenAppFromNotificationWarm(testSpec: FlickerTestParameter) : + OpenAppTransition(testSpec) { protected val taplInstrumentation = LauncherInstrumentation() override val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation) @@ -180,6 +180,32 @@ open class OpenAppFromNotificationWarm(testSpec: FlickerTestParameter) super.appWindowBecomesTopWindow() } + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun entireScreenCovered() = super.entireScreenCovered() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun navBarLayerIsVisible() = super.navBarLayerIsVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun navBarWindowIsVisible() = super.navBarWindowIsVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + companion object { /** * Creates the test configurations. @@ -194,4 +220,4 @@ open class OpenAppFromNotificationWarm(testSpec: FlickerTestParameter) .getConfigNonRotationTests(repetitions = 3) } } -}
\ No newline at end of file +} diff --git a/tools/localedata/OWNERS b/tools/localedata/OWNERS new file mode 100644 index 000000000000..2501679784d6 --- /dev/null +++ b/tools/localedata/OWNERS @@ -0,0 +1,2 @@ +# Bug component: 24949 +include platform/external/icu:/OWNERS |